From 21fc74eb9472a93bd2b4b09737d399faed82c984 Mon Sep 17 00:00:00 2001 From: chrisdengso <8185683+chrisdengso@users.noreply.github.com> Date: Thu, 22 Oct 2026 11:31:47 +0000 Subject: [PATCH] feat!: regenerate from OpenAPI spec with Speakeasy --- .devcontainer/Dockerfile | 8 - .devcontainer/devcontainer.json | 43 - .gitattributes | 2 + .github/workflows/ci.yml | 100 +- .github/workflows/publish-pypi.yml | 29 +- .github/workflows/release-doctor.yml | 21 - .gitignore | 27 +- .python-version | 1 - .release-please-manifest.json | 3 - .speakeasy/gen.lock | 1651 ++++++++ .speakeasy/gen.yaml | 102 + .stats.yml | 4 - .vscode/settings.json | 5 +- Brewfile | 2 - CONTRIBUTING.md | 127 +- README.md | 902 ++-- SECURITY.md | 27 - USAGE.md | 51 + api.md | 188 - bin/check-release-environment | 21 - bin/publish-pypi | 7 - docs/errors/authenticationerror.md | 15 + docs/errors/badrequesterror.md | 15 + docs/errors/conflicterror.md | 15 + docs/errors/internalservererror.md | 15 + docs/errors/notfounderror.md | 15 + docs/errors/permissiondeniederror.md | 15 + docs/errors/ratelimiterror.md | 15 + docs/models/actionrequest.md | 17 + docs/models/actionstat.md | 11 + docs/models/addwordlistwordsrequest.md | 9 + docs/models/addwordlistwordsrequestbody.md | 8 + docs/models/addwordlistwordsresponse.md | 12 + docs/models/adult.md | 10 + docs/models/alcohol.md | 10 + docs/models/audiorequest.md | 11 + docs/models/audioresponse.md | 11 + docs/models/authenticationerrorcode.md | 27 + docs/models/author.md | 12 + docs/models/authorstatus.md | 21 + docs/models/badrequesterrorcode.md | 23 + docs/models/block.md | 9 + docs/models/cannabis.md | 10 + docs/models/casebook.md | 13 + docs/models/casebookagreement.md | 19 + docs/models/casebookanswer.md | 20 + docs/models/channel.md | 28 + docs/models/channelcircumvention.md | 10 + docs/models/checkstatus.md | 19 + docs/models/classifieroutput.md | 16 + docs/models/codeabuse.md | 10 + docs/models/comparator.md | 16 + docs/models/compassioninsight.md | 13 + docs/models/conflicterrorcode.md | 21 + docs/models/contentleafrequest.md | 29 + docs/models/contentleafresponse.md | 29 + docs/models/contentrequest.md | 37 + docs/models/contentresponse.md | 14 + docs/models/contenttype.md | 27 + docs/models/createactionpositionrequest.md | 17 + docs/models/createactionpositionresponse.md | 21 + .../createactionpossiblevaluerequest.md | 8 + .../createactionpossiblevalueresponse.md | 8 + .../createactionqueuebehaviourrequest.md | 17 + .../createactionqueuebehaviourresponse.md | 21 + docs/models/createactionrequest.md | 18 + docs/models/createactionresponse.md | 22 + docs/models/createactiontyperequest.md | 22 + docs/models/createactiontyperesponse.md | 26 + docs/models/createauthormetadata.md | 14 + docs/models/createauthorrequest.md | 17 + docs/models/createchannelcontenttype.md | 23 + docs/models/createchannelrequest.md | 9 + docs/models/createchannelrequestbody.md | 11 + docs/models/createprojectrequest.md | 12 + docs/models/createwebhookeventtyperequest.md | 23 + docs/models/createwebhookeventtyperesponse.md | 27 + docs/models/createwebhookpayloadversion.md | 20 + docs/models/createwebhookrequest.md | 11 + docs/models/createwebhookresponse.md | 16 + docs/models/crypto.md | 10 + docs/models/curiosityinsight.md | 13 + docs/models/currentproject.md | 9 + docs/models/dailyreviewcount.md | 9 + docs/models/dataaudiorequest.md | 11 + docs/models/dataimagerequest.md | 12 + docs/models/datatextrequest.md | 11 + docs/models/datavideorequest.md | 11 + docs/models/deleteactionrequest.md | 8 + docs/models/deleteactionresponse.md | 11 + docs/models/deleteauthorrequest.md | 8 + docs/models/deleteauthorresponse.md | 10 + docs/models/deletechannelrequest.md | 8 + docs/models/deletechannelresponse.md | 11 + docs/models/deleteprojectrequest.md | 8 + docs/models/deleteprojectresponse.md | 11 + docs/models/deletewebhookrequest.md | 8 + docs/models/deletewebhookresponse.md | 11 + docs/models/drugs.md | 10 + docs/models/duplicatechannelrequest.md | 9 + docs/models/duplicatechannelrequestbody.md | 9 + docs/models/duplicateprojectrequest.md | 9 + docs/models/duplicateprojectrequestbody.md | 8 + docs/models/entities1.md | 12 + docs/models/entities2.md | 12 + docs/models/entitymatcheroutput.md | 15 + docs/models/error.md | 9 + docs/models/errorissue.md | 10 + docs/models/evaluation.md | 14 + docs/models/executeactionrequest.md | 15 + docs/models/executeactionresponse.md | 10 + docs/models/extremism.md | 10 + docs/models/facedetection.md | 12 + docs/models/filter_.md | 32 + docs/models/filterlabel.md | 11 + docs/models/flaggedcontenttrend.md | 9 + docs/models/flaggingmode.md | 20 + docs/models/gambling.md | 10 + docs/models/getaccountresponse.md | 14 + docs/models/getactionposition.md | 21 + docs/models/getactionpossiblevalue.md | 8 + docs/models/getactionqueuebehaviour.md | 21 + docs/models/getactionrequest.md | 8 + docs/models/getactionresponse.md | 22 + docs/models/getactiontype.md | 26 + docs/models/getauthorrequest.md | 8 + docs/models/getchannelrequest.md | 8 + docs/models/getprojectrequest.md | 8 + docs/models/getqueuecontenttype.md | 25 + docs/models/getqueuequeue.md | 14 + docs/models/getqueuerecommendationaction.md | 19 + docs/models/getqueuerequest.md | 8 + docs/models/getqueueresponse.md | 10 + docs/models/getqueuestatsrequest.md | 9 + docs/models/getqueuestatsresponse.md | 13 + docs/models/getqueuetype.md | 20 + docs/models/getwebhookeventtype.md | 27 + docs/models/getwebhookpayloadversion.md | 20 + docs/models/getwebhookrequest.md | 8 + docs/models/getwebhookresponse.md | 16 + docs/models/getwebhooksecretresponse.md | 10 + .../getwordlistembeddingstatusrequest.md | 8 + .../getwordlistembeddingstatusresponse.md | 12 + docs/models/getwordlistrequest.md | 8 + docs/models/getwordlistresponse.md | 16 + docs/models/guideline.md | 12 + docs/models/hate.md | 10 + docs/models/illegaltrade.md | 10 + docs/models/illicit.md | 10 + docs/models/illicitalcohol.md | 10 + docs/models/illicitdrugs.md | 10 + docs/models/illicitfirearms.md | 10 + docs/models/illicitgambling.md | 10 + docs/models/illicittobacco.md | 10 + docs/models/imagerequest.md | 12 + docs/models/imageresponse.md | 12 + docs/models/insight.md | 41 + docs/models/internalservererrorcode.md | 21 + docs/models/isflagged.md | 20 + docs/models/languageinsight.md | 13 + docs/models/listactionsdata.md | 20 + docs/models/listactionsposition.md | 21 + docs/models/listactionspossiblevalue.md | 8 + docs/models/listactionsqueuebehaviour.md | 21 + docs/models/listactionsrequest.md | 10 + docs/models/listactionsresponse.md | 8 + docs/models/listactionsresponsebody.md | 11 + docs/models/listactionstype.md | 26 + docs/models/listauthorsrequest.md | 14 + docs/models/listauthorsresponse.md | 8 + docs/models/listauthorsresponsebody.md | 11 + docs/models/listauthorssortdirection.md | 16 + docs/models/listchannelsrequest.md | 10 + docs/models/listchannelsresponse.md | 8 + docs/models/listchannelsresponsebody.md | 11 + docs/models/listprojectsrequest.md | 9 + docs/models/listprojectsresponse.md | 8 + docs/models/listprojectsresponsebody.md | 11 + docs/models/listqueueitemsaction.md | 12 + docs/models/listqueueitemsdata.md | 18 + docs/models/listqueueitemslabel.md | 10 + docs/models/listqueueitemsrequest.md | 19 + docs/models/listqueueitemsresponse.md | 8 + docs/models/listqueueitemsresponsebody.md | 11 + docs/models/listqueueitemssortdirection.md | 16 + docs/models/listqueueitemsstatus.md | 20 + docs/models/listwebhooksdata.md | 14 + docs/models/listwebhookseventtype.md | 27 + docs/models/listwebhookspayloadversion.md | 20 + docs/models/listwebhooksrequest.md | 9 + docs/models/listwebhooksresponse.md | 8 + docs/models/listwebhooksresponsebody.md | 11 + docs/models/listwordlistsdata.md | 12 + docs/models/listwordlistsrequest.md | 9 + docs/models/listwordlistsresponse.md | 8 + docs/models/listwordlistsresponsebody.md | 11 + docs/models/lowqualitycontent.md | 11 + docs/models/matchedrule.md | 9 + docs/models/mediatype.md | 21 + docs/models/meta.md | 14 + docs/models/metadata.md | 14 + docs/models/metadatafilter.md | 9 + docs/models/metastatus.md | 18 + docs/models/method.md | 18 + docs/models/metrics.md | 10 + docs/models/modified.md | 23 + docs/models/notfounderrorcode.md | 35 + docs/models/pagination.md | 14 + docs/models/path.md | 17 + docs/models/permissiondeniederrorcode.md | 37 + docs/models/personalinformation.md | 10 + docs/models/personalstoryinsight.md | 13 + docs/models/piimasking.md | 9 + docs/models/policyrequest.md | 245 ++ docs/models/policyresponse.md | 19 + docs/models/political.md | 10 + docs/models/profanity.md | 10 + docs/models/project.md | 17 + docs/models/projectglobalflaggingmode.md | 20 + docs/models/projectwithkey.md | 18 + .../projectwithkeyglobalflaggingmode.md | 20 + docs/models/promotion.md | 10 + docs/models/publicauthor.md | 23 + docs/models/ratelimiterrorcode.md | 21 + docs/models/reasoncode.md | 17 + docs/models/reasoncodeenum.md | 27 + docs/models/recommendation.md | 12 + docs/models/religion.md | 10 + docs/models/removewordlistwordsrequest.md | 9 + docs/models/removewordlistwordsresponse.md | 12 + docs/models/resolvequeueitemrequest.md | 10 + docs/models/resolvequeueitemrequestbody.md | 8 + docs/models/resolvequeueitemresponse.md | 12 + docs/models/respectinsight.md | 13 + docs/models/reviewstats.md | 10 + docs/models/riskevaluation.md | 8 + docs/models/scores.md | 11 + docs/models/security.md | 8 + docs/models/selfharm.md | 10 + docs/models/selfpromotion.md | 10 + docs/models/sentimentinsight.md | 14 + docs/models/sexual.md | 10 + docs/models/sexualminors.md | 10 + docs/models/sexualsolicitation.md | 10 + docs/models/signalsrequest.md | 8 + docs/models/signalsresponse.md | 18 + docs/models/sortby.md | 20 + docs/models/sortfield.md | 15 + docs/models/spam.md | 10 + docs/models/status.md | 21 + docs/models/submitcontentbehavior.md | 16 + docs/models/submitcontentblock.md | 9 + .../models/submitcontentbrandimpersonation.md | 9 + docs/models/submitcontentclientaction.md | 13 + docs/models/submitcontentlabel.md | 11 + docs/models/submitcontentmatch.md | 13 + docs/models/submitcontentmetatype.md | 23 + docs/models/submitcontentobject.md | 11 + .../submitcontentrecommendationaction.md | 21 + docs/models/submitcontentrequest.md | 18 + docs/models/submitcontentresponse.md | 18 + docs/models/submitcontenttrustlevel.md | 9 + docs/models/textrequest.md | 11 + docs/models/textresponse.md | 11 + docs/models/threat.md | 10 + docs/models/tobacco.md | 10 + docs/models/topaction.md | 10 + docs/models/topic.md | 9 + docs/models/topreviewer.md | 13 + docs/models/toxicity.md | 10 + docs/models/transcriptionquality.md | 21 + docs/models/trends.md | 9 + docs/models/trustlevel.md | 9 + docs/models/unicodespoofing.md | 11 + docs/models/unresolvequeueitemrequest.md | 10 + docs/models/unresolvequeueitemrequestbody.md | 8 + docs/models/unresolvequeueitemresponse.md | 12 + docs/models/updateactionpositionrequest.md | 17 + docs/models/updateactionpositionresponse.md | 21 + .../updateactionpossiblevaluerequest.md | 8 + .../updateactionpossiblevalueresponse.md | 8 + .../updateactionqueuebehaviourrequest.md | 17 + .../updateactionqueuebehaviourresponse.md | 21 + docs/models/updateactionrequest.md | 9 + docs/models/updateactionrequestbody.md | 18 + docs/models/updateactionresponse.md | 22 + docs/models/updateactiontyperequest.md | 22 + docs/models/updateactiontyperesponse.md | 26 + docs/models/updateauthormetadata.md | 14 + docs/models/updateauthorrequest.md | 9 + docs/models/updateauthorrequestbody.md | 16 + docs/models/updatechannelcontenttype.md | 23 + docs/models/updatechannelflaggingmode.md | 16 + docs/models/updatechannelrequest.md | 9 + docs/models/updatechannelrequestbody.md | 23 + .../updatechanneltranscriptionquality.md | 17 + .../models/updateprojectglobalflaggingmode.md | 16 + docs/models/updateprojectrequest.md | 9 + docs/models/updateprojectrequestbody.md | 14 + docs/models/updatewebhookeventtyperequest.md | 23 + docs/models/updatewebhookeventtyperesponse.md | 27 + docs/models/updatewebhookpayloadversion.md | 20 + docs/models/updatewebhookrequest.md | 9 + docs/models/updatewebhookrequestbody.md | 11 + docs/models/updatewebhookresponse.md | 16 + docs/models/updatewordlistrequest.md | 9 + docs/models/updatewordlistrequestbody.md | 12 + docs/models/updatewordlistresponse.md | 16 + docs/models/urlmasking.md | 9 + docs/models/urlrisk.md | 13 + docs/models/utils/retryconfig.md | 24 + docs/models/value.md | 19 + docs/models/verdict.md | 20 + docs/models/videorequest.md | 11 + docs/models/videoresponse.md | 11 + docs/models/violence.md | 10 + docs/models/weapons.md | 10 + docs/models/withinunit.md | 22 + docs/sdks/account/README.md | 48 + docs/sdks/actions/README.md | 311 ++ docs/sdks/authors/README.md | 265 ++ docs/sdks/channels/README.md | 306 ++ docs/sdks/contentsdk/README.md | 65 + docs/sdks/items/README.md | 162 + docs/sdks/projects/README.md | 292 ++ docs/sdks/queuesdk/README.md | 101 + docs/sdks/webhooks/README.md | 239 ++ docs/sdks/webhooksecret/README.md | 48 + docs/sdks/wordlist/README.md | 200 + docs/sdks/words/README.md | 105 + examples/.keep | 4 - py.typed | 1 + pylintrc | 663 +++ pyproject.toml | 266 +- release-please-config.json | 66 - requirements-dev.lock | 110 - scripts/bootstrap | 30 - scripts/format | 14 - scripts/lint | 22 - scripts/prepare_readme.py | 35 + scripts/publish.sh | 6 + scripts/test | 38 - scripts/utils/ruffen-docs.py | 167 - scripts/utils/upload-artifact.sh | 27 - src/moderation_api/__init__.py | 111 +- src/moderation_api/_base_client.py | 2131 ---------- src/moderation_api/_client.py | 775 ---- src/moderation_api/_compat.py | 226 - src/moderation_api/_constants.py | 14 - src/moderation_api/_exceptions.py | 108 - src/moderation_api/_files.py | 173 - src/moderation_api/_hooks/__init__.py | 4 + src/moderation_api/_hooks/sdkhooks.py | 74 + src/moderation_api/_hooks/types.py | 124 + src/moderation_api/_models.py | 952 ----- src/moderation_api/_qs.py | 149 - src/moderation_api/_resource.py | 43 - src/moderation_api/_response.py | 835 ---- src/moderation_api/_streaming.py | 338 -- src/moderation_api/_types.py | 273 -- src/moderation_api/_utils/__init__.py | 64 - src/moderation_api/_utils/_compat.py | 45 - src/moderation_api/_utils/_datetime_parse.py | 136 - src/moderation_api/_utils/_json.py | 35 - src/moderation_api/_utils/_logs.py | 25 - src/moderation_api/_utils/_path.py | 127 - src/moderation_api/_utils/_proxy.py | 65 - src/moderation_api/_utils/_reflection.py | 42 - src/moderation_api/_utils/_resources_proxy.py | 24 - src/moderation_api/_utils/_streams.py | 12 - src/moderation_api/_utils/_sync.py | 58 - src/moderation_api/_utils/_transform.py | 457 --- src/moderation_api/_utils/_typing.py | 156 - src/moderation_api/_utils/_utils.py | 433 -- src/moderation_api/_version.py | 17 +- src/moderation_api/account.py | 195 + src/moderation_api/actions.py | 1629 ++++++++ src/moderation_api/authors.py | 1403 +++++++ src/moderation_api/basesdk.py | 396 ++ src/moderation_api/channels.py | 1585 +++++++ src/moderation_api/content_sdk.py | 325 ++ src/moderation_api/errors/__init__.py | 69 + .../errors/authenticationerror.py | 61 + src/moderation_api/errors/badrequesterror.py | 59 + src/moderation_api/errors/conflicterror.py | 57 + .../errors/internalservererror.py | 55 + .../errors/moderationapidefaulterror.py | 40 + .../errors/moderationapierror.py | 30 + .../errors/no_response_error.py | 17 + src/moderation_api/errors/notfounderror.py | 65 + .../errors/permissiondeniederror.py | 66 + src/moderation_api/errors/ratelimiterror.py | 58 + .../errors/responsevalidationerror.py | 27 + src/moderation_api/httpclient.py | 125 + src/moderation_api/items.py | 905 ++++ src/moderation_api/lib/.keep | 4 - src/moderation_api/models/__init__.py | 1624 ++++++++ .../models/addwordlistwordsop.py | 66 + .../models/authenticationerror.py | 23 + src/moderation_api/models/badrequesterror.py | 19 + src/moderation_api/models/channel.py | 190 + src/moderation_api/models/conflicterror.py | 12 + src/moderation_api/models/createactionop.py | 339 ++ src/moderation_api/models/createauthorop.py | 197 + src/moderation_api/models/createchannelop.py | 92 + src/moderation_api/models/createprojectop.py | 62 + src/moderation_api/models/createwebhookop.py | 193 + src/moderation_api/models/deleteactionop.py | 37 + src/moderation_api/models/deleteauthorop.py | 30 + src/moderation_api/models/deletechannelop.py | 37 + src/moderation_api/models/deleteprojectop.py | 37 + src/moderation_api/models/deletewebhookop.py | 37 + .../models/duplicatechannelop.py | 57 + .../models/duplicateprojectop.py | 52 + src/moderation_api/models/errorissue.py | 49 + src/moderation_api/models/executeactionop.py | 87 + src/moderation_api/models/getaccountop.py | 70 + src/moderation_api/models/getactionop.py | 199 + src/moderation_api/models/getauthorop.py | 18 + src/moderation_api/models/getchannelop.py | 18 + src/moderation_api/models/getprojectop.py | 18 + src/moderation_api/models/getqueueop.py | 405 ++ src/moderation_api/models/getqueuestatsop.py | 264 ++ src/moderation_api/models/getwebhookop.py | 135 + .../models/getwebhooksecretop.py | 19 + .../models/getwordlistembeddingstatusop.py | 49 + src/moderation_api/models/getwordlistop.py | 81 + .../models/internalservererror.py | 12 + src/moderation_api/models/listactionsop.py | 257 ++ src/moderation_api/models/listauthorsop.py | 143 + src/moderation_api/models/listchannelsop.py | 86 + src/moderation_api/models/listprojectsop.py | 77 + src/moderation_api/models/listqueueitemsop.py | 335 ++ src/moderation_api/models/listwebhooksop.py | 186 + src/moderation_api/models/listwordlistsop.py | 126 + src/moderation_api/models/notfounderror.py | 31 + src/moderation_api/models/pagination.py | 46 + .../models/permissiondeniederror.py | 33 + src/moderation_api/models/project.py | 97 + src/moderation_api/models/projectwithkey.py | 102 + src/moderation_api/models/publicauthor.py | 373 ++ src/moderation_api/models/ratelimiterror.py | 12 + .../models/removewordlistwordsop.py | 57 + .../models/resolvequeueitemop.py | 106 + src/moderation_api/models/security.py | 42 + src/moderation_api/models/submitcontentop.py | 3637 +++++++++++++++++ .../models/unresolvequeueitemop.py | 90 + src/moderation_api/models/updateactionop.py | 359 ++ src/moderation_api/models/updateauthorop.py | 211 + src/moderation_api/models/updatechannelop.py | 224 + src/moderation_api/models/updateprojectop.py | 128 + src/moderation_api/models/updatewebhookop.py | 213 + src/moderation_api/models/updatewordlistop.py | 133 + src/moderation_api/projects.py | 1487 +++++++ src/moderation_api/py.typed | 1 + src/moderation_api/queue_sdk.py | 499 +++ src/moderation_api/resources/__init__.py | 131 - src/moderation_api/resources/account.py | 135 - .../resources/actions/__init__.py | 33 - .../resources/actions/actions.py | 794 ---- .../resources/actions/execute.py | 354 -- src/moderation_api/resources/auth.py | 204 - src/moderation_api/resources/authors.py | 740 ---- src/moderation_api/resources/content.py | 384 -- .../resources/queue/__init__.py | 33 - src/moderation_api/resources/queue/items.py | 445 -- src/moderation_api/resources/queue/queue.py | 302 -- .../resources/webhook_secret.py | 143 - src/moderation_api/resources/webhooks.py | 618 --- .../resources/wordlist/__init__.py | 33 - .../resources/wordlist/wordlist.py | 470 --- .../resources/wordlist/words.py | 274 -- src/moderation_api/sdk.py | 207 + src/moderation_api/sdkconfiguration.py | 48 + src/moderation_api/types/__init__.py | 63 +- .../types/account_list_response.py | 31 - .../types/action_create_params.py | 77 - .../types/action_create_response.py | 82 - .../types/action_delete_response.py | 13 - .../types/action_list_params.py | 13 - .../types/action_list_response.py | 85 - .../types/action_retrieve_response.py | 82 - .../types/action_update_params.py | 77 - .../types/action_update_response.py | 82 - src/moderation_api/types/actions/__init__.py | 8 - .../actions/execute_execute_by_id_params.py | 24 - .../actions/execute_execute_by_id_response.py | 20 - .../types/actions/execute_execute_params.py | 30 - .../types/actions/execute_execute_response.py | 12 - .../types/auth_create_response.py | 16 - .../types/auth_retrieve_response.py | 13 - .../types/author_create_params.py | 66 - .../types/author_create_response.py | 132 - .../types/author_delete_response.py | 9 - .../types/author_list_params.py | 40 - .../types/author_list_response.py | 159 - .../types/author_retrieve_response.py | 132 - .../types/author_update_params.py | 63 - .../types/author_update_response.py | 132 - src/moderation_api/types/base64fileinput.py | 43 + src/moderation_api/types/basemodel.py | 77 + .../types/content_submit_params.py | 596 --- .../types/content_submit_response.py | 476 --- src/moderation_api/types/queue/__init__.py | 10 - .../types/queue/item_list_params.py | 36 - .../types/queue/item_list_response.py | 90 - .../types/queue/item_resolve_params.py | 15 - .../types/queue/item_resolve_response.py | 19 - .../types/queue/item_unresolve_params.py | 15 - .../types/queue/item_unresolve_response.py | 17 - .../types/queue_get_stats_params.py | 14 - .../types/queue_get_stats_response.py | 110 - .../types/queue_retrieve_response.py | 112 - .../types/webhook_create_params.py | 46 - .../types/webhook_create_response.py | 55 - .../types/webhook_delete_response.py | 13 - .../types/webhook_list_response.py | 58 - .../types/webhook_retrieve_response.py | 55 - .../types/webhook_secret_retrieve_response.py | 14 - .../types/webhook_update_params.py | 44 - .../types/webhook_update_response.py | 55 - src/moderation_api/types/wordlist/__init__.py | 8 - .../types/wordlist/word_add_params.py | 14 - .../types/wordlist/word_add_response.py | 20 - .../types/wordlist/word_remove_params.py | 14 - .../types/wordlist/word_remove_response.py | 20 - .../wordlist_get_embedding_status_response.py | 20 - .../types/wordlist_list_response.py | 30 - .../types/wordlist_retrieve_response.py | 32 - .../types/wordlist_update_params.py | 29 - .../types/wordlist_update_response.py | 32 - src/moderation_api/utils/__init__.py | 186 + src/moderation_api/utils/annotations.py | 79 + src/moderation_api/utils/datetimes.py | 41 + src/moderation_api/utils/dynamic_imports.py | 54 + src/moderation_api/utils/enums.py | 134 + src/moderation_api/utils/eventstreaming.py | 326 ++ src/moderation_api/utils/forms.py | 239 ++ src/moderation_api/utils/headers.py | 136 + src/moderation_api/utils/logger.py | 27 + src/moderation_api/utils/metadata.py | 119 + src/moderation_api/utils/queryparams.py | 217 + src/moderation_api/utils/requestbodies.py | 67 + src/moderation_api/utils/retries.py | 372 ++ src/moderation_api/utils/security.py | 215 + src/moderation_api/utils/serializers.py | 326 ++ src/moderation_api/utils/unions.py | 58 + .../utils/unmarshal_json_response.py | 38 + src/moderation_api/utils/url.py | 154 + src/moderation_api/utils/values.py | 142 + src/moderation_api/webhook_signature.py | 67 + src/moderation_api/webhooks.py | 1181 ++++++ src/moderation_api/webhooksecret.py | 195 + src/moderation_api/wordlist.py | 1061 +++++ src/moderation_api/words.py | 461 +++ tests/__init__.py | 1 - tests/api_resources/__init__.py | 1 - tests/api_resources/actions/__init__.py | 1 - tests/api_resources/actions/test_execute.py | 245 -- tests/api_resources/queue/__init__.py | 1 - tests/api_resources/queue/test_items.py | 398 -- tests/api_resources/test_account.py | 80 - tests/api_resources/test_actions.py | 496 --- tests/api_resources/test_auth.py | 154 - tests/api_resources/test_authors.py | 522 --- tests/api_resources/test_content.py | 242 -- tests/api_resources/test_queue.py | 210 - tests/api_resources/test_webhook_secret.py | 80 - tests/api_resources/test_webhooks.py | 464 --- tests/api_resources/test_wordlist.py | 363 -- tests/api_resources/wordlist/__init__.py | 1 - tests/api_resources/wordlist/test_words.py | 208 - tests/conftest.py | 84 - tests/sample_file.txt | 1 - tests/test_client.py | 2042 --------- tests/test_extract_files.py | 91 - tests/test_files.py | 148 - tests/test_models.py | 1017 ----- tests/test_qs.py | 78 - tests/test_required_args.py | 111 - tests/test_response.py | 277 -- tests/test_streaming.py | 252 -- tests/test_transform.py | 460 --- tests/test_utils/test_datetime_parse.py | 110 - tests/test_utils/test_json.py | 126 - tests/test_utils/test_path.py | 89 - tests/test_utils/test_proxy.py | 34 - tests/test_utils/test_typing.py | 73 - tests/utils.py | 167 - uv.lock | 1829 --------- 590 files changed, 37177 insertions(+), 28388 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/devcontainer.json create mode 100644 .gitattributes delete mode 100644 .github/workflows/release-doctor.yml delete mode 100644 .python-version delete mode 100644 .release-please-manifest.json create mode 100644 .speakeasy/gen.lock create mode 100644 .speakeasy/gen.yaml delete mode 100644 .stats.yml delete mode 100644 Brewfile delete mode 100644 SECURITY.md create mode 100644 USAGE.md delete mode 100644 api.md delete mode 100644 bin/check-release-environment delete mode 100644 bin/publish-pypi create mode 100644 docs/errors/authenticationerror.md create mode 100644 docs/errors/badrequesterror.md create mode 100644 docs/errors/conflicterror.md create mode 100644 docs/errors/internalservererror.md create mode 100644 docs/errors/notfounderror.md create mode 100644 docs/errors/permissiondeniederror.md create mode 100644 docs/errors/ratelimiterror.md create mode 100644 docs/models/actionrequest.md create mode 100644 docs/models/actionstat.md create mode 100644 docs/models/addwordlistwordsrequest.md create mode 100644 docs/models/addwordlistwordsrequestbody.md create mode 100644 docs/models/addwordlistwordsresponse.md create mode 100644 docs/models/adult.md create mode 100644 docs/models/alcohol.md create mode 100644 docs/models/audiorequest.md create mode 100644 docs/models/audioresponse.md create mode 100644 docs/models/authenticationerrorcode.md create mode 100644 docs/models/author.md create mode 100644 docs/models/authorstatus.md create mode 100644 docs/models/badrequesterrorcode.md create mode 100644 docs/models/block.md create mode 100644 docs/models/cannabis.md create mode 100644 docs/models/casebook.md create mode 100644 docs/models/casebookagreement.md create mode 100644 docs/models/casebookanswer.md create mode 100644 docs/models/channel.md create mode 100644 docs/models/channelcircumvention.md create mode 100644 docs/models/checkstatus.md create mode 100644 docs/models/classifieroutput.md create mode 100644 docs/models/codeabuse.md create mode 100644 docs/models/comparator.md create mode 100644 docs/models/compassioninsight.md create mode 100644 docs/models/conflicterrorcode.md create mode 100644 docs/models/contentleafrequest.md create mode 100644 docs/models/contentleafresponse.md create mode 100644 docs/models/contentrequest.md create mode 100644 docs/models/contentresponse.md create mode 100644 docs/models/contenttype.md create mode 100644 docs/models/createactionpositionrequest.md create mode 100644 docs/models/createactionpositionresponse.md create mode 100644 docs/models/createactionpossiblevaluerequest.md create mode 100644 docs/models/createactionpossiblevalueresponse.md create mode 100644 docs/models/createactionqueuebehaviourrequest.md create mode 100644 docs/models/createactionqueuebehaviourresponse.md create mode 100644 docs/models/createactionrequest.md create mode 100644 docs/models/createactionresponse.md create mode 100644 docs/models/createactiontyperequest.md create mode 100644 docs/models/createactiontyperesponse.md create mode 100644 docs/models/createauthormetadata.md create mode 100644 docs/models/createauthorrequest.md create mode 100644 docs/models/createchannelcontenttype.md create mode 100644 docs/models/createchannelrequest.md create mode 100644 docs/models/createchannelrequestbody.md create mode 100644 docs/models/createprojectrequest.md create mode 100644 docs/models/createwebhookeventtyperequest.md create mode 100644 docs/models/createwebhookeventtyperesponse.md create mode 100644 docs/models/createwebhookpayloadversion.md create mode 100644 docs/models/createwebhookrequest.md create mode 100644 docs/models/createwebhookresponse.md create mode 100644 docs/models/crypto.md create mode 100644 docs/models/curiosityinsight.md create mode 100644 docs/models/currentproject.md create mode 100644 docs/models/dailyreviewcount.md create mode 100644 docs/models/dataaudiorequest.md create mode 100644 docs/models/dataimagerequest.md create mode 100644 docs/models/datatextrequest.md create mode 100644 docs/models/datavideorequest.md create mode 100644 docs/models/deleteactionrequest.md create mode 100644 docs/models/deleteactionresponse.md create mode 100644 docs/models/deleteauthorrequest.md create mode 100644 docs/models/deleteauthorresponse.md create mode 100644 docs/models/deletechannelrequest.md create mode 100644 docs/models/deletechannelresponse.md create mode 100644 docs/models/deleteprojectrequest.md create mode 100644 docs/models/deleteprojectresponse.md create mode 100644 docs/models/deletewebhookrequest.md create mode 100644 docs/models/deletewebhookresponse.md create mode 100644 docs/models/drugs.md create mode 100644 docs/models/duplicatechannelrequest.md create mode 100644 docs/models/duplicatechannelrequestbody.md create mode 100644 docs/models/duplicateprojectrequest.md create mode 100644 docs/models/duplicateprojectrequestbody.md create mode 100644 docs/models/entities1.md create mode 100644 docs/models/entities2.md create mode 100644 docs/models/entitymatcheroutput.md create mode 100644 docs/models/error.md create mode 100644 docs/models/errorissue.md create mode 100644 docs/models/evaluation.md create mode 100644 docs/models/executeactionrequest.md create mode 100644 docs/models/executeactionresponse.md create mode 100644 docs/models/extremism.md create mode 100644 docs/models/facedetection.md create mode 100644 docs/models/filter_.md create mode 100644 docs/models/filterlabel.md create mode 100644 docs/models/flaggedcontenttrend.md create mode 100644 docs/models/flaggingmode.md create mode 100644 docs/models/gambling.md create mode 100644 docs/models/getaccountresponse.md create mode 100644 docs/models/getactionposition.md create mode 100644 docs/models/getactionpossiblevalue.md create mode 100644 docs/models/getactionqueuebehaviour.md create mode 100644 docs/models/getactionrequest.md create mode 100644 docs/models/getactionresponse.md create mode 100644 docs/models/getactiontype.md create mode 100644 docs/models/getauthorrequest.md create mode 100644 docs/models/getchannelrequest.md create mode 100644 docs/models/getprojectrequest.md create mode 100644 docs/models/getqueuecontenttype.md create mode 100644 docs/models/getqueuequeue.md create mode 100644 docs/models/getqueuerecommendationaction.md create mode 100644 docs/models/getqueuerequest.md create mode 100644 docs/models/getqueueresponse.md create mode 100644 docs/models/getqueuestatsrequest.md create mode 100644 docs/models/getqueuestatsresponse.md create mode 100644 docs/models/getqueuetype.md create mode 100644 docs/models/getwebhookeventtype.md create mode 100644 docs/models/getwebhookpayloadversion.md create mode 100644 docs/models/getwebhookrequest.md create mode 100644 docs/models/getwebhookresponse.md create mode 100644 docs/models/getwebhooksecretresponse.md create mode 100644 docs/models/getwordlistembeddingstatusrequest.md create mode 100644 docs/models/getwordlistembeddingstatusresponse.md create mode 100644 docs/models/getwordlistrequest.md create mode 100644 docs/models/getwordlistresponse.md create mode 100644 docs/models/guideline.md create mode 100644 docs/models/hate.md create mode 100644 docs/models/illegaltrade.md create mode 100644 docs/models/illicit.md create mode 100644 docs/models/illicitalcohol.md create mode 100644 docs/models/illicitdrugs.md create mode 100644 docs/models/illicitfirearms.md create mode 100644 docs/models/illicitgambling.md create mode 100644 docs/models/illicittobacco.md create mode 100644 docs/models/imagerequest.md create mode 100644 docs/models/imageresponse.md create mode 100644 docs/models/insight.md create mode 100644 docs/models/internalservererrorcode.md create mode 100644 docs/models/isflagged.md create mode 100644 docs/models/languageinsight.md create mode 100644 docs/models/listactionsdata.md create mode 100644 docs/models/listactionsposition.md create mode 100644 docs/models/listactionspossiblevalue.md create mode 100644 docs/models/listactionsqueuebehaviour.md create mode 100644 docs/models/listactionsrequest.md create mode 100644 docs/models/listactionsresponse.md create mode 100644 docs/models/listactionsresponsebody.md create mode 100644 docs/models/listactionstype.md create mode 100644 docs/models/listauthorsrequest.md create mode 100644 docs/models/listauthorsresponse.md create mode 100644 docs/models/listauthorsresponsebody.md create mode 100644 docs/models/listauthorssortdirection.md create mode 100644 docs/models/listchannelsrequest.md create mode 100644 docs/models/listchannelsresponse.md create mode 100644 docs/models/listchannelsresponsebody.md create mode 100644 docs/models/listprojectsrequest.md create mode 100644 docs/models/listprojectsresponse.md create mode 100644 docs/models/listprojectsresponsebody.md create mode 100644 docs/models/listqueueitemsaction.md create mode 100644 docs/models/listqueueitemsdata.md create mode 100644 docs/models/listqueueitemslabel.md create mode 100644 docs/models/listqueueitemsrequest.md create mode 100644 docs/models/listqueueitemsresponse.md create mode 100644 docs/models/listqueueitemsresponsebody.md create mode 100644 docs/models/listqueueitemssortdirection.md create mode 100644 docs/models/listqueueitemsstatus.md create mode 100644 docs/models/listwebhooksdata.md create mode 100644 docs/models/listwebhookseventtype.md create mode 100644 docs/models/listwebhookspayloadversion.md create mode 100644 docs/models/listwebhooksrequest.md create mode 100644 docs/models/listwebhooksresponse.md create mode 100644 docs/models/listwebhooksresponsebody.md create mode 100644 docs/models/listwordlistsdata.md create mode 100644 docs/models/listwordlistsrequest.md create mode 100644 docs/models/listwordlistsresponse.md create mode 100644 docs/models/listwordlistsresponsebody.md create mode 100644 docs/models/lowqualitycontent.md create mode 100644 docs/models/matchedrule.md create mode 100644 docs/models/mediatype.md create mode 100644 docs/models/meta.md create mode 100644 docs/models/metadata.md create mode 100644 docs/models/metadatafilter.md create mode 100644 docs/models/metastatus.md create mode 100644 docs/models/method.md create mode 100644 docs/models/metrics.md create mode 100644 docs/models/modified.md create mode 100644 docs/models/notfounderrorcode.md create mode 100644 docs/models/pagination.md create mode 100644 docs/models/path.md create mode 100644 docs/models/permissiondeniederrorcode.md create mode 100644 docs/models/personalinformation.md create mode 100644 docs/models/personalstoryinsight.md create mode 100644 docs/models/piimasking.md create mode 100644 docs/models/policyrequest.md create mode 100644 docs/models/policyresponse.md create mode 100644 docs/models/political.md create mode 100644 docs/models/profanity.md create mode 100644 docs/models/project.md create mode 100644 docs/models/projectglobalflaggingmode.md create mode 100644 docs/models/projectwithkey.md create mode 100644 docs/models/projectwithkeyglobalflaggingmode.md create mode 100644 docs/models/promotion.md create mode 100644 docs/models/publicauthor.md create mode 100644 docs/models/ratelimiterrorcode.md create mode 100644 docs/models/reasoncode.md create mode 100644 docs/models/reasoncodeenum.md create mode 100644 docs/models/recommendation.md create mode 100644 docs/models/religion.md create mode 100644 docs/models/removewordlistwordsrequest.md create mode 100644 docs/models/removewordlistwordsresponse.md create mode 100644 docs/models/resolvequeueitemrequest.md create mode 100644 docs/models/resolvequeueitemrequestbody.md create mode 100644 docs/models/resolvequeueitemresponse.md create mode 100644 docs/models/respectinsight.md create mode 100644 docs/models/reviewstats.md create mode 100644 docs/models/riskevaluation.md create mode 100644 docs/models/scores.md create mode 100644 docs/models/security.md create mode 100644 docs/models/selfharm.md create mode 100644 docs/models/selfpromotion.md create mode 100644 docs/models/sentimentinsight.md create mode 100644 docs/models/sexual.md create mode 100644 docs/models/sexualminors.md create mode 100644 docs/models/sexualsolicitation.md create mode 100644 docs/models/signalsrequest.md create mode 100644 docs/models/signalsresponse.md create mode 100644 docs/models/sortby.md create mode 100644 docs/models/sortfield.md create mode 100644 docs/models/spam.md create mode 100644 docs/models/status.md create mode 100644 docs/models/submitcontentbehavior.md create mode 100644 docs/models/submitcontentblock.md create mode 100644 docs/models/submitcontentbrandimpersonation.md create mode 100644 docs/models/submitcontentclientaction.md create mode 100644 docs/models/submitcontentlabel.md create mode 100644 docs/models/submitcontentmatch.md create mode 100644 docs/models/submitcontentmetatype.md create mode 100644 docs/models/submitcontentobject.md create mode 100644 docs/models/submitcontentrecommendationaction.md create mode 100644 docs/models/submitcontentrequest.md create mode 100644 docs/models/submitcontentresponse.md create mode 100644 docs/models/submitcontenttrustlevel.md create mode 100644 docs/models/textrequest.md create mode 100644 docs/models/textresponse.md create mode 100644 docs/models/threat.md create mode 100644 docs/models/tobacco.md create mode 100644 docs/models/topaction.md create mode 100644 docs/models/topic.md create mode 100644 docs/models/topreviewer.md create mode 100644 docs/models/toxicity.md create mode 100644 docs/models/transcriptionquality.md create mode 100644 docs/models/trends.md create mode 100644 docs/models/trustlevel.md create mode 100644 docs/models/unicodespoofing.md create mode 100644 docs/models/unresolvequeueitemrequest.md create mode 100644 docs/models/unresolvequeueitemrequestbody.md create mode 100644 docs/models/unresolvequeueitemresponse.md create mode 100644 docs/models/updateactionpositionrequest.md create mode 100644 docs/models/updateactionpositionresponse.md create mode 100644 docs/models/updateactionpossiblevaluerequest.md create mode 100644 docs/models/updateactionpossiblevalueresponse.md create mode 100644 docs/models/updateactionqueuebehaviourrequest.md create mode 100644 docs/models/updateactionqueuebehaviourresponse.md create mode 100644 docs/models/updateactionrequest.md create mode 100644 docs/models/updateactionrequestbody.md create mode 100644 docs/models/updateactionresponse.md create mode 100644 docs/models/updateactiontyperequest.md create mode 100644 docs/models/updateactiontyperesponse.md create mode 100644 docs/models/updateauthormetadata.md create mode 100644 docs/models/updateauthorrequest.md create mode 100644 docs/models/updateauthorrequestbody.md create mode 100644 docs/models/updatechannelcontenttype.md create mode 100644 docs/models/updatechannelflaggingmode.md create mode 100644 docs/models/updatechannelrequest.md create mode 100644 docs/models/updatechannelrequestbody.md create mode 100644 docs/models/updatechanneltranscriptionquality.md create mode 100644 docs/models/updateprojectglobalflaggingmode.md create mode 100644 docs/models/updateprojectrequest.md create mode 100644 docs/models/updateprojectrequestbody.md create mode 100644 docs/models/updatewebhookeventtyperequest.md create mode 100644 docs/models/updatewebhookeventtyperesponse.md create mode 100644 docs/models/updatewebhookpayloadversion.md create mode 100644 docs/models/updatewebhookrequest.md create mode 100644 docs/models/updatewebhookrequestbody.md create mode 100644 docs/models/updatewebhookresponse.md create mode 100644 docs/models/updatewordlistrequest.md create mode 100644 docs/models/updatewordlistrequestbody.md create mode 100644 docs/models/updatewordlistresponse.md create mode 100644 docs/models/urlmasking.md create mode 100644 docs/models/urlrisk.md create mode 100644 docs/models/utils/retryconfig.md create mode 100644 docs/models/value.md create mode 100644 docs/models/verdict.md create mode 100644 docs/models/videorequest.md create mode 100644 docs/models/videoresponse.md create mode 100644 docs/models/violence.md create mode 100644 docs/models/weapons.md create mode 100644 docs/models/withinunit.md create mode 100644 docs/sdks/account/README.md create mode 100644 docs/sdks/actions/README.md create mode 100644 docs/sdks/authors/README.md create mode 100644 docs/sdks/channels/README.md create mode 100644 docs/sdks/contentsdk/README.md create mode 100644 docs/sdks/items/README.md create mode 100644 docs/sdks/projects/README.md create mode 100644 docs/sdks/queuesdk/README.md create mode 100644 docs/sdks/webhooks/README.md create mode 100644 docs/sdks/webhooksecret/README.md create mode 100644 docs/sdks/wordlist/README.md create mode 100644 docs/sdks/words/README.md delete mode 100644 examples/.keep create mode 100644 py.typed create mode 100644 pylintrc delete mode 100644 release-please-config.json delete mode 100644 requirements-dev.lock delete mode 100755 scripts/bootstrap delete mode 100755 scripts/format delete mode 100755 scripts/lint create mode 100644 scripts/prepare_readme.py create mode 100644 scripts/publish.sh delete mode 100755 scripts/test delete mode 100644 scripts/utils/ruffen-docs.py delete mode 100755 scripts/utils/upload-artifact.sh delete mode 100644 src/moderation_api/_base_client.py delete mode 100644 src/moderation_api/_client.py delete mode 100644 src/moderation_api/_compat.py delete mode 100644 src/moderation_api/_constants.py delete mode 100644 src/moderation_api/_exceptions.py delete mode 100644 src/moderation_api/_files.py create mode 100644 src/moderation_api/_hooks/__init__.py create mode 100644 src/moderation_api/_hooks/sdkhooks.py create mode 100644 src/moderation_api/_hooks/types.py delete mode 100644 src/moderation_api/_models.py delete mode 100644 src/moderation_api/_qs.py delete mode 100644 src/moderation_api/_resource.py delete mode 100644 src/moderation_api/_response.py delete mode 100644 src/moderation_api/_streaming.py delete mode 100644 src/moderation_api/_types.py delete mode 100644 src/moderation_api/_utils/__init__.py delete mode 100644 src/moderation_api/_utils/_compat.py delete mode 100644 src/moderation_api/_utils/_datetime_parse.py delete mode 100644 src/moderation_api/_utils/_json.py delete mode 100644 src/moderation_api/_utils/_logs.py delete mode 100644 src/moderation_api/_utils/_path.py delete mode 100644 src/moderation_api/_utils/_proxy.py delete mode 100644 src/moderation_api/_utils/_reflection.py delete mode 100644 src/moderation_api/_utils/_resources_proxy.py delete mode 100644 src/moderation_api/_utils/_streams.py delete mode 100644 src/moderation_api/_utils/_sync.py delete mode 100644 src/moderation_api/_utils/_transform.py delete mode 100644 src/moderation_api/_utils/_typing.py delete mode 100644 src/moderation_api/_utils/_utils.py create mode 100644 src/moderation_api/account.py create mode 100644 src/moderation_api/actions.py create mode 100644 src/moderation_api/authors.py create mode 100644 src/moderation_api/basesdk.py create mode 100644 src/moderation_api/channels.py create mode 100644 src/moderation_api/content_sdk.py create mode 100644 src/moderation_api/errors/__init__.py create mode 100644 src/moderation_api/errors/authenticationerror.py create mode 100644 src/moderation_api/errors/badrequesterror.py create mode 100644 src/moderation_api/errors/conflicterror.py create mode 100644 src/moderation_api/errors/internalservererror.py create mode 100644 src/moderation_api/errors/moderationapidefaulterror.py create mode 100644 src/moderation_api/errors/moderationapierror.py create mode 100644 src/moderation_api/errors/no_response_error.py create mode 100644 src/moderation_api/errors/notfounderror.py create mode 100644 src/moderation_api/errors/permissiondeniederror.py create mode 100644 src/moderation_api/errors/ratelimiterror.py create mode 100644 src/moderation_api/errors/responsevalidationerror.py create mode 100644 src/moderation_api/httpclient.py create mode 100644 src/moderation_api/items.py delete mode 100644 src/moderation_api/lib/.keep create mode 100644 src/moderation_api/models/__init__.py create mode 100644 src/moderation_api/models/addwordlistwordsop.py create mode 100644 src/moderation_api/models/authenticationerror.py create mode 100644 src/moderation_api/models/badrequesterror.py create mode 100644 src/moderation_api/models/channel.py create mode 100644 src/moderation_api/models/conflicterror.py create mode 100644 src/moderation_api/models/createactionop.py create mode 100644 src/moderation_api/models/createauthorop.py create mode 100644 src/moderation_api/models/createchannelop.py create mode 100644 src/moderation_api/models/createprojectop.py create mode 100644 src/moderation_api/models/createwebhookop.py create mode 100644 src/moderation_api/models/deleteactionop.py create mode 100644 src/moderation_api/models/deleteauthorop.py create mode 100644 src/moderation_api/models/deletechannelop.py create mode 100644 src/moderation_api/models/deleteprojectop.py create mode 100644 src/moderation_api/models/deletewebhookop.py create mode 100644 src/moderation_api/models/duplicatechannelop.py create mode 100644 src/moderation_api/models/duplicateprojectop.py create mode 100644 src/moderation_api/models/errorissue.py create mode 100644 src/moderation_api/models/executeactionop.py create mode 100644 src/moderation_api/models/getaccountop.py create mode 100644 src/moderation_api/models/getactionop.py create mode 100644 src/moderation_api/models/getauthorop.py create mode 100644 src/moderation_api/models/getchannelop.py create mode 100644 src/moderation_api/models/getprojectop.py create mode 100644 src/moderation_api/models/getqueueop.py create mode 100644 src/moderation_api/models/getqueuestatsop.py create mode 100644 src/moderation_api/models/getwebhookop.py create mode 100644 src/moderation_api/models/getwebhooksecretop.py create mode 100644 src/moderation_api/models/getwordlistembeddingstatusop.py create mode 100644 src/moderation_api/models/getwordlistop.py create mode 100644 src/moderation_api/models/internalservererror.py create mode 100644 src/moderation_api/models/listactionsop.py create mode 100644 src/moderation_api/models/listauthorsop.py create mode 100644 src/moderation_api/models/listchannelsop.py create mode 100644 src/moderation_api/models/listprojectsop.py create mode 100644 src/moderation_api/models/listqueueitemsop.py create mode 100644 src/moderation_api/models/listwebhooksop.py create mode 100644 src/moderation_api/models/listwordlistsop.py create mode 100644 src/moderation_api/models/notfounderror.py create mode 100644 src/moderation_api/models/pagination.py create mode 100644 src/moderation_api/models/permissiondeniederror.py create mode 100644 src/moderation_api/models/project.py create mode 100644 src/moderation_api/models/projectwithkey.py create mode 100644 src/moderation_api/models/publicauthor.py create mode 100644 src/moderation_api/models/ratelimiterror.py create mode 100644 src/moderation_api/models/removewordlistwordsop.py create mode 100644 src/moderation_api/models/resolvequeueitemop.py create mode 100644 src/moderation_api/models/security.py create mode 100644 src/moderation_api/models/submitcontentop.py create mode 100644 src/moderation_api/models/unresolvequeueitemop.py create mode 100644 src/moderation_api/models/updateactionop.py create mode 100644 src/moderation_api/models/updateauthorop.py create mode 100644 src/moderation_api/models/updatechannelop.py create mode 100644 src/moderation_api/models/updateprojectop.py create mode 100644 src/moderation_api/models/updatewebhookop.py create mode 100644 src/moderation_api/models/updatewordlistop.py create mode 100644 src/moderation_api/projects.py create mode 100644 src/moderation_api/queue_sdk.py delete mode 100644 src/moderation_api/resources/__init__.py delete mode 100644 src/moderation_api/resources/account.py delete mode 100644 src/moderation_api/resources/actions/__init__.py delete mode 100644 src/moderation_api/resources/actions/actions.py delete mode 100644 src/moderation_api/resources/actions/execute.py delete mode 100644 src/moderation_api/resources/auth.py delete mode 100644 src/moderation_api/resources/authors.py delete mode 100644 src/moderation_api/resources/content.py delete mode 100644 src/moderation_api/resources/queue/__init__.py delete mode 100644 src/moderation_api/resources/queue/items.py delete mode 100644 src/moderation_api/resources/queue/queue.py delete mode 100644 src/moderation_api/resources/webhook_secret.py delete mode 100644 src/moderation_api/resources/webhooks.py delete mode 100644 src/moderation_api/resources/wordlist/__init__.py delete mode 100644 src/moderation_api/resources/wordlist/wordlist.py delete mode 100644 src/moderation_api/resources/wordlist/words.py create mode 100644 src/moderation_api/sdk.py create mode 100644 src/moderation_api/sdkconfiguration.py delete mode 100644 src/moderation_api/types/account_list_response.py delete mode 100644 src/moderation_api/types/action_create_params.py delete mode 100644 src/moderation_api/types/action_create_response.py delete mode 100644 src/moderation_api/types/action_delete_response.py delete mode 100644 src/moderation_api/types/action_list_params.py delete mode 100644 src/moderation_api/types/action_list_response.py delete mode 100644 src/moderation_api/types/action_retrieve_response.py delete mode 100644 src/moderation_api/types/action_update_params.py delete mode 100644 src/moderation_api/types/action_update_response.py delete mode 100644 src/moderation_api/types/actions/__init__.py delete mode 100644 src/moderation_api/types/actions/execute_execute_by_id_params.py delete mode 100644 src/moderation_api/types/actions/execute_execute_by_id_response.py delete mode 100644 src/moderation_api/types/actions/execute_execute_params.py delete mode 100644 src/moderation_api/types/actions/execute_execute_response.py delete mode 100644 src/moderation_api/types/auth_create_response.py delete mode 100644 src/moderation_api/types/auth_retrieve_response.py delete mode 100644 src/moderation_api/types/author_create_params.py delete mode 100644 src/moderation_api/types/author_create_response.py delete mode 100644 src/moderation_api/types/author_delete_response.py delete mode 100644 src/moderation_api/types/author_list_params.py delete mode 100644 src/moderation_api/types/author_list_response.py delete mode 100644 src/moderation_api/types/author_retrieve_response.py delete mode 100644 src/moderation_api/types/author_update_params.py delete mode 100644 src/moderation_api/types/author_update_response.py create mode 100644 src/moderation_api/types/base64fileinput.py create mode 100644 src/moderation_api/types/basemodel.py delete mode 100644 src/moderation_api/types/content_submit_params.py delete mode 100644 src/moderation_api/types/content_submit_response.py delete mode 100644 src/moderation_api/types/queue/__init__.py delete mode 100644 src/moderation_api/types/queue/item_list_params.py delete mode 100644 src/moderation_api/types/queue/item_list_response.py delete mode 100644 src/moderation_api/types/queue/item_resolve_params.py delete mode 100644 src/moderation_api/types/queue/item_resolve_response.py delete mode 100644 src/moderation_api/types/queue/item_unresolve_params.py delete mode 100644 src/moderation_api/types/queue/item_unresolve_response.py delete mode 100644 src/moderation_api/types/queue_get_stats_params.py delete mode 100644 src/moderation_api/types/queue_get_stats_response.py delete mode 100644 src/moderation_api/types/queue_retrieve_response.py delete mode 100644 src/moderation_api/types/webhook_create_params.py delete mode 100644 src/moderation_api/types/webhook_create_response.py delete mode 100644 src/moderation_api/types/webhook_delete_response.py delete mode 100644 src/moderation_api/types/webhook_list_response.py delete mode 100644 src/moderation_api/types/webhook_retrieve_response.py delete mode 100644 src/moderation_api/types/webhook_secret_retrieve_response.py delete mode 100644 src/moderation_api/types/webhook_update_params.py delete mode 100644 src/moderation_api/types/webhook_update_response.py delete mode 100644 src/moderation_api/types/wordlist/__init__.py delete mode 100644 src/moderation_api/types/wordlist/word_add_params.py delete mode 100644 src/moderation_api/types/wordlist/word_add_response.py delete mode 100644 src/moderation_api/types/wordlist/word_remove_params.py delete mode 100644 src/moderation_api/types/wordlist/word_remove_response.py delete mode 100644 src/moderation_api/types/wordlist_get_embedding_status_response.py delete mode 100644 src/moderation_api/types/wordlist_list_response.py delete mode 100644 src/moderation_api/types/wordlist_retrieve_response.py delete mode 100644 src/moderation_api/types/wordlist_update_params.py delete mode 100644 src/moderation_api/types/wordlist_update_response.py create mode 100644 src/moderation_api/utils/__init__.py create mode 100644 src/moderation_api/utils/annotations.py create mode 100644 src/moderation_api/utils/datetimes.py create mode 100644 src/moderation_api/utils/dynamic_imports.py create mode 100644 src/moderation_api/utils/enums.py create mode 100644 src/moderation_api/utils/eventstreaming.py create mode 100644 src/moderation_api/utils/forms.py create mode 100644 src/moderation_api/utils/headers.py create mode 100644 src/moderation_api/utils/logger.py create mode 100644 src/moderation_api/utils/metadata.py create mode 100644 src/moderation_api/utils/queryparams.py create mode 100644 src/moderation_api/utils/requestbodies.py create mode 100644 src/moderation_api/utils/retries.py create mode 100644 src/moderation_api/utils/security.py create mode 100644 src/moderation_api/utils/serializers.py create mode 100644 src/moderation_api/utils/unions.py create mode 100644 src/moderation_api/utils/unmarshal_json_response.py create mode 100644 src/moderation_api/utils/url.py create mode 100644 src/moderation_api/utils/values.py create mode 100644 src/moderation_api/webhook_signature.py create mode 100644 src/moderation_api/webhooks.py create mode 100644 src/moderation_api/webhooksecret.py create mode 100644 src/moderation_api/wordlist.py create mode 100644 src/moderation_api/words.py delete mode 100644 tests/__init__.py delete mode 100644 tests/api_resources/__init__.py delete mode 100644 tests/api_resources/actions/__init__.py delete mode 100644 tests/api_resources/actions/test_execute.py delete mode 100644 tests/api_resources/queue/__init__.py delete mode 100644 tests/api_resources/queue/test_items.py delete mode 100644 tests/api_resources/test_account.py delete mode 100644 tests/api_resources/test_actions.py delete mode 100644 tests/api_resources/test_auth.py delete mode 100644 tests/api_resources/test_authors.py delete mode 100644 tests/api_resources/test_content.py delete mode 100644 tests/api_resources/test_queue.py delete mode 100644 tests/api_resources/test_webhook_secret.py delete mode 100644 tests/api_resources/test_webhooks.py delete mode 100644 tests/api_resources/test_wordlist.py delete mode 100644 tests/api_resources/wordlist/__init__.py delete mode 100644 tests/api_resources/wordlist/test_words.py delete mode 100644 tests/conftest.py delete mode 100644 tests/sample_file.txt delete mode 100644 tests/test_client.py delete mode 100644 tests/test_extract_files.py delete mode 100644 tests/test_files.py delete mode 100644 tests/test_models.py delete mode 100644 tests/test_qs.py delete mode 100644 tests/test_required_args.py delete mode 100644 tests/test_response.py delete mode 100644 tests/test_streaming.py delete mode 100644 tests/test_transform.py delete mode 100644 tests/test_utils/test_datetime_parse.py delete mode 100644 tests/test_utils/test_json.py delete mode 100644 tests/test_utils/test_path.py delete mode 100644 tests/test_utils/test_proxy.py delete mode 100644 tests/test_utils/test_typing.py delete mode 100644 tests/utils.py delete mode 100644 uv.lock diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 62c2d13..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -ARG VARIANT="3.9" -FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} - -USER vscode - -COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ - -RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index e01283d..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,43 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/debian -{ - "name": "Debian", - "build": { - "dockerfile": "Dockerfile", - "context": ".." - }, - - "postStartCommand": "uv sync --all-extras", - - "customizations": { - "vscode": { - "extensions": [ - "ms-python.python" - ], - "settings": { - "terminal.integrated.shell.linux": "/bin/bash", - "python.pythonPath": ".venv/bin/python", - "python.defaultInterpreterPath": ".venv/bin/python", - "python.typeChecking": "basic", - "terminal.integrated.env.linux": { - "PATH": "${env:PATH}" - } - } - } - }, - "features": { - "ghcr.io/devcontainers/features/node:1": {} - } - - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - - // Configure tool-specific properties. - // "customizations": {}, - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" -} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4d75d59 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# This allows generated code to be indexed correctly +*.py linguist-generated=false \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27965ee..dfb09de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,95 +1,25 @@ +# Maintained in moderation-api/sdk-generator (custom/python). Edits here are overwritten. name: CI on: push: - branches: - - '**' - - '!integrated/**' - - '!stl-preview-head/**' - - '!stl-preview-base/**' - - '!generated' - - '!codegen/**' - - 'codegen/stl/**' + branches: [main] pull_request: - branches-ignore: - - 'stl-preview-head/**' - - 'stl-preview-base/**' -jobs: - lint: - timeout-minutes: 10 - name: lint - runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Install uv - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 - with: - version: '0.10.2' - - - name: Install dependencies - run: uv sync --all-extras - - - name: Run lints - run: ./scripts/lint +permissions: + contents: read +jobs: build: - if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') - timeout-minutes: 10 - name: build - permissions: - contents: read - id-token: write - runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Install uv - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 + - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 with: - version: '0.10.2' - - - name: Install dependencies - run: uv sync --all-extras - - - name: Run build - run: uv build - - - name: Get GitHub OIDC Token - if: |- - github.repository == 'stainless-sdks/moderation-api-python' && - !startsWith(github.ref, 'refs/heads/stl/') - id: github-oidc - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: core.setOutput('github_token', await core.getIDToken()); - - - name: Upload tarball - if: |- - github.repository == 'stainless-sdks/moderation-api-python' && - !startsWith(github.ref, 'refs/heads/stl/') - env: - URL: https://pkg.stainless.com/s - AUTH: ${{ steps.github-oidc.outputs.github_token }} - SHA: ${{ github.sha }} - run: ./scripts/utils/upload-artifact.sh - - test: - timeout-minutes: 10 - name: test - runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Install uv - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 - with: - version: '0.10.2' - - - name: Bootstrap - run: ./scripts/bootstrap - - - name: Run tests - run: ./scripts/test + version: "0.9.13" + - run: uv sync --group dev + - run: uv run pylint --rcfile=pylintrc src + - run: uv run mypy src + - run: uv run pyright src + - run: uv build + - name: Import the package + run: uv run python -c "from moderation_api import ModerationAPI; from moderation_api.webhook_signature import construct_event" diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index fac4999..7ce47e1 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -1,28 +1,29 @@ -# This workflow is triggered when a GitHub release is created. -# It can also be run manually to re-publish to PyPI in case it failed for some reason. -# You can run this workflow by navigating to https://www.github.com/moderation-api/sdk-python/actions/workflows/publish-pypi.yml +# Maintained in moderation-api/sdk-generator (custom/python). Edits here are overwritten. name: Publish PyPI on: - workflow_dispatch: - release: types: [published] + workflow_dispatch: + +permissions: + contents: read jobs: publish: - name: publish runs-on: ubuntu-latest - steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Install uv - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 + - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 with: - version: '0.9.13' - - - name: Publish to PyPI + version: "0.9.13" + - name: Check the release tag matches pyproject.toml + if: github.event_name == 'release' run: | - bash ./bin/publish-pypi + version=$(sed -n 's/^version = "\(.*\)"$/\1/p' pyproject.toml | head -1) + if [ "${GITHUB_REF_NAME#v}" != "$version" ]; then + echo "::error::Release tag $GITHUB_REF_NAME does not match pyproject.toml version $version" + exit 1 + fi + - run: bash scripts/publish.sh env: PYPI_TOKEN: ${{ secrets.MODERATION_API_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml deleted file mode 100644 index 28dd0fe..0000000 --- a/.github/workflows/release-doctor.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Release Doctor -on: - pull_request: - branches: - - main - workflow_dispatch: - -jobs: - release_doctor: - name: release doctor - runs-on: ubuntu-latest - if: github.repository == 'moderation-api/sdk-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') - - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Check release environment - run: | - bash ./bin/check-release-environment - env: - PYPI_TOKEN: ${{ secrets.MODERATION_API_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.gitignore b/.gitignore index 3824f4c..d69301d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,13 @@ -.prism.log -.stdy.log -_dev - -__pycache__ -.mypy_cache - -dist - -.venv -.idea - +.venv/ +venv/ +src/*.egg-info/ +**/__pycache__/ +.pytest_cache/ +.python-version +.DS_Store +pyrightconfig.json +**/.speakeasy/temp/ +**/.speakeasy/logs/ +.speakeasy/reports .env -.envrc -codegen.log -Brewfile.lock.json +.env.local diff --git a/.python-version b/.python-version deleted file mode 100644 index 43077b2..0000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.9.18 diff --git a/.release-please-manifest.json b/.release-please-manifest.json deleted file mode 100644 index 507912c..0000000 --- a/.release-please-manifest.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - ".": "1.42.0" -} \ No newline at end of file diff --git a/.speakeasy/gen.lock b/.speakeasy/gen.lock new file mode 100644 index 0000000..ebbc3c7 --- /dev/null +++ b/.speakeasy/gen.lock @@ -0,0 +1,1651 @@ +lockVersion: 2.0.0 +id: 9620bfba-8df9-4d0e-8796-8423b39f97f0 +management: + docChecksum: 3320e8867e978fb3c091936a85b4b990 + docVersion: 1.1.0 + speakeasyVersion: internal + generationVersion: internal + releaseVersion: 2.0.0 + configChecksum: eeab91f42de23d385040d77b9e9d21de + repoURL: https://github.com/moderation-api/sdk-python + installationURL: https://github.com/moderation-api/sdk-python.git + published: true +features: + python: + additionalDependencies: 1.1.0 + additionalProperties: 1.0.2 + constsAndDefaults: 1.0.8 + core: 6.1.4 + defaultEnabledRetries: 0.2.0 + enumUnions: 0.1.1 + envVarSecurityUsage: 0.3.3 + examples: 3.0.4 + flatRequests: 1.0.1 + flattening: 3.1.1 + globalSecurity: 3.0.7 + globalSecurityCallbacks: 1.0.0 + globalSecurityFlattening: 1.0.0 + globalServerURLs: 3.2.1 + groups: 3.0.1 + ignores: 3.0.1 + methodArguments: 1.1.1 + nameOverrides: 3.0.3 + nullables: 1.0.2 + openEnums: 1.0.5 + pagination: 3.0.10 + responseFormat: 1.1.0 + retries: 3.1.2 + sdkHooks: 1.3.0 + unions: 3.1.12 +trackedFiles: + .gitattributes: + last_write_checksum: sha1:53134de3ada576f37c22276901e1b5b6d85cd2da + .vscode/settings.json: + last_write_checksum: sha1:f84632c81029fcdda8c3b0c768d02b836fc80526 + USAGE.md: + last_write_checksum: sha1:35241f8e427a0234c0001470e89c984c3b8518da + docs/errors/authenticationerror.md: + last_write_checksum: sha1:cdb9eaf19220813ba7f8e63c39807eec5fb8f95f + docs/errors/badrequesterror.md: + last_write_checksum: sha1:5c91688d22f321d3dcec650091bdcd61b01f34a4 + docs/errors/conflicterror.md: + last_write_checksum: sha1:f5e67eedc35f8a93c2ba9675a5bdf7ebd696e446 + docs/errors/internalservererror.md: + last_write_checksum: sha1:f17be7e390331a5523a66684f9681f907ce4f9b9 + docs/errors/notfounderror.md: + last_write_checksum: sha1:f1b0b0c97c7796ee821c6312960fac56404600c4 + docs/errors/permissiondeniederror.md: + last_write_checksum: sha1:5cbd352b22ff1b417d3529604fc6325e746a6df3 + docs/errors/ratelimiterror.md: + last_write_checksum: sha1:c3525f444f391db06263cef2cfe877ea8f015469 + docs/models/actionrequest.md: + last_write_checksum: sha1:eb97e24bc728f013d6a86346ad5edea0b5406373 + docs/models/actionstat.md: + last_write_checksum: sha1:dd4e71ad441cb258bb22a3823e1b334637635546 + docs/models/addwordlistwordsrequest.md: + last_write_checksum: sha1:67ecf74f25a496e8d3000e4903cc0de557d8ae18 + docs/models/addwordlistwordsrequestbody.md: + last_write_checksum: sha1:2c2d0ee0bddfafe1779220ee07aee53179e3b6fe + docs/models/addwordlistwordsresponse.md: + last_write_checksum: sha1:a8c13caecc06d1dccdd85db6ebd20a24cd42cfb7 + docs/models/adult.md: + last_write_checksum: sha1:194bc3084d569fc2bb26899942dee223ea69fc41 + docs/models/alcohol.md: + last_write_checksum: sha1:4d9eade3ffde7dc311481a156704ee4dc52715bd + docs/models/audiorequest.md: + last_write_checksum: sha1:c66c1f8a42e100aaa611130ab915647e54cdccf4 + docs/models/audioresponse.md: + last_write_checksum: sha1:9f7d896d79666963d3d884e6b12ffad79ce32c0d + docs/models/authenticationerrorcode.md: + last_write_checksum: sha1:67fd46b3807d5cbdbf5785b53b24e38785f31ae4 + docs/models/author.md: + last_write_checksum: sha1:534fc4c86f2ef71bf154b995d07ab1a00a86fd1b + docs/models/authorstatus.md: + last_write_checksum: sha1:7d7a67666c6ec82daa47ba9726c4d92792314ce1 + docs/models/badrequesterrorcode.md: + last_write_checksum: sha1:db01c352f4c47ed2fb309d384daada84fa288601 + docs/models/block.md: + last_write_checksum: sha1:a59f8f13228667d39879853ad05332c707c0afc8 + docs/models/cannabis.md: + last_write_checksum: sha1:f2837d187a268df000bcd19928b2b2e75cdf187c + docs/models/casebook.md: + last_write_checksum: sha1:06f2d38ae97a4fd3e1ac632ce85bd3ebd7e47fe8 + docs/models/casebookagreement.md: + last_write_checksum: sha1:7880dea0021ba88be8c71f1ca4f338d64389d312 + docs/models/casebookanswer.md: + last_write_checksum: sha1:679b43586bfba9db51ad0452ec8e51a1fcc37c88 + docs/models/channel.md: + last_write_checksum: sha1:1eccfaf5e0fe2775c92f478c52009a55745ae2bb + docs/models/channelcircumvention.md: + last_write_checksum: sha1:dfe4dde2cd4ad0ad751259472da6f28fbd89fd28 + docs/models/checkstatus.md: + last_write_checksum: sha1:36c36e20ac95b903bc8198bebaf7cbab2818f968 + docs/models/classifieroutput.md: + last_write_checksum: sha1:b019db75a1906cd78f17d0053e6376bbbc94ee10 + docs/models/codeabuse.md: + last_write_checksum: sha1:333ee43a88629531a32a0a4ec3f7e9a78e31eae4 + docs/models/comparator.md: + last_write_checksum: sha1:c8227a52509cefabe09f336ca28a3c4a0c6c785e + docs/models/compassioninsight.md: + last_write_checksum: sha1:3a6b37f719e4301f94db8afca1b6be115fc87bf2 + docs/models/conflicterrorcode.md: + last_write_checksum: sha1:39a3e6ae6711fc773739d8076effac117e23aca3 + docs/models/contentleafrequest.md: + last_write_checksum: sha1:1fa9fcef61c099d7ee1bac5a905e2ae1308a5fc9 + docs/models/contentleafresponse.md: + last_write_checksum: sha1:314309bbbe5ef8180a6bfd693cb36aed188ec628 + docs/models/contentrequest.md: + last_write_checksum: sha1:682c7c7f78778b8a08c6a64e9230dcbfe6a26a08 + docs/models/contentresponse.md: + last_write_checksum: sha1:8ab7d73288f6fb995609db9fdcbcd4044c5c1423 + docs/models/contenttype.md: + last_write_checksum: sha1:6284641dc647d602fd81adb8c7bb8925ec1cad70 + docs/models/createactionpositionrequest.md: + last_write_checksum: sha1:594af65ab48fe70cd7a81fbd9b58b5001296dc8c + docs/models/createactionpositionresponse.md: + last_write_checksum: sha1:846d64a52922d8ccab56b5c429a4f08c6a51ce11 + docs/models/createactionpossiblevaluerequest.md: + last_write_checksum: sha1:bd132f9936ee1ece77aad523f3738cc4b3536594 + docs/models/createactionpossiblevalueresponse.md: + last_write_checksum: sha1:71117a7df91dc02be4166cfa96774dc1cbf54a9c + docs/models/createactionqueuebehaviourrequest.md: + last_write_checksum: sha1:cd7d936482bd85528a81b9d7fd765285554577a5 + docs/models/createactionqueuebehaviourresponse.md: + last_write_checksum: sha1:661904e9ebeff0db3f2fbb3db788c585ec3abb52 + docs/models/createactionrequest.md: + last_write_checksum: sha1:aa2351b0ac02f2fb01dc9c49876286321a6cdf46 + docs/models/createactionresponse.md: + last_write_checksum: sha1:f72815df42b9c6296431df92f36615b4af72e131 + docs/models/createactiontyperequest.md: + last_write_checksum: sha1:ccc0359529a1e131695415109f2df690193430ec + docs/models/createactiontyperesponse.md: + last_write_checksum: sha1:0ae0e1015843f1c7d5610c8e95ec8a81a23369ca + docs/models/createauthormetadata.md: + last_write_checksum: sha1:ed6f2bb159071080f1ddde183b7e09a0033a693b + docs/models/createauthorrequest.md: + last_write_checksum: sha1:3940374edcfb8174b20a0345fd5ac382ac96636f + docs/models/createchannelcontenttype.md: + last_write_checksum: sha1:b3757eb17490479fcec9b45bfaa758ccc75ffe02 + docs/models/createchannelrequest.md: + last_write_checksum: sha1:379cd7d7d019aa16fa349d08fd340f9d761ef3d9 + docs/models/createchannelrequestbody.md: + last_write_checksum: sha1:a716f74f46bb226942ba825a5365762920dc0143 + docs/models/createprojectrequest.md: + last_write_checksum: sha1:3002ce5c3ad3795024e372370dfb648e4d437c49 + docs/models/createwebhookeventtyperequest.md: + last_write_checksum: sha1:6cdd6e585c43838aeabce0a0fd7b011d379d0f2a + docs/models/createwebhookeventtyperesponse.md: + last_write_checksum: sha1:0a604e50578f61dfea4d7248bd12e03b126428ea + docs/models/createwebhookpayloadversion.md: + last_write_checksum: sha1:fc2b9c87fa4930fea544ebf72a0491d9ee5c2c77 + docs/models/createwebhookrequest.md: + last_write_checksum: sha1:f39353f0fa80861118ecc5451344392aa9a037b6 + docs/models/createwebhookresponse.md: + last_write_checksum: sha1:d3e4e95d3258e1a3e2c6f9b647eb035089a64022 + docs/models/crypto.md: + last_write_checksum: sha1:db443484ad40bac81119ff67c24a0216b6e3c08d + docs/models/curiosityinsight.md: + last_write_checksum: sha1:1821f1fa75be68d3e3bbc18307d3db1b00ad6e29 + docs/models/currentproject.md: + last_write_checksum: sha1:2fd22e845cf2a3fac9dc06eb116e9bfaba9a2fe1 + docs/models/dailyreviewcount.md: + last_write_checksum: sha1:ea66f88370089f3db1268a31befa907ed03f9e21 + docs/models/dataaudiorequest.md: + last_write_checksum: sha1:1608d0ba903dd972968ef75d224d294ef9dc07ea + docs/models/dataimagerequest.md: + last_write_checksum: sha1:f318ff8465cb47eb9155cbbaaf13a1830d436db1 + docs/models/datatextrequest.md: + last_write_checksum: sha1:f4780379c485adfce973fca45cd4b3961d6be8e7 + docs/models/datavideorequest.md: + last_write_checksum: sha1:948cd457b3bc2ed3c352ae6d4331807883a349f9 + docs/models/deleteactionrequest.md: + last_write_checksum: sha1:10e359260d656c7788c04628c785c80f206d924a + docs/models/deleteactionresponse.md: + last_write_checksum: sha1:d8128ce2a339c801bfa0c91ed1b1243cc974a3d1 + docs/models/deleteauthorrequest.md: + last_write_checksum: sha1:93036cc1490c705a88d040faf0f7794b94f4b9be + docs/models/deleteauthorresponse.md: + last_write_checksum: sha1:470ec6b2dd0f1e3fa81c9b67bdf00524270de610 + docs/models/deletechannelrequest.md: + last_write_checksum: sha1:f15a63f69416a0228c603522ae4ec1e918deaae9 + docs/models/deletechannelresponse.md: + last_write_checksum: sha1:6d002e7c91c4ae391d1535613ec29479c99510d2 + docs/models/deleteprojectrequest.md: + last_write_checksum: sha1:0fae4904915f4471b38bd3c021789f7facf99fab + docs/models/deleteprojectresponse.md: + last_write_checksum: sha1:57597dca05a2fcd6f6b1ef129f7cd834cc5316ba + docs/models/deletewebhookrequest.md: + last_write_checksum: sha1:1c8d21f01f7423bb195f37fb7fae5c01c6a4b4be + docs/models/deletewebhookresponse.md: + last_write_checksum: sha1:9c274b660824d02e9fc18bab98ef18fd9027add1 + docs/models/drugs.md: + last_write_checksum: sha1:7161f3ce2338c523d90d1556437b4fab5eb5ac8d + docs/models/duplicatechannelrequest.md: + last_write_checksum: sha1:9af71095b84629af4e61f4bd845577c6f45cb186 + docs/models/duplicatechannelrequestbody.md: + last_write_checksum: sha1:8dacee6cf4bfdc132a374cadc95ece2dd989d8d8 + docs/models/duplicateprojectrequest.md: + last_write_checksum: sha1:cbfe1635be415e13dba4ca774d97a2e4172d5723 + docs/models/duplicateprojectrequestbody.md: + last_write_checksum: sha1:5234197ebdc408326170b4ce06b7a1ffa716f07a + docs/models/entities1.md: + last_write_checksum: sha1:b3d757af0318e20c42c7b1a5cda989ff2b74c1cf + docs/models/entities2.md: + last_write_checksum: sha1:50daf5790ec89fcf04576c5f80f5cc683d45d739 + docs/models/entitymatcheroutput.md: + last_write_checksum: sha1:981a6db66252c12b525c687b2784da8b6d6c8adc + docs/models/error.md: + last_write_checksum: sha1:ce64e8572d1e52bc72bb6026b926434f43c1fb5f + docs/models/errorissue.md: + last_write_checksum: sha1:87c23a8bfded35f36e16efd98e1adbc71f6c21df + docs/models/evaluation.md: + last_write_checksum: sha1:dc81ec6315567fca488efc6da9a16b4ca6899925 + docs/models/executeactionrequest.md: + last_write_checksum: sha1:4c6c1eb3e55d5125fc51d27c093cf522f672a6b3 + docs/models/executeactionresponse.md: + last_write_checksum: sha1:0295450e86664ce938a9b7bb2050e1e06b768543 + docs/models/extremism.md: + last_write_checksum: sha1:5fac6b4e17a881eba7ad0b594693f1db235283c0 + docs/models/facedetection.md: + last_write_checksum: sha1:4f04c10a8d8451004fb295916b8553f7ff59abab + docs/models/filter_.md: + last_write_checksum: sha1:8fcade253298593df1b69abb9152bde3a541803f + docs/models/filterlabel.md: + last_write_checksum: sha1:4facc20237eb19a90c6f4dc186fe78dc5a548f40 + docs/models/flaggedcontenttrend.md: + last_write_checksum: sha1:6f146aac7ca954fd2612722c5edecc8ad76808cb + docs/models/flaggingmode.md: + last_write_checksum: sha1:2b62179f3ffb684c32dbd22701c564701c6f608e + docs/models/gambling.md: + last_write_checksum: sha1:343bbfe2f0a7f12e50f78e0c6da7eda073de026d + docs/models/getaccountresponse.md: + last_write_checksum: sha1:7f88c1b881320e8cf86763cd05088e75e549a22e + docs/models/getactionposition.md: + last_write_checksum: sha1:f604aaad6f43b41e124be60b2036e160722a1f49 + docs/models/getactionpossiblevalue.md: + last_write_checksum: sha1:237ca6b0c406e49120acf0b4587bf5c1c71ac8d2 + docs/models/getactionqueuebehaviour.md: + last_write_checksum: sha1:3f1e1988781d3f52fec875063ee677bfba485db2 + docs/models/getactionrequest.md: + last_write_checksum: sha1:debf92a4e624cd34b5c0be89e484f58b6ee249a6 + docs/models/getactionresponse.md: + last_write_checksum: sha1:bad30829267fbc4ba772be0f905f4a6537ec164f + docs/models/getactiontype.md: + last_write_checksum: sha1:2c93577a65ac4a4e32889986e486b5efd0848646 + docs/models/getauthorrequest.md: + last_write_checksum: sha1:50606459859f66e5aad6dae61df11b93237c17ea + docs/models/getchannelrequest.md: + last_write_checksum: sha1:6f995844a8ce5166bcb1994a0d2ebe2458e98fa9 + docs/models/getprojectrequest.md: + last_write_checksum: sha1:36ed032fdb5e6f8a3f9be1ed427a1a17d58d0e2f + docs/models/getqueuecontenttype.md: + last_write_checksum: sha1:4cbf028f26c123a95a021932502a165966549d9f + docs/models/getqueuequeue.md: + last_write_checksum: sha1:c8ef905a7df4b648a35c47552d5366b7f1bb5451 + docs/models/getqueuerecommendationaction.md: + last_write_checksum: sha1:bc6702b02de201c183034d644f701aa1afbd6901 + docs/models/getqueuerequest.md: + last_write_checksum: sha1:1e58ca4255b8dfce11679ebdc0f425964c098161 + docs/models/getqueueresponse.md: + last_write_checksum: sha1:92fbe19cabcc7751157bd1043491e26bed955085 + docs/models/getqueuestatsrequest.md: + last_write_checksum: sha1:7973e8c487c678f623912fb1da347b9aa716dbc1 + docs/models/getqueuestatsresponse.md: + last_write_checksum: sha1:fcbe4d64e0d11aa962ba42bdad4fb15c09a08d48 + docs/models/getqueuetype.md: + last_write_checksum: sha1:3d139324f660493267bc2584e25f2954de263c4b + docs/models/getwebhookeventtype.md: + last_write_checksum: sha1:2c8b1d5bb03b136c6553b17e5bdd00b830e7a394 + docs/models/getwebhookpayloadversion.md: + last_write_checksum: sha1:60b692ba995e47aa4a6207788a11c118717b48b9 + docs/models/getwebhookrequest.md: + last_write_checksum: sha1:b0aec79d7152020859f0bf4d4fada8b5740d8704 + docs/models/getwebhookresponse.md: + last_write_checksum: sha1:aab2bc255c774bcb7e7c7f21fb349be95f021e98 + docs/models/getwebhooksecretresponse.md: + last_write_checksum: sha1:2b522faf79d9aeb7a83d515b6f9a9caf7e5c3e7e + docs/models/getwordlistembeddingstatusrequest.md: + last_write_checksum: sha1:fab592cd4a7264d7f4e193e2e58a46d63c128ec7 + docs/models/getwordlistembeddingstatusresponse.md: + last_write_checksum: sha1:8a14bd9f775b10af8cc179aa5e7a072775c4e956 + docs/models/getwordlistrequest.md: + last_write_checksum: sha1:e7b3e87e9c12fc9dd9f49d0f4d6a73a6470ef6ee + docs/models/getwordlistresponse.md: + last_write_checksum: sha1:47a1730eb606700aba8d367e0f3613f741c9a31b + docs/models/guideline.md: + last_write_checksum: sha1:52714c42bd978f68e39c4835c47aaf0349dcabd6 + docs/models/hate.md: + last_write_checksum: sha1:4b0739af2e5513062b1d5b7ea7491f33e6044528 + docs/models/illegaltrade.md: + last_write_checksum: sha1:7bd0e3701aa236dd887e8ddf2fa091711115d7e2 + docs/models/illicit.md: + last_write_checksum: sha1:d9e89a6d603564c397827f47a39fb5b4288a88f0 + docs/models/illicitalcohol.md: + last_write_checksum: sha1:fe87a95c77a23b354a405e201626f38de8c2102d + docs/models/illicitdrugs.md: + last_write_checksum: sha1:6f5e4943d74b05a27e188d98240e8f7181efecf0 + docs/models/illicitfirearms.md: + last_write_checksum: sha1:b3b74a44072073b961d92ea386ca1939d63425a5 + docs/models/illicitgambling.md: + last_write_checksum: sha1:f11dc12049e4595868fdeeb8d6ed6ad8e33218f4 + docs/models/illicittobacco.md: + last_write_checksum: sha1:ddf3b2d96a696cff6d42053e67a85988992f24fe + docs/models/imagerequest.md: + last_write_checksum: sha1:7abfa9d0eae07bb7cd24d02e0fc07eca35744873 + docs/models/imageresponse.md: + last_write_checksum: sha1:3564cb96f25e0d06378ea7497485adc67144edb3 + docs/models/insight.md: + last_write_checksum: sha1:b5caab43652173b483a5081e842c1391c4ad227e + docs/models/internalservererrorcode.md: + last_write_checksum: sha1:e10bd4ab0ff00c78ad783e9079edd382a2832cc3 + docs/models/isflagged.md: + last_write_checksum: sha1:5945535d190a3658b78ae9f24eedeb39c6fcc564 + docs/models/languageinsight.md: + last_write_checksum: sha1:24e3ed1573936724d99cb0927df88a73297763f4 + docs/models/listactionsdata.md: + last_write_checksum: sha1:383349fd9b27227df3de1b0012d815e5640b0b27 + docs/models/listactionsposition.md: + last_write_checksum: sha1:bf792b65ea690dd608d2840aa49e5eedc0b09309 + docs/models/listactionspossiblevalue.md: + last_write_checksum: sha1:f2c0a3f6f51162add8a05a518ab2d706508134e7 + docs/models/listactionsqueuebehaviour.md: + last_write_checksum: sha1:64482ff4675f11131fce0dc1279544a67a94ee3a + docs/models/listactionsrequest.md: + last_write_checksum: sha1:617592e0bcae925a030a2b267fa04c66173dc6e8 + docs/models/listactionsresponse.md: + last_write_checksum: sha1:58f2ed5f656ee6cf7cd83b0565dc21d1a23ac034 + docs/models/listactionsresponsebody.md: + last_write_checksum: sha1:d41707e051228afda35737c1f88b59eafd909845 + docs/models/listactionstype.md: + last_write_checksum: sha1:0acfca31cf44c1d30a705feb78bafa018407152c + docs/models/listauthorsrequest.md: + last_write_checksum: sha1:9e56c7bd72b8886a4700f6404428f480166b2445 + docs/models/listauthorsresponse.md: + last_write_checksum: sha1:54190f4a9d6e175bcb07946f024079d9d75f73a1 + docs/models/listauthorsresponsebody.md: + last_write_checksum: sha1:e13bbad68a5b35808b3d816918b4f6376d51b925 + docs/models/listauthorssortdirection.md: + last_write_checksum: sha1:3782d7e8077d7823c2be30217f7184df75eb5499 + docs/models/listchannelsrequest.md: + last_write_checksum: sha1:3588e16e2514f0c0101fee1af2a19f8a777669e1 + docs/models/listchannelsresponse.md: + last_write_checksum: sha1:597fababd7f7feea9d615789bf18e3b24ecaa6cd + docs/models/listchannelsresponsebody.md: + last_write_checksum: sha1:844cd001968fbb83868bc6fdf9bbed8b0ee08184 + docs/models/listprojectsrequest.md: + last_write_checksum: sha1:50a2e97251581e5444d463b3a5021543d2aedb53 + docs/models/listprojectsresponse.md: + last_write_checksum: sha1:da5ad1598b3d60e808ffd38508a531b26ba694ec + docs/models/listprojectsresponsebody.md: + last_write_checksum: sha1:a810cd219dae76dab77a95d485f8770245d0a21f + docs/models/listqueueitemsaction.md: + last_write_checksum: sha1:9abd259ba70374326c6017c1dd947f4b81548381 + docs/models/listqueueitemsdata.md: + last_write_checksum: sha1:2207fdc0a837fcf1d8c243051baddc03fcc48413 + docs/models/listqueueitemslabel.md: + last_write_checksum: sha1:0c4fcb2565877c1ba680c14f5931e031bb8d01e3 + docs/models/listqueueitemsrequest.md: + last_write_checksum: sha1:906c170681dae9725ee465ef305ac37cde2fbdfa + docs/models/listqueueitemsresponse.md: + last_write_checksum: sha1:e64d55d94911de55deceb1229745943edbd32f81 + docs/models/listqueueitemsresponsebody.md: + last_write_checksum: sha1:1d4ae5766877b9d20e15ba39ed9efdbb286bcc7d + docs/models/listqueueitemssortdirection.md: + last_write_checksum: sha1:ca8cd8f662954c9a0a95befa5bb22993fdd0a8f5 + docs/models/listqueueitemsstatus.md: + last_write_checksum: sha1:c02460d6c404820ed84c868bf8b5e0b438671c53 + docs/models/listwebhooksdata.md: + last_write_checksum: sha1:429716b17fa368d72aacf324cce21a446ec03b43 + docs/models/listwebhookseventtype.md: + last_write_checksum: sha1:c056714bd9a7d55dfbe514b72440bcc06f5ebf2e + docs/models/listwebhookspayloadversion.md: + last_write_checksum: sha1:1e5703533be94f7611a849073310f8472c877b45 + docs/models/listwebhooksrequest.md: + last_write_checksum: sha1:bb9bea952aa562a98b6c2e9a6dd4dea37e087714 + docs/models/listwebhooksresponse.md: + last_write_checksum: sha1:0d455d264c8f505d8ece9be38a7eabf0296241cc + docs/models/listwebhooksresponsebody.md: + last_write_checksum: sha1:4b69602067699ec7dd1c6cfb8806ec412c9233bd + docs/models/listwordlistsdata.md: + last_write_checksum: sha1:c494cff76fdb516204f6e9e8e3b7c31648d36f12 + docs/models/listwordlistsrequest.md: + last_write_checksum: sha1:c958bfb208d5b19743b33f13154de1ece52782f1 + docs/models/listwordlistsresponse.md: + last_write_checksum: sha1:42695dbad3b56ec850f5f607f68932de9ceaffac + docs/models/listwordlistsresponsebody.md: + last_write_checksum: sha1:ddc4e89135b32a4d76b2c044d04217d21367cae4 + docs/models/lowqualitycontent.md: + last_write_checksum: sha1:4f0be91d2004422b98e25125bb2c99d3b8dcbb0d + docs/models/matchedrule.md: + last_write_checksum: sha1:38b4b6b7b5a492a54f94b3c7aa28df84a5d2272b + docs/models/mediatype.md: + last_write_checksum: sha1:b01559d3bfd412e12037c883500e1b63e83584e7 + docs/models/meta.md: + last_write_checksum: sha1:16400e8b19a2b2e40958ca16043bb346fe91f802 + docs/models/metadata.md: + last_write_checksum: sha1:3c8b975ac90d4d1b22e32ce536262cb44b3a944d + docs/models/metadatafilter.md: + last_write_checksum: sha1:687f14cadd5f224288ea1092ad37ecf83a63347e + docs/models/metastatus.md: + last_write_checksum: sha1:ca093563fe510508ebda1b9e0bb7418c9760988c + docs/models/method.md: + last_write_checksum: sha1:7ad0046f6601782c6f7f7130e60f22bfefbdd020 + docs/models/metrics.md: + last_write_checksum: sha1:cdf314c36b96f304b7a118b4d68c6680af473922 + docs/models/modified.md: + last_write_checksum: sha1:f9beaef1f80aa3d97168d9486d263117541f4eb8 + docs/models/notfounderrorcode.md: + last_write_checksum: sha1:a27312bea3d9a56e398b702a77267fbf82522128 + docs/models/pagination.md: + last_write_checksum: sha1:4b3da29ce00e5aa486e79a68fdd00b9741432a6c + docs/models/path.md: + last_write_checksum: sha1:08671134358fba8ec3f55d1cfdc6b79bfbe5a58f + docs/models/permissiondeniederrorcode.md: + last_write_checksum: sha1:edbc722271cfa0fabce1468ca964302b35f0de7d + docs/models/personalinformation.md: + last_write_checksum: sha1:89bb52124c85a33aeb6f76541153c7cad633bfb9 + docs/models/personalstoryinsight.md: + last_write_checksum: sha1:dd1bed3be2352c6e9e996f757957c2cdd5ff45ff + docs/models/piimasking.md: + last_write_checksum: sha1:294d1f75f8af8eb9a2a971d1ce1b527a69e2474f + docs/models/policyrequest.md: + last_write_checksum: sha1:7e2a70b2e90bb3a3e861621208e4dc552b5ee176 + docs/models/policyresponse.md: + last_write_checksum: sha1:9d3a5ab4cff0968a140ab778127e35d24926286c + docs/models/political.md: + last_write_checksum: sha1:deb07e4e829c7c2994766f2f787272cf0fce34e3 + docs/models/profanity.md: + last_write_checksum: sha1:c7552728b6eef5b380a57e9a4c1c3216c5b74805 + docs/models/project.md: + last_write_checksum: sha1:9c90aec9e27e572b30379a6f44fc78d746dabf7e + docs/models/projectglobalflaggingmode.md: + last_write_checksum: sha1:e849c01b3b79fd799d4f74664020a06b849d7c2b + docs/models/projectwithkey.md: + last_write_checksum: sha1:0d6790cc417b2da344ea0e9aee416f221e8cdd57 + docs/models/projectwithkeyglobalflaggingmode.md: + last_write_checksum: sha1:0b0c5937fb850c9bbc165416821c2ee89b470698 + docs/models/promotion.md: + last_write_checksum: sha1:52b573d17f9566555dd3407a1ea04f1b7825fa15 + docs/models/publicauthor.md: + last_write_checksum: sha1:d921dc5c5445bb99f1e8f616aaf400ef36ef604b + docs/models/ratelimiterrorcode.md: + last_write_checksum: sha1:d3d0923b8a898b1d740ce6bfc04a262f8e7666d4 + docs/models/reasoncode.md: + last_write_checksum: sha1:3ed986792eb1e1d34f25c9e4443d61ca91c03d45 + docs/models/reasoncodeenum.md: + last_write_checksum: sha1:fba885e8add400d72b780c52abb91ffd8a5fe95f + docs/models/recommendation.md: + last_write_checksum: sha1:b81db234abae913cb2f60001d2b2c0311a98a6b9 + docs/models/religion.md: + last_write_checksum: sha1:5759f26a6aa19140b1595347d3284cfd22fa7a27 + docs/models/removewordlistwordsrequest.md: + last_write_checksum: sha1:d988db8bd18157da2ffdeadf3026a65b4900f0fa + docs/models/removewordlistwordsresponse.md: + last_write_checksum: sha1:5efef48f1d38df8e532615c71c4f05de2b0e28f8 + docs/models/resolvequeueitemrequest.md: + last_write_checksum: sha1:3594c3dc472ebd170c6af8b99e52bf2c8dd99418 + docs/models/resolvequeueitemrequestbody.md: + last_write_checksum: sha1:d9aa0601044346030a10359a0e1c42108aa116c7 + docs/models/resolvequeueitemresponse.md: + last_write_checksum: sha1:556b1fe9662ce8aec24d6bb251a707a3f0a8cd22 + docs/models/respectinsight.md: + last_write_checksum: sha1:4b28f5bb242fad6e0b615fff2c6845196b98f4f4 + docs/models/reviewstats.md: + last_write_checksum: sha1:31e64b9015e09f258b913c6b3771d19f2387a60b + docs/models/riskevaluation.md: + last_write_checksum: sha1:fd518fc81e32b686c95cbd4d16d47888d3b83f04 + docs/models/scores.md: + last_write_checksum: sha1:c83b10da5cf13a45af72e7fbd45ce5b1a17bb482 + docs/models/security.md: + last_write_checksum: sha1:ecd84303a5deb757a5d13d9ff6b21a728d33864f + docs/models/selfharm.md: + last_write_checksum: sha1:6320028f05f531572c730808b2938dcf1cde7ebe + docs/models/selfpromotion.md: + last_write_checksum: sha1:94f78a81d2ed320750b0c2986f0df396aeecbe6c + docs/models/sentimentinsight.md: + last_write_checksum: sha1:0a570e388a6d4a35760cd0f7a773229f04c1cee3 + docs/models/sexual.md: + last_write_checksum: sha1:28e303ba0e257bf1560ae49d28ca80f6368e2cc2 + docs/models/sexualminors.md: + last_write_checksum: sha1:b8864fba7dba41a103f9f214378c8fbfd64064c3 + docs/models/sexualsolicitation.md: + last_write_checksum: sha1:2f1982d8ef92a37a33e18531bfabf048d983fb61 + docs/models/signalsrequest.md: + last_write_checksum: sha1:6f00ff737cabd9e206a803817ffd66812d9ca382 + docs/models/signalsresponse.md: + last_write_checksum: sha1:328f5e9dbbfa3c89008ee8e529c0dc32988aa39f + docs/models/sortby.md: + last_write_checksum: sha1:7502f304bf2afedf61d3fff2929cd1be44f0fba4 + docs/models/sortfield.md: + last_write_checksum: sha1:335359823a0169c262fbc1effe9a41872ae582e7 + docs/models/spam.md: + last_write_checksum: sha1:0d895e3de8859c08e407860e45082d61a58ff734 + docs/models/status.md: + last_write_checksum: sha1:109a5dc9dd757318d08f390859a924ccc86654f0 + docs/models/submitcontentbehavior.md: + last_write_checksum: sha1:4b27b4fa8c4cf72cdc494a75de289db823f521ad + docs/models/submitcontentblock.md: + last_write_checksum: sha1:7f268c9317eebdff8fe339b19a08cc37366c1e3d + docs/models/submitcontentbrandimpersonation.md: + last_write_checksum: sha1:be69cae997d0a7a8ee1f603f20a89db0f29d63bd + docs/models/submitcontentclientaction.md: + last_write_checksum: sha1:d622a0faa416082a35c0b884186a80536e74dba5 + docs/models/submitcontentlabel.md: + last_write_checksum: sha1:930bccdc5d42d90ff4b515549d37ff8565f67500 + docs/models/submitcontentmatch.md: + last_write_checksum: sha1:0274c97db44347138a4495210be37b88c4f87bcb + docs/models/submitcontentmetatype.md: + last_write_checksum: sha1:5a92d030516d14406f717f66be7340fb80a1cbbd + docs/models/submitcontentobject.md: + last_write_checksum: sha1:e69f7ded03211f298df2ffbd5fb185c31901ad49 + docs/models/submitcontentrecommendationaction.md: + last_write_checksum: sha1:06144cc93759541217b52422e5824aea219a3b79 + docs/models/submitcontentrequest.md: + last_write_checksum: sha1:1ce333377608cd5dd272f90b4c88f2898fef28da + docs/models/submitcontentresponse.md: + last_write_checksum: sha1:5ee6a1d316786c81e73bef8dfbd3dc83ef0bd4e2 + docs/models/submitcontenttrustlevel.md: + last_write_checksum: sha1:05eb0efa74f798971e34f40eecf67eb9bfe9e9c3 + docs/models/textrequest.md: + last_write_checksum: sha1:4529f2616ee38260d041098b726352fc28bfad4f + docs/models/textresponse.md: + last_write_checksum: sha1:d4868dde320b6bc60ed18585dacab8d7ad8d2863 + docs/models/threat.md: + last_write_checksum: sha1:7a7af15a688027b76b2d8afc59b58c2ccd072478 + docs/models/tobacco.md: + last_write_checksum: sha1:ad150ed49039de6563644441c9afbb2a00efc6e1 + docs/models/topaction.md: + last_write_checksum: sha1:88f8810aaa151cea4e4d3bbc62244c7c7d93c070 + docs/models/topic.md: + last_write_checksum: sha1:eefe5aa4e12093288213aa8cbec131a61cad6eb9 + docs/models/topreviewer.md: + last_write_checksum: sha1:f9e07f2a9b03bb833b0a5d28fc52a88ae8fee9e9 + docs/models/toxicity.md: + last_write_checksum: sha1:57689c8564fa374c992a5785fa8d20154dbdad22 + docs/models/transcriptionquality.md: + last_write_checksum: sha1:47dff7ac460bd5095b55d9376c48b6c011346e57 + docs/models/trends.md: + last_write_checksum: sha1:74f38d5e4d17a22345dac8bcb0a3f52f9c4027a0 + docs/models/trustlevel.md: + last_write_checksum: sha1:59c7fab1ac94b8a21cebb6f15e753672b4ac8f1f + docs/models/unicodespoofing.md: + last_write_checksum: sha1:f226ab0193558bada5d79ae06526d468409f932e + docs/models/unresolvequeueitemrequest.md: + last_write_checksum: sha1:3613c863d0130e2c32107ca84a294e1fcd4c4b1e + docs/models/unresolvequeueitemrequestbody.md: + last_write_checksum: sha1:c15925f824a99518690275583046a1a12bd8ac6f + docs/models/unresolvequeueitemresponse.md: + last_write_checksum: sha1:af305267a7668adac330d292a9e5b0eaeec431e3 + docs/models/updateactionpositionrequest.md: + last_write_checksum: sha1:8e7aaf03c455f6bf0bee5ee8d21e0bdd013a31fa + docs/models/updateactionpositionresponse.md: + last_write_checksum: sha1:ea29eb26f7cf344540de9944d779be2a7bed28a2 + docs/models/updateactionpossiblevaluerequest.md: + last_write_checksum: sha1:d0ec3fcfd2c3ee747480597ccac94f885c59bf8d + docs/models/updateactionpossiblevalueresponse.md: + last_write_checksum: sha1:e06918a5d4a1673d8d811193ae60b4f84ef8edef + docs/models/updateactionqueuebehaviourrequest.md: + last_write_checksum: sha1:7c63837e29e3fd26901b9fcbda52dbcfa3b309b4 + docs/models/updateactionqueuebehaviourresponse.md: + last_write_checksum: sha1:205931c65c91088e37bb6149a19dda68e5707747 + docs/models/updateactionrequest.md: + last_write_checksum: sha1:4d5f7d7b5a44d7836c6fabb7e47a89549df5d643 + docs/models/updateactionrequestbody.md: + last_write_checksum: sha1:7e85045ea6a3b5572f4adacca145739c4162bb04 + docs/models/updateactionresponse.md: + last_write_checksum: sha1:14a10e3efaa503c5b9c2984f16d50f8786f58205 + docs/models/updateactiontyperequest.md: + last_write_checksum: sha1:d8c6795362df3a54d4329fdb990f5ee9495b899d + docs/models/updateactiontyperesponse.md: + last_write_checksum: sha1:fb4dae33d85ade070e29724790a85f665693ab16 + docs/models/updateauthormetadata.md: + last_write_checksum: sha1:315bcc1ec596952c947cf2051dd69def9007069c + docs/models/updateauthorrequest.md: + last_write_checksum: sha1:7d8a9d5d028ec52164f74e03e0d67459a4cf75e2 + docs/models/updateauthorrequestbody.md: + last_write_checksum: sha1:16944b8b257aff425b8eb79ef1e22d0832801e55 + docs/models/updatechannelcontenttype.md: + last_write_checksum: sha1:d5df8d7e3a52cbeca26537120140f16f6de2a781 + docs/models/updatechannelflaggingmode.md: + last_write_checksum: sha1:ddea7a5e5f89b27ea30d86216f9e462d080bce62 + docs/models/updatechannelrequest.md: + last_write_checksum: sha1:144d65c5086faca93044a6558f1edeff6b9d99ee + docs/models/updatechannelrequestbody.md: + last_write_checksum: sha1:585a8c8a883776ed5754354ecbebc9eb6edd3cfb + docs/models/updatechanneltranscriptionquality.md: + last_write_checksum: sha1:0b7a22b990ca2c517d4053d1905a0f1241ece6a1 + docs/models/updateprojectglobalflaggingmode.md: + last_write_checksum: sha1:f0910e3537462d37015d98f2d15000a921ee0e8f + docs/models/updateprojectrequest.md: + last_write_checksum: sha1:2763ac4f30963d67fd20e50b819e14c58d803103 + docs/models/updateprojectrequestbody.md: + last_write_checksum: sha1:e80979b7c22958d3814e861592c3462beb6503a2 + docs/models/updatewebhookeventtyperequest.md: + last_write_checksum: sha1:cc9b3d6c2c854021ccdea11088d096805575b7f5 + docs/models/updatewebhookeventtyperesponse.md: + last_write_checksum: sha1:9a54b94a3f17baae6dd6d30679556c5790f79589 + docs/models/updatewebhookpayloadversion.md: + last_write_checksum: sha1:3fb625590fd0e45de0ed945c6de287de6a411202 + docs/models/updatewebhookrequest.md: + last_write_checksum: sha1:2f856f2d3c9e6e791ff44fe99a0ad3cdc5b7a76a + docs/models/updatewebhookrequestbody.md: + last_write_checksum: sha1:b7a27316af235c9c6cab03dd8424da0d42a5d95d + docs/models/updatewebhookresponse.md: + last_write_checksum: sha1:f9e9209f9c2ae24296c9349c891ef2bc2592f167 + docs/models/updatewordlistrequest.md: + last_write_checksum: sha1:74d6cdcec18b85512667c324f8fe0ed950f650dc + docs/models/updatewordlistrequestbody.md: + last_write_checksum: sha1:3f7ae7eee8f69e4f566d07bee25f7a4e9700b648 + docs/models/updatewordlistresponse.md: + last_write_checksum: sha1:9b0be88824c40c531fd9a28186deb81dd57a5714 + docs/models/urlmasking.md: + last_write_checksum: sha1:1551b67cf8f5c707d417b0c8e81e65aef6e8a672 + docs/models/urlrisk.md: + last_write_checksum: sha1:a467fd73c14b6e96363a76026e147262445a8043 + docs/models/utils/retryconfig.md: + last_write_checksum: sha1:562c0f21e308ad10c27f85f75704c15592c6929d + docs/models/value.md: + last_write_checksum: sha1:6559385f850170d1c5a3d0d39e0f16ccdebbf707 + docs/models/verdict.md: + last_write_checksum: sha1:cfd45be70ec09d5a0b63b1dc6e464b045cceb4a8 + docs/models/videorequest.md: + last_write_checksum: sha1:d2b29e49555d9e547978deba766ff723eed0b67c + docs/models/videoresponse.md: + last_write_checksum: sha1:3cf75dcd72f529b7330c56f232011269b2dc644b + docs/models/violence.md: + last_write_checksum: sha1:0eeff10131ccfab92ffcf4a9be115d012d8cb2f4 + docs/models/weapons.md: + last_write_checksum: sha1:7de86b3e6826e5e810a5048ff36db2ad4a6ad2bc + docs/models/withinunit.md: + last_write_checksum: sha1:ca17d9ca4c86ddfec9b1808b0c10228fd81ac2c3 + docs/sdks/account/README.md: + last_write_checksum: sha1:db3a8dbca78efa43f42d9f23e1b1547e6c1e0a19 + docs/sdks/actions/README.md: + last_write_checksum: sha1:c4460f2cc7cd099c050ae82a392a3608a785235b + docs/sdks/authors/README.md: + last_write_checksum: sha1:6ba4a26ef3b113814303e844a05405d13f04246e + docs/sdks/channels/README.md: + last_write_checksum: sha1:01c0e51bc630e4f5cb937e39faf535e894547fd3 + docs/sdks/contentsdk/README.md: + last_write_checksum: sha1:f6e58b5addb3276b3713196774eebe39a5a81994 + docs/sdks/items/README.md: + last_write_checksum: sha1:0e30c68ea1709057ded218b75d981f1384dcf717 + docs/sdks/projects/README.md: + last_write_checksum: sha1:a256679120b221c4cabb8026f7ccceaea7ca22c7 + docs/sdks/queuesdk/README.md: + last_write_checksum: sha1:46831280e88b175731c5445b59ebf979f2f19a97 + docs/sdks/webhooks/README.md: + last_write_checksum: sha1:8b6a1a57557bd3ade5be000a4c86d950886c5368 + docs/sdks/webhooksecret/README.md: + last_write_checksum: sha1:b725388cf13440a7c5b9c41678fcdf698f04acd5 + docs/sdks/wordlist/README.md: + last_write_checksum: sha1:3c9e2a62bb98649066c4d11eb9398e6ae9d00095 + docs/sdks/words/README.md: + last_write_checksum: sha1:5ed80bd0483f466dbbde1bb12fdbedd35db3fe93 + py.typed: + last_write_checksum: sha1:8efc425ffe830805ffcc0f3055871bdcdc542c60 + pylintrc: + last_write_checksum: sha1:cb40e5fd2885dcf20f7c572a590b2656efe282a0 + pyproject.toml: + last_write_checksum: sha1:27ffe316031b4b01b46112d983d5359914d4fb29 + scripts/prepare_readme.py: + last_write_checksum: sha1:7b330dc0eb57c916a1158fd90c6227f8dc517c83 + scripts/publish.sh: + last_write_checksum: sha1:b290b25b36dca3d5eb1a2e66a2e1bcf2e7326cf3 + src/moderation_api/__init__.py: + last_write_checksum: sha1:da077c0bdfcef64a4a5aea91a17292f72fa2b088 + src/moderation_api/_hooks/__init__.py: + last_write_checksum: sha1:45a380490e968d8d95994dd97554ac60eeb6ae60 + src/moderation_api/_hooks/sdkhooks.py: + last_write_checksum: sha1:5eee8c6fd5883db601d25d7cb29e251dcb436c24 + src/moderation_api/_hooks/types.py: + last_write_checksum: sha1:bb2a67c388c536a910bd205bd9582065c5d2de39 + src/moderation_api/_version.py: + last_write_checksum: sha1:add169876c532d6f961eb23dc56c92cb3cdc02e2 + src/moderation_api/account.py: + last_write_checksum: sha1:4fbbbf7834d250353a52cb7d0a9752738c6ccea8 + src/moderation_api/actions.py: + last_write_checksum: sha1:9125beb7c8ea7c0e0aa6f7b3355ad963f9ba8ef0 + src/moderation_api/authors.py: + last_write_checksum: sha1:846c678bfa45f5ee6afdc79ece4fb62fe87b639d + src/moderation_api/basesdk.py: + last_write_checksum: sha1:026fbd5780e5d5c70d00c0e8503e124ce2cf9779 + src/moderation_api/channels.py: + last_write_checksum: sha1:427f7ba3e717e4cdadbff8bca70a602c9a2a9c81 + src/moderation_api/content_sdk.py: + last_write_checksum: sha1:146690a11ff1ecb2f3e99d3026234c84f21beb69 + src/moderation_api/errors/__init__.py: + last_write_checksum: sha1:043e566785713d1aee1f2709cf3083cb51df01f6 + src/moderation_api/errors/authenticationerror.py: + last_write_checksum: sha1:25027f18db3e06adcae00114d10d346e19c47914 + src/moderation_api/errors/badrequesterror.py: + last_write_checksum: sha1:2956ade602678ebaaf6dd406bb40e3435f8af599 + src/moderation_api/errors/conflicterror.py: + last_write_checksum: sha1:752e430486a83a8cc9b48bbf13a507dacc9607dd + src/moderation_api/errors/internalservererror.py: + last_write_checksum: sha1:a0ee3fc3c9cb5a1f5928573c28182fa941130dbd + src/moderation_api/errors/moderationapidefaulterror.py: + last_write_checksum: sha1:8791d1fd68f77ac40a27c05a6aea5dc2ccb78271 + src/moderation_api/errors/moderationapierror.py: + last_write_checksum: sha1:a3ce6cfba08e226380cd903cd303eb395b70eb0a + src/moderation_api/errors/no_response_error.py: + last_write_checksum: sha1:7f326424a7d5ae1bcd5c89a0d6b3dbda9138942f + src/moderation_api/errors/notfounderror.py: + last_write_checksum: sha1:8e16b46d89276693c817e89e107c72124c42fcd2 + src/moderation_api/errors/permissiondeniederror.py: + last_write_checksum: sha1:45e587a5860c39b85743a6cfaadc10418a85f11a + src/moderation_api/errors/ratelimiterror.py: + last_write_checksum: sha1:b21ea5a561809730626bedac760606e2150706bf + src/moderation_api/errors/responsevalidationerror.py: + last_write_checksum: sha1:4b7e233bbe80ffc0e929120c19ac748eb857f92f + src/moderation_api/httpclient.py: + last_write_checksum: sha1:5e55338d6ee9f01ab648cad4380201a8a3da7dd7 + src/moderation_api/items.py: + last_write_checksum: sha1:7dc42580494127c2b375b29304988888151494b9 + src/moderation_api/models/__init__.py: + last_write_checksum: sha1:c4fa9982b774e655159dda1956668dbf9b0e43b3 + src/moderation_api/models/addwordlistwordsop.py: + last_write_checksum: sha1:c5c1894b1e51c5ce56a5d8646d45b4d3c1974095 + src/moderation_api/models/authenticationerror.py: + last_write_checksum: sha1:63c299ddbc05b97c083d497836594c1619815fab + src/moderation_api/models/badrequesterror.py: + last_write_checksum: sha1:98b0f2c371d65c3a66ae33e97a68bdaaf3798d15 + src/moderation_api/models/channel.py: + last_write_checksum: sha1:3f3a27e1b1c4d719244f22748c0af2b043729b27 + src/moderation_api/models/conflicterror.py: + last_write_checksum: sha1:43e729d2dcf16ae6791a6c4ee1b5cab373611296 + src/moderation_api/models/createactionop.py: + last_write_checksum: sha1:3fb83d6d1beba89fb37af052a71af1147ac69358 + src/moderation_api/models/createauthorop.py: + last_write_checksum: sha1:e92acefb61096af66649fd37c23a2b72ef6046c9 + src/moderation_api/models/createchannelop.py: + last_write_checksum: sha1:19f3857bbc2c1d66f906465518a4ae26e34ea666 + src/moderation_api/models/createprojectop.py: + last_write_checksum: sha1:dc16578de2a09db059dccfe18712b42d767f1a62 + src/moderation_api/models/createwebhookop.py: + last_write_checksum: sha1:199fce3d361060801ee5d5cade7ae2e1aeab5aad + src/moderation_api/models/deleteactionop.py: + last_write_checksum: sha1:e67d2d3bfe957f5fca569e2f7e525db0bc94dd34 + src/moderation_api/models/deleteauthorop.py: + last_write_checksum: sha1:771ce954981d79a62afd9c8eec14eec997b0b45b + src/moderation_api/models/deletechannelop.py: + last_write_checksum: sha1:b1798fa3342ed1d046d89f40ae7f72a41c6a9e87 + src/moderation_api/models/deleteprojectop.py: + last_write_checksum: sha1:2e84a7999f679a87ef393eb0c44805bd687d2e7d + src/moderation_api/models/deletewebhookop.py: + last_write_checksum: sha1:a939d8c4b658243b85a6a9c593a08df239a14ca2 + src/moderation_api/models/duplicatechannelop.py: + last_write_checksum: sha1:ac62e5fe7064a3b241935390d57002ed98bcc1e1 + src/moderation_api/models/duplicateprojectop.py: + last_write_checksum: sha1:6fe73d3171359ec8c6cb22a531ccf28a6bbf291f + src/moderation_api/models/errorissue.py: + last_write_checksum: sha1:12ac40f4f3533dad0cb604a67f2f70e6b07254f0 + src/moderation_api/models/executeactionop.py: + last_write_checksum: sha1:923f75e923f5e10a1a0b3ff02d9bc611b63903c6 + src/moderation_api/models/getaccountop.py: + last_write_checksum: sha1:a47c301c8eefb64ef6468497247da0660b5faeb3 + src/moderation_api/models/getactionop.py: + last_write_checksum: sha1:dca3e65624e7408e9a0c9795e31c6dfe09974e85 + src/moderation_api/models/getauthorop.py: + last_write_checksum: sha1:a84b1ba037e1ae2a831d3f959aca22c3c3b01cbb + src/moderation_api/models/getchannelop.py: + last_write_checksum: sha1:1ee37733cbe95895109c9e6c1d38b6d6bfd75b68 + src/moderation_api/models/getprojectop.py: + last_write_checksum: sha1:66b15cd300d9810dbb258211e2bb77f7d08fb2bd + src/moderation_api/models/getqueueop.py: + last_write_checksum: sha1:e586fe5237231be4b08ab14f1a071f9bf9c4003b + src/moderation_api/models/getqueuestatsop.py: + last_write_checksum: sha1:32a13c31e6639df3c760e2a4e1f8257aba89ae20 + src/moderation_api/models/getwebhookop.py: + last_write_checksum: sha1:cd0f09ad7fcd112799150e749d2f72fe4481ad39 + src/moderation_api/models/getwebhooksecretop.py: + last_write_checksum: sha1:713a5a666cbc7cfb8066e319a043edc6958df653 + src/moderation_api/models/getwordlistembeddingstatusop.py: + last_write_checksum: sha1:4545450042d151c39d73e754b2dfe7fb349176b5 + src/moderation_api/models/getwordlistop.py: + last_write_checksum: sha1:74d2fcc7cb7743bc89a72067849df48de349afbd + src/moderation_api/models/internalservererror.py: + last_write_checksum: sha1:aa2999b9b9209cf1a43bdf0d065ccf890f79007b + src/moderation_api/models/listactionsop.py: + last_write_checksum: sha1:4339bba35f07346cac5fa6a11c458290f0b5c247 + src/moderation_api/models/listauthorsop.py: + last_write_checksum: sha1:80b1a4e27d70205191258717c110f37c2444f73b + src/moderation_api/models/listchannelsop.py: + last_write_checksum: sha1:cf3ab07af3ad06ae35fa6e3ed6783f292d77e914 + src/moderation_api/models/listprojectsop.py: + last_write_checksum: sha1:71cf5c9825f4f7e2a6aa31149946079f7a45cb08 + src/moderation_api/models/listqueueitemsop.py: + last_write_checksum: sha1:dcc1020c38bf1082f0f11d81ff0fbc1e4d2a51ef + src/moderation_api/models/listwebhooksop.py: + last_write_checksum: sha1:50a7e8633bf1620d954efa3a172565ab4987e62a + src/moderation_api/models/listwordlistsop.py: + last_write_checksum: sha1:baf3d8259aace9098bb4d9f2c935f30e1cafe11f + src/moderation_api/models/notfounderror.py: + last_write_checksum: sha1:a44e99269c4be4942eb84a4d9fa364a9b17f90db + src/moderation_api/models/pagination.py: + last_write_checksum: sha1:b39970391aa653cb9cf835c0fc185bad7117d86a + src/moderation_api/models/permissiondeniederror.py: + last_write_checksum: sha1:f65ba7d760ec371c17688cf7c1de4ba9bd2c7a15 + src/moderation_api/models/project.py: + last_write_checksum: sha1:8b63414b298ed52321834567de4da1f6becb40e9 + src/moderation_api/models/projectwithkey.py: + last_write_checksum: sha1:6517b6324c0193f660d410d1a9d0b392e3ff6203 + src/moderation_api/models/publicauthor.py: + last_write_checksum: sha1:70595f937559ff2ecd1eeca6f0839957cf92bac2 + src/moderation_api/models/ratelimiterror.py: + last_write_checksum: sha1:0e2a9611cd0b938fcdcab514d2f667fb6286bfe1 + src/moderation_api/models/removewordlistwordsop.py: + last_write_checksum: sha1:be5d8c7bb7363b6b5e7ce7f3735f54bdeec5abb7 + src/moderation_api/models/resolvequeueitemop.py: + last_write_checksum: sha1:ae162695dadc0b0fc4eced69b05a871624bd4b55 + src/moderation_api/models/security.py: + last_write_checksum: sha1:dc71db919301e2ed408358e7b2066a8cb30c581b + src/moderation_api/models/submitcontentop.py: + last_write_checksum: sha1:a6fba99be7e4e3c70d6a4e9687802de74471c7da + src/moderation_api/models/unresolvequeueitemop.py: + last_write_checksum: sha1:914c3ca13be46eaad14be65b5f46192bdd295bb9 + src/moderation_api/models/updateactionop.py: + last_write_checksum: sha1:6c99e2fc62056160c68f81631c503d6a1eb5f8b0 + src/moderation_api/models/updateauthorop.py: + last_write_checksum: sha1:72485f29fbcab4044a845c3151082ff0bac187e2 + src/moderation_api/models/updatechannelop.py: + last_write_checksum: sha1:444b3b5c68884d846d8db28f07b031fdc4a27c76 + src/moderation_api/models/updateprojectop.py: + last_write_checksum: sha1:700ccc086f59950dcbbf78d2f52796064fa63c12 + src/moderation_api/models/updatewebhookop.py: + last_write_checksum: sha1:b46ec55d7634cc70bf7bc79060df40748936e190 + src/moderation_api/models/updatewordlistop.py: + last_write_checksum: sha1:64915feada242e2965189c5a9715226ca5271adf + src/moderation_api/projects.py: + last_write_checksum: sha1:a4aece7aba0496936fd398d185c0de035fe4828f + src/moderation_api/py.typed: + last_write_checksum: sha1:8efc425ffe830805ffcc0f3055871bdcdc542c60 + src/moderation_api/queue_sdk.py: + last_write_checksum: sha1:e7bce884630981420dc919f0e256f86e0b7e48f5 + src/moderation_api/sdk.py: + last_write_checksum: sha1:580ff8ec9ef32cc4f9a4e488a4a4f319f3a0304c + src/moderation_api/sdkconfiguration.py: + last_write_checksum: sha1:86812fbbd7c1369f15f5163bf60163f933b1012e + src/moderation_api/types/__init__.py: + last_write_checksum: sha1:f9ad14217f832e74f594285960125add50324be9 + src/moderation_api/types/base64fileinput.py: + last_write_checksum: sha1:1522687ae3398374c35710cad993a6e82b5ab99d + src/moderation_api/types/basemodel.py: + last_write_checksum: sha1:10d84aedeb9d35edfdadf2c3020caa1d24d8b584 + src/moderation_api/utils/__init__.py: + last_write_checksum: sha1:b9cb5851a618ff388d0c56127f6a7c595aa51978 + src/moderation_api/utils/annotations.py: + last_write_checksum: sha1:a4824ad65f730303e4e1e3ec1febf87b4eb46dbc + src/moderation_api/utils/datetimes.py: + last_write_checksum: sha1:8d08dfebf00458efa19e0c19455eddcc9f14b8ca + src/moderation_api/utils/dynamic_imports.py: + last_write_checksum: sha1:a1940c63feb8eddfd8026de53384baf5056d5dcc + src/moderation_api/utils/enums.py: + last_write_checksum: sha1:bc8c3c1285ae09ba8a094ee5c3d9c7f41fa1284d + src/moderation_api/utils/eventstreaming.py: + last_write_checksum: sha1:7d1dc68f8b48486ab646653aa05cc38752e1f912 + src/moderation_api/utils/forms.py: + last_write_checksum: sha1:dcf527960992b8baef3b21937c7e6c4e652630c0 + src/moderation_api/utils/headers.py: + last_write_checksum: sha1:7c6df233ee006332b566a8afa9ce9a245941d935 + src/moderation_api/utils/logger.py: + last_write_checksum: sha1:6fd9da93e110195a66583034c4b00e92d2463e00 + src/moderation_api/utils/metadata.py: + last_write_checksum: sha1:e703e5cbb5255144aacf86898d1420529afaaff8 + src/moderation_api/utils/queryparams.py: + last_write_checksum: sha1:b94c3f314fd3da0d1d215afc2731f48748e2aa59 + src/moderation_api/utils/requestbodies.py: + last_write_checksum: sha1:e1fef575283b7fe7fe2ad392dbbb3fb105309124 + src/moderation_api/utils/retries.py: + last_write_checksum: sha1:251e4dd969bb6c06025afe1bad1b5035e94c2060 + src/moderation_api/utils/security.py: + last_write_checksum: sha1:e5404e621afd5570d69de6fbfa08f1ea141a3ee5 + src/moderation_api/utils/serializers.py: + last_write_checksum: sha1:2fb543339b424a0818b126e69695df5c91cd4d7b + src/moderation_api/utils/unions.py: + last_write_checksum: sha1:cf05df60e2a9578806d903f79ec6f4758f491044 + src/moderation_api/utils/unmarshal_json_response.py: + last_write_checksum: sha1:094e8e49b6c9b1fd020197b6132943e9cbfa78f9 + src/moderation_api/utils/url.py: + last_write_checksum: sha1:0269c7e27f701e0c72d44bc2a36ac4d44ef2108c + src/moderation_api/utils/values.py: + last_write_checksum: sha1:c0eaaf8779a4ba3ae068579a57182667efef0cb7 + src/moderation_api/webhooks.py: + last_write_checksum: sha1:936385719eb90b786be4a31af8d98e71aa7bf52f + src/moderation_api/webhooksecret.py: + last_write_checksum: sha1:6081057d585229e1dc0bc9143c3803cbc65887b2 + src/moderation_api/wordlist.py: + last_write_checksum: sha1:e1fcc0296b270714e3997e13a6c69e080cc3efd2 + src/moderation_api/words.py: + last_write_checksum: sha1:ef2d2aba8e3067bbc9e1fb735a77bc2216d3f71f +examples: + listAuthors: + speakeasy-default-list-authors: + parameters: + query: + page: 1 + pageSize: 20 + sortDirection: "desc" + responses: + "200": + application/json: {"data": [{"id": "", "first_seen": 4938.47, "last_seen": 5385.02, "status": "suspended", "trust_level": {"level": 3349.53, "manual": false}, "block": null, "risk_evaluation": {}, "metrics": {"total_content": 7036.05, "flagged_content": 1522.44}, "metadata": {}}], "pagination": {"page": 669906, "pageSize": 764456, "total": 2694, "totalPages": 420848, "hasMore": true}} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://terrible-order.com/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://doting-creator.name/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://ignorant-brook.net"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://fair-spirit.com/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://biodegradable-bar.net"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://warmhearted-eternity.com/"} + createAuthor: + speakeasy-default-create-author: + requestBody: + application/json: {"external_id": ""} + responses: + "200": + application/json: {"id": "", "first_seen": 4812.06, "last_seen": 5162.05, "status": "enabled", "trust_level": {"level": 9033.2, "manual": true}, "block": {}, "risk_evaluation": null, "metrics": {"total_content": 3527.05, "flagged_content": 6547.27}, "metadata": {}} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://worldly-oil.info"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://glorious-runway.net/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://cluttered-vision.net"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://secret-netsuke.biz/"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://favorite-mozzarella.biz/"} + getAuthor: + speakeasy-default-get-author: + parameters: + path: + id: "" + responses: + "200": + application/json: {"id": "", "first_seen": 6376.87, "last_seen": 9245.24, "status": "suspended", "trust_level": {"level": 7361.01, "manual": true}, "block": {}, "risk_evaluation": null, "metrics": {"total_content": 5954.29, "flagged_content": 964.41}, "metadata": {}} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://thorny-grandson.net"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://tinted-caption.org"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://sorrowful-zebra.biz"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://worthless-lamp.biz/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://mindless-certification.info/"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://snappy-precedent.info/"} + updateAuthor: + speakeasy-default-update-author: + parameters: + path: + id: "" + requestBody: + application/json: {} + responses: + "200": + application/json: {"id": "", "first_seen": 4195.42, "last_seen": 5769.79, "status": "blocked", "trust_level": {"level": 3132.55, "manual": false}, "block": null, "risk_evaluation": {}, "metrics": {"total_content": 75.01, "flagged_content": 9143.95}, "metadata": {}} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://edible-cycle.net/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://legal-advertisement.org/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://tough-vibraphone.info"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://appropriate-pharmacopoeia.info/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://shiny-muscat.biz"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://trivial-label.org"} + deleteAuthor: + speakeasy-default-delete-author: + parameters: + path: + id: "" + responses: + "200": + application/json: {"success": false} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://heavenly-cycle.org"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://dead-forgery.name/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://fragrant-baseboard.net"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://doting-affect.biz/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://devoted-complication.com"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://palatable-transparency.com/"} + getQueue: + speakeasy-default-get-queue: + parameters: + path: + id: "" + responses: + "200": + application/json: {"queue": {"id": "", "name": "", "description": "furiously certification tighten", "filter": {}, "unresolvedItemsCount": 1559.96, "resolvedItemsCount": 1949.18, "totalItemsCount": 1912.44}} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://actual-thyme.biz/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://weary-precedent.com"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://polite-pepper.com"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://posh-exhaust.name"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://insistent-sermon.info"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://burly-markup.info"} + getQueueStats: + speakeasy-default-get-queue-stats: + parameters: + path: + id: "" + query: + withinDays: 30 + responses: + "200": + application/json: {"reviewStats": {"averageTimeToReview": 3713.81, "totalReviewed": 9855.4, "totalPending": 2413.77}, "actionStats": [{"actionId": "", "actionName": "", "count": 4507.74, "percentageOfTotal": 3785.01}], "topReviewers": [], "trends": {"dailyReviewCounts": [{"date": "2024-07-26", "count": 794.3}], "flaggedContentTrends": []}} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://accomplished-councilman.com/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://married-accelerator.name"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://stingy-wheel.info"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://sudden-nudge.biz/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://incomplete-populist.name"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://quixotic-version.name/"} + listQueueItems: + speakeasy-default-list-queue-items: + parameters: + path: + id: "" + query: + page: 1 + pageSize: 20 + sortDirection: "desc" + responses: + "200": + application/json: {"data": [{"id": "", "content": "", "labels": [{"label": "", "score": 3108.5, "flagged": false}], "flagged": false, "status": "resolved", "timestamp": 79.16}], "pagination": {"page": 42752, "pageSize": 933228, "total": 306038, "totalPages": 947297, "hasMore": true}} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://back-status.info"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://complicated-premier.info/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://nocturnal-vanadyl.info/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://hot-corporation.info/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://vast-pension.biz/"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://glorious-marten.biz/"} + resolveQueueItem: + speakeasy-default-resolve-queue-item: + parameters: + path: + id: "" + itemId: "" + requestBody: + application/json: {} + responses: + "200": + application/json: {"success": true, "resolvedAt": ""} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://heartfelt-orchid.net/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://rotating-heartbeat.net"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://discrete-maintainer.org/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://powerful-wombat.org"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://precious-napkin.biz"} + unresolveQueueItem: + speakeasy-default-unresolve-queue-item: + parameters: + path: + id: "" + itemId: "" + requestBody: + application/json: {} + responses: + "200": + application/json: {"success": false, "status": "", "unresolvedAt": ""} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://passionate-partridge.net"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://velvety-pocket-watch.info/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://jaunty-mantua.name/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://shameless-cleaner.biz/"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://vivacious-edge.net/"} + listActions: + speakeasy-default-list-actions: + parameters: + query: + page: 1 + pageSize: 20 + responses: + "200": + application/json: {"data": [{"id": "", "createdAt": "1725087768413", "name": "", "builtIn": false, "queueBehaviour": "NO_CHANGE", "filterInQueueIds": ["", "", ""], "position": "ALL_QUEUES", "possibleValues": [], "valueRequired": false, "freeText": false}], "pagination": {"page": 985516, "pageSize": 779890, "total": 313182, "totalPages": 263422, "hasMore": false}} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://alive-dividend.org"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://energetic-coliseum.info"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://that-awareness.info/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://big-blowgun.com/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://glaring-t-shirt.org"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://sure-footed-valuable.com"} + createAction: + speakeasy-default-create-action: + requestBody: + application/json: {"name": "", "builtIn": false, "queueBehaviour": "NO_CHANGE", "position": "ALL_QUEUES", "valueRequired": false, "freeText": false} + responses: + "200": + application/json: {"id": "", "createdAt": "1720941674984", "name": "", "builtIn": false, "queueBehaviour": "NO_CHANGE", "filterInQueueIds": [], "position": "ALL_QUEUES", "possibleValues": [], "valueRequired": false, "freeText": false} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://tense-piglet.biz"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://ornate-earth.com/"} + "409": + application/json: {"type": "conflict_error", "code": "resource_conflict", "message": "Action with this key already exists", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://electric-injunction.info/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://far-premeditation.com/"} + getAction: + speakeasy-default-get-action: + parameters: + path: + id: "" + responses: + "200": + application/json: {"id": "", "createdAt": "1718494238717", "name": "", "builtIn": false, "queueBehaviour": "NO_CHANGE", "filterInQueueIds": ["", ""], "position": "ALL_QUEUES", "possibleValues": [{"value": ""}], "valueRequired": false, "freeText": false} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://homely-bowler.net"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://bony-disappointment.org"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://reckless-disposer.biz"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://political-subsidy.name/"} + updateAction: + speakeasy-default-update-action: + parameters: + path: + id: "" + requestBody: + application/json: {"builtIn": false, "queueBehaviour": "NO_CHANGE", "position": "ALL_QUEUES", "valueRequired": false, "freeText": false} + responses: + "200": + application/json: {"id": "", "createdAt": "1714762506766", "name": "", "builtIn": false, "queueBehaviour": "NO_CHANGE", "filterInQueueIds": [], "position": "ALL_QUEUES", "possibleValues": [], "valueRequired": false, "freeText": false} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://second-hand-pigsty.biz/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://accurate-morbidity.biz"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://useless-leading.biz"} + "409": + application/json: {"type": "conflict_error", "code": "resource_conflict", "message": "Action with this key already exists", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://advanced-bog.biz/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://needy-longboat.org"} + deleteAction: + speakeasy-default-delete-action: + parameters: + path: + id: "" + responses: + "200": + application/json: {"deleted": false, "id": ""} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://parallel-whack.net"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://advanced-barge.net/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://raw-offset.info"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://filthy-acquaintance.com"} + executeAction: + speakeasy-default-execute-action: + requestBody: + application/json: {"actionKey": ""} + responses: + "200": + application/json: {"success": false} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://near-bog.org"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://harmful-atrium.biz/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://upright-slime.info/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://practical-slipper.net/"} + listWebhooks: + speakeasy-default-list-webhooks: + parameters: + query: + page: 1 + pageSize: 20 + responses: + "200": + application/json: {"data": [], "pagination": {"page": 110833, "pageSize": 63381, "total": 164724, "totalPages": 881973, "hasMore": false}} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://grubby-tabletop.info"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://circular-legend.biz"} + createWebhook: + speakeasy-default-create-webhook: + requestBody: + application/json: {"name": "", "url": "https://experienced-sailor.biz/", "eventTypes": ["QUEUE_ITEM_ACTION"]} + responses: + "200": + application/json: {"id": "", "name": "", "url": "https://experienced-seagull.name/", "eventTypes": ["AUTHOR_UPDATED"], "payloadVersion": "V2", "createdAt": "1722763687109"} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://burly-permafrost.name"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://flickering-bonnet.name"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://exhausted-swordfish.net/"} + getWebhook: + speakeasy-default-get-webhook: + parameters: + path: + id: "" + responses: + "200": + application/json: {"id": "", "name": "", "url": "https://unknown-tray.net", "eventTypes": [], "payloadVersion": "V2", "createdAt": "1705930134329"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://low-doorpost.net/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://avaricious-overcoat.net/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://same-translation.net/"} + updateWebhook: + speakeasy-default-update-webhook: + parameters: + path: + id: "" + requestBody: + application/json: {} + responses: + "200": + application/json: {"id": "", "name": "", "url": "https://nimble-affect.name", "eventTypes": [], "payloadVersion": "V1", "createdAt": "1713502971647"} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://better-guacamole.biz/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://impeccable-story.info/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://brown-developmental.info"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://gigantic-jump.biz"} + deleteWebhook: + speakeasy-default-delete-webhook: + parameters: + path: + id: "" + responses: + "200": + application/json: {"deleted": true, "id": ""} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://familiar-scratch.org"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://timely-sweatshop.info/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://hospitable-sundae.com/"} + getWebhookSecret: + speakeasy-default-get-webhook-secret: + responses: + "200": + application/json: {"secret": ""} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://worse-palate.net/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://clean-spring.info"} + getAccount: + speakeasy-default-get-account: + responses: + "200": + application/json: {"id": "", "paid_plan_name": "", "text_api_quota": 1657.23, "remaining_quota": 9607.83} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://enchanted-permafrost.info"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://wasteful-digestive.net"} + listWordlists: + speakeasy-default-list-wordlists: + parameters: + query: + page: 1 + pageSize: 20 + responses: + "200": + application/json: {"data": [{"id": "", "name": "", "description": "unwilling urgently mantua obediently hoarse into provided vet serenade down", "createdAt": "1705747111828", "userId": ""}], "pagination": {"page": 544370, "pageSize": 720052, "total": 588579, "totalPages": 529938, "hasMore": true}} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://meaty-chapel.name"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://other-outrun.net"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://wise-mythology.org"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://honorable-vein.com"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://curly-gray.info"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://merry-pile.com/"} + getWordlist: + speakeasy-default-get-wordlist: + parameters: + path: + id: "" + responses: + "200": + application/json: {"id": "", "name": "", "words": [], "createdAt": "1708371957170", "organizationId": "", "userId": "", "strict": false} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://posh-harp.info"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://drab-pleasure.biz"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://acidic-hygienic.biz"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://next-blossom.net"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://livid-fuel.name"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://jagged-coordination.com"} + updateWordlist: + speakeasy-default-update-wordlist: + parameters: + path: + id: "" + requestBody: + application/json: {} + responses: + "200": + application/json: {"id": "", "name": "", "words": [""], "createdAt": "1723114860829", "organizationId": "", "userId": "", "strict": false} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://triangular-republican.info"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://lanky-editor.info"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://surprised-gripper.net/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://crooked-milestone.biz"} + getWordlistEmbeddingStatus: + speakeasy-default-get-wordlist-embedding-status: + parameters: + path: + id: "" + responses: + "200": + application/json: {"progress": 1381.39, "totalWords": 7094.28, "remainingWords": 9583.15} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://quixotic-requirement.name"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://nimble-thread.org"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://responsible-awareness.com/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://worst-hutch.info/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://agreeable-independence.name/"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://impolite-sediment.name"} + addWordlistWords: + speakeasy-default-add-wordlist-words: + parameters: + path: + id: "" + requestBody: + application/json: {"words": []} + responses: + "200": + application/json: {"addedCount": 7700.73, "totalCount": 8887.39, "addedWords": []} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://noxious-fledgling.info/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://tempting-diversity.biz/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://dismal-castanet.org/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://serene-skean.com"} + removeWordlistWords: + speakeasy-default-remove-wordlist-words: + parameters: + path: + id: "" + query: + words: ["", "", ""] + responses: + "200": + application/json: {"removedCount": 6472.46, "totalCount": 1784.41, "removedWords": [""]} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://coarse-flight.com/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://unfortunate-toothbrush.com/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://key-expense.com"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://sophisticated-slime.name"} + submitContent: + speakeasy-default-submit-content: + requestBody: + application/json: {"content": {"type": "image"}} + responses: + "200": + application/json: {"content": {"id": "", "masked": true, "unicode_cleaned": true, "modified": {"key": "", "key1": ""}}, "author": {"id": "", "block": {}, "status": "blocked", "trust_level": {"level": 8664.71, "manual": false}}, "evaluation": {"flagged": false, "flag_probability": 8008.81, "severity_score": 348.65}, "recommendation": {"action": "allow", "reason_codes": ["rule_match"]}, "casebook": {"verdict": "reject", "similarity": 2676.55, "agreement": 7706.56, "confidence": 9365.91, "case_count": 6691.67, "topic": {"id": "", "label": ""}}, "policies": [], "insights": [], "meta": {"status": "partial_success", "timestamp": 5209.71, "channel_key": "", "usage": 2898.59}} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://sparse-collaboration.name"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://second-hand-devastation.name/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://white-atrium.info"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://worthless-solvency.info/"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://enchanted-amnesty.name/"} + listProjects: + speakeasy-default-list-projects: + parameters: + query: + page: 1 + pageSize: 20 + responses: + "200": + application/json: {"data": [{"id": "", "name": "", "description": "scarily however out solidly reproach", "domain": "wasteful-lounge.com", "context": "", "globalFlaggingMode": "ACTIVE", "enableRequestLogging": false, "casebookEnabled": false, "createdAt": "1719550777772", "updatedAt": "1735687514389"}], "pagination": {"page": 468054, "pageSize": 647138, "total": 740816, "totalPages": 925291, "hasMore": false}} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://deadly-barracks.info/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://accurate-surface.com"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://zany-archaeology.com"} + createProject: + speakeasy-default-create-project: + requestBody: + application/json: {"name": "", "usedByMinors": false} + responses: + "200": + application/json: {"id": "", "name": "", "description": "accidentally per justly inconsequential amidst", "domain": "impeccable-lashes.net", "context": "", "globalFlaggingMode": "ACTIVE", "enableRequestLogging": false, "casebookEnabled": false, "createdAt": "1722120233197", "updatedAt": "1735673297432", "secretKey": ""} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://elastic-typeface.name"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://fake-suspension.info/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://lazy-marketplace.org/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://difficult-mentor.name"} + getProject: + speakeasy-default-get-project: + parameters: + path: + id: "" + responses: + "200": + application/json: {"id": "", "name": "", "description": "for loyally prance dutiful decriminalize noted", "domain": "well-worn-e-mail.net", "context": "", "globalFlaggingMode": "ACTIVE", "enableRequestLogging": false, "casebookEnabled": true, "createdAt": "1733283153583", "updatedAt": "1735660743971"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://drab-stitcher.biz"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://unsightly-cricket.net/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://linear-ocelot.com/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://sour-avalanche.org"} + updateProject: + speakeasy-default-update-project: + parameters: + path: + id: "" + requestBody: + application/json: {} + responses: + "200": + application/json: {"id": "", "name": "", "description": null, "domain": "wiggly-gown.info", "context": "", "globalFlaggingMode": "DRY_RUN", "enableRequestLogging": false, "casebookEnabled": false, "createdAt": "1723019639943", "updatedAt": "1735642941794"} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://enchanting-behest.name"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://scary-strategy.com"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://intelligent-adrenalin.org"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://informal-loyalty.biz/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://paltry-marketplace.com/"} + deleteProject: + speakeasy-default-delete-project: + parameters: + path: + id: "" + responses: + "200": + application/json: {"deleted": true, "id": ""} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://brown-lawmaker.com/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://specific-swing.net/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://genuine-shoulder.name/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://rewarding-quit.biz/"} + duplicateProject: + speakeasy-default-duplicate-project: + parameters: + path: + id: "" + requestBody: + application/json: {} + responses: + "200": + application/json: {"id": "", "name": "", "description": "messy volunteer quash cluttered", "domain": "unkempt-bowling.name", "context": "", "globalFlaggingMode": "DRY_RUN", "enableRequestLogging": true, "casebookEnabled": false, "createdAt": "1720065871635", "updatedAt": "1735618179596", "secretKey": ""} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://wicked-brochure.net"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://rowdy-humor.net"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://winged-developing.org/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://lazy-fund.org/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://sentimental-thread.info"} + listChannels: + speakeasy-default-list-channels: + parameters: + path: + projectId: "" + query: + page: 1 + pageSize: 20 + responses: + "200": + application/json: {"data": [{"id": "", "projectId": "", "key": "", "name": "", "description": "within greedily why primary ceramics that pricey trash", "isDefault": false, "contentType": "post", "flaggingMode": "ACTIVE", "contextEnabled": true, "enableImageModeration": false, "enableVideoModeration": false, "enableAudioModeration": true, "enableVoiceModeration": true, "includeTranscriptInResponse": true, "transcriptionQuality": "SPEED", "unicodeSpoofDetectionEnabled": true, "frameCaptureInterval": 9833.48, "maxFramesPerVideo": 441460, "spreadFrames": false, "createdAt": "1725717609091", "updatedAt": "1735644620528"}], "pagination": {"page": 16950, "pageSize": 792684, "total": 944522, "totalPages": 636345, "hasMore": false}} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://authentic-dress.name/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://understated-produce.net"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://cruel-vein.info"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://primary-completion.biz/"} + createChannel: + speakeasy-default-create-channel: + parameters: + path: + projectId: "" + requestBody: + application/json: {"name": ""} + responses: + "200": + application/json: {"id": "", "projectId": "", "key": "", "name": "", "description": "regular ouch unlike slow fondly finally meanwhile though toaster", "isDefault": false, "contentType": "voice", "flaggingMode": "ACTIVE", "contextEnabled": false, "enableImageModeration": false, "enableVideoModeration": true, "enableAudioModeration": false, "enableVoiceModeration": true, "includeTranscriptInResponse": true, "transcriptionQuality": "BALANCED", "unicodeSpoofDetectionEnabled": true, "frameCaptureInterval": 9068.96, "maxFramesPerVideo": 217785, "spreadFrames": true, "createdAt": "1709542405374", "updatedAt": "1735665242908"} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://experienced-jazz.name/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://witty-goodwill.net"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://deep-lifestyle.name"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://ill-airbus.biz"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://bad-mathematics.biz/"} + getChannel: + speakeasy-default-get-channel: + parameters: + path: + id: "" + responses: + "200": + application/json: {"id": "", "projectId": "", "key": "", "name": "", "description": null, "isDefault": true, "contentType": "event", "flaggingMode": "DRY_RUN", "contextEnabled": false, "enableImageModeration": false, "enableVideoModeration": false, "enableAudioModeration": false, "enableVoiceModeration": true, "includeTranscriptInResponse": true, "transcriptionQuality": "SPEED", "unicodeSpoofDetectionEnabled": false, "frameCaptureInterval": 9732.58, "maxFramesPerVideo": 449675, "spreadFrames": false, "createdAt": "1709021755362", "updatedAt": "1735658089359"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://orderly-lace.com"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://crushing-avalanche.biz/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://silky-redesign.name/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://super-crocodile.org/"} + updateChannel: + speakeasy-default-update-channel: + parameters: + path: + id: "" + requestBody: + application/json: {} + responses: + "200": + application/json: {"id": "", "projectId": "", "key": "", "name": "", "description": "utterly taxicab rebuild amid common searchingly", "isDefault": false, "contentType": "comment", "flaggingMode": "ACTIVE", "contextEnabled": false, "enableImageModeration": true, "enableVideoModeration": false, "enableAudioModeration": true, "enableVoiceModeration": false, "includeTranscriptInResponse": true, "transcriptionQuality": "SPEED", "unicodeSpoofDetectionEnabled": false, "frameCaptureInterval": 242.32, "maxFramesPerVideo": 79933, "spreadFrames": false, "createdAt": "1714165181265", "updatedAt": "1735635444170"} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://jam-packed-midwife.net"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://enchanted-executor.name/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://tinted-seagull.biz"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://bouncy-sanity.org"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://glaring-procurement.name/"} + deleteChannel: + speakeasy-default-delete-channel: + parameters: + path: + id: "" + responses: + "200": + application/json: {"deleted": true, "id": ""} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://wise-accountability.info/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://brilliant-makeover.name"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://specific-vibration.info"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://wasteful-suv.com/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://impeccable-injunction.net"} + duplicateChannel: + speakeasy-default-duplicate-channel: + parameters: + path: + id: "" + requestBody: + application/json: {} + responses: + "200": + application/json: {"id": "", "projectId": "", "key": "", "name": "", "description": null, "isDefault": true, "contentType": "event", "flaggingMode": "DRY_RUN", "contextEnabled": false, "enableImageModeration": true, "enableVideoModeration": true, "enableAudioModeration": true, "enableVoiceModeration": false, "includeTranscriptInResponse": true, "transcriptionQuality": "BALANCED", "unicodeSpoofDetectionEnabled": false, "frameCaptureInterval": 3446.41, "maxFramesPerVideo": 287196, "spreadFrames": false, "createdAt": "1727448237026", "updatedAt": "1735634690271"} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://educated-unblinking.name"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://uniform-statue.biz/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://dependent-nudge.biz/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://insecure-premium.com/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://upset-hydrant.info/"} +examplesVersion: 1.0.2 diff --git a/.speakeasy/gen.yaml b/.speakeasy/gen.yaml new file mode 100644 index 0000000..c1f98cf --- /dev/null +++ b/.speakeasy/gen.yaml @@ -0,0 +1,102 @@ +configVersion: 2.0.0 +generation: + sdkClassName: ModerationAPI + maintainOpenAPIOrder: true + usageSnippets: + optionalPropertyRendering: withExample + sdkInitStyle: constructor + useClassNamesForArrayFields: true + fixes: + nameResolutionDec2023: true + nameResolutionFeb2025: true + parameterOrderingFeb2024: true + requestResponseComponentNamesFeb2024: true + securityFeb2025: true + sharedErrorComponentsApr2025: true + sharedNestedComponentsJan2026: true + nameOverrideFeb2026: true + openSingleValueEnumsOct2026: true + auth: + oAuth2ClientCredentialsEnabled: false + oAuth2PasswordEnabled: false + hoistGlobalSecurity: true + inferSSEOverload: true + sdkHooksConfigAccess: true + schemas: + allOfMergeStrategy: shallowMerge + requestBodyFieldName: body + versioningStrategy: automatic + persistentEdits: {} + tests: + generateTests: false + generateNewTests: true + skipResponseBodyAssertions: false +python: + version: 2.0.0 + additionalDependencies: + dev: {} + main: {} + allowedRedefinedBuiltins: + - id + - object + - input + - dir + asyncMode: both + authors: + - Moderation API + baseErrorName: ModerationAPIError + bodyVariantOverloads: false + clientServerStatusCodesAsErrors: true + constFieldCasing: normal + defaultErrorName: ModerationAPIDefaultError + description: The official Python library for the Moderation API + durationFormat: true + enableCustomCodeRegions: false + enumFormat: union + envVarPrefix: MODAPI + errorSchemaValidation: true + eventStreamClassNames: + async: EventStreamAsync + sync: EventStream + fixFlags: + asyncPaginationSep2025: true + conflictResistantModelImportsFeb2026: true + responseRequiredSep2024: true + flattenGlobalSecurity: true + flattenRequests: true + flatteningOrder: parameters-first + forwardCompatibleEnumsByDefault: true + forwardCompatibleUnionsByDefault: tagged-only + httpClientLibrary: httpx + imports: + option: openapi + paths: + callbacks: "" + errors: errors + operations: "" + shared: "" + webhooks: "" + inferUnionDiscriminators: true + inputModelSuffix: input + inputTypedDictSuffix: TypedDict + license: Apache-2.0 + maxMethodParams: 4 + methodArguments: infer-optional-args + methodTimeoutArgument: timeout-ms + methodTimeoutUnits: milliseconds + moduleName: "" + multipartArrayFormat: standard + optionalDependencies: {} + outputModelSuffix: output + packageManager: uv + packageName: moderation_api + preApplyUnionDiscriminators: true + pytestFilterWarnings: [] + pytestTimeout: 0 + rawResponseHelpers: false + responseFormat: flat + responseSchemaValidation: true + sseFlatResponse: false + templateVersion: v2 + useAsyncHooks: false + uuidFormat: false diff --git a/.stats.yml b/.stats.yml deleted file mode 100644 index ac64174..0000000 --- a/.stats.yml +++ /dev/null @@ -1,4 +0,0 @@ -configured_endpoints: 34 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/moderation-api/moderation-api-a4c5163e3271601b36a74223b756d304ce8246f832f8c531d53cdd30f9ee4c68.yml -openapi_spec_hash: fd9fe8a54c1c46032bd5fccb995f04b8 -config_hash: 6e090a71f4d354167d93b4534a0e7f05 diff --git a/.vscode/settings.json b/.vscode/settings.json index 5b01030..8d79f0a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "python.analysis.importFormat": "relative", + "python.testing.pytestArgs": ["tests", "-vv"], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "pylint.args": ["--rcfile=pylintrc"] } diff --git a/Brewfile b/Brewfile deleted file mode 100644 index c43041c..0000000 --- a/Brewfile +++ /dev/null @@ -1,2 +0,0 @@ -brew "uv" - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3d1ba2a..d585717 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,121 +1,26 @@ -## Setting up the environment +# Contributing to This Repository -### With `uv` +Thank you for your interest in contributing to this repository. Please note that this repository contains generated code. As such, we do not accept direct changes or pull requests. Instead, we encourage you to follow the guidelines below to report issues and suggest improvements. -We use [uv](https://docs.astral.sh/uv/) to manage dependencies because it will automatically provision a Python environment with the expected Python version. To set it up, run: +## How to Report Issues -```sh -$ ./scripts/bootstrap -``` +If you encounter any bugs or have suggestions for improvements, please open an issue on GitHub. When reporting an issue, please provide as much detail as possible to help us reproduce the problem. This includes: -Or [install uv manually](https://docs.astral.sh/uv/getting-started/installation/) and run: +- A clear and descriptive title +- Steps to reproduce the issue +- Expected and actual behavior +- Any relevant logs, screenshots, or error messages +- Information about your environment (e.g., operating system, software versions) + - For example can be collected using the `npx envinfo` command from your terminal if you have Node.js installed -```sh -$ uv sync --all-extras -``` +## Issue Triage and Upstream Fixes -You can then run scripts using `uv run python script.py` or by manually activating the virtual environment: +We will review and triage issues as quickly as possible. Our goal is to address bugs and incorporate improvements in the upstream source code. Fixes will be included in the next generation of the generated code. -```sh -# manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work -$ source .venv/bin/activate +## Contact -# now you can omit the `uv run` prefix -$ python script.py -``` +If you have any questions or need further assistance, please feel free to reach out by opening an issue. -### Without `uv` +Thank you for your understanding and cooperation! -Alternatively if you don't want to install `uv`, you can stick with the standard `pip` setup by ensuring you have the Python version specified in `.python-version`, create a virtual environment however you desire and then install dependencies using this command: - -```sh -$ pip install -r requirements-dev.lock -``` - -## Modifying/Adding code - -Most of the SDK is generated code. Modifications to code will be persisted between generations, but may -result in merge conflicts between manual patches and changes from the generator. The generator will never -modify the contents of the `src/moderation_api/lib/` and `examples/` directories. - -## Adding and running examples - -All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. - -```py -# add an example to examples/.py - -#!/usr/bin/env -S uv run python -… -``` - -```sh -$ chmod +x examples/.py -# run the example against your api -$ ./examples/.py -``` - -## Using the repository from source - -If you’d like to use the repository from source, you can either install from git or link to a cloned repository: - -To install via git: - -```sh -$ pip install git+ssh://git@github.com/moderation-api/sdk-python.git -``` - -Alternatively, you can build from source and install the wheel file: - -Building this package will create two files in the `dist/` directory, a `.tar.gz` containing the source files and a `.whl` that can be used to install the package efficiently. - -To create a distributable version of the library, all you have to do is run this command: - -```sh -$ uv build -# or -$ python -m build -``` - -Then to install: - -```sh -$ pip install ./path-to-wheel-file.whl -``` - -## Running tests - -```sh -$ ./scripts/test -``` - -## Linting and formatting - -This repository uses [ruff](https://github.com/astral-sh/ruff) and -[black](https://github.com/psf/black) to format the code in the repository. - -To lint: - -```sh -$ ./scripts/lint -``` - -To format and fix all ruff issues automatically: - -```sh -$ ./scripts/format -``` - -## Publishing and releases - -Changes made to this repository via the automated release PR pipeline should publish to PyPI automatically. If -the changes aren't made through the automated pipeline, you may want to make releases manually. - -### Publish with a GitHub workflow - -You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/moderation-api/sdk-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up. - -### Publish manually - -If you need to manually release a package, you can run the `bin/publish-pypi` script with a `PYPI_TOKEN` set on -the environment. +The Maintainers diff --git a/README.md b/README.md index a19b6cf..5b6f333 100644 --- a/README.md +++ b/README.md @@ -1,423 +1,789 @@ -# Moderation API Python API library - - -[![PyPI version](https://img.shields.io/pypi/v/moderation_api.svg?label=pypi%20(stable))](https://pypi.org/project/moderation_api/) + +# Moderation API Python SDK + +[![PyPI](https://img.shields.io/pypi/v/moderation_api.svg)](https://pypi.org/project/moderation_api/) + +The official Python library for [Moderation API](https://moderationapi.com). Use it to moderate text, images, video and audio, manage authors, review queues, actions and wordlists, and verify webhooks. It supports both synchronous and `asyncio` code. + +- Get an API key in the [dashboard](https://dash.moderationapi.com). +- Read the [documentation](https://docs.moderationapi.com) and the [API reference](https://docs.moderationapi.com/api-reference/introduction). + + + +## Summary + +Moderation API: Moderate text, images, video and audio in real time, manage authors, review queues and actions, and receive webhooks. + +More information about the API can be found at https://docs.moderationapi.com + + + +## Table of Contents + +* [Moderation API Python SDK](#moderation-api-python-sdk) + * [SDK Installation](#sdk-installation) + * [IDE Support](#ide-support) + * [SDK Example Usage](#sdk-example-usage) + * [Moderating content](#moderating-content) + * [Verifying webhooks](#verifying-webhooks) + * [Authentication](#authentication) + * [Available Resources and Operations](#available-resources-and-operations) + * [Pagination](#pagination) + * [Retries](#retries) + * [Error Handling](#error-handling) + * [Server Selection](#server-selection) + * [Custom HTTP Client](#custom-http-client) + * [Resource Management](#resource-management) + * [Debugging](#debugging) + * [Timeouts](#timeouts) + * [Requirements](#requirements) + * [Support](#support) +* [Development](#development) + * [Maturity](#maturity) + * [Contributions](#contributions) + + + + +## SDK Installation + +> [!NOTE] +> **Python version upgrade policy** +> +> Once a Python version reaches its [official end of life date](https://devguide.python.org/versions/), a 3-month grace period is provided for users to upgrade. Following this grace period, the minimum python version supported in the SDK will be updated. + +The SDK can be installed with *uv*, *pip*, or *poetry* package managers. + +### uv + +*uv* is a fast Python package installer and resolver, designed as a drop-in replacement for pip and pip-tools. It's recommended for its speed and modern Python tooling capabilities. + +```bash +uv add moderation_api +``` -The Moderation API Python library provides convenient access to the Moderation API REST API from any Python 3.9+ -application. The library includes type definitions for all request params and response fields, -and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). +### PIP -It is generated with [Stainless](https://www.stainless.com/). +*PIP* is the default package installer for Python, enabling easy installation and management of packages from PyPI via the command line. -## Documentation +```bash +pip install moderation_api +``` -The REST API documentation can be found on [docs.moderationapi.com](https://docs.moderationapi.com). The full API of this library can be found in [api.md](api.md). +### Poetry -## Installation +*Poetry* is a modern tool that simplifies dependency management and package publishing by using a single `pyproject.toml` file to handle project metadata and dependencies. -```sh -# install from PyPI -pip install moderation_api +```bash +poetry add moderation_api ``` -## Usage +### Shell and script usage with `uv` + +You can use this SDK in a Python shell with [uv](https://docs.astral.sh/uv/) and the `uvx` command that comes with it like so: -The full API of this library can be found in [api.md](api.md). +```shell +uvx --from moderation_api python +``` + +It's also possible to write a standalone Python script without needing to set up a whole project like so: ```python -import os +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.10" +# dependencies = [ +# "moderation_api", +# ] +# /// + from moderation_api import ModerationAPI -client = ModerationAPI( - secret_key=os.environ.get("MODAPI_SECRET_KEY"), # This is the default and can be omitted +sdk = ModerationAPI( + # SDK arguments ) -response = client.content.submit( - content={ - "text": "text", - "type": "text", - }, -) -print(response.recommendation) +# Rest of script here... ``` -While you can provide a `secret_key` keyword argument, -we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) -to add `MODAPI_SECRET_KEY="My Secret Key"` to your `.env` file -so that your Secret Key is not stored in source control. +Once that is saved to a file, you can run it with `uv run script.py` where +`script.py` can be replaced with the actual file name. + + + +## IDE Support + +### PyCharm -## Async usage +Generally, the SDK will work well with most IDEs out of the box. However, when using PyCharm, you can enjoy much better integration with Pydantic by installing an additional plugin. -Simply import `AsyncModerationAPI` instead of `ModerationAPI` and use `await` with each API call: +- [PyCharm Pydantic Plugin](https://docs.pydantic.dev/latest/integrations/pycharm/) + + + +## SDK Example Usage + +### Example ```python +# Synchronous Example +from moderation_api import ModerationAPI import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.content.submit(content={ + "type": "text", + "text": "Hey, check out my profile for a deal!", + }, content_id="message-48213", author_id="user-1092", conversation_id="room-general", metadata={ + "platform": "ios", + }) + + # Handle response + print(res) +``` + +
+ +The same SDK client can also be used to make asynchronous requests by importing asyncio. + +```python +# Asynchronous Example import asyncio -from moderation_api import AsyncModerationAPI +from moderation_api import ModerationAPI +import os -client = AsyncModerationAPI( - secret_key=os.environ.get("MODAPI_SECRET_KEY"), # This is the default and can be omitted -) +async def main(): + async with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), + ) as ma_client: -async def main() -> None: - response = await client.content.submit( - content={ - "text": "text", + res = await ma_client.content.submit_async(content={ "type": "text", - }, - ) - print(response.recommendation) + "text": "Hey, check out my profile for a deal!", + }, content_id="message-48213", author_id="user-1092", conversation_id="room-general", metadata={ + "platform": "ios", + }) + # Handle response + print(res) asyncio.run(main()) ``` + + +## Moderating content -Functionality between the synchronous and asynchronous clients is otherwise identical. +Use `content.submit` to moderate text, images, video, audio, or objects with several fields. All parameters except `content` are optional. -### With aiohttp +The client reads your API key from the `MODAPI_SECRET_KEY` environment variable. To set it in code, pass `secret_key`. -By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend. +```python +from moderation_api import ModerationAPI + +with ModerationAPI() as client: # reads MODAPI_SECRET_KEY + # Text + client.content.submit( + content={"type": "text", "text": "Your text here"}, + content_id="message-123", # your ID for the content + author_id="user-123", # your ID for the user who wrote it + conversation_id="room-123", # groups related content + metadata={"platform": "ios"}, + ) -You can enable this by installing `aiohttp`: + # Image, video and audio: send a URL + client.content.submit(content={"type": "image", "url": "https://example.com/image.jpg"}) + client.content.submit(content={"type": "video", "url": "https://example.com/video.mp4"}) + client.content.submit(content={"type": "audio", "url": "https://example.com/audio.mp3"}) -```sh -# install from PyPI -pip install moderation_api[aiohttp] + # An object with several fields, moderated together + client.content.submit( + content={ + "type": "object", + "data": { + "title": {"type": "text", "text": "Post title"}, + "body": {"type": "text", "text": "Post content"}, + "thumbnail": {"type": "image", "url": "https://example.com/thumb.jpg"}, + }, + }, + ) ``` -Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: +Every method has an `async` version with the suffix `_async`, for example `await client.content.submit_async(...)`. + +### Using the result + +`evaluation.flagged` tells you if any policy flagged the content. `recommendation.action` is the action the API recommends after it applies your thresholds and rules: `allow`, `review` or `reject`. ```python -import os -import asyncio -from moderation_api import DefaultAioHttpClient -from moderation_api import AsyncModerationAPI - - -async def main() -> None: - async with AsyncModerationAPI( - secret_key=os.environ.get("MODAPI_SECRET_KEY"), # This is the default and can be omitted - http_client=DefaultAioHttpClient(), - ) as client: - response = await client.content.submit( - content={ - "text": "text", - "type": "text", - }, - ) - print(response.recommendation) +from moderation_api import ModerationAPI + +with ModerationAPI() as client: + result = client.content.submit(content={"type": "text", "text": "Some content"}) + if result.evaluation.flagged: + print("A policy flagged the content") -asyncio.run(main()) + if result.recommendation.action == "reject": + pass # Block the content + elif result.recommendation.action == "review": + pass # Publish or hold it, and send it to a review queue + else: + pass # Publish the content + + for policy in result.policies: + print(f"{policy.id}: flagged={policy.flagged}") ``` -## Using types +## Verifying webhooks -Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like: +Moderation API signs every webhook delivery with your project's webhook signing secret. The signature is in the `modapi-signature` header. -- Serializing back into JSON, `model.to_json()` -- Converting to a dictionary, `model.to_dict()` +### Webhook signing secret -Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`. +Find the secret on the [API keys page in the dashboard](https://dash.moderationapi.com/project/latest/configure/api-keys), or get it with `client.webhook_secret.retrieve()`. Store it in the `MODAPI_WEBHOOK_SECRET` environment variable. It is not your API key. Get it once, for example in a setup script, and do not call `retrieve` for each delivery, so verification does not depend on a call to the API. Keep it as secret as your API key: anyone with it can sign webhooks. -## Nested params +### Verify a delivery -Nested parameters are dictionaries, typed using `TypedDict`, for example: +`construct_event` checks the signature against the raw body and returns the event as a dictionary. It reads the secret from `MODAPI_WEBHOOK_SECRET`; to set it in code, pass `secret`. It raises `WebhookSignatureError` if the signature does not match. ```python -from moderation_api import ModerationAPI +from flask import Flask, request +from moderation_api.webhook_signature import WebhookSignatureError, construct_event -client = ModerationAPI() +app = Flask(__name__) -response = client.content.submit( - content={ - "text": "text", - "type": "text", - }, - client_action={"action": "review"}, -) -print(response.client_action) +@app.post("/webhooks/moderation") +def moderation_webhook(): + try: + event = construct_event( + request.get_data(), # the raw body, not parsed JSON + request.headers.get("modapi-signature", ""), + ) + except WebhookSignatureError: + return "Invalid signature", 400 + + print(event["type"]) + return "", 200 ``` + -## Handling errors -When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `moderation_api.APIConnectionError` is raised. + +## Authentication -When the API returns a non-success status code (that is, 4xx or 5xx -response), a subclass of `moderation_api.APIStatusError` is raised, containing `status_code` and `response` properties. +### Per-Client Security Schemes -All errors inherit from `moderation_api.APIError`. +This SDK supports the following security scheme globally: +| Name | Type | Scheme | Environment Variable | +| ------------ | ---- | ----------- | -------------------- | +| `secret_key` | http | HTTP Bearer | `MODAPI_SECRET_KEY` | + +To authenticate with the API the `secret_key` parameter must be set when initializing the SDK client instance. For example: ```python -import moderation_api from moderation_api import ModerationAPI +import os -client = ModerationAPI() -try: - client.content.submit( - content={ - "text": "text", - "type": "text", - }, - ) -except moderation_api.APIConnectionError as e: - print("The server could not be reached") - print(e.__cause__) # an underlying Exception, likely raised within httpx. -except moderation_api.RateLimitError as e: - print("A 429 status code was received; we should back off a bit.") -except moderation_api.APIStatusError as e: - print("Another non-200-range status code was received") - print(e.status_code) - print(e.response) -``` +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.authors.list(request={}) + + while res is not None: + # Handle items -Error codes are as follows: + res = res.next() -| Status Code | Error Type | -| ----------- | -------------------------- | -| 400 | `BadRequestError` | -| 401 | `AuthenticationError` | -| 403 | `PermissionDeniedError` | -| 404 | `NotFoundError` | -| 422 | `UnprocessableEntityError` | -| 429 | `RateLimitError` | -| >=500 | `InternalServerError` | -| N/A | `APIConnectionError` | +``` + + +### Project and organization keys -### Retries +Moderation API has two kinds of API keys: -Certain errors are automatically retried 2 times by default, with a short exponential backoff. -Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, -429 Rate Limit, and >=500 Internal errors are all retried by default. +- A **project key** works with one project: content, authors, review queues, actions, wordlists, webhooks and the account. Most integrations need only this key. +- An **organization key** works with the [management API](https://docs.moderationapi.com/api-reference/management-api): projects and channels. The management API is available on Enterprise plans. -You can use the `max_retries` option to configure or disable retry settings: +Each key works only with its own endpoints. With the wrong key the API returns `403` with the code `project_key_required` or `organization_key_required`. If you use both, create one client for each key: ```python +import os from moderation_api import ModerationAPI -# Configure the default for all requests: -client = ModerationAPI( - # default is 2 - max_retries=0, -) +# Project key, from MODAPI_SECRET_KEY +client = ModerationAPI() -# Or, configure per-request: -client.with_options(max_retries=5).content.submit( - content={ - "text": "text", - "type": "text", - }, -) +# Organization key +management = ModerationAPI(secret_key=os.environ["MODAPI_ORGANIZATION_KEY"]) + +res = management.projects.list() +while res is not None: + for project in res.result.data: + print(project.id, project.name) + res = res.next() ``` -### Timeouts +Create the organization client only where you manage projects and channels, for example in a setup script, and keep the organization key out of the code that moderates content. + + + + +## Available Resources and Operations + +
+Available methods + +### [Account](docs/sdks/account/README.md) + +* [retrieve](docs/sdks/account/README.md#retrieve) - Get account details + +### [Actions](docs/sdks/actions/README.md) + +* [list](docs/sdks/actions/README.md#list) - List moderation actions +* [create](docs/sdks/actions/README.md#create) - Create an action +* [retrieve](docs/sdks/actions/README.md#retrieve) - Get an action +* [update](docs/sdks/actions/README.md#update) - Update an action +* [delete](docs/sdks/actions/README.md#delete) - Delete an action +* [execute](docs/sdks/actions/README.md#execute) - Execute moderation action + +### [Authors](docs/sdks/authors/README.md) + +* [list](docs/sdks/authors/README.md#list) - List authors +* [create](docs/sdks/authors/README.md#create) - Create a new author +* [retrieve](docs/sdks/authors/README.md#retrieve) - Get author details +* [update](docs/sdks/authors/README.md#update) - Update author details +* [delete](docs/sdks/authors/README.md#delete) - Delete an author + +### [Channels](docs/sdks/channels/README.md) + +* [list](docs/sdks/channels/README.md#list) - List channels +* [create](docs/sdks/channels/README.md#create) - Create a channel +* [retrieve](docs/sdks/channels/README.md#retrieve) - Get a channel +* [update](docs/sdks/channels/README.md#update) - Update a channel +* [delete](docs/sdks/channels/README.md#delete) - Delete a channel +* [duplicate](docs/sdks/channels/README.md#duplicate) - Duplicate a channel + +### [Content](docs/sdks/contentsdk/README.md) + +* [submit](docs/sdks/contentsdk/README.md#submit) -By default requests time out after 1 minute. You can configure this with a `timeout` option, -which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object: +### [Projects](docs/sdks/projects/README.md) +* [list](docs/sdks/projects/README.md#list) - List projects +* [create](docs/sdks/projects/README.md#create) - Create a project +* [retrieve](docs/sdks/projects/README.md#retrieve) - Get a project +* [update](docs/sdks/projects/README.md#update) - Update a project +* [delete](docs/sdks/projects/README.md#delete) - Delete a project +* [duplicate](docs/sdks/projects/README.md#duplicate) - Duplicate a project + +### [Queue](docs/sdks/queuesdk/README.md) + +* [retrieve](docs/sdks/queuesdk/README.md#retrieve) - Get a queue +* [get_stats](docs/sdks/queuesdk/README.md#get_stats) - Get queue statistics + +#### [Queue.Items](docs/sdks/items/README.md) + +* [list](docs/sdks/items/README.md#list) - Get queue items +* [resolve](docs/sdks/items/README.md#resolve) - Resolve a queue item +* [unresolve](docs/sdks/items/README.md#unresolve) - Unresolve a queue item + +### [Webhooks](docs/sdks/webhooks/README.md) + +* [list](docs/sdks/webhooks/README.md#list) - List webhooks +* [create](docs/sdks/webhooks/README.md#create) - Create a webhook +* [retrieve](docs/sdks/webhooks/README.md#retrieve) - Get a webhook +* [update](docs/sdks/webhooks/README.md#update) - Update a webhook +* [delete](docs/sdks/webhooks/README.md#delete) - Delete a webhook + +### [WebhookSecret](docs/sdks/webhooksecret/README.md) + +* [retrieve](docs/sdks/webhooksecret/README.md#retrieve) - Get the webhook signing secret + +### [Wordlist](docs/sdks/wordlist/README.md) + +* [list](docs/sdks/wordlist/README.md#list) - List wordlists +* [retrieve](docs/sdks/wordlist/README.md#retrieve) - Get wordlist +* [update](docs/sdks/wordlist/README.md#update) - Update wordlist +* [get_embedding_status](docs/sdks/wordlist/README.md#get_embedding_status) - Get embedding status + +#### [Wordlist.Words](docs/sdks/words/README.md) + +* [add](docs/sdks/words/README.md#add) - Add words to wordlist +* [remove](docs/sdks/words/README.md#remove) - Remove words from wordlist + +
+ + + +## Pagination + +Some of the endpoints in this SDK support pagination. To use pagination, you make your SDK calls as usual, but the +returned response object will have a `Next` method that can be called to pull down the next group of results. If the +return value of `Next` is `None`, then there are no more pages to be fetched. + +Here's an example of one such pagination call: ```python from moderation_api import ModerationAPI +import os -# Configure the default for all requests: -client = ModerationAPI( - # 20 seconds (default is 1 minute) - timeout=20.0, -) -# More granular control: -client = ModerationAPI( - timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0), -) +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.authors.list(request={}) + + while res is not None: + # Handle items + + res = res.next() -# Override per-request: -client.with_options(timeout=5.0).content.submit( - content={ - "text": "text", - "type": "text", - }, -) ``` + + + +## Retries -On timeout, an `APITimeoutError` is thrown. +Some of the endpoints in this SDK support retries. If you use the SDK without any configuration, it will fall back to the default retry strategy provided by the API. However, the default retry strategy can be overridden on a per-operation basis, or across the entire SDK. -Note that requests that time out are [retried twice by default](#retries). +To change the default retry strategy for a single API call, simply provide a `RetryConfig` object to the call: +```python +from moderation_api import ModerationAPI +from moderation_api.utils import BackoffStrategy, RetryConfig +import os -## Advanced -### Logging +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: -We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module. + res = ma_client.authors.list(request={}, + RetryConfig("backoff", BackoffStrategy(1, 50, 1.1, 100), False)) -You can enable logging by setting the environment variable `MODERATION_API_LOG` to `info`. + while res is not None: + # Handle items + + res = res.next() -```shell -$ export MODERATION_API_LOG=info ``` -Or to `debug` for more verbose logging. +If you'd like to override the default retry strategy for all operations that support retries, you can use the `retry_config` optional parameter when initializing the SDK: +```python +from moderation_api import ModerationAPI +from moderation_api.utils import BackoffStrategy, RetryConfig +import os + + +with ModerationAPI( + retry_config=RetryConfig("backoff", BackoffStrategy(1, 50, 1.1, 100), False), + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: -### How to tell whether `None` means `null` or missing + res = ma_client.authors.list(request={}) -In an API response, a field may be explicitly `null`, or missing entirely; in either case, its value is `None` in this library. You can differentiate the two cases with `.model_fields_set`: + while res is not None: + # Handle items + + res = res.next() -```py -if response.my_field is None: - if 'my_field' not in response.model_fields_set: - print('Got json like {}, without a "my_field" key present at all.') - else: - print('Got json like {"my_field": null}.') ``` + -### Accessing raw response data (e.g. headers) + +## Error Handling -The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g., +[`ModerationAPIError`](./src/moderation_api/errors/moderationapierror.py) is the base class for all HTTP error responses. It has the following properties: -```py -from moderation_api import ModerationAPI +| Property | Type | Description | +| ------------------ | ---------------- | --------------------------------------------------------------------------------------- | +| `err.message` | `str` | Error message | +| `err.status_code` | `int` | HTTP response status code eg `404` | +| `err.headers` | `httpx.Headers` | HTTP response headers | +| `err.body` | `str` | HTTP body. Can be empty string if no body is returned. | +| `err.raw_response` | `httpx.Response` | Raw HTTP response | +| `err.data` | | Optional. Some errors may contain structured data. [See Error Classes](#error-classes). | -client = ModerationAPI() -response = client.content.with_raw_response.submit( - content={ - "text": "text", - "type": "text", - }, -) -print(response.headers.get('X-My-Header')) +### Example +```python +from moderation_api import ModerationAPI, errors +import os +from typing import Literal -content = response.parse() # get the object that `content.submit()` would have returned -print(content.recommendation) -``` -These methods return an [`APIResponse`](https://github.com/moderation-api/sdk-python/tree/main/src/moderation_api/_response.py) object. +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + res = None + try: -The async client returns an [`AsyncAPIResponse`](https://github.com/moderation-api/sdk-python/tree/main/src/moderation_api/_response.py) with the same structure, the only difference being `await`able methods for reading the response content. + res = ma_client.authors.list(request={}) -#### `.with_streaming_response` + while res is not None: + # Handle items -The above interface eagerly reads the full response body when you make the request, which may not always be what you want. + res = res.next() -To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods. -```python -with client.content.with_streaming_response.submit( - content={ - "text": "text", - "type": "text", - }, -) as response: - print(response.headers.get("X-My-Header")) + except errors.ModerationAPIError as e: + # The base class for HTTP error responses + print(e.message) + print(e.status_code) + print(e.body) + print(e.headers) + print(e.raw_response) - for line in response.iter_lines(): - print(line) + # Depending on the method different errors may be thrown + if isinstance(e, errors.BadRequestError): + print(e.data.type) # Literal["invalid_request_error"] + print(e.data.code) # models.BadRequestErrorCode + print(e.data.message) # str + print(e.data.issues) # Optional[List[models.ErrorIssue]] + print(e.data.request_id) # str ``` -The context manager is required so that the response will reliably be closed. +### Error Classes +**Primary errors:** +* [`ModerationAPIError`](./src/moderation_api/errors/moderationapierror.py): The base class for HTTP error responses. + * [`AuthenticationError`](./src/moderation_api/errors/authenticationerror.py): The API key is missing, invalid or revoked. Status code `401`. + * [`RateLimitError`](./src/moderation_api/errors/ratelimiterror.py): Too many requests. Retry after the number of seconds in the `Retry-After` header. Status code `429`. -### Making custom/undocumented requests +
Less common errors (10) -This library is typed for convenient access to the documented API. +
-If you need to access undocumented endpoints, params, or response properties, the library can still be used. +**Network errors:** +* [`httpx.RequestError`](https://www.python-httpx.org/exceptions/#httpx.RequestError): Base class for request errors. + * [`httpx.ConnectError`](https://www.python-httpx.org/exceptions/#httpx.ConnectError): HTTP client was unable to make a request to a server. + * [`httpx.TimeoutException`](https://www.python-httpx.org/exceptions/#httpx.TimeoutException): HTTP request timed out. -#### Undocumented endpoints -To make requests to undocumented endpoints, you can make requests using `client.get`, `client.post`, and other -http verbs. Options on the client will be respected (such as retries) when making this request. +**Inherit from [`ModerationAPIError`](./src/moderation_api/errors/moderationapierror.py)**: +* [`BadRequestError`](./src/moderation_api/errors/badrequesterror.py): The request is malformed or a parameter is invalid. Status code `400`. Applicable to 32 of 42 methods.* +* [`NotFoundError`](./src/moderation_api/errors/notfounderror.py): The resource does not exist or the key cannot see it. Status code `404`. Applicable to 31 of 42 methods.* +* [`PermissionDeniedError`](./src/moderation_api/errors/permissiondeniederror.py): The API key cannot do this, or the plan limit for this resource is reached. Status code `403`. Applicable to 27 of 42 methods.* +* [`InternalServerError`](./src/moderation_api/errors/internalservererror.py): Something went wrong on our side. Retry with backoff. Status code `500`. Applicable to 15 of 42 methods.* +* [`ConflictError`](./src/moderation_api/errors/conflicterror.py): The request conflicts with the current state of a resource. Status code `409`. Applicable to 2 of 42 methods.* +* [`ResponseValidationError`](./src/moderation_api/errors/responsevalidationerror.py): Type mismatch between the response data and the expected Pydantic model. Provides access to the Pydantic validation error via the `cause` attribute. -```py -import httpx +
-response = client.post( - "/foo", - cast_to=httpx.Response, - body={"my_param": True}, -) +\* Check [the method documentation](#available-resources-and-operations) to see if the error is applicable. + + + +## Server Selection + +### Override Server URL Per-Client + +The default server can be overridden globally by passing a URL to the `server_url: str` optional parameter when initializing the SDK client instance. For example: +```python +from moderation_api import ModerationAPI +import os -print(response.headers.get("x-foo")) -``` -#### Undocumented request params +with ModerationAPI( + server_url="https://moderationapi.com/api/v1", + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: -If you want to explicitly send an extra param, you can do so with the `extra_query`, `extra_body`, and `extra_headers` request -options. + res = ma_client.authors.list(request={}) -#### Undocumented response properties + while res is not None: + # Handle items -To access undocumented response properties, you can access the extra fields like `response.unknown_prop`. You -can also get all the extra fields on the Pydantic model as a dict with -[`response.model_extra`](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel.model_extra). + res = res.next() -### Configuring the HTTP client +``` + -You can directly override the [httpx client](https://www.python-httpx.org/api/#client) to customize it for your use case, including: + +## Custom HTTP Client -- Support for [proxies](https://www.python-httpx.org/advanced/proxies/) -- Custom [transports](https://www.python-httpx.org/advanced/transports/) -- Additional [advanced](https://www.python-httpx.org/advanced/clients/) functionality +The Python SDK makes API calls using the [httpx](https://www.python-httpx.org/) HTTP library. In order to provide a convenient way to configure timeouts, cookies, proxies, custom headers, and other low-level configuration, you can initialize the SDK client with your own HTTP client instance. +Depending on whether you are using the sync or async version of the SDK, you can pass an instance of `HttpClient` or `AsyncHttpClient` respectively, which are Protocol's ensuring that the client has the necessary methods to make API calls. +This allows you to wrap the client with your own custom logic, such as adding custom headers, logging, or error handling, or you can just pass an instance of `httpx.Client` or `httpx.AsyncClient` directly. +For example, you could specify a header for every request that this sdk makes as follows: ```python +from moderation_api import ModerationAPI import httpx -from moderation_api import ModerationAPI, DefaultHttpxClient - -client = ModerationAPI( - # Or use the `MODERATION_API_BASE_URL` env var - base_url="http://my.test.server.example.com:8083", - http_client=DefaultHttpxClient( - proxy="http://my.test.proxy.example.com", - transport=httpx.HTTPTransport(local_address="0.0.0.0"), - ), -) + +http_client = httpx.Client(headers={"x-custom-header": "someValue"}) +s = ModerationAPI(client=http_client) ``` -You can also customize the client on a per-request basis by using `with_options()`: +or you could wrap the client with your own custom logic: +```python +from moderation_api import ModerationAPI +from moderation_api.httpclient import AsyncHttpClient +import httpx +class CustomClient(AsyncHttpClient): + client: AsyncHttpClient + + def __init__(self, client: AsyncHttpClient): + self.client = client + + async def send( + self, + request: httpx.Request, + *, + stream: bool = False, + auth: Union[ + httpx._types.AuthTypes, httpx._client.UseClientDefault, None + ] = httpx.USE_CLIENT_DEFAULT, + follow_redirects: Union[ + bool, httpx._client.UseClientDefault + ] = httpx.USE_CLIENT_DEFAULT, + ) -> httpx.Response: + request.headers["Client-Level-Header"] = "added by client" + + return await self.client.send( + request, stream=stream, auth=auth, follow_redirects=follow_redirects + ) + + def build_request( + self, + method: str, + url: httpx._types.URLTypes, + *, + content: Optional[httpx._types.RequestContent] = None, + data: Optional[httpx._types.RequestData] = None, + files: Optional[httpx._types.RequestFiles] = None, + json: Optional[Any] = None, + params: Optional[httpx._types.QueryParamTypes] = None, + headers: Optional[httpx._types.HeaderTypes] = None, + cookies: Optional[httpx._types.CookieTypes] = None, + timeout: Union[ + httpx._types.TimeoutTypes, httpx._client.UseClientDefault + ] = httpx.USE_CLIENT_DEFAULT, + extensions: Optional[httpx._types.RequestExtensions] = None, + ) -> httpx.Request: + return self.client.build_request( + method, + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + timeout=timeout, + extensions=extensions, + ) + +s = ModerationAPI(async_client=CustomClient(httpx.AsyncClient())) +``` +### httpx2 (Pydantic's httpx fork) + +[httpx2](https://httpx2.pydantic.dev/) is Pydantic's maintained fork of `httpx`. To run this SDK on httpx2, call `alias_httpx()` at your program's entry point, before importing the SDK, so every `import httpx` — including the ones inside the SDK — resolves to `httpx2`: ```python -client.with_options(http_client=DefaultHttpxClient(...)) +import httpx2 + +httpx2.alias_httpx() + +from moderation_api import ModerationAPI + +s = ModerationAPI() ``` -### Managing HTTP resources +An SDK can also be generated against httpx2 directly, so it depends on the fork instead of `httpx`, by setting `python.httpClientLibrary: httpx2` in `gen.yaml`. + + + +## Resource Management -By default the library closes underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__). You can manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting. +The `ModerationAPI` class implements the context manager protocol and registers a finalizer function to close the underlying sync and async HTTPX clients it uses under the hood. This will close HTTP connections, release memory and free up other resources held by the SDK. In short-lived Python programs and notebooks that make a few SDK method calls, resource management may not be a concern. However, in longer-lived programs, it is beneficial to create a single SDK instance via a [context manager][context-manager] and reuse it across the application. -```py +[context-manager]: https://docs.python.org/3/reference/datamodel.html#context-managers + +```python from moderation_api import ModerationAPI +import os +def main(): -with ModerationAPI() as client: - # make requests here - ... + with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), + ) as ma_client: + # Rest of application here... + + +# Or when using async: +async def amain(): -# HTTP client is now closed + async with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), + ) as ma_client: + # Rest of application here... ``` + -## Versioning + +## Debugging -This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: +You can setup your SDK to emit debug logs for SDK requests and responses. -1. Changes that only affect static types, without breaking runtime behavior. -2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ -3. Changes that we do not expect to impact the vast majority of users in practice. +You can pass your own logger class directly into your SDK. +```python +from moderation_api import ModerationAPI +import logging -We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. +logging.basicConfig(level=logging.DEBUG) +s = ModerationAPI(debug_logger=logging.getLogger("moderation_api")) +``` -We are keen for your feedback; please open an [issue](https://www.github.com/moderation-api/sdk-python/issues) with questions, bugs, or suggestions. +You can also enable a default debug logger by setting an environment variable `MODAPI_DEBUG` to true. + -### Determining the installed version + -If you've upgraded to the latest version but aren't seeing any new features you were expecting then your python environment is likely still using an older version. + +## Timeouts -You can determine the version that is being used at runtime with: +Set a timeout in milliseconds for all requests on the client, or for one request: -```py -import moderation_api -print(moderation_api.__version__) +```python +from moderation_api import ModerationAPI + +with ModerationAPI(timeout_ms=10_000) as client: + client.content.submit(content={"type": "text", "text": "Hello"}, timeout_ms=5_000) ``` ## Requirements -Python 3.9 or higher. +Python 3.10 or later. + + +## Support + +- [API reference](https://docs.moderationapi.com/api-reference/introduction) +- [Errors and error codes](https://docs.moderationapi.com/api-reference/errors) +- [Rate limits](https://docs.moderationapi.com/api-reference/rate-limits) +- [Guides](https://docs.moderationapi.com/get-started/introduction) +- Email [support@moderationapi.com](mailto:support@moderationapi.com) + +Found a bug? Open an issue in this repository. This package is generated from the [Moderation API OpenAPI spec](https://api.moderationapi.com/v1/openapi.json), so pull requests to the generated code can't be merged as they are, but we use them to fix the generator. + + +# Development + +## Maturity + +This SDK is in beta, and there may be breaking changes between versions without a major version update. Therefore, we recommend pinning usage +to a specific package version. This way, you can install the same version each time without breaking changes unless you are intentionally +looking for the latest version. + +## Contributions -## Contributing +While we value open-source contributions to this SDK, this library is generated programmatically. Any manual changes added to internal files will be overwritten on the next generation. +We look forward to hearing your feedback. Feel free to open a PR or an issue with a proof of concept and we'll do our best to include it in a future release. -See [the contributing documentation](./CONTRIBUTING.md). +### SDK Created by [Speakeasy](https://www.speakeasy.com/?utm_source=moderation-api&utm_campaign=python) diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 29cf638..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,27 +0,0 @@ -# Security Policy - -## Reporting Security Issues - -This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. - -To report a security issue, please contact the Stainless team at security@stainless.com. - -## Responsible Disclosure - -We appreciate the efforts of security researchers and individuals who help us maintain the security of -SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible -disclosure practices by allowing us a reasonable amount of time to investigate and address the issue -before making any information public. - -## Reporting Non-SDK Related Security Issues - -If you encounter security issues that are not directly related to SDKs but pertain to the services -or products provided by Moderation API, please follow the respective company's security reporting guidelines. - -### Moderation API Terms and Policies - -Please contact support@moderationapi.com for any questions or concerns regarding the security of our services. - ---- - -Thank you for helping us keep the SDKs and systems they interact with secure. diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 0000000..ffbd7b0 --- /dev/null +++ b/USAGE.md @@ -0,0 +1,51 @@ + +```python +# Synchronous Example +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.content.submit(content={ + "type": "text", + "text": "Hey, check out my profile for a deal!", + }, content_id="message-48213", author_id="user-1092", conversation_id="room-general", metadata={ + "platform": "ios", + }) + + # Handle response + print(res) +``` + +
+ +The same SDK client can also be used to make asynchronous requests by importing asyncio. + +```python +# Asynchronous Example +import asyncio +from moderation_api import ModerationAPI +import os + +async def main(): + + async with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), + ) as ma_client: + + res = await ma_client.content.submit_async(content={ + "type": "text", + "text": "Hey, check out my profile for a deal!", + }, content_id="message-48213", author_id="user-1092", conversation_id="room-general", metadata={ + "platform": "ios", + }) + + # Handle response + print(res) + +asyncio.run(main()) +``` + \ No newline at end of file diff --git a/api.md b/api.md deleted file mode 100644 index bf7cc9f..0000000 --- a/api.md +++ /dev/null @@ -1,188 +0,0 @@ -# Authors - -Types: - -```python -from moderation_api.types import ( - AuthorCreateResponse, - AuthorRetrieveResponse, - AuthorUpdateResponse, - AuthorListResponse, - AuthorDeleteResponse, -) -``` - -Methods: - -- client.authors.create(\*\*params) -> AuthorCreateResponse -- client.authors.retrieve(id) -> AuthorRetrieveResponse -- client.authors.update(id, \*\*params) -> AuthorUpdateResponse -- client.authors.list(\*\*params) -> AuthorListResponse -- client.authors.delete(id) -> AuthorDeleteResponse - -# Queue - -Types: - -```python -from moderation_api.types import WebhookEvent, QueueRetrieveResponse, QueueGetStatsResponse -``` - -Methods: - -- client.queue.retrieve(id) -> QueueRetrieveResponse -- client.queue.get_stats(id, \*\*params) -> QueueGetStatsResponse - -## Items - -Types: - -```python -from moderation_api.types.queue import ItemListResponse, ItemResolveResponse, ItemUnresolveResponse -``` - -Methods: - -- client.queue.items.list(id, \*\*params) -> ItemListResponse -- client.queue.items.resolve(item_id, \*, id, \*\*params) -> ItemResolveResponse -- client.queue.items.unresolve(item_id, \*, id, \*\*params) -> ItemUnresolveResponse - -# Actions - -Types: - -```python -from moderation_api.types import ( - ActionCreateResponse, - ActionRetrieveResponse, - ActionUpdateResponse, - ActionListResponse, - ActionDeleteResponse, -) -``` - -Methods: - -- client.actions.create(\*\*params) -> ActionCreateResponse -- client.actions.retrieve(id) -> ActionRetrieveResponse -- client.actions.update(id, \*\*params) -> ActionUpdateResponse -- client.actions.list(\*\*params) -> ActionListResponse -- client.actions.delete(id) -> ActionDeleteResponse - -## Execute - -Types: - -```python -from moderation_api.types.actions import ExecuteExecuteResponse, ExecuteExecuteByIDResponse -``` - -Methods: - -- client.actions.execute.execute(\*\*params) -> ExecuteExecuteResponse -- client.actions.execute.execute_by_id(action_id, \*\*params) -> ExecuteExecuteByIDResponse - -# Content - -Types: - -```python -from moderation_api.types import ContentSubmitResponse -``` - -Methods: - -- client.content.stream() -> None -- client.content.submit(\*\*params) -> ContentSubmitResponse - -# Account - -Types: - -```python -from moderation_api.types import AccountListResponse -``` - -Methods: - -- client.account.list() -> AccountListResponse - -# Auth - -Types: - -```python -from moderation_api.types import AuthCreateResponse, AuthRetrieveResponse -``` - -Methods: - -- client.auth.create() -> AuthCreateResponse -- client.auth.retrieve() -> AuthRetrieveResponse - -# Wordlist - -Types: - -```python -from moderation_api.types import ( - WordlistRetrieveResponse, - WordlistUpdateResponse, - WordlistListResponse, - WordlistGetEmbeddingStatusResponse, -) -``` - -Methods: - -- client.wordlist.retrieve(id) -> WordlistRetrieveResponse -- client.wordlist.update(id, \*\*params) -> WordlistUpdateResponse -- client.wordlist.list() -> WordlistListResponse -- client.wordlist.get_embedding_status(id) -> WordlistGetEmbeddingStatusResponse - -## Words - -Types: - -```python -from moderation_api.types.wordlist import WordAddResponse, WordRemoveResponse -``` - -Methods: - -- client.wordlist.words.add(id, \*\*params) -> WordAddResponse -- client.wordlist.words.remove(id, \*\*params) -> WordRemoveResponse - -# Webhooks - -Types: - -```python -from moderation_api.types import ( - WebhookCreateResponse, - WebhookRetrieveResponse, - WebhookUpdateResponse, - WebhookListResponse, - WebhookDeleteResponse, -) -``` - -Methods: - -- client.webhooks.create(\*\*params) -> WebhookCreateResponse -- client.webhooks.retrieve(id) -> WebhookRetrieveResponse -- client.webhooks.update(id, \*\*params) -> WebhookUpdateResponse -- client.webhooks.list() -> WebhookListResponse -- client.webhooks.delete(id) -> WebhookDeleteResponse - -# WebhookSecret - -Types: - -```python -from moderation_api.types import WebhookSecretRetrieveResponse -``` - -Methods: - -- client.webhook_secret.retrieve() -> WebhookSecretRetrieveResponse diff --git a/bin/check-release-environment b/bin/check-release-environment deleted file mode 100644 index b845b0f..0000000 --- a/bin/check-release-environment +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash - -errors=() - -if [ -z "${PYPI_TOKEN}" ]; then - errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") -fi - -lenErrors=${#errors[@]} - -if [[ lenErrors -gt 0 ]]; then - echo -e "Found the following errors in the release environment:\n" - - for error in "${errors[@]}"; do - echo -e "- $error\n" - done - - exit 1 -fi - -echo "The environment is ready to push releases!" diff --git a/bin/publish-pypi b/bin/publish-pypi deleted file mode 100644 index e72ca2f..0000000 --- a/bin/publish-pypi +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -set -eux -rm -rf dist -mkdir -p dist -uv build -uv publish --token=$PYPI_TOKEN diff --git a/docs/errors/authenticationerror.md b/docs/errors/authenticationerror.md new file mode 100644 index 0000000..c0ac57c --- /dev/null +++ b/docs/errors/authenticationerror.md @@ -0,0 +1,15 @@ +# AuthenticationError + +The API key is missing, invalid or revoked. + + +## Fields + +| Field | Type | Required | Description | Example | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | *Literal["authentication_error"]* | :heavy_check_mark: | Broad error category. Stable; safe to switch on. | | +| `code` | [models.AuthenticationErrorCode](../models/authenticationerrorcode.md) | :heavy_check_mark: | Exactly what went wrong. Stable; safe to branch on. New codes may be added.

- `unauthorized`: Authentication failed.
- `missing_api_key`: No API key in the `Authorization` header.
- `malformed_api_key`: The `Authorization` header does not contain a valid key format.
- `invalid_api_key`: The API key does not exist or was revoked. | unauthorized | +| `message` | *str* | :heavy_check_mark: | Human-readable explanation. | Authorization not provided | +| `issues` | List[[models.ErrorIssue](../models/errorissue.md)] | :heavy_minus_sign: | Validation problems, when the request body is invalid. | | +| `request_id` | *str* | :heavy_check_mark: | Identifies this request. Include it when you contact support. | h8fkz6kkzoa446ffve37atys | +| `doc_url` | *str* | :heavy_check_mark: | Documentation for this `code`. | | \ No newline at end of file diff --git a/docs/errors/badrequesterror.md b/docs/errors/badrequesterror.md new file mode 100644 index 0000000..4e5610a --- /dev/null +++ b/docs/errors/badrequesterror.md @@ -0,0 +1,15 @@ +# BadRequestError + +The request is malformed or a parameter is invalid. + + +## Fields + +| Field | Type | Required | Description | Example | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | *Literal["invalid_request_error"]* | :heavy_check_mark: | Broad error category. Stable; safe to switch on. | | +| `code` | [models.BadRequestErrorCode](../models/badrequesterrorcode.md) | :heavy_check_mark: | Exactly what went wrong. Stable; safe to branch on. New codes may be added.

- `invalid_request`: A parameter is invalid. `message` says which one.
- `validation_failed`: The request body does not match the schema. `issues` lists each problem with its `path`. | invalid_request | +| `message` | *str* | :heavy_check_mark: | Human-readable explanation. | Invalid input data | +| `issues` | List[[models.ErrorIssue](../models/errorissue.md)] | :heavy_minus_sign: | Validation problems, when the request body is invalid. | | +| `request_id` | *str* | :heavy_check_mark: | Identifies this request. Include it when you contact support. | h8fkz6kkzoa446ffve37atys | +| `doc_url` | *str* | :heavy_check_mark: | Documentation for this `code`. | | \ No newline at end of file diff --git a/docs/errors/conflicterror.md b/docs/errors/conflicterror.md new file mode 100644 index 0000000..e7459bb --- /dev/null +++ b/docs/errors/conflicterror.md @@ -0,0 +1,15 @@ +# ConflictError + +The request conflicts with the current state of a resource. + + +## Fields + +| Field | Type | Required | Description | Example | +| ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | *Literal["conflict_error"]* | :heavy_check_mark: | Broad error category. Stable; safe to switch on. | | +| `code` | [models.ConflictErrorCode](../models/conflicterrorcode.md) | :heavy_check_mark: | Exactly what went wrong. Stable; safe to branch on. New codes may be added.

- `resource_conflict`: The request conflicts with an existing resource. | resource_conflict | +| `message` | *str* | :heavy_check_mark: | Human-readable explanation. | Action with this key already exists | +| `issues` | List[[models.ErrorIssue](../models/errorissue.md)] | :heavy_minus_sign: | Validation problems, when the request body is invalid. | | +| `request_id` | *str* | :heavy_check_mark: | Identifies this request. Include it when you contact support. | h8fkz6kkzoa446ffve37atys | +| `doc_url` | *str* | :heavy_check_mark: | Documentation for this `code`. | | \ No newline at end of file diff --git a/docs/errors/internalservererror.md b/docs/errors/internalservererror.md new file mode 100644 index 0000000..d948ab4 --- /dev/null +++ b/docs/errors/internalservererror.md @@ -0,0 +1,15 @@ +# InternalServerError + +Something went wrong on our side. Retry with backoff. + + +## Fields + +| Field | Type | Required | Description | Example | +| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `type` | *Literal["api_error"]* | :heavy_check_mark: | Broad error category. Stable; safe to switch on. | | +| `code` | [models.InternalServerErrorCode](../models/internalservererrorcode.md) | :heavy_check_mark: | Exactly what went wrong. Stable; safe to branch on. New codes may be added.

- `internal_error`: Something went wrong on our side. Retry with backoff. | internal_error | +| `message` | *str* | :heavy_check_mark: | Human-readable explanation. | Internal server error | +| `issues` | List[[models.ErrorIssue](../models/errorissue.md)] | :heavy_minus_sign: | Validation problems, when the request body is invalid. | | +| `request_id` | *str* | :heavy_check_mark: | Identifies this request. Include it when you contact support. | h8fkz6kkzoa446ffve37atys | +| `doc_url` | *str* | :heavy_check_mark: | Documentation for this `code`. | | \ No newline at end of file diff --git a/docs/errors/notfounderror.md b/docs/errors/notfounderror.md new file mode 100644 index 0000000..8ed4191 --- /dev/null +++ b/docs/errors/notfounderror.md @@ -0,0 +1,15 @@ +# NotFoundError + +The resource does not exist or the key cannot see it. + + +## Fields + +| Field | Type | Required | Description | Example | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | *Literal["not_found_error"]* | :heavy_check_mark: | Broad error category. Stable; safe to switch on. | | +| `code` | [models.NotFoundErrorCode](../models/notfounderrorcode.md) | :heavy_check_mark: | Exactly what went wrong. Stable; safe to branch on. New codes may be added.

- `resource_not_found`: The resource does not exist.
- `action_not_found`: The action does not exist.
- `author_not_found`: The author does not exist.
- `queue_not_found`: The review queue does not exist.
- `wordlist_not_found`: The wordlist does not exist.
- `webhook_not_found`: The webhook does not exist.
- `project_not_found`: The project does not exist.
- `channel_not_found`: The channel does not exist. | resource_not_found | +| `message` | *str* | :heavy_check_mark: | Human-readable explanation. | Not found | +| `issues` | List[[models.ErrorIssue](../models/errorissue.md)] | :heavy_minus_sign: | Validation problems, when the request body is invalid. | | +| `request_id` | *str* | :heavy_check_mark: | Identifies this request. Include it when you contact support. | h8fkz6kkzoa446ffve37atys | +| `doc_url` | *str* | :heavy_check_mark: | Documentation for this `code`. | | \ No newline at end of file diff --git a/docs/errors/permissiondeniederror.md b/docs/errors/permissiondeniederror.md new file mode 100644 index 0000000..a814db3 --- /dev/null +++ b/docs/errors/permissiondeniederror.md @@ -0,0 +1,15 @@ +# PermissionDeniedError + +The API key cannot do this, or the plan limit for this resource is reached. + + +## Fields + +| Field | Type | Required | Description | Example | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `type` | *Literal["permission_error"]* | :heavy_check_mark: | Broad error category. Stable; safe to switch on. | | +| `code` | [models.PermissionDeniedErrorCode](../models/permissiondeniederrorcode.md) | :heavy_check_mark: | Exactly what went wrong. Stable; safe to branch on. New codes may be added.

- `permission_denied`: The API key cannot perform this request.
- `account_disabled`: The organization account is disabled.
- `quota_exceeded`: The organization has used all API requests on its plan.
- `plan_feature_unavailable`: The current plan does not include this feature.
- `plan_limit_exceeded`: The request goes over a limit of the current plan.
- `project_key_required`: This endpoint needs a project API key, but an organization key was used.
- `organization_key_required`: This endpoint needs an organization API key, but a project key was used.
- `missing_key_permission`: The organization key does not have the permission this endpoint needs.
- `project_not_reachable`: The organization key is limited to other projects. | permission_denied | +| `message` | *str* | :heavy_check_mark: | Human-readable explanation. | Insufficient access | +| `issues` | List[[models.ErrorIssue](../models/errorissue.md)] | :heavy_minus_sign: | Validation problems, when the request body is invalid. | | +| `request_id` | *str* | :heavy_check_mark: | Identifies this request. Include it when you contact support. | h8fkz6kkzoa446ffve37atys | +| `doc_url` | *str* | :heavy_check_mark: | Documentation for this `code`. | | \ No newline at end of file diff --git a/docs/errors/ratelimiterror.md b/docs/errors/ratelimiterror.md new file mode 100644 index 0000000..68d0515 --- /dev/null +++ b/docs/errors/ratelimiterror.md @@ -0,0 +1,15 @@ +# RateLimitError + +Too many requests. Retry after the number of seconds in the `Retry-After` header. + + +## Fields + +| Field | Type | Required | Description | Example | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `type` | *Literal["rate_limit_error"]* | :heavy_check_mark: | Broad error category. Stable; safe to switch on. | | +| `code` | [models.RateLimitErrorCode](../models/ratelimiterrorcode.md) | :heavy_check_mark: | Exactly what went wrong. Stable; safe to branch on. New codes may be added.

- `rate_limit_exceeded`: Too many requests. Wait for the `Retry-After` seconds. | rate_limit_exceeded | +| `message` | *str* | :heavy_check_mark: | Human-readable explanation. | Rate limit exceeded | +| `issues` | List[[models.ErrorIssue](../models/errorissue.md)] | :heavy_minus_sign: | Validation problems, when the request body is invalid. | | +| `request_id` | *str* | :heavy_check_mark: | Identifies this request. Include it when you contact support. | h8fkz6kkzoa446ffve37atys | +| `doc_url` | *str* | :heavy_check_mark: | Documentation for this `code`. | | \ No newline at end of file diff --git a/docs/models/actionrequest.md b/docs/models/actionrequest.md new file mode 100644 index 0000000..c4efab8 --- /dev/null +++ b/docs/models/actionrequest.md @@ -0,0 +1,17 @@ +# ActionRequest + +Your recommendation for the content: allow, review, or reject. + +## Example Usage + +```python +from moderation_api.models import ActionRequest +value: ActionRequest = "review" +``` + + +## Values + +- `"review"` +- `"allow"` +- `"reject"` diff --git a/docs/models/actionstat.md b/docs/models/actionstat.md new file mode 100644 index 0000000..0134a12 --- /dev/null +++ b/docs/models/actionstat.md @@ -0,0 +1,11 @@ +# ActionStat + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------ | ------------------------------------------------ | ------------------------------------------------ | ------------------------------------------------ | +| `action_id` | *str* | :heavy_check_mark: | ID of the moderation action | +| `action_name` | *str* | :heavy_check_mark: | Name of the moderation action | +| `count` | *float* | :heavy_check_mark: | Number of times this action was taken | +| `percentage_of_total` | *float* | :heavy_check_mark: | Percentage this action represents of all actions | \ No newline at end of file diff --git a/docs/models/addwordlistwordsrequest.md b/docs/models/addwordlistwordsrequest.md new file mode 100644 index 0000000..0f25b77 --- /dev/null +++ b/docs/models/addwordlistwordsrequest.md @@ -0,0 +1,9 @@ +# AddWordlistWordsRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | +| `id` | *str* | :heavy_check_mark: | ID of the wordlist to add words to | +| `body` | [models.AddWordlistWordsRequestBody](../models/addwordlistwordsrequestbody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/addwordlistwordsrequestbody.md b/docs/models/addwordlistwordsrequestbody.md new file mode 100644 index 0000000..93f3e48 --- /dev/null +++ b/docs/models/addwordlistwordsrequestbody.md @@ -0,0 +1,8 @@ +# AddWordlistWordsRequestBody + + +## Fields + +| Field | Type | Required | Description | +| ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | +| `words` | List[*str*] | :heavy_check_mark: | Array of words to add to the wordlist. Duplicate words will be ignored. | \ No newline at end of file diff --git a/docs/models/addwordlistwordsresponse.md b/docs/models/addwordlistwordsresponse.md new file mode 100644 index 0000000..a93e309 --- /dev/null +++ b/docs/models/addwordlistwordsresponse.md @@ -0,0 +1,12 @@ +# AddWordlistWordsResponse + +Words successfully added to wordlist + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------- | --------------------------------- | --------------------------------- | --------------------------------- | +| `added_count` | *float* | :heavy_check_mark: | Number of words added | +| `total_count` | *float* | :heavy_check_mark: | Total number of words in wordlist | +| `added_words` | List[*str*] | :heavy_check_mark: | List of words that were added | \ No newline at end of file diff --git a/docs/models/adult.md b/docs/models/adult.md new file mode 100644 index 0000000..ff0d133 --- /dev/null +++ b/docs/models/adult.md @@ -0,0 +1,10 @@ +# Adult + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["adult"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/alcohol.md b/docs/models/alcohol.md new file mode 100644 index 0000000..e9d1476 --- /dev/null +++ b/docs/models/alcohol.md @@ -0,0 +1,10 @@ +# Alcohol + + +## Fields + +| Field | Type | Required | Description | +| -------------------- | -------------------- | -------------------- | -------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["alcohol"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/audiorequest.md b/docs/models/audiorequest.md new file mode 100644 index 0000000..65cf0db --- /dev/null +++ b/docs/models/audiorequest.md @@ -0,0 +1,11 @@ +# AudioRequest + +Audio + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------- | ---------------------------- | ---------------------------- | ---------------------------- | +| `type` | *Literal["audio"]* | :heavy_check_mark: | N/A | +| `url` | *str* | :heavy_check_mark: | The URL of the audio content | \ No newline at end of file diff --git a/docs/models/audioresponse.md b/docs/models/audioresponse.md new file mode 100644 index 0000000..70a4c2e --- /dev/null +++ b/docs/models/audioresponse.md @@ -0,0 +1,11 @@ +# AudioResponse + +Audio + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------- | ---------------------------- | ---------------------------- | ---------------------------- | +| `type` | *Literal["audio"]* | :heavy_check_mark: | N/A | +| `url` | *str* | :heavy_check_mark: | The URL of the audio content | \ No newline at end of file diff --git a/docs/models/authenticationerrorcode.md b/docs/models/authenticationerrorcode.md new file mode 100644 index 0000000..108f5e8 --- /dev/null +++ b/docs/models/authenticationerrorcode.md @@ -0,0 +1,27 @@ +# AuthenticationErrorCode + +Exactly what went wrong. Stable; safe to branch on. New codes may be added. + +- `unauthorized`: Authentication failed. +- `missing_api_key`: No API key in the `Authorization` header. +- `malformed_api_key`: The `Authorization` header does not contain a valid key format. +- `invalid_api_key`: The API key does not exist or was revoked. + +## Example Usage + +```python +from moderation_api.models import AuthenticationErrorCode + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: AuthenticationErrorCode = "unauthorized" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"unauthorized"` +- `"missing_api_key"` +- `"malformed_api_key"` +- `"invalid_api_key"` diff --git a/docs/models/author.md b/docs/models/author.md new file mode 100644 index 0000000..bacdb89 --- /dev/null +++ b/docs/models/author.md @@ -0,0 +1,12 @@ +# Author + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | Author ID in Moderation API | +| `external_id` | *OptionalNullable[str]* | :heavy_minus_sign: | The author's ID from your system | +| `block` | [Nullable[models.SubmitContentBlock]](../models/submitcontentblock.md) | :heavy_check_mark: | Block or suspension details, if applicable. Null if the author is enabled. | +| `status` | [models.AuthorStatus](../models/authorstatus.md) | :heavy_check_mark: | Current author status | +| `trust_level` | [models.SubmitContentTrustLevel](../models/submitcontenttrustlevel.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/authorstatus.md b/docs/models/authorstatus.md new file mode 100644 index 0000000..ffe489b --- /dev/null +++ b/docs/models/authorstatus.md @@ -0,0 +1,21 @@ +# AuthorStatus + +Current author status + +## Example Usage + +```python +from moderation_api.models import AuthorStatus + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: AuthorStatus = "enabled" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"enabled"` +- `"suspended"` +- `"blocked"` diff --git a/docs/models/badrequesterrorcode.md b/docs/models/badrequesterrorcode.md new file mode 100644 index 0000000..2511d6e --- /dev/null +++ b/docs/models/badrequesterrorcode.md @@ -0,0 +1,23 @@ +# BadRequestErrorCode + +Exactly what went wrong. Stable; safe to branch on. New codes may be added. + +- `invalid_request`: A parameter is invalid. `message` says which one. +- `validation_failed`: The request body does not match the schema. `issues` lists each problem with its `path`. + +## Example Usage + +```python +from moderation_api.models import BadRequestErrorCode + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: BadRequestErrorCode = "invalid_request" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"invalid_request"` +- `"validation_failed"` diff --git a/docs/models/block.md b/docs/models/block.md new file mode 100644 index 0000000..7aa5451 --- /dev/null +++ b/docs/models/block.md @@ -0,0 +1,9 @@ +# Block + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| `until` | *OptionalNullable[float]* | :heavy_minus_sign: | The timestamp until which they are blocked if the author is suspended. | +| `reason` | *OptionalNullable[str]* | :heavy_minus_sign: | The moderators reason why the author was blocked or suspended. | \ No newline at end of file diff --git a/docs/models/cannabis.md b/docs/models/cannabis.md new file mode 100644 index 0000000..2cb089c --- /dev/null +++ b/docs/models/cannabis.md @@ -0,0 +1,10 @@ +# Cannabis + + +## Fields + +| Field | Type | Required | Description | +| --------------------- | --------------------- | --------------------- | --------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["cannabis"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/casebook.md b/docs/models/casebook.md new file mode 100644 index 0000000..7d188f9 --- /dev/null +++ b/docs/models/casebook.md @@ -0,0 +1,13 @@ +# Casebook + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `verdict` | [models.Verdict](../models/verdict.md) | :heavy_check_mark: | The ruling your past decisions point to for this content. | +| `similarity` | *float* | :heavy_check_mark: | How close the nearest matching case is, from 0 to 1. 1 means the content is identical to something you have already decided. | +| `agreement` | *float* | :heavy_check_mark: | How unanimous the matching cases are, from 0 to 1: the share of them that decided this way, ignoring how many there are. Always at least 0.8 when a ruling is returned — below that the casebook reports a disagreement instead of picking a side — so it tells you how clean the consensus was, and is not a threshold to re-apply yourself. | +| `confidence` | *float* | :heavy_check_mark: | How strongly the casebook holds this ruling, from 0 to 1: the agreement scaled by how much evidence backs it, so a handful of close, recent cases outweighs one distant one. Older cases count for less, halving in weight roughly every 180 days. This is the number to use in rules when you want a strength condition. | +| `case_count` | *float* | :heavy_check_mark: | How many of your past cases backed this ruling. | +| `topic` | [Nullable[models.Topic]](../models/topic.md) | :heavy_check_mark: | The topic the closest matching case is filed under, or null when it has not been grouped into one yet. | \ No newline at end of file diff --git a/docs/models/casebookagreement.md b/docs/models/casebookagreement.md new file mode 100644 index 0000000..62828c3 --- /dev/null +++ b/docs/models/casebookagreement.md @@ -0,0 +1,19 @@ +# CasebookAgreement + +## Example Usage + +```python +from moderation_api.models import CasebookAgreement + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: CasebookAgreement = "ALL" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"ALL"` +- `"OVERRULED"` +- `"AGREED"` diff --git a/docs/models/casebookanswer.md b/docs/models/casebookanswer.md new file mode 100644 index 0000000..857b020 --- /dev/null +++ b/docs/models/casebookanswer.md @@ -0,0 +1,20 @@ +# CasebookAnswer + +## Example Usage + +```python +from moderation_api.models import CasebookAnswer + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: CasebookAnswer = "ALL" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"ALL"` +- `"ALLOWED"` +- `"REJECTED"` +- `"NO_ANSWER"` diff --git a/docs/models/channel.md b/docs/models/channel.md new file mode 100644 index 0000000..7c3e152 --- /dev/null +++ b/docs/models/channel.md @@ -0,0 +1,28 @@ +# Channel + + +## Fields + +| Field | Type | Required | Description | +| ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the channel. | +| `project_id` | *str* | :heavy_check_mark: | The ID of the project the channel is in. | +| `key` | *str* | :heavy_check_mark: | The key you pass as `channel` when you moderate content. Unique in the project. | +| `name` | *str* | :heavy_check_mark: | The channel's name. | +| `description` | *Nullable[str]* | :heavy_check_mark: | The channel's description. | +| `is_default` | *bool* | :heavy_check_mark: | Whether requests that name no channel use this one. Each project has one default channel. | +| `content_type` | [Nullable[models.ContentType]](../models/contenttype.md) | :heavy_check_mark: | N/A | +| `flagging_mode` | [models.FlaggingMode](../models/flaggingmode.md) | :heavy_check_mark: | `ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it. | +| `context_enabled` | *bool* | :heavy_check_mark: | Whether earlier messages in the conversation are read when moderating a message. | +| `enable_image_moderation` | *bool* | :heavy_check_mark: | Whether images are moderated. | +| `enable_video_moderation` | *bool* | :heavy_check_mark: | Whether videos are moderated. | +| `enable_audio_moderation` | *bool* | :heavy_check_mark: | Whether audio is moderated. | +| `enable_voice_moderation` | *bool* | :heavy_check_mark: | Whether real-time voice streams are moderated. | +| `include_transcript_in_response` | *bool* | :heavy_check_mark: | Whether audio and video transcripts are returned in responses. | +| `transcription_quality` | [models.TranscriptionQuality](../models/transcriptionquality.md) | :heavy_check_mark: | Trade-off between transcription speed and accuracy. | +| `unicode_spoof_detection_enabled` | *bool* | :heavy_check_mark: | Whether look-alike Unicode characters are normalized before moderation. | +| `frame_capture_interval` | *float* | :heavy_check_mark: | Seconds between frames captured from a video. | +| `max_frames_per_video` | *int* | :heavy_check_mark: | The most frames moderated per video. | +| `spread_frames` | *bool* | :heavy_check_mark: | Whether frames are spread evenly across the whole video. | +| `created_at` | *str* | :heavy_check_mark: | When the channel was created. | +| `updated_at` | *str* | :heavy_check_mark: | When the channel was last changed. | \ No newline at end of file diff --git a/docs/models/channelcircumvention.md b/docs/models/channelcircumvention.md new file mode 100644 index 0000000..5eece52 --- /dev/null +++ b/docs/models/channelcircumvention.md @@ -0,0 +1,10 @@ +# ChannelCircumvention + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["channel_circumvent"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/checkstatus.md b/docs/models/checkstatus.md new file mode 100644 index 0000000..cd2ef4e --- /dev/null +++ b/docs/models/checkstatus.md @@ -0,0 +1,19 @@ +# CheckStatus + +## Example Usage + +```python +from moderation_api.models import CheckStatus + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: CheckStatus = "all" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"all"` +- `"checked"` +- `"unchecked"` diff --git a/docs/models/classifieroutput.md b/docs/models/classifieroutput.md new file mode 100644 index 0000000..2a4a19b --- /dev/null +++ b/docs/models/classifieroutput.md @@ -0,0 +1,16 @@ +# ClassifierOutput + +Classifier policy. + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The unique identifier for the classifier output. | +| `type` | *Literal["classifier"]* | :heavy_check_mark: | N/A | +| `probability` | *float* | :heavy_check_mark: | N/A | +| `flagged` | *bool* | :heavy_check_mark: | N/A | +| `flagged_fields` | List[*str*] | :heavy_minus_sign: | The keys of the flagged fields if submitting an object. | +| `labels` | List[[models.SubmitContentLabel](../models/submitcontentlabel.md)] | :heavy_minus_sign: | N/A | +| `data` | Dict[str, *Any*] | :heavy_minus_sign: | Optional structured data produced by the policy. For face detection: { count, faces: [{ confidence, gender, age }] }. | \ No newline at end of file diff --git a/docs/models/codeabuse.md b/docs/models/codeabuse.md new file mode 100644 index 0000000..c6f082d --- /dev/null +++ b/docs/models/codeabuse.md @@ -0,0 +1,10 @@ +# CodeAbuse + + +## Fields + +| Field | Type | Required | Description | +| ----------------------- | ----------------------- | ----------------------- | ----------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["code_abuse"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/comparator.md b/docs/models/comparator.md new file mode 100644 index 0000000..9bbcfe1 --- /dev/null +++ b/docs/models/comparator.md @@ -0,0 +1,16 @@ +# Comparator + +Flag images that contain "at least" or "fewer than" the configured number of faces. Defaults to at_least. + +## Example Usage + +```python +from moderation_api.models import Comparator +value: Comparator = "at_least" +``` + + +## Values + +- `"at_least"` +- `"fewer_than"` diff --git a/docs/models/compassioninsight.md b/docs/models/compassioninsight.md new file mode 100644 index 0000000..69f27ac --- /dev/null +++ b/docs/models/compassioninsight.md @@ -0,0 +1,13 @@ +# CompassionInsight + +Compassion insight. Identifies with or shows concern, empathy, or support for the feelings/emotions of others. + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------- | ------------------------------------- | ------------------------------------- | ------------------------------------- | +| `id` | *Literal["compassion"]* | :heavy_check_mark: | N/A | +| `type` | *Literal["insight"]* | :heavy_check_mark: | N/A | +| `probability` | *float* | :heavy_check_mark: | N/A | +| `value` | *Nullable[bool]* | :heavy_check_mark: | Whether the content shows compassion. | \ No newline at end of file diff --git a/docs/models/conflicterrorcode.md b/docs/models/conflicterrorcode.md new file mode 100644 index 0000000..c29b48c --- /dev/null +++ b/docs/models/conflicterrorcode.md @@ -0,0 +1,21 @@ +# ConflictErrorCode + +Exactly what went wrong. Stable; safe to branch on. New codes may be added. + +- `resource_conflict`: The request conflicts with an existing resource. + +## Example Usage + +```python +from moderation_api.models import ConflictErrorCode + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: ConflictErrorCode = "resource_conflict" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"resource_conflict"` diff --git a/docs/models/contentleafrequest.md b/docs/models/contentleafrequest.md new file mode 100644 index 0000000..a92f5c0 --- /dev/null +++ b/docs/models/contentleafrequest.md @@ -0,0 +1,29 @@ +# ContentLeafRequest + + +## Supported Types + +### `models.DataTextRequest` + +```python +value: models.DataTextRequest = /* values here */ +``` + +### `models.DataImageRequest` + +```python +value: models.DataImageRequest = /* values here */ +``` + +### `models.DataVideoRequest` + +```python +value: models.DataVideoRequest = /* values here */ +``` + +### `models.DataAudioRequest` + +```python +value: models.DataAudioRequest = /* values here */ +``` + diff --git a/docs/models/contentleafresponse.md b/docs/models/contentleafresponse.md new file mode 100644 index 0000000..7380c85 --- /dev/null +++ b/docs/models/contentleafresponse.md @@ -0,0 +1,29 @@ +# ContentLeafResponse + + +## Supported Types + +### `models.TextResponse` + +```python +value: models.TextResponse = /* values here */ +``` + +### `models.ImageResponse` + +```python +value: models.ImageResponse = /* values here */ +``` + +### `models.VideoResponse` + +```python +value: models.VideoResponse = /* values here */ +``` + +### `models.AudioResponse` + +```python +value: models.AudioResponse = /* values here */ +``` + diff --git a/docs/models/contentrequest.md b/docs/models/contentrequest.md new file mode 100644 index 0000000..9b66635 --- /dev/null +++ b/docs/models/contentrequest.md @@ -0,0 +1,37 @@ +# ContentRequest + +The content sent for moderation + + +## Supported Types + +### `models.TextRequest` + +```python +value: models.TextRequest = /* values here */ +``` + +### `models.ImageRequest` + +```python +value: models.ImageRequest = /* values here */ +``` + +### `models.VideoRequest` + +```python +value: models.VideoRequest = /* values here */ +``` + +### `models.AudioRequest` + +```python +value: models.AudioRequest = /* values here */ +``` + +### `models.SubmitContentObject` + +```python +value: models.SubmitContentObject = /* values here */ +``` + diff --git a/docs/models/contentresponse.md b/docs/models/contentresponse.md new file mode 100644 index 0000000..a0b2a16 --- /dev/null +++ b/docs/models/contentresponse.md @@ -0,0 +1,14 @@ +# ContentResponse + +Potentially modified content. + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `id` | *str* | :heavy_check_mark: | The unique identifier for the content. Either the contentId provided by you or an autogenerated ID. | +| `masked` | *bool* | :heavy_check_mark: | Whether any values have been masked. | +| `unicode_cleaned` | *bool* | :heavy_check_mark: | Whether Unicode spoofing normalization rewrote the content — confusables folded to their Latin lookalikes, invisible characters and combining-mark abuse stripped. | +| `modified` | [Nullable[models.Modified]](../models/modified.md) | :heavy_check_mark: | The modified content, if any. | +| `transcript` | *OptionalNullable[str]* | :heavy_minus_sign: | The transcribed text from audio content. Only present when audio moderation is used and transcript inclusion is enabled on the channel. | \ No newline at end of file diff --git a/docs/models/contenttype.md b/docs/models/contenttype.md new file mode 100644 index 0000000..e578bbe --- /dev/null +++ b/docs/models/contenttype.md @@ -0,0 +1,27 @@ +# ContentType + +The kind of content the channel moderates. + +## Example Usage + +```python +from moderation_api.models import ContentType + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: ContentType = "profile" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"profile"` +- `"message"` +- `"post"` +- `"comment"` +- `"event"` +- `"product"` +- `"review"` +- `"voice"` +- `"other"` diff --git a/docs/models/createactionpositionrequest.md b/docs/models/createactionpositionrequest.md new file mode 100644 index 0000000..486ecf9 --- /dev/null +++ b/docs/models/createactionpositionrequest.md @@ -0,0 +1,17 @@ +# CreateActionPositionRequest + +Show the action in all queues, selected queues or no queues (to use via API only). + +## Example Usage + +```python +from moderation_api.models import CreateActionPositionRequest +value: CreateActionPositionRequest = "ALL_QUEUES" +``` + + +## Values + +- `"ALL_QUEUES"` +- `"SOME_QUEUES"` +- `"HIDDEN"` diff --git a/docs/models/createactionpositionresponse.md b/docs/models/createactionpositionresponse.md new file mode 100644 index 0000000..262807c --- /dev/null +++ b/docs/models/createactionpositionresponse.md @@ -0,0 +1,21 @@ +# CreateActionPositionResponse + +Show the action in all queues, selected queues or no queues (to use via API only). + +## Example Usage + +```python +from moderation_api.models import CreateActionPositionResponse + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: CreateActionPositionResponse = "ALL_QUEUES" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"ALL_QUEUES"` +- `"SOME_QUEUES"` +- `"HIDDEN"` diff --git a/docs/models/createactionpossiblevaluerequest.md b/docs/models/createactionpossiblevaluerequest.md new file mode 100644 index 0000000..d6388cb --- /dev/null +++ b/docs/models/createactionpossiblevaluerequest.md @@ -0,0 +1,8 @@ +# CreateActionPossibleValueRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------------ | ------------------------ | ------------------------ | ------------------------ | +| `value` | *str* | :heavy_check_mark: | The value of the action. | \ No newline at end of file diff --git a/docs/models/createactionpossiblevalueresponse.md b/docs/models/createactionpossiblevalueresponse.md new file mode 100644 index 0000000..4b54dba --- /dev/null +++ b/docs/models/createactionpossiblevalueresponse.md @@ -0,0 +1,8 @@ +# CreateActionPossibleValueResponse + + +## Fields + +| Field | Type | Required | Description | +| ------------------------ | ------------------------ | ------------------------ | ------------------------ | +| `value` | *str* | :heavy_check_mark: | The value of the action. | \ No newline at end of file diff --git a/docs/models/createactionqueuebehaviourrequest.md b/docs/models/createactionqueuebehaviourrequest.md new file mode 100644 index 0000000..3197384 --- /dev/null +++ b/docs/models/createactionqueuebehaviourrequest.md @@ -0,0 +1,17 @@ +# CreateActionQueueBehaviourRequest + +Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. + +## Example Usage + +```python +from moderation_api.models import CreateActionQueueBehaviourRequest +value: CreateActionQueueBehaviourRequest = "REMOVE" +``` + + +## Values + +- `"REMOVE"` +- `"ADD"` +- `"NO_CHANGE"` diff --git a/docs/models/createactionqueuebehaviourresponse.md b/docs/models/createactionqueuebehaviourresponse.md new file mode 100644 index 0000000..16f8ac5 --- /dev/null +++ b/docs/models/createactionqueuebehaviourresponse.md @@ -0,0 +1,21 @@ +# CreateActionQueueBehaviourResponse + +Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. + +## Example Usage + +```python +from moderation_api.models import CreateActionQueueBehaviourResponse + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: CreateActionQueueBehaviourResponse = "REMOVE" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"REMOVE"` +- `"ADD"` +- `"NO_CHANGE"` diff --git a/docs/models/createactionrequest.md b/docs/models/createactionrequest.md new file mode 100644 index 0000000..c7e0d37 --- /dev/null +++ b/docs/models/createactionrequest.md @@ -0,0 +1,18 @@ +# CreateActionRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `key` | *OptionalNullable[str]* | :heavy_minus_sign: | User defined key of the action. | +| `name` | *str* | :heavy_check_mark: | The name of the action. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The description of the action. | +| `type` | [OptionalNullable[models.CreateActionTypeRequest]](../models/createactiontyperequest.md) | :heavy_minus_sign: | The type of the action. | +| `built_in` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the action is a built-in action or a custom one. | +| `queue_behaviour` | [Optional[models.CreateActionQueueBehaviourRequest]](../models/createactionqueuebehaviourrequest.md) | :heavy_minus_sign: | Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. | +| `filter_in_queue_ids` | List[*str*] | :heavy_minus_sign: | The IDs of the queues the action is available in. | +| `position` | [Optional[models.CreateActionPositionRequest]](../models/createactionpositionrequest.md) | :heavy_minus_sign: | Show the action in all queues, selected queues or no queues (to use via API only). | +| `possible_values` | List[[models.CreateActionPossibleValueRequest](../models/createactionpossiblevaluerequest.md)] | :heavy_minus_sign: | The possible values of the action. The user will be prompted to select one of these values when executing the action. | +| `value_required` | *Optional[bool]* | :heavy_minus_sign: | Whether the action requires a value to be executed. | +| `free_text` | *Optional[bool]* | :heavy_minus_sign: | Whether the action allows any text to be entered as a value or if it must be one of the possible values. | \ No newline at end of file diff --git a/docs/models/createactionresponse.md b/docs/models/createactionresponse.md new file mode 100644 index 0000000..a004dff --- /dev/null +++ b/docs/models/createactionresponse.md @@ -0,0 +1,22 @@ +# CreateActionResponse + +Action created successfully + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the action. | +| `key` | *OptionalNullable[str]* | :heavy_minus_sign: | User defined key of the action. | +| `created_at` | *str* | :heavy_check_mark: | The date the action was created. | +| `name` | *str* | :heavy_check_mark: | The name of the action. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The description of the action. | +| `type` | [OptionalNullable[models.CreateActionTypeResponse]](../models/createactiontyperesponse.md) | :heavy_minus_sign: | The type of the action. | +| `built_in` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the action is a built-in action or a custom one. | +| `queue_behaviour` | [Optional[models.CreateActionQueueBehaviourResponse]](../models/createactionqueuebehaviourresponse.md) | :heavy_minus_sign: | Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. | +| `filter_in_queue_ids` | List[*str*] | :heavy_check_mark: | The IDs of the queues the action is available in. | +| `position` | [Optional[models.CreateActionPositionResponse]](../models/createactionpositionresponse.md) | :heavy_minus_sign: | Show the action in all queues, selected queues or no queues (to use via API only). | +| `possible_values` | List[[models.CreateActionPossibleValueResponse](../models/createactionpossiblevalueresponse.md)] | :heavy_check_mark: | The possible values of the action. The user will be prompted to select one of these values when executing the action. | +| `value_required` | *Optional[bool]* | :heavy_minus_sign: | Whether the action requires a value to be executed. | +| `free_text` | *Optional[bool]* | :heavy_minus_sign: | Whether the action allows any text to be entered as a value or if it must be one of the possible values. | \ No newline at end of file diff --git a/docs/models/createactiontyperequest.md b/docs/models/createactiontyperequest.md new file mode 100644 index 0000000..41d1b78 --- /dev/null +++ b/docs/models/createactiontyperequest.md @@ -0,0 +1,22 @@ +# CreateActionTypeRequest + +## Example Usage + +```python +from moderation_api.models import CreateActionTypeRequest +value: CreateActionTypeRequest = "AUTHOR_BLOCK" +``` + + +## Values + +- `"AUTHOR_BLOCK"` +- `"AUTHOR_BLOCK_TEMP"` +- `"AUTHOR_UNBLOCK"` +- `"AUTHOR_DELETE"` +- `"AUTHOR_REPORT"` +- `"AUTHOR_WARN"` +- `"AUTHOR_CUSTOM"` +- `"ITEM_REJECT"` +- `"ITEM_ALLOW"` +- `"ITEM_CUSTOM"` diff --git a/docs/models/createactiontyperesponse.md b/docs/models/createactiontyperesponse.md new file mode 100644 index 0000000..d2409d0 --- /dev/null +++ b/docs/models/createactiontyperesponse.md @@ -0,0 +1,26 @@ +# CreateActionTypeResponse + +## Example Usage + +```python +from moderation_api.models import CreateActionTypeResponse + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: CreateActionTypeResponse = "AUTHOR_BLOCK" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"AUTHOR_BLOCK"` +- `"AUTHOR_BLOCK_TEMP"` +- `"AUTHOR_UNBLOCK"` +- `"AUTHOR_DELETE"` +- `"AUTHOR_REPORT"` +- `"AUTHOR_WARN"` +- `"AUTHOR_CUSTOM"` +- `"ITEM_REJECT"` +- `"ITEM_ALLOW"` +- `"ITEM_CUSTOM"` diff --git a/docs/models/createauthormetadata.md b/docs/models/createauthormetadata.md new file mode 100644 index 0000000..7ba2473 --- /dev/null +++ b/docs/models/createauthormetadata.md @@ -0,0 +1,14 @@ +# CreateAuthorMetadata + +Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process. + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------- | --------------------------------------------- | --------------------------------------------- | --------------------------------------------- | +| `email_verified` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the author's email is verified | +| `phone_verified` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the author's phone number is verified | +| `identity_verified` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the author's identity is verified | +| `is_paying_customer` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the author is a paying customer | +| `__pydantic_extra__` | Dict[str, *Any*] | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/createauthorrequest.md b/docs/models/createauthorrequest.md new file mode 100644 index 0000000..eaffa5e --- /dev/null +++ b/docs/models/createauthorrequest.md @@ -0,0 +1,17 @@ +# CreateAuthorRequest + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| `profile_picture` | *OptionalNullable[str]* | :heavy_minus_sign: | URL of the author's profile picture | +| `external_link` | *OptionalNullable[str]* | :heavy_minus_sign: | URL of the author's external profile | +| `name` | *OptionalNullable[str]* | :heavy_minus_sign: | Author name or identifier | +| `email` | *OptionalNullable[str]* | :heavy_minus_sign: | Author email address | +| `company` | *OptionalNullable[str]* | :heavy_minus_sign: | The author's company or organization | +| `metadata` | [Optional[models.CreateAuthorMetadata]](../models/createauthormetadata.md) | :heavy_minus_sign: | Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process. | +| `first_seen` | *Optional[float]* | :heavy_minus_sign: | Timestamp when author first appeared | +| `last_seen` | *Optional[float]* | :heavy_minus_sign: | Timestamp of last activity | +| `manual_trust_level` | *OptionalNullable[float]* | :heavy_minus_sign: | N/A | +| `external_id` | *str* | :heavy_check_mark: | External ID of the user, typically the ID of the author in your database. | \ No newline at end of file diff --git a/docs/models/createchannelcontenttype.md b/docs/models/createchannelcontenttype.md new file mode 100644 index 0000000..a081551 --- /dev/null +++ b/docs/models/createchannelcontenttype.md @@ -0,0 +1,23 @@ +# CreateChannelContentType + +The kind of content the channel moderates. + +## Example Usage + +```python +from moderation_api.models import CreateChannelContentType +value: CreateChannelContentType = "profile" +``` + + +## Values + +- `"profile"` +- `"message"` +- `"post"` +- `"comment"` +- `"event"` +- `"product"` +- `"review"` +- `"voice"` +- `"other"` diff --git a/docs/models/createchannelrequest.md b/docs/models/createchannelrequest.md new file mode 100644 index 0000000..a26d058 --- /dev/null +++ b/docs/models/createchannelrequest.md @@ -0,0 +1,9 @@ +# CreateChannelRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | +| `project_id` | *str* | :heavy_check_mark: | The ID of the project to add the channel to. | +| `body` | [models.CreateChannelRequestBody](../models/createchannelrequestbody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/createchannelrequestbody.md b/docs/models/createchannelrequestbody.md new file mode 100644 index 0000000..af2f4d0 --- /dev/null +++ b/docs/models/createchannelrequestbody.md @@ -0,0 +1,11 @@ +# CreateChannelRequestBody + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | *str* | :heavy_check_mark: | The channel's name. | +| `key` | *Optional[str]* | :heavy_minus_sign: | The key you pass as `channel` when you moderate content. Defaults to the name in lowercase with dashes. Must be unique in the project. | +| `description` | *Optional[str]* | :heavy_minus_sign: | The channel's description. | +| `content_type` | [Optional[models.CreateChannelContentType]](../models/createchannelcontenttype.md) | :heavy_minus_sign: | The kind of content the channel moderates. | \ No newline at end of file diff --git a/docs/models/createprojectrequest.md b/docs/models/createprojectrequest.md new file mode 100644 index 0000000..d2f2c24 --- /dev/null +++ b/docs/models/createprojectrequest.md @@ -0,0 +1,12 @@ +# CreateProjectRequest + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| `name` | *str* | :heavy_check_mark: | The project's name. | +| `domain` | *Optional[str]* | :heavy_minus_sign: | The website or app the project moderates content for. Used to write the context when none is given. | +| `context` | *Optional[str]* | :heavy_minus_sign: | What the platform is and who uses it. | +| `brief` | *Optional[str]* | :heavy_minus_sign: | What you moderate, the problems you see and your guidelines. | +| `used_by_minors` | *Optional[bool]* | :heavy_minus_sign: | Whether children use the platform. | \ No newline at end of file diff --git a/docs/models/createwebhookeventtyperequest.md b/docs/models/createwebhookeventtyperequest.md new file mode 100644 index 0000000..fb137e9 --- /dev/null +++ b/docs/models/createwebhookeventtyperequest.md @@ -0,0 +1,23 @@ +# CreateWebhookEventTypeRequest + +## Example Usage + +```python +from moderation_api.models import CreateWebhookEventTypeRequest +value: CreateWebhookEventTypeRequest = "QUEUE_ITEM_NEW" +``` + + +## Values + +- `"QUEUE_ITEM_NEW"` +- `"QUEUE_ITEM_COMPLETED"` +- `"QUEUE_ITEM_ACTION"` +- `"QUEUE_ITEM_REJECTED"` +- `"QUEUE_ITEM_ALLOWED"` +- `"AUTHOR_BLOCKED"` +- `"AUTHOR_UNBLOCKED"` +- `"AUTHOR_SUSPENDED"` +- `"AUTHOR_UPDATED"` +- `"AUTHOR_TRUST_LEVEL_CHANGED"` +- `"AUTHOR_ACTION"` diff --git a/docs/models/createwebhookeventtyperesponse.md b/docs/models/createwebhookeventtyperesponse.md new file mode 100644 index 0000000..8af5923 --- /dev/null +++ b/docs/models/createwebhookeventtyperesponse.md @@ -0,0 +1,27 @@ +# CreateWebhookEventTypeResponse + +## Example Usage + +```python +from moderation_api.models import CreateWebhookEventTypeResponse + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: CreateWebhookEventTypeResponse = "QUEUE_ITEM_NEW" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"QUEUE_ITEM_NEW"` +- `"QUEUE_ITEM_COMPLETED"` +- `"QUEUE_ITEM_ACTION"` +- `"QUEUE_ITEM_REJECTED"` +- `"QUEUE_ITEM_ALLOWED"` +- `"AUTHOR_BLOCKED"` +- `"AUTHOR_UNBLOCKED"` +- `"AUTHOR_SUSPENDED"` +- `"AUTHOR_UPDATED"` +- `"AUTHOR_TRUST_LEVEL_CHANGED"` +- `"AUTHOR_ACTION"` diff --git a/docs/models/createwebhookpayloadversion.md b/docs/models/createwebhookpayloadversion.md new file mode 100644 index 0000000..d36fde4 --- /dev/null +++ b/docs/models/createwebhookpayloadversion.md @@ -0,0 +1,20 @@ +# CreateWebhookPayloadVersion + +Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API. + +## Example Usage + +```python +from moderation_api.models import CreateWebhookPayloadVersion + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: CreateWebhookPayloadVersion = "V1" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"V1"` +- `"V2"` diff --git a/docs/models/createwebhookrequest.md b/docs/models/createwebhookrequest.md new file mode 100644 index 0000000..e9ef743 --- /dev/null +++ b/docs/models/createwebhookrequest.md @@ -0,0 +1,11 @@ +# CreateWebhookRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | +| `name` | *str* | :heavy_check_mark: | The webhook's name, used to identify it in the dashboard | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The webhook's description | +| `url` | *str* | :heavy_check_mark: | The webhook's URL. We'll call this URL when an event occurs. | +| `event_types` | List[[models.CreateWebhookEventTypeRequest](../models/createwebhookeventtyperequest.md)] | :heavy_check_mark: | Event types this webhook subscribes to. One webhook URL receives all events you list here. | \ No newline at end of file diff --git a/docs/models/createwebhookresponse.md b/docs/models/createwebhookresponse.md new file mode 100644 index 0000000..db693cb --- /dev/null +++ b/docs/models/createwebhookresponse.md @@ -0,0 +1,16 @@ +# CreateWebhookResponse + +Webhook created successfully + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the webhook. | +| `name` | *str* | :heavy_check_mark: | The webhook's name. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The webhook's description. | +| `url` | *str* | :heavy_check_mark: | The URL we call when a subscribed event occurs. | +| `event_types` | List[[models.CreateWebhookEventTypeResponse](../models/createwebhookeventtyperesponse.md)] | :heavy_check_mark: | Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead. | +| `payload_version` | [models.CreateWebhookPayloadVersion](../models/createwebhookpayloadversion.md) | :heavy_check_mark: | Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API. | +| `created_at` | *str* | :heavy_check_mark: | The date the webhook was created. | \ No newline at end of file diff --git a/docs/models/crypto.md b/docs/models/crypto.md new file mode 100644 index 0000000..6214c0c --- /dev/null +++ b/docs/models/crypto.md @@ -0,0 +1,10 @@ +# Crypto + + +## Fields + +| Field | Type | Required | Description | +| ------------------- | ------------------- | ------------------- | ------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["crypto"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/curiosityinsight.md b/docs/models/curiosityinsight.md new file mode 100644 index 0000000..de062dc --- /dev/null +++ b/docs/models/curiosityinsight.md @@ -0,0 +1,13 @@ +# CuriosityInsight + +Curiosity insight. Attempts to clarify or ask follow-up questions to better understand another person or idea. + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------ | ------------------------------------ | ------------------------------------ | ------------------------------------ | +| `id` | *Literal["curiosity"]* | :heavy_check_mark: | N/A | +| `type` | *Literal["insight"]* | :heavy_check_mark: | N/A | +| `probability` | *float* | :heavy_check_mark: | N/A | +| `value` | *Nullable[bool]* | :heavy_check_mark: | Whether the content shows curiosity. | \ No newline at end of file diff --git a/docs/models/currentproject.md b/docs/models/currentproject.md new file mode 100644 index 0000000..6dbc072 --- /dev/null +++ b/docs/models/currentproject.md @@ -0,0 +1,9 @@ +# CurrentProject + + +## Fields + +| Field | Type | Required | Description | +| --------------------------- | --------------------------- | --------------------------- | --------------------------- | +| `id` | *str* | :heavy_check_mark: | ID of the current project | +| `name` | *str* | :heavy_check_mark: | Name of the current project | \ No newline at end of file diff --git a/docs/models/dailyreviewcount.md b/docs/models/dailyreviewcount.md new file mode 100644 index 0000000..3a366ba --- /dev/null +++ b/docs/models/dailyreviewcount.md @@ -0,0 +1,9 @@ +# DailyReviewCount + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------ | ------------------------------ | ------------------------------ | ------------------------------ | +| `date_` | *str* | :heavy_check_mark: | Date in YYYY-MM-DD format | +| `count` | *float* | :heavy_check_mark: | Number of reviews on this date | \ No newline at end of file diff --git a/docs/models/dataaudiorequest.md b/docs/models/dataaudiorequest.md new file mode 100644 index 0000000..f898153 --- /dev/null +++ b/docs/models/dataaudiorequest.md @@ -0,0 +1,11 @@ +# DataAudioRequest + +Audio + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------- | ---------------------------- | ---------------------------- | ---------------------------- | +| `type` | *Literal["audio"]* | :heavy_check_mark: | N/A | +| `url` | *str* | :heavy_check_mark: | The URL of the audio content | \ No newline at end of file diff --git a/docs/models/dataimagerequest.md b/docs/models/dataimagerequest.md new file mode 100644 index 0000000..2a13caa --- /dev/null +++ b/docs/models/dataimagerequest.md @@ -0,0 +1,12 @@ +# DataImageRequest + +Image + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | *Literal["image"]* | :heavy_check_mark: | N/A | +| `url` | *Optional[str]* | :heavy_minus_sign: | A public URL of the image content. Either url or data must be provided. | +| `data` | *Optional[str]* | :heavy_minus_sign: | Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue. | \ No newline at end of file diff --git a/docs/models/datatextrequest.md b/docs/models/datatextrequest.md new file mode 100644 index 0000000..3a3f6e7 --- /dev/null +++ b/docs/models/datatextrequest.md @@ -0,0 +1,11 @@ +# DataTextRequest + +Text + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `type` | *Literal["text"]* | :heavy_check_mark: | N/A | +| `text` | *str* | :heavy_check_mark: | The content text | \ No newline at end of file diff --git a/docs/models/datavideorequest.md b/docs/models/datavideorequest.md new file mode 100644 index 0000000..3cc4880 --- /dev/null +++ b/docs/models/datavideorequest.md @@ -0,0 +1,11 @@ +# DataVideoRequest + +Video + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------- | --------------------------------- | --------------------------------- | --------------------------------- | +| `type` | *Literal["video"]* | :heavy_check_mark: | N/A | +| `url` | *str* | :heavy_check_mark: | A public URL of the video content | \ No newline at end of file diff --git a/docs/models/deleteactionrequest.md b/docs/models/deleteactionrequest.md new file mode 100644 index 0000000..a3584c5 --- /dev/null +++ b/docs/models/deleteactionrequest.md @@ -0,0 +1,8 @@ +# DeleteActionRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the action to delete. | \ No newline at end of file diff --git a/docs/models/deleteactionresponse.md b/docs/models/deleteactionresponse.md new file mode 100644 index 0000000..88d4fec --- /dev/null +++ b/docs/models/deleteactionresponse.md @@ -0,0 +1,11 @@ +# DeleteActionResponse + +Action deleted successfully + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | +| `deleted` | *bool* | :heavy_check_mark: | Whether the action was deleted. | +| `id` | *str* | :heavy_check_mark: | The ID of the action. | \ No newline at end of file diff --git a/docs/models/deleteauthorrequest.md b/docs/models/deleteauthorrequest.md new file mode 100644 index 0000000..14a1c85 --- /dev/null +++ b/docs/models/deleteauthorrequest.md @@ -0,0 +1,8 @@ +# DeleteAuthorRequest + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | Either external ID or the ID assigned by moderation API. | \ No newline at end of file diff --git a/docs/models/deleteauthorresponse.md b/docs/models/deleteauthorresponse.md new file mode 100644 index 0000000..6f7c01f --- /dev/null +++ b/docs/models/deleteauthorresponse.md @@ -0,0 +1,10 @@ +# DeleteAuthorResponse + +Successful response + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `success` | *bool* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/deletechannelrequest.md b/docs/models/deletechannelrequest.md new file mode 100644 index 0000000..1fd301d --- /dev/null +++ b/docs/models/deletechannelrequest.md @@ -0,0 +1,8 @@ +# DeleteChannelRequest + + +## Fields + +| Field | Type | Required | Description | +| ---------------------- | ---------------------- | ---------------------- | ---------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the channel. | \ No newline at end of file diff --git a/docs/models/deletechannelresponse.md b/docs/models/deletechannelresponse.md new file mode 100644 index 0000000..cbaf326 --- /dev/null +++ b/docs/models/deletechannelresponse.md @@ -0,0 +1,11 @@ +# DeleteChannelResponse + +Channel deleted successfully + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------- | -------------------------------- | -------------------------------- | -------------------------------- | +| `deleted` | *bool* | :heavy_check_mark: | Whether the channel was deleted. | +| `id` | *str* | :heavy_check_mark: | The ID of the channel. | \ No newline at end of file diff --git a/docs/models/deleteprojectrequest.md b/docs/models/deleteprojectrequest.md new file mode 100644 index 0000000..357f737 --- /dev/null +++ b/docs/models/deleteprojectrequest.md @@ -0,0 +1,8 @@ +# DeleteProjectRequest + + +## Fields + +| Field | Type | Required | Description | +| ---------------------- | ---------------------- | ---------------------- | ---------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the project. | \ No newline at end of file diff --git a/docs/models/deleteprojectresponse.md b/docs/models/deleteprojectresponse.md new file mode 100644 index 0000000..1b5de05 --- /dev/null +++ b/docs/models/deleteprojectresponse.md @@ -0,0 +1,11 @@ +# DeleteProjectResponse + +Project deleted successfully + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------- | -------------------------------- | -------------------------------- | -------------------------------- | +| `deleted` | *bool* | :heavy_check_mark: | Whether the project was deleted. | +| `id` | *str* | :heavy_check_mark: | The ID of the project. | \ No newline at end of file diff --git a/docs/models/deletewebhookrequest.md b/docs/models/deletewebhookrequest.md new file mode 100644 index 0000000..d3581f1 --- /dev/null +++ b/docs/models/deletewebhookrequest.md @@ -0,0 +1,8 @@ +# DeleteWebhookRequest + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------- | -------------------------------- | -------------------------------- | -------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the webhook to delete. | \ No newline at end of file diff --git a/docs/models/deletewebhookresponse.md b/docs/models/deletewebhookresponse.md new file mode 100644 index 0000000..7523d5f --- /dev/null +++ b/docs/models/deletewebhookresponse.md @@ -0,0 +1,11 @@ +# DeleteWebhookResponse + +Webhook deleted successfully + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------- | -------------------------------- | -------------------------------- | -------------------------------- | +| `deleted` | *bool* | :heavy_check_mark: | Whether the webhook was deleted. | +| `id` | *str* | :heavy_check_mark: | The ID of the webhook. | \ No newline at end of file diff --git a/docs/models/drugs.md b/docs/models/drugs.md new file mode 100644 index 0000000..22169d8 --- /dev/null +++ b/docs/models/drugs.md @@ -0,0 +1,10 @@ +# Drugs + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["drugs"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/duplicatechannelrequest.md b/docs/models/duplicatechannelrequest.md new file mode 100644 index 0000000..d47fb9f --- /dev/null +++ b/docs/models/duplicatechannelrequest.md @@ -0,0 +1,9 @@ +# DuplicateChannelRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | +| `id` | *str* | :heavy_check_mark: | The ID of the channel to copy. | +| `body` | [models.DuplicateChannelRequestBody](../models/duplicatechannelrequestbody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/duplicatechannelrequestbody.md b/docs/models/duplicatechannelrequestbody.md new file mode 100644 index 0000000..9f3f457 --- /dev/null +++ b/docs/models/duplicatechannelrequestbody.md @@ -0,0 +1,9 @@ +# DuplicateChannelRequestBody + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | +| `name` | *Optional[str]* | :heavy_minus_sign: | Name of the copy. Defaults to the original name with "(copy)". | +| `key` | *Optional[str]* | :heavy_minus_sign: | Key of the copy. Defaults to the original key with a timestamp. Must be unique in the project. | \ No newline at end of file diff --git a/docs/models/duplicateprojectrequest.md b/docs/models/duplicateprojectrequest.md new file mode 100644 index 0000000..dbdd3b8 --- /dev/null +++ b/docs/models/duplicateprojectrequest.md @@ -0,0 +1,9 @@ +# DuplicateProjectRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | +| `id` | *str* | :heavy_check_mark: | The ID of the project to copy. | +| `body` | [models.DuplicateProjectRequestBody](../models/duplicateprojectrequestbody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/duplicateprojectrequestbody.md b/docs/models/duplicateprojectrequestbody.md new file mode 100644 index 0000000..d4914a5 --- /dev/null +++ b/docs/models/duplicateprojectrequestbody.md @@ -0,0 +1,8 @@ +# DuplicateProjectRequestBody + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | +| `name` | *Optional[str]* | :heavy_minus_sign: | Name of the copy. Defaults to the original name with "(copy)". | \ No newline at end of file diff --git a/docs/models/entities1.md b/docs/models/entities1.md new file mode 100644 index 0000000..e4553eb --- /dev/null +++ b/docs/models/entities1.md @@ -0,0 +1,12 @@ +# Entities1 + + +## Fields + +| Field | Type | Required | Description | +| -------------------- | -------------------- | -------------------- | -------------------- | +| `enable` | *Optional[bool]* | :heavy_minus_sign: | N/A | +| `flag` | *Optional[bool]* | :heavy_minus_sign: | N/A | +| `should_mask` | *Optional[bool]* | :heavy_minus_sign: | N/A | +| `mask` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `__pydantic_extra__` | Dict[str, *Any*] | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/entities2.md b/docs/models/entities2.md new file mode 100644 index 0000000..2a69bcb --- /dev/null +++ b/docs/models/entities2.md @@ -0,0 +1,12 @@ +# Entities2 + + +## Fields + +| Field | Type | Required | Description | +| -------------------- | -------------------- | -------------------- | -------------------- | +| `enable` | *Optional[bool]* | :heavy_minus_sign: | N/A | +| `flag` | *Optional[bool]* | :heavy_minus_sign: | N/A | +| `should_mask` | *Optional[bool]* | :heavy_minus_sign: | N/A | +| `mask` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `__pydantic_extra__` | Dict[str, *Any*] | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/entitymatcheroutput.md b/docs/models/entitymatcheroutput.md new file mode 100644 index 0000000..32743df --- /dev/null +++ b/docs/models/entitymatcheroutput.md @@ -0,0 +1,15 @@ +# EntityMatcherOutput + +Entity matcher policy. + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | +| `id` | *str* | :heavy_check_mark: | N/A | +| `type` | *Literal["entity_matcher"]* | :heavy_check_mark: | N/A | +| `probability` | *float* | :heavy_check_mark: | N/A | +| `flagged` | *bool* | :heavy_check_mark: | N/A | +| `flagged_fields` | List[*str*] | :heavy_minus_sign: | N/A | +| `matches` | List[[models.SubmitContentMatch](../models/submitcontentmatch.md)] | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/error.md b/docs/models/error.md new file mode 100644 index 0000000..b425f90 --- /dev/null +++ b/docs/models/error.md @@ -0,0 +1,9 @@ +# Error + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `id` | *str* | :heavy_check_mark: | N/A | +| `message` | *str* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/errorissue.md b/docs/models/errorissue.md new file mode 100644 index 0000000..8a74844 --- /dev/null +++ b/docs/models/errorissue.md @@ -0,0 +1,10 @@ +# ErrorIssue + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | +| `message` | *str* | :heavy_check_mark: | What is wrong. | +| `path` | List[[models.Path](../models/path.md)] | :heavy_minus_sign: | Location of the invalid field in the request body. | +| `code` | *Optional[str]* | :heavy_minus_sign: | Validation rule that failed. | \ No newline at end of file diff --git a/docs/models/evaluation.md b/docs/models/evaluation.md new file mode 100644 index 0000000..04daf3f --- /dev/null +++ b/docs/models/evaluation.md @@ -0,0 +1,14 @@ +# Evaluation + +The evaluation of the content after running the channel policies. + + +## Fields + +| Field | Type | Required | Description | +| ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| `flagged` | *bool* | :heavy_check_mark: | Whether the content was flagged by any policy. | +| `flag_probability` | *float* | :heavy_check_mark: | The probability that the content should be flagged. | +| `severity_score` | *float* | :heavy_check_mark: | The severity score of the content. A higher score indicates more severe content. | +| `unicode_spoofed` | *Optional[bool]* | :heavy_minus_sign: | Whether the content was flagged for Unicode spoofing. | +| `shadow_flagged` | *Optional[bool]* | :heavy_minus_sign: | Whether the content would have been flagged by analyze-only policies (flag: false). | \ No newline at end of file diff --git a/docs/models/executeactionrequest.md b/docs/models/executeactionrequest.md new file mode 100644 index 0000000..5feba6d --- /dev/null +++ b/docs/models/executeactionrequest.md @@ -0,0 +1,15 @@ +# ExecuteActionRequest + +Input parameters + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| `action_key` | *str* | :heavy_check_mark: | ID or key of the action to execute | +| `content_ids` | List[*str*] | :heavy_minus_sign: | IDs of the content items to apply the action to. Provide this or authorIds. | +| `author_ids` | List[*str*] | :heavy_minus_sign: | IDs of the authors to apply the action to. Provide this or contentIds. | +| `value` | *Optional[str]* | :heavy_minus_sign: | Optional value to provide with the action | +| `queue_id` | *Optional[str]* | :heavy_minus_sign: | Optional queue ID if the action is queue-specific | +| `duration` | *Optional[float]* | :heavy_minus_sign: | Optional duration in milliseconds for actions with timeouts | \ No newline at end of file diff --git a/docs/models/executeactionresponse.md b/docs/models/executeactionresponse.md new file mode 100644 index 0000000..166eb25 --- /dev/null +++ b/docs/models/executeactionresponse.md @@ -0,0 +1,10 @@ +# ExecuteActionResponse + +Execution result + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------- | -------------------------------------------- | -------------------------------------------- | -------------------------------------------- | +| `success` | *bool* | :heavy_check_mark: | Whether the action was executed successfully | \ No newline at end of file diff --git a/docs/models/extremism.md b/docs/models/extremism.md new file mode 100644 index 0000000..df082d8 --- /dev/null +++ b/docs/models/extremism.md @@ -0,0 +1,10 @@ +# Extremism + + +## Fields + +| Field | Type | Required | Description | +| ---------------------- | ---------------------- | ---------------------- | ---------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["extremism"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/facedetection.md b/docs/models/facedetection.md new file mode 100644 index 0000000..6b12dc5 --- /dev/null +++ b/docs/models/facedetection.md @@ -0,0 +1,12 @@ +# FaceDetection + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["face_detection"]* | :heavy_check_mark: | N/A | +| `comparator` | [Optional[models.Comparator]](../models/comparator.md) | :heavy_minus_sign: | Flag images that contain "at least" or "fewer than" the configured number of faces. Defaults to at_least. | +| `count` | *Optional[int]* | :heavy_minus_sign: | Number of faces the comparator applies to. Defaults to 1, so the default rule flags any image containing a face. | \ No newline at end of file diff --git a/docs/models/filter_.md b/docs/models/filter_.md new file mode 100644 index 0000000..053f209 --- /dev/null +++ b/docs/models/filter_.md @@ -0,0 +1,32 @@ +# Filter + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| `is_flagged` | [Optional[models.IsFlagged]](../models/isflagged.md) | :heavy_minus_sign: | N/A | +| `casebook_answer` | [Optional[models.CasebookAnswer]](../models/casebookanswer.md) | :heavy_minus_sign: | N/A | +| `casebook_agreement` | [Optional[models.CasebookAgreement]](../models/casebookagreement.md) | :heavy_minus_sign: | N/A | +| `labels` | List[*str*] | :heavy_minus_sign: | N/A | +| `filter_labels` | List[[models.FilterLabel](../models/filterlabel.md)] | :heavy_minus_sign: | N/A | +| `metadata_filters` | List[[models.MetadataFilter](../models/metadatafilter.md)] | :heavy_minus_sign: | N/A | +| `filtered_channel_ids` | List[*str*] | :heavy_minus_sign: | N/A | +| `filtered_action_ids` | List[*str*] | :heavy_minus_sign: | N/A | +| `languages` | List[*str*] | :heavy_minus_sign: | N/A | +| `content_types` | List[[models.GetQueueContentType](../models/getqueuecontenttype.md)] | :heavy_minus_sign: | N/A | +| `media_types` | List[[models.MediaType](../models/mediatype.md)] | :heavy_minus_sign: | N/A | +| `author_trust_levels` | List[*int*] | :heavy_minus_sign: | N/A | +| `recommendation_actions` | List[[models.GetQueueRecommendationAction](../models/getqueuerecommendationaction.md)] | :heavy_minus_sign: | N/A | +| `min_severity` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `max_severity` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `after_date` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `before_date` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `within` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `within_unit` | [Optional[models.WithinUnit]](../models/withinunit.md) | :heavy_minus_sign: | N/A | +| `clear_date_window` | *Optional[bool]* | :heavy_minus_sign: | N/A | +| `check_status` | [OptionalNullable[models.CheckStatus]](../models/checkstatus.md) | :heavy_minus_sign: | N/A | +| `author_id` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `content_id` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `conversation_ids` | List[*Nullable[str]*] | :heavy_minus_sign: | N/A | +| `search` | List[*str*] | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/filterlabel.md b/docs/models/filterlabel.md new file mode 100644 index 0000000..511fa35 --- /dev/null +++ b/docs/models/filterlabel.md @@ -0,0 +1,11 @@ +# FilterLabel + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------ | ------------------------------------------------ | ------------------------------------------------ | ------------------------------------------------ | +| `label` | *str* | :heavy_check_mark: | N/A | +| `type` | [models.GetQueueType](../models/getqueuetype.md) | :heavy_check_mark: | N/A | +| `min_threshold` | *OptionalNullable[float]* | :heavy_minus_sign: | N/A | +| `max_threshold` | *OptionalNullable[float]* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/flaggedcontenttrend.md b/docs/models/flaggedcontenttrend.md new file mode 100644 index 0000000..0daae98 --- /dev/null +++ b/docs/models/flaggedcontenttrend.md @@ -0,0 +1,9 @@ +# FlaggedContentTrend + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| `label` | *str* | :heavy_check_mark: | Content flag/label | +| `trend` | *float* | :heavy_check_mark: | Trend indicator (-1 to 1) showing if this type of flagged content is increasing or decreasing | \ No newline at end of file diff --git a/docs/models/flaggingmode.md b/docs/models/flaggingmode.md new file mode 100644 index 0000000..172f89e --- /dev/null +++ b/docs/models/flaggingmode.md @@ -0,0 +1,20 @@ +# FlaggingMode + +`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it. + +## Example Usage + +```python +from moderation_api.models import FlaggingMode + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: FlaggingMode = "DRY_RUN" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"DRY_RUN"` +- `"ACTIVE"` diff --git a/docs/models/gambling.md b/docs/models/gambling.md new file mode 100644 index 0000000..eb661be --- /dev/null +++ b/docs/models/gambling.md @@ -0,0 +1,10 @@ +# Gambling + + +## Fields + +| Field | Type | Required | Description | +| --------------------- | --------------------- | --------------------- | --------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["gambling"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/getaccountresponse.md b/docs/models/getaccountresponse.md new file mode 100644 index 0000000..741fff2 --- /dev/null +++ b/docs/models/getaccountresponse.md @@ -0,0 +1,14 @@ +# GetAccountResponse + +Account details retrieved successfully + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | ID of the account | +| `paid_plan_name` | *str* | :heavy_check_mark: | Name of the paid plan | +| `text_api_quota` | *float* | :heavy_check_mark: | Text API quota | +| `remaining_quota` | *float* | :heavy_check_mark: | Remaining quota | +| `current_project` | [Optional[models.CurrentProject]](../models/currentproject.md) | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/getactionposition.md b/docs/models/getactionposition.md new file mode 100644 index 0000000..b1683c6 --- /dev/null +++ b/docs/models/getactionposition.md @@ -0,0 +1,21 @@ +# GetActionPosition + +Show the action in all queues, selected queues or no queues (to use via API only). + +## Example Usage + +```python +from moderation_api.models import GetActionPosition + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: GetActionPosition = "ALL_QUEUES" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"ALL_QUEUES"` +- `"SOME_QUEUES"` +- `"HIDDEN"` diff --git a/docs/models/getactionpossiblevalue.md b/docs/models/getactionpossiblevalue.md new file mode 100644 index 0000000..14ebf1c --- /dev/null +++ b/docs/models/getactionpossiblevalue.md @@ -0,0 +1,8 @@ +# GetActionPossibleValue + + +## Fields + +| Field | Type | Required | Description | +| ------------------------ | ------------------------ | ------------------------ | ------------------------ | +| `value` | *str* | :heavy_check_mark: | The value of the action. | \ No newline at end of file diff --git a/docs/models/getactionqueuebehaviour.md b/docs/models/getactionqueuebehaviour.md new file mode 100644 index 0000000..28e5f8d --- /dev/null +++ b/docs/models/getactionqueuebehaviour.md @@ -0,0 +1,21 @@ +# GetActionQueueBehaviour + +Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. + +## Example Usage + +```python +from moderation_api.models import GetActionQueueBehaviour + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: GetActionQueueBehaviour = "REMOVE" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"REMOVE"` +- `"ADD"` +- `"NO_CHANGE"` diff --git a/docs/models/getactionrequest.md b/docs/models/getactionrequest.md new file mode 100644 index 0000000..ff92c80 --- /dev/null +++ b/docs/models/getactionrequest.md @@ -0,0 +1,8 @@ +# GetActionRequest + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------- | ---------------------------- | ---------------------------- | ---------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the action to get. | \ No newline at end of file diff --git a/docs/models/getactionresponse.md b/docs/models/getactionresponse.md new file mode 100644 index 0000000..5e5ccd7 --- /dev/null +++ b/docs/models/getactionresponse.md @@ -0,0 +1,22 @@ +# GetActionResponse + +Action retrieved successfully + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the action. | +| `key` | *OptionalNullable[str]* | :heavy_minus_sign: | User defined key of the action. | +| `created_at` | *str* | :heavy_check_mark: | The date the action was created. | +| `name` | *str* | :heavy_check_mark: | The name of the action. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The description of the action. | +| `type` | [OptionalNullable[models.GetActionType]](../models/getactiontype.md) | :heavy_minus_sign: | The type of the action. | +| `built_in` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the action is a built-in action or a custom one. | +| `queue_behaviour` | [Optional[models.GetActionQueueBehaviour]](../models/getactionqueuebehaviour.md) | :heavy_minus_sign: | Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. | +| `filter_in_queue_ids` | List[*str*] | :heavy_check_mark: | The IDs of the queues the action is available in. | +| `position` | [Optional[models.GetActionPosition]](../models/getactionposition.md) | :heavy_minus_sign: | Show the action in all queues, selected queues or no queues (to use via API only). | +| `possible_values` | List[[models.GetActionPossibleValue](../models/getactionpossiblevalue.md)] | :heavy_check_mark: | The possible values of the action. The user will be prompted to select one of these values when executing the action. | +| `value_required` | *Optional[bool]* | :heavy_minus_sign: | Whether the action requires a value to be executed. | +| `free_text` | *Optional[bool]* | :heavy_minus_sign: | Whether the action allows any text to be entered as a value or if it must be one of the possible values. | \ No newline at end of file diff --git a/docs/models/getactiontype.md b/docs/models/getactiontype.md new file mode 100644 index 0000000..58283cc --- /dev/null +++ b/docs/models/getactiontype.md @@ -0,0 +1,26 @@ +# GetActionType + +## Example Usage + +```python +from moderation_api.models import GetActionType + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: GetActionType = "AUTHOR_BLOCK" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"AUTHOR_BLOCK"` +- `"AUTHOR_BLOCK_TEMP"` +- `"AUTHOR_UNBLOCK"` +- `"AUTHOR_DELETE"` +- `"AUTHOR_REPORT"` +- `"AUTHOR_WARN"` +- `"AUTHOR_CUSTOM"` +- `"ITEM_REJECT"` +- `"ITEM_ALLOW"` +- `"ITEM_CUSTOM"` diff --git a/docs/models/getauthorrequest.md b/docs/models/getauthorrequest.md new file mode 100644 index 0000000..79475c0 --- /dev/null +++ b/docs/models/getauthorrequest.md @@ -0,0 +1,8 @@ +# GetAuthorRequest + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | Either external ID or the ID assigned by moderation API. | \ No newline at end of file diff --git a/docs/models/getchannelrequest.md b/docs/models/getchannelrequest.md new file mode 100644 index 0000000..30c2038 --- /dev/null +++ b/docs/models/getchannelrequest.md @@ -0,0 +1,8 @@ +# GetChannelRequest + + +## Fields + +| Field | Type | Required | Description | +| ---------------------- | ---------------------- | ---------------------- | ---------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the channel. | \ No newline at end of file diff --git a/docs/models/getprojectrequest.md b/docs/models/getprojectrequest.md new file mode 100644 index 0000000..be100b7 --- /dev/null +++ b/docs/models/getprojectrequest.md @@ -0,0 +1,8 @@ +# GetProjectRequest + + +## Fields + +| Field | Type | Required | Description | +| ---------------------- | ---------------------- | ---------------------- | ---------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the project. | \ No newline at end of file diff --git a/docs/models/getqueuecontenttype.md b/docs/models/getqueuecontenttype.md new file mode 100644 index 0000000..773d87e --- /dev/null +++ b/docs/models/getqueuecontenttype.md @@ -0,0 +1,25 @@ +# GetQueueContentType + +## Example Usage + +```python +from moderation_api.models import GetQueueContentType + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: GetQueueContentType = "profile" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"profile"` +- `"message"` +- `"post"` +- `"comment"` +- `"event"` +- `"product"` +- `"review"` +- `"voice"` +- `"other"` diff --git a/docs/models/getqueuequeue.md b/docs/models/getqueuequeue.md new file mode 100644 index 0000000..d4675d5 --- /dev/null +++ b/docs/models/getqueuequeue.md @@ -0,0 +1,14 @@ +# GetQueueQueue + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------- | ------------------------------------- | ------------------------------------- | ------------------------------------- | +| `id` | *str* | :heavy_check_mark: | N/A | +| `name` | *str* | :heavy_check_mark: | N/A | +| `description` | *str* | :heavy_check_mark: | N/A | +| `filter_` | [models.Filter](../models/filter_.md) | :heavy_check_mark: | N/A | +| `unresolved_items_count` | *float* | :heavy_check_mark: | N/A | +| `resolved_items_count` | *float* | :heavy_check_mark: | N/A | +| `total_items_count` | *float* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/getqueuerecommendationaction.md b/docs/models/getqueuerecommendationaction.md new file mode 100644 index 0000000..b352d84 --- /dev/null +++ b/docs/models/getqueuerecommendationaction.md @@ -0,0 +1,19 @@ +# GetQueueRecommendationAction + +## Example Usage + +```python +from moderation_api.models import GetQueueRecommendationAction + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: GetQueueRecommendationAction = "review" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"review"` +- `"allow"` +- `"reject"` diff --git a/docs/models/getqueuerequest.md b/docs/models/getqueuerequest.md new file mode 100644 index 0000000..bad6034 --- /dev/null +++ b/docs/models/getqueuerequest.md @@ -0,0 +1,8 @@ +# GetQueueRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `id` | *str* | :heavy_check_mark: | The queue ID | \ No newline at end of file diff --git a/docs/models/getqueueresponse.md b/docs/models/getqueueresponse.md new file mode 100644 index 0000000..2efad7c --- /dev/null +++ b/docs/models/getqueueresponse.md @@ -0,0 +1,10 @@ +# GetQueueResponse + +Successful response + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | +| `queue` | [models.GetQueueQueue](../models/getqueuequeue.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/getqueuestatsrequest.md b/docs/models/getqueuestatsrequest.md new file mode 100644 index 0000000..03b9f05 --- /dev/null +++ b/docs/models/getqueuestatsrequest.md @@ -0,0 +1,9 @@ +# GetQueueStatsRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `id` | *str* | :heavy_check_mark: | The queue ID | +| `within_days` | *Optional[int]* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/getqueuestatsresponse.md b/docs/models/getqueuestatsresponse.md new file mode 100644 index 0000000..517dc54 --- /dev/null +++ b/docs/models/getqueuestatsresponse.md @@ -0,0 +1,13 @@ +# GetQueueStatsResponse + +Successful response + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- | +| `review_stats` | [models.ReviewStats](../models/reviewstats.md) | :heavy_check_mark: | N/A | +| `action_stats` | List[[models.ActionStat](../models/actionstat.md)] | :heavy_check_mark: | N/A | +| `top_reviewers` | List[[models.TopReviewer](../models/topreviewer.md)] | :heavy_check_mark: | List of top reviewers and their statistics | +| `trends` | [models.Trends](../models/trends.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/getqueuetype.md b/docs/models/getqueuetype.md new file mode 100644 index 0000000..5acbb5b --- /dev/null +++ b/docs/models/getqueuetype.md @@ -0,0 +1,20 @@ +# GetQueueType + +## Example Usage + +```python +from moderation_api.models import GetQueueType + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: GetQueueType = "FLAGGED" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"FLAGGED"` +- `"NOT_FLAGGED"` +- `"THRESHOLDS"` +- `"MATCHED"` diff --git a/docs/models/getwebhookeventtype.md b/docs/models/getwebhookeventtype.md new file mode 100644 index 0000000..f9df021 --- /dev/null +++ b/docs/models/getwebhookeventtype.md @@ -0,0 +1,27 @@ +# GetWebhookEventType + +## Example Usage + +```python +from moderation_api.models import GetWebhookEventType + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: GetWebhookEventType = "QUEUE_ITEM_NEW" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"QUEUE_ITEM_NEW"` +- `"QUEUE_ITEM_COMPLETED"` +- `"QUEUE_ITEM_ACTION"` +- `"QUEUE_ITEM_REJECTED"` +- `"QUEUE_ITEM_ALLOWED"` +- `"AUTHOR_BLOCKED"` +- `"AUTHOR_UNBLOCKED"` +- `"AUTHOR_SUSPENDED"` +- `"AUTHOR_UPDATED"` +- `"AUTHOR_TRUST_LEVEL_CHANGED"` +- `"AUTHOR_ACTION"` diff --git a/docs/models/getwebhookpayloadversion.md b/docs/models/getwebhookpayloadversion.md new file mode 100644 index 0000000..675492a --- /dev/null +++ b/docs/models/getwebhookpayloadversion.md @@ -0,0 +1,20 @@ +# GetWebhookPayloadVersion + +Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API. + +## Example Usage + +```python +from moderation_api.models import GetWebhookPayloadVersion + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: GetWebhookPayloadVersion = "V1" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"V1"` +- `"V2"` diff --git a/docs/models/getwebhookrequest.md b/docs/models/getwebhookrequest.md new file mode 100644 index 0000000..77553c0 --- /dev/null +++ b/docs/models/getwebhookrequest.md @@ -0,0 +1,8 @@ +# GetWebhookRequest + + +## Fields + +| Field | Type | Required | Description | +| ----------------------------- | ----------------------------- | ----------------------------- | ----------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the webhook to get. | \ No newline at end of file diff --git a/docs/models/getwebhookresponse.md b/docs/models/getwebhookresponse.md new file mode 100644 index 0000000..0870e64 --- /dev/null +++ b/docs/models/getwebhookresponse.md @@ -0,0 +1,16 @@ +# GetWebhookResponse + +Webhook retrieved successfully + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the webhook. | +| `name` | *str* | :heavy_check_mark: | The webhook's name. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The webhook's description. | +| `url` | *str* | :heavy_check_mark: | The URL we call when a subscribed event occurs. | +| `event_types` | List[[models.GetWebhookEventType](../models/getwebhookeventtype.md)] | :heavy_check_mark: | Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead. | +| `payload_version` | [models.GetWebhookPayloadVersion](../models/getwebhookpayloadversion.md) | :heavy_check_mark: | Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API. | +| `created_at` | *str* | :heavy_check_mark: | The date the webhook was created. | \ No newline at end of file diff --git a/docs/models/getwebhooksecretresponse.md b/docs/models/getwebhooksecretresponse.md new file mode 100644 index 0000000..2933562 --- /dev/null +++ b/docs/models/getwebhooksecretresponse.md @@ -0,0 +1,10 @@ +# GetWebhookSecretResponse + +Signing secret retrieved successfully + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `secret` | *str* | :heavy_check_mark: | The signing secret for this project. Every webhook delivery is signed with HMAC-SHA256 over the raw JSON body, hex-encoded in the `modapi-signature` header. | \ No newline at end of file diff --git a/docs/models/getwordlistembeddingstatusrequest.md b/docs/models/getwordlistembeddingstatusrequest.md new file mode 100644 index 0000000..75064a2 --- /dev/null +++ b/docs/models/getwordlistembeddingstatusrequest.md @@ -0,0 +1,8 @@ +# GetWordlistEmbeddingStatusRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------ | ------------------------------------------------ | ------------------------------------------------ | ------------------------------------------------ | +| `id` | *str* | :heavy_check_mark: | ID of the wordlist to check embedding status for | \ No newline at end of file diff --git a/docs/models/getwordlistembeddingstatusresponse.md b/docs/models/getwordlistembeddingstatusresponse.md new file mode 100644 index 0000000..003c0da --- /dev/null +++ b/docs/models/getwordlistembeddingstatusresponse.md @@ -0,0 +1,12 @@ +# GetWordlistEmbeddingStatusResponse + +Embedding status details + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | +| `progress` | *float* | :heavy_check_mark: | Percentage of words that have been embedded (0-100) | +| `total_words` | *float* | :heavy_check_mark: | Total number of words in the wordlist | +| `remaining_words` | *float* | :heavy_check_mark: | Number of words still waiting to be embedded | \ No newline at end of file diff --git a/docs/models/getwordlistrequest.md b/docs/models/getwordlistrequest.md new file mode 100644 index 0000000..af605a0 --- /dev/null +++ b/docs/models/getwordlistrequest.md @@ -0,0 +1,8 @@ +# GetWordlistRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------------- | ------------------------- | ------------------------- | ------------------------- | +| `id` | *str* | :heavy_check_mark: | ID of the wordlist to get | \ No newline at end of file diff --git a/docs/models/getwordlistresponse.md b/docs/models/getwordlistresponse.md new file mode 100644 index 0000000..0059540 --- /dev/null +++ b/docs/models/getwordlistresponse.md @@ -0,0 +1,16 @@ +# GetWordlistResponse + +Successful response + + +## Fields + +| Field | Type | Required | Description | +| ----------------------------- | ----------------------------- | ----------------------------- | ----------------------------- | +| `id` | *str* | :heavy_check_mark: | ID of the wordlist | +| `name` | *Nullable[str]* | :heavy_check_mark: | N/A | +| `words` | List[*str*] | :heavy_check_mark: | Words in the wordlist | +| `created_at` | *str* | :heavy_check_mark: | Creation date of the wordlist | +| `organization_id` | *str* | :heavy_check_mark: | ID of the organization | +| `user_id` | *Nullable[str]* | :heavy_check_mark: | N/A | +| `strict` | *bool* | :heavy_check_mark: | Strict mode | \ No newline at end of file diff --git a/docs/models/guideline.md b/docs/models/guideline.md new file mode 100644 index 0000000..88b54cf --- /dev/null +++ b/docs/models/guideline.md @@ -0,0 +1,12 @@ +# Guideline + + +## Fields + +| Field | Type | Required | Description | +| ---------------------- | ---------------------- | ---------------------- | ---------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["guideline"]* | :heavy_check_mark: | N/A | +| `instructions` | *str* | :heavy_check_mark: | N/A | +| `guideline_key` | *str* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/hate.md b/docs/models/hate.md new file mode 100644 index 0000000..5bec43d --- /dev/null +++ b/docs/models/hate.md @@ -0,0 +1,10 @@ +# Hate + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["hate"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/illegaltrade.md b/docs/models/illegaltrade.md new file mode 100644 index 0000000..a540269 --- /dev/null +++ b/docs/models/illegaltrade.md @@ -0,0 +1,10 @@ +# IllegalTrade + + +## Fields + +| Field | Type | Required | Description | +| -------------------------- | -------------------------- | -------------------------- | -------------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["illegal_trade"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/illicit.md b/docs/models/illicit.md new file mode 100644 index 0000000..fbecd74 --- /dev/null +++ b/docs/models/illicit.md @@ -0,0 +1,10 @@ +# Illicit + + +## Fields + +| Field | Type | Required | Description | +| -------------------- | -------------------- | -------------------- | -------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["illicit"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/illicitalcohol.md b/docs/models/illicitalcohol.md new file mode 100644 index 0000000..32481ab --- /dev/null +++ b/docs/models/illicitalcohol.md @@ -0,0 +1,10 @@ +# IllicitAlcohol + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------- | ---------------------------- | ---------------------------- | ---------------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["illicit_alcohol"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/illicitdrugs.md b/docs/models/illicitdrugs.md new file mode 100644 index 0000000..d4058fc --- /dev/null +++ b/docs/models/illicitdrugs.md @@ -0,0 +1,10 @@ +# IllicitDrugs + + +## Fields + +| Field | Type | Required | Description | +| -------------------------- | -------------------------- | -------------------------- | -------------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["illicit_drugs"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/illicitfirearms.md b/docs/models/illicitfirearms.md new file mode 100644 index 0000000..2b0ccfe --- /dev/null +++ b/docs/models/illicitfirearms.md @@ -0,0 +1,10 @@ +# IllicitFirearms + + +## Fields + +| Field | Type | Required | Description | +| ----------------------------- | ----------------------------- | ----------------------------- | ----------------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["illicit_firearms"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/illicitgambling.md b/docs/models/illicitgambling.md new file mode 100644 index 0000000..e285a7b --- /dev/null +++ b/docs/models/illicitgambling.md @@ -0,0 +1,10 @@ +# IllicitGambling + + +## Fields + +| Field | Type | Required | Description | +| ----------------------------- | ----------------------------- | ----------------------------- | ----------------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["illicit_gambling"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/illicittobacco.md b/docs/models/illicittobacco.md new file mode 100644 index 0000000..ebb7158 --- /dev/null +++ b/docs/models/illicittobacco.md @@ -0,0 +1,10 @@ +# IllicitTobacco + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------- | ---------------------------- | ---------------------------- | ---------------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["illicit_tobacco"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/imagerequest.md b/docs/models/imagerequest.md new file mode 100644 index 0000000..b2807b9 --- /dev/null +++ b/docs/models/imagerequest.md @@ -0,0 +1,12 @@ +# ImageRequest + +Image + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | *Literal["image"]* | :heavy_check_mark: | N/A | +| `url` | *Optional[str]* | :heavy_minus_sign: | A public URL of the image content. Either url or data must be provided. | +| `data` | *Optional[str]* | :heavy_minus_sign: | Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue. | \ No newline at end of file diff --git a/docs/models/imageresponse.md b/docs/models/imageresponse.md new file mode 100644 index 0000000..e47838a --- /dev/null +++ b/docs/models/imageresponse.md @@ -0,0 +1,12 @@ +# ImageResponse + +Image + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | *Literal["image"]* | :heavy_check_mark: | N/A | +| `url` | *Optional[str]* | :heavy_minus_sign: | A public URL of the image content. Either url or data must be provided. | +| `data` | *Optional[str]* | :heavy_minus_sign: | Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue. | \ No newline at end of file diff --git a/docs/models/insight.md b/docs/models/insight.md new file mode 100644 index 0000000..02edb55 --- /dev/null +++ b/docs/models/insight.md @@ -0,0 +1,41 @@ +# Insight + + +## Supported Types + +### `models.SentimentInsight` + +```python +value: models.SentimentInsight = /* values here */ +``` + +### `models.LanguageInsight` + +```python +value: models.LanguageInsight = /* values here */ +``` + +### `models.CuriosityInsight` + +```python +value: models.CuriosityInsight = /* values here */ +``` + +### `models.RespectInsight` + +```python +value: models.RespectInsight = /* values here */ +``` + +### `models.PersonalStoryInsight` + +```python +value: models.PersonalStoryInsight = /* values here */ +``` + +### `models.CompassionInsight` + +```python +value: models.CompassionInsight = /* values here */ +``` + diff --git a/docs/models/internalservererrorcode.md b/docs/models/internalservererrorcode.md new file mode 100644 index 0000000..aad638d --- /dev/null +++ b/docs/models/internalservererrorcode.md @@ -0,0 +1,21 @@ +# InternalServerErrorCode + +Exactly what went wrong. Stable; safe to branch on. New codes may be added. + +- `internal_error`: Something went wrong on our side. Retry with backoff. + +## Example Usage + +```python +from moderation_api.models import InternalServerErrorCode + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: InternalServerErrorCode = "internal_error" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"internal_error"` diff --git a/docs/models/isflagged.md b/docs/models/isflagged.md new file mode 100644 index 0000000..2eb8478 --- /dev/null +++ b/docs/models/isflagged.md @@ -0,0 +1,20 @@ +# IsFlagged + +## Example Usage + +```python +from moderation_api.models import IsFlagged + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: IsFlagged = "ALL" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"ALL"` +- `"FLAGGED"` +- `"NOT_FLAGGED"` +- `"SHADOW_FLAGGED"` diff --git a/docs/models/languageinsight.md b/docs/models/languageinsight.md new file mode 100644 index 0000000..575c396 --- /dev/null +++ b/docs/models/languageinsight.md @@ -0,0 +1,13 @@ +# LanguageInsight + +Language insight + + +## Fields + +| Field | Type | Required | Description | +| --------------------- | --------------------- | --------------------- | --------------------- | +| `id` | *Literal["language"]* | :heavy_check_mark: | N/A | +| `type` | *Literal["insight"]* | :heavy_check_mark: | N/A | +| `probability` | *float* | :heavy_check_mark: | N/A | +| `value` | *Nullable[str]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/listactionsdata.md b/docs/models/listactionsdata.md new file mode 100644 index 0000000..6d86e93 --- /dev/null +++ b/docs/models/listactionsdata.md @@ -0,0 +1,20 @@ +# ListActionsData + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the action. | +| `key` | *OptionalNullable[str]* | :heavy_minus_sign: | User defined key of the action. | +| `created_at` | *str* | :heavy_check_mark: | The date the action was created. | +| `name` | *str* | :heavy_check_mark: | The name of the action. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The description of the action. | +| `type` | [OptionalNullable[models.ListActionsType]](../models/listactionstype.md) | :heavy_minus_sign: | The type of the action. | +| `built_in` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the action is a built-in action or a custom one. | +| `queue_behaviour` | [Optional[models.ListActionsQueueBehaviour]](../models/listactionsqueuebehaviour.md) | :heavy_minus_sign: | Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. | +| `filter_in_queue_ids` | List[*str*] | :heavy_check_mark: | The IDs of the queues the action is available in. | +| `position` | [Optional[models.ListActionsPosition]](../models/listactionsposition.md) | :heavy_minus_sign: | Show the action in all queues, selected queues or no queues (to use via API only). | +| `possible_values` | List[[models.ListActionsPossibleValue](../models/listactionspossiblevalue.md)] | :heavy_check_mark: | The possible values of the action. The user will be prompted to select one of these values when executing the action. | +| `value_required` | *Optional[bool]* | :heavy_minus_sign: | Whether the action requires a value to be executed. | +| `free_text` | *Optional[bool]* | :heavy_minus_sign: | Whether the action allows any text to be entered as a value or if it must be one of the possible values. | \ No newline at end of file diff --git a/docs/models/listactionsposition.md b/docs/models/listactionsposition.md new file mode 100644 index 0000000..7c0050e --- /dev/null +++ b/docs/models/listactionsposition.md @@ -0,0 +1,21 @@ +# ListActionsPosition + +Show the action in all queues, selected queues or no queues (to use via API only). + +## Example Usage + +```python +from moderation_api.models import ListActionsPosition + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: ListActionsPosition = "ALL_QUEUES" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"ALL_QUEUES"` +- `"SOME_QUEUES"` +- `"HIDDEN"` diff --git a/docs/models/listactionspossiblevalue.md b/docs/models/listactionspossiblevalue.md new file mode 100644 index 0000000..c171ef1 --- /dev/null +++ b/docs/models/listactionspossiblevalue.md @@ -0,0 +1,8 @@ +# ListActionsPossibleValue + + +## Fields + +| Field | Type | Required | Description | +| ------------------------ | ------------------------ | ------------------------ | ------------------------ | +| `value` | *str* | :heavy_check_mark: | The value of the action. | \ No newline at end of file diff --git a/docs/models/listactionsqueuebehaviour.md b/docs/models/listactionsqueuebehaviour.md new file mode 100644 index 0000000..035a666 --- /dev/null +++ b/docs/models/listactionsqueuebehaviour.md @@ -0,0 +1,21 @@ +# ListActionsQueueBehaviour + +Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. + +## Example Usage + +```python +from moderation_api.models import ListActionsQueueBehaviour + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: ListActionsQueueBehaviour = "REMOVE" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"REMOVE"` +- `"ADD"` +- `"NO_CHANGE"` diff --git a/docs/models/listactionsrequest.md b/docs/models/listactionsrequest.md new file mode 100644 index 0000000..3a4eb16 --- /dev/null +++ b/docs/models/listactionsrequest.md @@ -0,0 +1,10 @@ +# ListActionsRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `queue_id` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `page` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `page_size` | *Optional[int]* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/listactionsresponse.md b/docs/models/listactionsresponse.md new file mode 100644 index 0000000..819f56f --- /dev/null +++ b/docs/models/listactionsresponse.md @@ -0,0 +1,8 @@ +# ListActionsResponse + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| `result` | [models.ListActionsResponseBody](../models/listactionsresponsebody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/listactionsresponsebody.md b/docs/models/listactionsresponsebody.md new file mode 100644 index 0000000..bf17b26 --- /dev/null +++ b/docs/models/listactionsresponsebody.md @@ -0,0 +1,11 @@ +# ListActionsResponseBody + +Successful response + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| `data` | List[[models.ListActionsData](../models/listactionsdata.md)] | :heavy_check_mark: | The items on this page. | +| `pagination` | [models.Pagination](../models/pagination.md) | :heavy_check_mark: | Where this page is in the full list. | \ No newline at end of file diff --git a/docs/models/listactionstype.md b/docs/models/listactionstype.md new file mode 100644 index 0000000..2772817 --- /dev/null +++ b/docs/models/listactionstype.md @@ -0,0 +1,26 @@ +# ListActionsType + +## Example Usage + +```python +from moderation_api.models import ListActionsType + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: ListActionsType = "AUTHOR_BLOCK" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"AUTHOR_BLOCK"` +- `"AUTHOR_BLOCK_TEMP"` +- `"AUTHOR_UNBLOCK"` +- `"AUTHOR_DELETE"` +- `"AUTHOR_REPORT"` +- `"AUTHOR_WARN"` +- `"AUTHOR_CUSTOM"` +- `"ITEM_REJECT"` +- `"ITEM_ALLOW"` +- `"ITEM_CUSTOM"` diff --git a/docs/models/listauthorsrequest.md b/docs/models/listauthorsrequest.md new file mode 100644 index 0000000..af86db3 --- /dev/null +++ b/docs/models/listauthorsrequest.md @@ -0,0 +1,14 @@ +# ListAuthorsRequest + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| `page` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `page_size` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `sort_by` | [Optional[models.SortBy]](../models/sortby.md) | :heavy_minus_sign: | N/A | +| `sort_direction` | [Optional[models.ListAuthorsSortDirection]](../models/listauthorssortdirection.md) | :heavy_minus_sign: | Sort direction | +| `member_since_date` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `last_active_date` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `content_types` | List[*str*] | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/listauthorsresponse.md b/docs/models/listauthorsresponse.md new file mode 100644 index 0000000..d1c88a4 --- /dev/null +++ b/docs/models/listauthorsresponse.md @@ -0,0 +1,8 @@ +# ListAuthorsResponse + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| `result` | [models.ListAuthorsResponseBody](../models/listauthorsresponsebody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/listauthorsresponsebody.md b/docs/models/listauthorsresponsebody.md new file mode 100644 index 0000000..8cbc113 --- /dev/null +++ b/docs/models/listauthorsresponsebody.md @@ -0,0 +1,11 @@ +# ListAuthorsResponseBody + +Successful response + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ | +| `data` | List[[models.PublicAuthor](../models/publicauthor.md)] | :heavy_check_mark: | The items on this page. | +| `pagination` | [models.Pagination](../models/pagination.md) | :heavy_check_mark: | Where this page is in the full list. | \ No newline at end of file diff --git a/docs/models/listauthorssortdirection.md b/docs/models/listauthorssortdirection.md new file mode 100644 index 0000000..3cf8389 --- /dev/null +++ b/docs/models/listauthorssortdirection.md @@ -0,0 +1,16 @@ +# ListAuthorsSortDirection + +Sort direction + +## Example Usage + +```python +from moderation_api.models import ListAuthorsSortDirection +value: ListAuthorsSortDirection = "asc" +``` + + +## Values + +- `"asc"` +- `"desc"` diff --git a/docs/models/listchannelsrequest.md b/docs/models/listchannelsrequest.md new file mode 100644 index 0000000..ea66146 --- /dev/null +++ b/docs/models/listchannelsrequest.md @@ -0,0 +1,10 @@ +# ListChannelsRequest + + +## Fields + +| Field | Type | Required | Description | +| ---------------------- | ---------------------- | ---------------------- | ---------------------- | +| `project_id` | *str* | :heavy_check_mark: | The ID of the project. | +| `page` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `page_size` | *Optional[int]* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/listchannelsresponse.md b/docs/models/listchannelsresponse.md new file mode 100644 index 0000000..a0e4e45 --- /dev/null +++ b/docs/models/listchannelsresponse.md @@ -0,0 +1,8 @@ +# ListChannelsResponse + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | +| `result` | [models.ListChannelsResponseBody](../models/listchannelsresponsebody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/listchannelsresponsebody.md b/docs/models/listchannelsresponsebody.md new file mode 100644 index 0000000..0cc0537 --- /dev/null +++ b/docs/models/listchannelsresponsebody.md @@ -0,0 +1,11 @@ +# ListChannelsResponseBody + +Channels retrieved successfully + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------- | -------------------------------------------- | -------------------------------------------- | -------------------------------------------- | +| `data` | List[[models.Channel](../models/channel.md)] | :heavy_check_mark: | The items on this page. | +| `pagination` | [models.Pagination](../models/pagination.md) | :heavy_check_mark: | Where this page is in the full list. | \ No newline at end of file diff --git a/docs/models/listprojectsrequest.md b/docs/models/listprojectsrequest.md new file mode 100644 index 0000000..248fa6b --- /dev/null +++ b/docs/models/listprojectsrequest.md @@ -0,0 +1,9 @@ +# ListProjectsRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `page` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `page_size` | *Optional[int]* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/listprojectsresponse.md b/docs/models/listprojectsresponse.md new file mode 100644 index 0000000..b065292 --- /dev/null +++ b/docs/models/listprojectsresponse.md @@ -0,0 +1,8 @@ +# ListProjectsResponse + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | +| `result` | [models.ListProjectsResponseBody](../models/listprojectsresponsebody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/listprojectsresponsebody.md b/docs/models/listprojectsresponsebody.md new file mode 100644 index 0000000..5a094f5 --- /dev/null +++ b/docs/models/listprojectsresponsebody.md @@ -0,0 +1,11 @@ +# ListProjectsResponseBody + +Projects retrieved successfully + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------- | -------------------------------------------- | -------------------------------------------- | -------------------------------------------- | +| `data` | List[[models.Project](../models/project.md)] | :heavy_check_mark: | The items on this page. | +| `pagination` | [models.Pagination](../models/pagination.md) | :heavy_check_mark: | Where this page is in the full list. | \ No newline at end of file diff --git a/docs/models/listqueueitemsaction.md b/docs/models/listqueueitemsaction.md new file mode 100644 index 0000000..0775572 --- /dev/null +++ b/docs/models/listqueueitemsaction.md @@ -0,0 +1,12 @@ +# ListQueueItemsAction + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | Action ID | +| `name` | *str* | :heavy_check_mark: | Action name | +| `comment` | *Optional[str]* | :heavy_minus_sign: | Action comment | +| `timestamp` | *float* | :heavy_check_mark: | Unix timestamp of when the action was taken | +| `reviewer` | *Optional[str]* | :heavy_minus_sign: | Moderator userID | \ No newline at end of file diff --git a/docs/models/listqueueitemsdata.md b/docs/models/listqueueitemsdata.md new file mode 100644 index 0000000..3830d22 --- /dev/null +++ b/docs/models/listqueueitemsdata.md @@ -0,0 +1,18 @@ +# ListQueueItemsData + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | Content ID | +| `content` | *str* | :heavy_check_mark: | The content to be moderated | +| `language` | *Optional[str]* | :heavy_minus_sign: | Content language | +| `labels` | List[[models.ListQueueItemsLabel](../models/listqueueitemslabel.md)] | :heavy_check_mark: | N/A | +| `conversation_id` | *Optional[str]* | :heavy_minus_sign: | Conversation ID | +| `author_id` | *Optional[str]* | :heavy_minus_sign: | Author ID | +| `actions` | List[[models.ListQueueItemsAction](../models/listqueueitemsaction.md)] | :heavy_minus_sign: | Action IDs taken on this item | +| `flagged` | *bool* | :heavy_check_mark: | Whether the item is flagged by any label | +| `status` | [models.ListQueueItemsStatus](../models/listqueueitemsstatus.md) | :heavy_check_mark: | Status of the item | +| `timestamp` | *float* | :heavy_check_mark: | Unix timestamp of when the item was created | +| `content_type` | *Optional[str]* | :heavy_minus_sign: | Type of the content object | \ No newline at end of file diff --git a/docs/models/listqueueitemslabel.md b/docs/models/listqueueitemslabel.md new file mode 100644 index 0000000..a486ff3 --- /dev/null +++ b/docs/models/listqueueitemslabel.md @@ -0,0 +1,10 @@ +# ListQueueItemsLabel + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------- | -------------------------------- | -------------------------------- | -------------------------------- | +| `label` | *str* | :heavy_check_mark: | Label name | +| `score` | *float* | :heavy_check_mark: | Confidence score of the label | +| `flagged` | *bool* | :heavy_check_mark: | Whether this label caused a flag | \ No newline at end of file diff --git a/docs/models/listqueueitemsrequest.md b/docs/models/listqueueitemsrequest.md new file mode 100644 index 0000000..60fa671 --- /dev/null +++ b/docs/models/listqueueitemsrequest.md @@ -0,0 +1,19 @@ +# ListQueueItemsRequest + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The queue ID | +| `page` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `page_size` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `sort_field` | [Optional[models.SortField]](../models/sortfield.md) | :heavy_minus_sign: | N/A | +| `sort_direction` | [Optional[models.ListQueueItemsSortDirection]](../models/listqueueitemssortdirection.md) | :heavy_minus_sign: | Sort direction | +| `conversation_ids` | List[*str*] | :heavy_minus_sign: | N/A | +| `labels` | List[*str*] | :heavy_minus_sign: | N/A | +| `after_date` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `before_date` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `include_resolved` | *Optional[bool]* | :heavy_minus_sign: | N/A | +| `author_id` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `filtered_action_ids` | List[*str*] | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/listqueueitemsresponse.md b/docs/models/listqueueitemsresponse.md new file mode 100644 index 0000000..4c96a6b --- /dev/null +++ b/docs/models/listqueueitemsresponse.md @@ -0,0 +1,8 @@ +# ListQueueItemsResponse + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | +| `result` | [models.ListQueueItemsResponseBody](../models/listqueueitemsresponsebody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/listqueueitemsresponsebody.md b/docs/models/listqueueitemsresponsebody.md new file mode 100644 index 0000000..5a86db7 --- /dev/null +++ b/docs/models/listqueueitemsresponsebody.md @@ -0,0 +1,11 @@ +# ListQueueItemsResponseBody + +Successful response + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | +| `data` | List[[models.ListQueueItemsData](../models/listqueueitemsdata.md)] | :heavy_check_mark: | The items on this page. | +| `pagination` | [models.Pagination](../models/pagination.md) | :heavy_check_mark: | Where this page is in the full list. | \ No newline at end of file diff --git a/docs/models/listqueueitemssortdirection.md b/docs/models/listqueueitemssortdirection.md new file mode 100644 index 0000000..06d2a9f --- /dev/null +++ b/docs/models/listqueueitemssortdirection.md @@ -0,0 +1,16 @@ +# ListQueueItemsSortDirection + +Sort direction + +## Example Usage + +```python +from moderation_api.models import ListQueueItemsSortDirection +value: ListQueueItemsSortDirection = "asc" +``` + + +## Values + +- `"asc"` +- `"desc"` diff --git a/docs/models/listqueueitemsstatus.md b/docs/models/listqueueitemsstatus.md new file mode 100644 index 0000000..7ff2e6a --- /dev/null +++ b/docs/models/listqueueitemsstatus.md @@ -0,0 +1,20 @@ +# ListQueueItemsStatus + +Status of the item + +## Example Usage + +```python +from moderation_api.models import ListQueueItemsStatus + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: ListQueueItemsStatus = "pending" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"pending"` +- `"resolved"` diff --git a/docs/models/listwebhooksdata.md b/docs/models/listwebhooksdata.md new file mode 100644 index 0000000..9f917d3 --- /dev/null +++ b/docs/models/listwebhooksdata.md @@ -0,0 +1,14 @@ +# ListWebhooksData + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the webhook. | +| `name` | *str* | :heavy_check_mark: | The webhook's name. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The webhook's description. | +| `url` | *str* | :heavy_check_mark: | The URL we call when a subscribed event occurs. | +| `event_types` | List[[models.ListWebhooksEventType](../models/listwebhookseventtype.md)] | :heavy_check_mark: | Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead. | +| `payload_version` | [models.ListWebhooksPayloadVersion](../models/listwebhookspayloadversion.md) | :heavy_check_mark: | Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API. | +| `created_at` | *str* | :heavy_check_mark: | The date the webhook was created. | \ No newline at end of file diff --git a/docs/models/listwebhookseventtype.md b/docs/models/listwebhookseventtype.md new file mode 100644 index 0000000..5ab6f6f --- /dev/null +++ b/docs/models/listwebhookseventtype.md @@ -0,0 +1,27 @@ +# ListWebhooksEventType + +## Example Usage + +```python +from moderation_api.models import ListWebhooksEventType + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: ListWebhooksEventType = "QUEUE_ITEM_NEW" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"QUEUE_ITEM_NEW"` +- `"QUEUE_ITEM_COMPLETED"` +- `"QUEUE_ITEM_ACTION"` +- `"QUEUE_ITEM_REJECTED"` +- `"QUEUE_ITEM_ALLOWED"` +- `"AUTHOR_BLOCKED"` +- `"AUTHOR_UNBLOCKED"` +- `"AUTHOR_SUSPENDED"` +- `"AUTHOR_UPDATED"` +- `"AUTHOR_TRUST_LEVEL_CHANGED"` +- `"AUTHOR_ACTION"` diff --git a/docs/models/listwebhookspayloadversion.md b/docs/models/listwebhookspayloadversion.md new file mode 100644 index 0000000..4bcead2 --- /dev/null +++ b/docs/models/listwebhookspayloadversion.md @@ -0,0 +1,20 @@ +# ListWebhooksPayloadVersion + +Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API. + +## Example Usage + +```python +from moderation_api.models import ListWebhooksPayloadVersion + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: ListWebhooksPayloadVersion = "V1" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"V1"` +- `"V2"` diff --git a/docs/models/listwebhooksrequest.md b/docs/models/listwebhooksrequest.md new file mode 100644 index 0000000..f72c4f1 --- /dev/null +++ b/docs/models/listwebhooksrequest.md @@ -0,0 +1,9 @@ +# ListWebhooksRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `page` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `page_size` | *Optional[int]* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/listwebhooksresponse.md b/docs/models/listwebhooksresponse.md new file mode 100644 index 0000000..39c48fe --- /dev/null +++ b/docs/models/listwebhooksresponse.md @@ -0,0 +1,8 @@ +# ListWebhooksResponse + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | +| `result` | [models.ListWebhooksResponseBody](../models/listwebhooksresponsebody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/listwebhooksresponsebody.md b/docs/models/listwebhooksresponsebody.md new file mode 100644 index 0000000..aab3dde --- /dev/null +++ b/docs/models/listwebhooksresponsebody.md @@ -0,0 +1,11 @@ +# ListWebhooksResponseBody + +Webhooks retrieved successfully + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | +| `data` | List[[models.ListWebhooksData](../models/listwebhooksdata.md)] | :heavy_check_mark: | The items on this page. | +| `pagination` | [models.Pagination](../models/pagination.md) | :heavy_check_mark: | Where this page is in the full list. | \ No newline at end of file diff --git a/docs/models/listwordlistsdata.md b/docs/models/listwordlistsdata.md new file mode 100644 index 0000000..f5d3b88 --- /dev/null +++ b/docs/models/listwordlistsdata.md @@ -0,0 +1,12 @@ +# ListWordlistsData + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------- | --------------------------------- | --------------------------------- | --------------------------------- | +| `id` | *str* | :heavy_check_mark: | Unique identifier of the wordlist | +| `name` | *Nullable[str]* | :heavy_check_mark: | Name of the wordlist | +| `description` | *Nullable[str]* | :heavy_check_mark: | Description of the wordlist | +| `created_at` | *Nullable[str]* | :heavy_check_mark: | When the wordlist was created | +| `user_id` | *Nullable[str]* | :heavy_check_mark: | User who created the wordlist | \ No newline at end of file diff --git a/docs/models/listwordlistsrequest.md b/docs/models/listwordlistsrequest.md new file mode 100644 index 0000000..a63caee --- /dev/null +++ b/docs/models/listwordlistsrequest.md @@ -0,0 +1,9 @@ +# ListWordlistsRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `page` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `page_size` | *Optional[int]* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/listwordlistsresponse.md b/docs/models/listwordlistsresponse.md new file mode 100644 index 0000000..d5ba17d --- /dev/null +++ b/docs/models/listwordlistsresponse.md @@ -0,0 +1,8 @@ +# ListWordlistsResponse + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | +| `result` | [models.ListWordlistsResponseBody](../models/listwordlistsresponsebody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/listwordlistsresponsebody.md b/docs/models/listwordlistsresponsebody.md new file mode 100644 index 0000000..8456078 --- /dev/null +++ b/docs/models/listwordlistsresponsebody.md @@ -0,0 +1,11 @@ +# ListWordlistsResponseBody + +Successful response + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | +| `data` | List[[models.ListWordlistsData](../models/listwordlistsdata.md)] | :heavy_check_mark: | The items on this page. | +| `pagination` | [models.Pagination](../models/pagination.md) | :heavy_check_mark: | Where this page is in the full list. | \ No newline at end of file diff --git a/docs/models/lowqualitycontent.md b/docs/models/lowqualitycontent.md new file mode 100644 index 0000000..2f4eee9 --- /dev/null +++ b/docs/models/lowqualitycontent.md @@ -0,0 +1,11 @@ +# LowQualityContent + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["low_quality"]* | :heavy_check_mark: | N/A | +| `min_words` | *Optional[int]* | :heavy_minus_sign: | Flag content with fewer than this many words as low-effort. Defaults to 3. Set to disable by omitting. | \ No newline at end of file diff --git a/docs/models/matchedrule.md b/docs/models/matchedrule.md new file mode 100644 index 0000000..dc8c304 --- /dev/null +++ b/docs/models/matchedrule.md @@ -0,0 +1,9 @@ +# MatchedRule + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `key` | *str* | :heavy_check_mark: | N/A | +| `name` | *str* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/mediatype.md b/docs/models/mediatype.md new file mode 100644 index 0000000..209778b --- /dev/null +++ b/docs/models/mediatype.md @@ -0,0 +1,21 @@ +# MediaType + +## Example Usage + +```python +from moderation_api.models import MediaType + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: MediaType = "text" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"text"` +- `"image"` +- `"video"` +- `"object"` +- `"audio"` diff --git a/docs/models/meta.md b/docs/models/meta.md new file mode 100644 index 0000000..e52dd9a --- /dev/null +++ b/docs/models/meta.md @@ -0,0 +1,14 @@ +# Meta + +Metadata about the moderation request + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | +| `status` | [models.MetaStatus](../models/metastatus.md) | :heavy_check_mark: | N/A | +| `timestamp` | *float* | :heavy_check_mark: | N/A | +| `channel_key` | *str* | :heavy_check_mark: | The unique key of the channel where the content was handled. Either the channel provided by you or automatically routed. | +| `usage` | *float* | :heavy_check_mark: | N/A | +| `processing_time` | *Optional[str]* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/metadata.md b/docs/models/metadata.md new file mode 100644 index 0000000..8f1376e --- /dev/null +++ b/docs/models/metadata.md @@ -0,0 +1,14 @@ +# Metadata + +Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process. + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------- | --------------------------------------------- | --------------------------------------------- | --------------------------------------------- | +| `email_verified` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the author's email is verified | +| `phone_verified` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the author's phone number is verified | +| `identity_verified` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the author's identity is verified | +| `is_paying_customer` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the author is a paying customer | +| `__pydantic_extra__` | Dict[str, *Any*] | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/metadatafilter.md b/docs/models/metadatafilter.md new file mode 100644 index 0000000..fa1f938 --- /dev/null +++ b/docs/models/metadatafilter.md @@ -0,0 +1,9 @@ +# MetadataFilter + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `key` | *str* | :heavy_check_mark: | N/A | +| `value` | *str* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/metastatus.md b/docs/models/metastatus.md new file mode 100644 index 0000000..02e0a50 --- /dev/null +++ b/docs/models/metastatus.md @@ -0,0 +1,18 @@ +# MetaStatus + +## Example Usage + +```python +from moderation_api.models import MetaStatus + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: MetaStatus = "success" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"success"` +- `"partial_success"` diff --git a/docs/models/method.md b/docs/models/method.md new file mode 100644 index 0000000..2ff0b20 --- /dev/null +++ b/docs/models/method.md @@ -0,0 +1,18 @@ +# Method + +## Example Usage + +```python +from moderation_api.models import Method + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: Method = "registered_domain_token" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"registered_domain_token"` +- `"subdomain_token"` diff --git a/docs/models/metrics.md b/docs/models/metrics.md new file mode 100644 index 0000000..cecaa25 --- /dev/null +++ b/docs/models/metrics.md @@ -0,0 +1,10 @@ +# Metrics + + +## Fields + +| Field | Type | Required | Description | +| ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | +| `total_content` | *float* | :heavy_check_mark: | Total pieces of content | +| `flagged_content` | *float* | :heavy_check_mark: | Number of flagged content pieces | +| `average_sentiment` | *OptionalNullable[float]* | :heavy_minus_sign: | Average sentiment score of content (-1 to 1). Requires a sentiment model in your project. | \ No newline at end of file diff --git a/docs/models/modified.md b/docs/models/modified.md new file mode 100644 index 0000000..cc29cd8 --- /dev/null +++ b/docs/models/modified.md @@ -0,0 +1,23 @@ +# Modified + + +## Supported Types + +### `str` + +```python +value: str = /* values here */ +``` + +### `Dict[str, Any]` + +```python +value: Dict[str, Any] = /* values here */ +``` + +### `Dict[str, models.ContentLeafResponse]` + +```python +value: Dict[str, models.ContentLeafResponse] = /* values here */ +``` + diff --git a/docs/models/notfounderrorcode.md b/docs/models/notfounderrorcode.md new file mode 100644 index 0000000..706a615 --- /dev/null +++ b/docs/models/notfounderrorcode.md @@ -0,0 +1,35 @@ +# NotFoundErrorCode + +Exactly what went wrong. Stable; safe to branch on. New codes may be added. + +- `resource_not_found`: The resource does not exist. +- `action_not_found`: The action does not exist. +- `author_not_found`: The author does not exist. +- `queue_not_found`: The review queue does not exist. +- `wordlist_not_found`: The wordlist does not exist. +- `webhook_not_found`: The webhook does not exist. +- `project_not_found`: The project does not exist. +- `channel_not_found`: The channel does not exist. + +## Example Usage + +```python +from moderation_api.models import NotFoundErrorCode + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: NotFoundErrorCode = "resource_not_found" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"resource_not_found"` +- `"action_not_found"` +- `"author_not_found"` +- `"queue_not_found"` +- `"wordlist_not_found"` +- `"webhook_not_found"` +- `"project_not_found"` +- `"channel_not_found"` diff --git a/docs/models/pagination.md b/docs/models/pagination.md new file mode 100644 index 0000000..1050c99 --- /dev/null +++ b/docs/models/pagination.md @@ -0,0 +1,14 @@ +# Pagination + +Where this page is in the full list. + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------- | -------------------------------- | -------------------------------- | -------------------------------- | +| `page` | *int* | :heavy_check_mark: | The current page, starting at 1. | +| `page_size` | *int* | :heavy_check_mark: | Number of items per page. | +| `total` | *int* | :heavy_check_mark: | Total number of items. | +| `total_pages` | *int* | :heavy_check_mark: | Total number of pages. | +| `has_more` | *bool* | :heavy_check_mark: | Whether there is a next page. | \ No newline at end of file diff --git a/docs/models/path.md b/docs/models/path.md new file mode 100644 index 0000000..dfb1514 --- /dev/null +++ b/docs/models/path.md @@ -0,0 +1,17 @@ +# Path + + +## Supported Types + +### `str` + +```python +value: str = /* values here */ +``` + +### `float` + +```python +value: float = /* values here */ +``` + diff --git a/docs/models/permissiondeniederrorcode.md b/docs/models/permissiondeniederrorcode.md new file mode 100644 index 0000000..7136670 --- /dev/null +++ b/docs/models/permissiondeniederrorcode.md @@ -0,0 +1,37 @@ +# PermissionDeniedErrorCode + +Exactly what went wrong. Stable; safe to branch on. New codes may be added. + +- `permission_denied`: The API key cannot perform this request. +- `account_disabled`: The organization account is disabled. +- `quota_exceeded`: The organization has used all API requests on its plan. +- `plan_feature_unavailable`: The current plan does not include this feature. +- `plan_limit_exceeded`: The request goes over a limit of the current plan. +- `project_key_required`: This endpoint needs a project API key, but an organization key was used. +- `organization_key_required`: This endpoint needs an organization API key, but a project key was used. +- `missing_key_permission`: The organization key does not have the permission this endpoint needs. +- `project_not_reachable`: The organization key is limited to other projects. + +## Example Usage + +```python +from moderation_api.models import PermissionDeniedErrorCode + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: PermissionDeniedErrorCode = "permission_denied" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"permission_denied"` +- `"account_disabled"` +- `"quota_exceeded"` +- `"plan_feature_unavailable"` +- `"plan_limit_exceeded"` +- `"project_key_required"` +- `"organization_key_required"` +- `"missing_key_permission"` +- `"project_not_reachable"` diff --git a/docs/models/personalinformation.md b/docs/models/personalinformation.md new file mode 100644 index 0000000..0a658e6 --- /dev/null +++ b/docs/models/personalinformation.md @@ -0,0 +1,10 @@ +# PersonalInformation + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------- | --------------------------------- | --------------------------------- | --------------------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["personal_information"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/personalstoryinsight.md b/docs/models/personalstoryinsight.md new file mode 100644 index 0000000..fe86780 --- /dev/null +++ b/docs/models/personalstoryinsight.md @@ -0,0 +1,13 @@ +# PersonalStoryInsight + +Personal story insight. Includes a personal experience or story as a source of support for the statements made in the comment. + + +## Fields + +| Field | Type | Required | Description | +| ----------------------------------------- | ----------------------------------------- | ----------------------------------------- | ----------------------------------------- | +| `id` | *Literal["personal_story"]* | :heavy_check_mark: | N/A | +| `type` | *Literal["insight"]* | :heavy_check_mark: | N/A | +| `probability` | *float* | :heavy_check_mark: | N/A | +| `value` | *Nullable[bool]* | :heavy_check_mark: | Whether the content shows personal story. | \ No newline at end of file diff --git a/docs/models/piimasking.md b/docs/models/piimasking.md new file mode 100644 index 0000000..412d2d6 --- /dev/null +++ b/docs/models/piimasking.md @@ -0,0 +1,9 @@ +# PIIMasking + + +## Fields + +| Field | Type | Required | Description | +| ----------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------- | +| `id` | *Literal["pii"]* | :heavy_check_mark: | N/A | +| `entities` | Dict[str, [models.Entities1](../models/entities1.md)] | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/policyrequest.md b/docs/models/policyrequest.md new file mode 100644 index 0000000..f7a47cb --- /dev/null +++ b/docs/models/policyrequest.md @@ -0,0 +1,245 @@ +# PolicyRequest + + +## Supported Types + +### `models.Toxicity` + +```python +value: models.Toxicity = /* values here */ +``` + +### `models.PersonalInformation` + +```python +value: models.PersonalInformation = /* values here */ +``` + +### `models.Hate` + +```python +value: models.Hate = /* values here */ +``` + +### `models.Illicit` + +```python +value: models.Illicit = /* values here */ +``` + +### `models.IllicitDrugs` + +```python +value: models.IllicitDrugs = /* values here */ +``` + +### `models.IllicitAlcohol` + +```python +value: models.IllicitAlcohol = /* values here */ +``` + +### `models.IllicitFirearms` + +```python +value: models.IllicitFirearms = /* values here */ +``` + +### `models.IllicitTobacco` + +```python +value: models.IllicitTobacco = /* values here */ +``` + +### `models.IllicitGambling` + +```python +value: models.IllicitGambling = /* values here */ +``` + +### `models.Cannabis` + +```python +value: models.Cannabis = /* values here */ +``` + +### `models.Adult` + +```python +value: models.Adult = /* values here */ +``` + +### `models.Crypto` + +```python +value: models.Crypto = /* values here */ +``` + +### `models.Sexual` + +```python +value: models.Sexual = /* values here */ +``` + +### `models.SexualMinors` + +```python +value: models.SexualMinors = /* values here */ +``` + +### `models.Profanity` + +```python +value: models.Profanity = /* values here */ +``` + +### `models.Violence` + +```python +value: models.Violence = /* values here */ +``` + +### `models.SelfHarm` + +```python +value: models.SelfHarm = /* values here */ +``` + +### `models.Spam` + +```python +value: models.Spam = /* values here */ +``` + +### `models.LowQualityContent` + +```python +value: models.LowQualityContent = /* values here */ +``` + +### `models.FaceDetection` + +```python +value: models.FaceDetection = /* values here */ +``` + +### `models.SelfPromotion` + +```python +value: models.SelfPromotion = /* values here */ +``` + +### `models.Political` + +```python +value: models.Political = /* values here */ +``` + +### `models.Religion` + +```python +value: models.Religion = /* values here */ +``` + +### `models.Threat` + +```python +value: models.Threat = /* values here */ +``` + +### `models.SexualSolicitation` + +```python +value: models.SexualSolicitation = /* values here */ +``` + +### `models.ChannelCircumvention` + +```python +value: models.ChannelCircumvention = /* values here */ +``` + +### `models.IllegalTrade` + +```python +value: models.IllegalTrade = /* values here */ +``` + +### `models.Alcohol` + +```python +value: models.Alcohol = /* values here */ +``` + +### `models.Drugs` + +```python +value: models.Drugs = /* values here */ +``` + +### `models.Gambling` + +```python +value: models.Gambling = /* values here */ +``` + +### `models.Tobacco` + +```python +value: models.Tobacco = /* values here */ +``` + +### `models.Weapons` + +```python +value: models.Weapons = /* values here */ +``` + +### `models.Promotion` + +```python +value: models.Promotion = /* values here */ +``` + +### `models.Extremism` + +```python +value: models.Extremism = /* values here */ +``` + +### `models.CodeAbuse` + +```python +value: models.CodeAbuse = /* values here */ +``` + +### `models.UnicodeSpoofing` + +```python +value: models.UnicodeSpoofing = /* values here */ +``` + +### `models.PIIMasking` + +```python +value: models.PIIMasking = /* values here */ +``` + +### `models.URLMasking` + +```python +value: models.URLMasking = /* values here */ +``` + +### `models.URLRisk` + +```python +value: models.URLRisk = /* values here */ +``` + +### `models.Guideline` + +```python +value: models.Guideline = /* values here */ +``` + diff --git a/docs/models/policyresponse.md b/docs/models/policyresponse.md new file mode 100644 index 0000000..47f2c37 --- /dev/null +++ b/docs/models/policyresponse.md @@ -0,0 +1,19 @@ +# PolicyResponse + +Policy output schema. + + +## Supported Types + +### `models.ClassifierOutput` + +```python +value: models.ClassifierOutput = /* values here */ +``` + +### `models.EntityMatcherOutput` + +```python +value: models.EntityMatcherOutput = /* values here */ +``` + diff --git a/docs/models/political.md b/docs/models/political.md new file mode 100644 index 0000000..a2bcb4b --- /dev/null +++ b/docs/models/political.md @@ -0,0 +1,10 @@ +# Political + + +## Fields + +| Field | Type | Required | Description | +| ---------------------- | ---------------------- | ---------------------- | ---------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["political"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/profanity.md b/docs/models/profanity.md new file mode 100644 index 0000000..aed0a50 --- /dev/null +++ b/docs/models/profanity.md @@ -0,0 +1,10 @@ +# Profanity + + +## Fields + +| Field | Type | Required | Description | +| ---------------------- | ---------------------- | ---------------------- | ---------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["profanity"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/project.md b/docs/models/project.md new file mode 100644 index 0000000..07b0e05 --- /dev/null +++ b/docs/models/project.md @@ -0,0 +1,17 @@ +# Project + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the project. | +| `name` | *str* | :heavy_check_mark: | The project's name. | +| `description` | *Nullable[str]* | :heavy_check_mark: | The project's description. | +| `domain` | *Nullable[str]* | :heavy_check_mark: | The website or app the project moderates content for. | +| `context` | *Nullable[str]* | :heavy_check_mark: | What the platform is and who uses it. AI moderation reads it to judge content in context. | +| `global_flagging_mode` | [models.ProjectGlobalFlaggingMode](../models/projectglobalflaggingmode.md) | :heavy_check_mark: | `ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it, for testing a setup. | +| `enable_request_logging` | *bool* | :heavy_check_mark: | Whether moderated content is stored and shown in the dashboard. | +| `casebook_enabled` | *bool* | :heavy_check_mark: | Whether decisions made in the review queue are recorded in the project's casebook. | +| `created_at` | *str* | :heavy_check_mark: | When the project was created. | +| `updated_at` | *str* | :heavy_check_mark: | When the project was last changed. | \ No newline at end of file diff --git a/docs/models/projectglobalflaggingmode.md b/docs/models/projectglobalflaggingmode.md new file mode 100644 index 0000000..f6078be --- /dev/null +++ b/docs/models/projectglobalflaggingmode.md @@ -0,0 +1,20 @@ +# ProjectGlobalFlaggingMode + +`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it, for testing a setup. + +## Example Usage + +```python +from moderation_api.models import ProjectGlobalFlaggingMode + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: ProjectGlobalFlaggingMode = "DRY_RUN" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"DRY_RUN"` +- `"ACTIVE"` diff --git a/docs/models/projectwithkey.md b/docs/models/projectwithkey.md new file mode 100644 index 0000000..e2dd049 --- /dev/null +++ b/docs/models/projectwithkey.md @@ -0,0 +1,18 @@ +# ProjectWithKey + + +## Fields + +| Field | Type | Required | Description | +| ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the project. | +| `name` | *str* | :heavy_check_mark: | The project's name. | +| `description` | *Nullable[str]* | :heavy_check_mark: | The project's description. | +| `domain` | *Nullable[str]* | :heavy_check_mark: | The website or app the project moderates content for. | +| `context` | *Nullable[str]* | :heavy_check_mark: | What the platform is and who uses it. AI moderation reads it to judge content in context. | +| `global_flagging_mode` | [models.ProjectWithKeyGlobalFlaggingMode](../models/projectwithkeyglobalflaggingmode.md) | :heavy_check_mark: | `ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it, for testing a setup. | +| `enable_request_logging` | *bool* | :heavy_check_mark: | Whether moderated content is stored and shown in the dashboard. | +| `casebook_enabled` | *bool* | :heavy_check_mark: | Whether decisions made in the review queue are recorded in the project's casebook. | +| `created_at` | *str* | :heavy_check_mark: | When the project was created. | +| `updated_at` | *str* | :heavy_check_mark: | When the project was last changed. | +| `secret_key` | *str* | :heavy_check_mark: | A secret API key for the new project. Use it to moderate content in this project. It is also shown in the project's settings. | \ No newline at end of file diff --git a/docs/models/projectwithkeyglobalflaggingmode.md b/docs/models/projectwithkeyglobalflaggingmode.md new file mode 100644 index 0000000..8b09537 --- /dev/null +++ b/docs/models/projectwithkeyglobalflaggingmode.md @@ -0,0 +1,20 @@ +# ProjectWithKeyGlobalFlaggingMode + +`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it, for testing a setup. + +## Example Usage + +```python +from moderation_api.models import ProjectWithKeyGlobalFlaggingMode + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: ProjectWithKeyGlobalFlaggingMode = "DRY_RUN" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"DRY_RUN"` +- `"ACTIVE"` diff --git a/docs/models/promotion.md b/docs/models/promotion.md new file mode 100644 index 0000000..16f76ff --- /dev/null +++ b/docs/models/promotion.md @@ -0,0 +1,10 @@ +# Promotion + + +## Fields + +| Field | Type | Required | Description | +| ---------------------- | ---------------------- | ---------------------- | ---------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["promotion"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/publicauthor.md b/docs/models/publicauthor.md new file mode 100644 index 0000000..11d6f64 --- /dev/null +++ b/docs/models/publicauthor.md @@ -0,0 +1,23 @@ +# PublicAuthor + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | Author ID in Moderation API | +| `external_id` | *OptionalNullable[str]* | :heavy_minus_sign: | The author's ID from your system | +| `profile_picture` | *OptionalNullable[str]* | :heavy_minus_sign: | URL of the author's profile picture | +| `external_link` | *OptionalNullable[str]* | :heavy_minus_sign: | URL of the author's external profile | +| `name` | *OptionalNullable[str]* | :heavy_minus_sign: | Author name or identifier | +| `email` | *OptionalNullable[str]* | :heavy_minus_sign: | Author email address | +| `company` | *OptionalNullable[str]* | :heavy_minus_sign: | The author's company or organization | +| `first_seen` | *float* | :heavy_check_mark: | Timestamp when author first appeared | +| `last_seen` | *float* | :heavy_check_mark: | Timestamp of last activity | +| `last_incident` | *OptionalNullable[float]* | :heavy_minus_sign: | Timestamp of last incident | +| `status` | [models.Status](../models/status.md) | :heavy_check_mark: | Current author status | +| `trust_level` | [models.TrustLevel](../models/trustlevel.md) | :heavy_check_mark: | N/A | +| `block` | [Nullable[models.Block]](../models/block.md) | :heavy_check_mark: | Block or suspension details, if applicable. Null if the author is enabled. | +| `risk_evaluation` | [Nullable[models.RiskEvaluation]](../models/riskevaluation.md) | :heavy_check_mark: | Risk assessment details, if available. | +| `metrics` | [models.Metrics](../models/metrics.md) | :heavy_check_mark: | N/A | +| `metadata` | [models.Metadata](../models/metadata.md) | :heavy_check_mark: | Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process. | \ No newline at end of file diff --git a/docs/models/ratelimiterrorcode.md b/docs/models/ratelimiterrorcode.md new file mode 100644 index 0000000..8e44d04 --- /dev/null +++ b/docs/models/ratelimiterrorcode.md @@ -0,0 +1,21 @@ +# RateLimitErrorCode + +Exactly what went wrong. Stable; safe to branch on. New codes may be added. + +- `rate_limit_exceeded`: Too many requests. Wait for the `Retry-After` seconds. + +## Example Usage + +```python +from moderation_api.models import RateLimitErrorCode + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: RateLimitErrorCode = "rate_limit_exceeded" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"rate_limit_exceeded"` diff --git a/docs/models/reasoncode.md b/docs/models/reasoncode.md new file mode 100644 index 0000000..248bf89 --- /dev/null +++ b/docs/models/reasoncode.md @@ -0,0 +1,17 @@ +# ReasonCode + + +## Supported Types + +### `models.ReasonCodeEnum` + +```python +value: models.ReasonCodeEnum = /* values here */ +``` + +### `str` + +```python +value: str = /* values here */ +``` + diff --git a/docs/models/reasoncodeenum.md b/docs/models/reasoncodeenum.md new file mode 100644 index 0000000..2dd14b4 --- /dev/null +++ b/docs/models/reasoncodeenum.md @@ -0,0 +1,27 @@ +# ReasonCodeEnum + +## Example Usage + +```python +from moderation_api.models import ReasonCodeEnum + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: ReasonCodeEnum = "severity_reject" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"severity_reject"` +- `"severity_review"` +- `"author_block"` +- `"dry_run"` +- `"trusted_allow"` +- `"untrusted_severity"` +- `"rule_match"` +- `"rule_default"` +- `"rule_fallback"` +- `"client_override"` +- `"casebook_match"` diff --git a/docs/models/recommendation.md b/docs/models/recommendation.md new file mode 100644 index 0000000..b732268 --- /dev/null +++ b/docs/models/recommendation.md @@ -0,0 +1,12 @@ +# Recommendation + +The recommendation for the content based on the evaluation. + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `action` | [models.SubmitContentRecommendationAction](../models/submitcontentrecommendationaction.md) | :heavy_check_mark: | The action to take based on the recommendation | +| `reason_codes` | List[[models.ReasonCode](../models/reasoncode.md)] | :heavy_check_mark: | Reason codes for the recommendation. Standard codes plus a `rule:` code identifying the rule that produced the action. Can be used to display a reason to the user. | +| `matched_rules` | List[[models.MatchedRule](../models/matchedrule.md)] | :heavy_minus_sign: | Rules that matched during evaluation, if rules engine is active. | \ No newline at end of file diff --git a/docs/models/religion.md b/docs/models/religion.md new file mode 100644 index 0000000..e3a852e --- /dev/null +++ b/docs/models/religion.md @@ -0,0 +1,10 @@ +# Religion + + +## Fields + +| Field | Type | Required | Description | +| --------------------- | --------------------- | --------------------- | --------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["religion"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/removewordlistwordsrequest.md b/docs/models/removewordlistwordsrequest.md new file mode 100644 index 0000000..6d4cfe6 --- /dev/null +++ b/docs/models/removewordlistwordsrequest.md @@ -0,0 +1,9 @@ +# RemoveWordlistWordsRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------ | ------------------------------------------ | ------------------------------------------ | ------------------------------------------ | +| `id` | *str* | :heavy_check_mark: | ID of the wordlist to remove words from | +| `words` | List[*str*] | :heavy_check_mark: | Array of words to remove from the wordlist | \ No newline at end of file diff --git a/docs/models/removewordlistwordsresponse.md b/docs/models/removewordlistwordsresponse.md new file mode 100644 index 0000000..1116bdc --- /dev/null +++ b/docs/models/removewordlistwordsresponse.md @@ -0,0 +1,12 @@ +# RemoveWordlistWordsResponse + +Words successfully removed from wordlist + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------- | --------------------------------- | --------------------------------- | --------------------------------- | +| `removed_count` | *float* | :heavy_check_mark: | Number of words removed | +| `total_count` | *float* | :heavy_check_mark: | Total number of words in wordlist | +| `removed_words` | List[*str*] | :heavy_check_mark: | List of words removed | \ No newline at end of file diff --git a/docs/models/resolvequeueitemrequest.md b/docs/models/resolvequeueitemrequest.md new file mode 100644 index 0000000..06db45e --- /dev/null +++ b/docs/models/resolvequeueitemrequest.md @@ -0,0 +1,10 @@ +# ResolveQueueItemRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | +| `id` | *str* | :heavy_check_mark: | The queue ID | +| `item_id` | *str* | :heavy_check_mark: | The item ID to resolve | +| `body` | [models.ResolveQueueItemRequestBody](../models/resolvequeueitemrequestbody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/resolvequeueitemrequestbody.md b/docs/models/resolvequeueitemrequestbody.md new file mode 100644 index 0000000..6765922 --- /dev/null +++ b/docs/models/resolvequeueitemrequestbody.md @@ -0,0 +1,8 @@ +# ResolveQueueItemRequestBody + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `comment` | *Optional[str]* | :heavy_minus_sign: | Optional comment | \ No newline at end of file diff --git a/docs/models/resolvequeueitemresponse.md b/docs/models/resolvequeueitemresponse.md new file mode 100644 index 0000000..1a23f37 --- /dev/null +++ b/docs/models/resolvequeueitemresponse.md @@ -0,0 +1,12 @@ +# ResolveQueueItemResponse + +Successful response + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------ | ------------------------------------ | ------------------------------------ | ------------------------------------ | +| `success` | *bool* | :heavy_check_mark: | N/A | +| `resolved_at` | *str* | :heavy_check_mark: | Timestamp when the item was resolved | +| `comment` | *Optional[str]* | :heavy_minus_sign: | Optional comment | \ No newline at end of file diff --git a/docs/models/respectinsight.md b/docs/models/respectinsight.md new file mode 100644 index 0000000..43c2346 --- /dev/null +++ b/docs/models/respectinsight.md @@ -0,0 +1,13 @@ +# RespectInsight + +Respect insight. Shows deference or appreciation to others, or acknowledges the validity of another person. + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------- | ---------------------------------- | ---------------------------------- | ---------------------------------- | +| `id` | *Literal["respect"]* | :heavy_check_mark: | N/A | +| `type` | *Literal["insight"]* | :heavy_check_mark: | N/A | +| `probability` | *float* | :heavy_check_mark: | N/A | +| `value` | *Nullable[bool]* | :heavy_check_mark: | Whether the content shows respect. | \ No newline at end of file diff --git a/docs/models/reviewstats.md b/docs/models/reviewstats.md new file mode 100644 index 0000000..67c754b --- /dev/null +++ b/docs/models/reviewstats.md @@ -0,0 +1,10 @@ +# ReviewStats + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------- | ---------------------------------------------- | ---------------------------------------------- | ---------------------------------------------- | +| `average_time_to_review` | *float* | :heavy_check_mark: | Average time in milliseconds to review an item | +| `total_reviewed` | *float* | :heavy_check_mark: | Total number of items reviewed | +| `total_pending` | *float* | :heavy_check_mark: | Total number of items pending review | \ No newline at end of file diff --git a/docs/models/riskevaluation.md b/docs/models/riskevaluation.md new file mode 100644 index 0000000..a97f3e0 --- /dev/null +++ b/docs/models/riskevaluation.md @@ -0,0 +1,8 @@ +# RiskEvaluation + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------- | +| `risk_level` | *OptionalNullable[float]* | :heavy_minus_sign: | Calculated risk level based on more than 10 behavioral signals. | \ No newline at end of file diff --git a/docs/models/scores.md b/docs/models/scores.md new file mode 100644 index 0000000..d415194 --- /dev/null +++ b/docs/models/scores.md @@ -0,0 +1,11 @@ +# Scores + +Positive and negative scored independently: mixed content scores high on both. Present whenever `value` is set. + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------ | ------------------------------------ | ------------------------------------ | ------------------------------------ | +| `positive` | *float* | :heavy_check_mark: | How positive the content is, 0 to 1. | +| `negative` | *float* | :heavy_check_mark: | How negative the content is, 0 to 1. | \ No newline at end of file diff --git a/docs/models/security.md b/docs/models/security.md new file mode 100644 index 0000000..d3a526c --- /dev/null +++ b/docs/models/security.md @@ -0,0 +1,8 @@ +# Security + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `secret_key` | *Optional[str]* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/selfharm.md b/docs/models/selfharm.md new file mode 100644 index 0000000..eef2219 --- /dev/null +++ b/docs/models/selfharm.md @@ -0,0 +1,10 @@ +# SelfHarm + + +## Fields + +| Field | Type | Required | Description | +| ---------------------- | ---------------------- | ---------------------- | ---------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["self_harm"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/selfpromotion.md b/docs/models/selfpromotion.md new file mode 100644 index 0000000..220696b --- /dev/null +++ b/docs/models/selfpromotion.md @@ -0,0 +1,10 @@ +# SelfPromotion + + +## Fields + +| Field | Type | Required | Description | +| --------------------------- | --------------------------- | --------------------------- | --------------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["self_promotion"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/sentimentinsight.md b/docs/models/sentimentinsight.md new file mode 100644 index 0000000..8eb26d8 --- /dev/null +++ b/docs/models/sentimentinsight.md @@ -0,0 +1,14 @@ +# SentimentInsight + +Sentiment insight + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| `id` | *Literal["sentiment"]* | :heavy_check_mark: | N/A | +| `type` | *Literal["insight"]* | :heavy_check_mark: | N/A | +| `probability` | *float* | :heavy_check_mark: | N/A | +| `value` | [Nullable[models.Value]](../models/value.md) | :heavy_check_mark: | N/A | +| `scores` | [Optional[models.Scores]](../models/scores.md) | :heavy_minus_sign: | Positive and negative scored independently: mixed content scores high on both. Present whenever `value` is set. | \ No newline at end of file diff --git a/docs/models/sexual.md b/docs/models/sexual.md new file mode 100644 index 0000000..915dc4b --- /dev/null +++ b/docs/models/sexual.md @@ -0,0 +1,10 @@ +# Sexual + + +## Fields + +| Field | Type | Required | Description | +| ------------------- | ------------------- | ------------------- | ------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["sexual"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/sexualminors.md b/docs/models/sexualminors.md new file mode 100644 index 0000000..ec37137 --- /dev/null +++ b/docs/models/sexualminors.md @@ -0,0 +1,10 @@ +# SexualMinors + + +## Fields + +| Field | Type | Required | Description | +| -------------------------- | -------------------------- | -------------------------- | -------------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["sexual_minors"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/sexualsolicitation.md b/docs/models/sexualsolicitation.md new file mode 100644 index 0000000..c05e3c9 --- /dev/null +++ b/docs/models/sexualsolicitation.md @@ -0,0 +1,10 @@ +# SexualSolicitation + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------- | -------------------------------- | -------------------------------- | -------------------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["sexual_solicitation"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/signalsrequest.md b/docs/models/signalsrequest.md new file mode 100644 index 0000000..4a34024 --- /dev/null +++ b/docs/models/signalsrequest.md @@ -0,0 +1,8 @@ +# SignalsRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `flag` | *Optional[bool]* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/signalsresponse.md b/docs/models/signalsresponse.md new file mode 100644 index 0000000..9387f97 --- /dev/null +++ b/docs/models/signalsresponse.md @@ -0,0 +1,18 @@ +# SignalsResponse + +Observable properties of a URL (URL Risk only). Absent for allow/block list matches. + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | +| `brand_impersonation` | [Nullable[models.SubmitContentBrandImpersonation]](../models/submitcontentbrandimpersonation.md) | :heavy_check_mark: | N/A | +| `has_suspicious_characters` | *bool* | :heavy_check_mark: | N/A | +| `is_link_shortener` | *bool* | :heavy_check_mark: | N/A | +| `domain_age_days` | *Nullable[int]* | :heavy_check_mark: | N/A | +| `has_email_setup` | *Nullable[bool]* | :heavy_check_mark: | N/A | +| `redirect_count` | *Nullable[int]* | :heavy_check_mark: | N/A | +| `final_url` | *Nullable[str]* | :heavy_check_mark: | N/A | +| `bot_protection` | *Nullable[bool]* | :heavy_check_mark: | N/A | +| `is_reported` | *bool* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/sortby.md b/docs/models/sortby.md new file mode 100644 index 0000000..c4a3a18 --- /dev/null +++ b/docs/models/sortby.md @@ -0,0 +1,20 @@ +# SortBy + +## Example Usage + +```python +from moderation_api.models import SortBy +value: SortBy = "trustLevel" +``` + + +## Values + +- `"trustLevel"` +- `"violationCount"` +- `"reportCount"` +- `"memberSince"` +- `"lastActive"` +- `"contentCount"` +- `"flaggedContentRatio"` +- `"averageSentiment"` diff --git a/docs/models/sortfield.md b/docs/models/sortfield.md new file mode 100644 index 0000000..05a3712 --- /dev/null +++ b/docs/models/sortfield.md @@ -0,0 +1,15 @@ +# SortField + +## Example Usage + +```python +from moderation_api.models import SortField +value: SortField = "createdAt" +``` + + +## Values + +- `"createdAt"` +- `"severity"` +- `"reviewedAt"` diff --git a/docs/models/spam.md b/docs/models/spam.md new file mode 100644 index 0000000..b15bd4a --- /dev/null +++ b/docs/models/spam.md @@ -0,0 +1,10 @@ +# Spam + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["spam"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/status.md b/docs/models/status.md new file mode 100644 index 0000000..eaf321c --- /dev/null +++ b/docs/models/status.md @@ -0,0 +1,21 @@ +# Status + +Current author status + +## Example Usage + +```python +from moderation_api.models import Status + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: Status = "enabled" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"enabled"` +- `"suspended"` +- `"blocked"` diff --git a/docs/models/submitcontentbehavior.md b/docs/models/submitcontentbehavior.md new file mode 100644 index 0000000..55171e8 --- /dev/null +++ b/docs/models/submitcontentbehavior.md @@ -0,0 +1,16 @@ +# SubmitContentBehavior + +How your recommendation combines with ours. Defaults to 'escalate', which only applies it when stricter than ours; 'override' replaces ours outright. + +## Example Usage + +```python +from moderation_api.models import SubmitContentBehavior +value: SubmitContentBehavior = "override" +``` + + +## Values + +- `"override"` +- `"escalate"` diff --git a/docs/models/submitcontentblock.md b/docs/models/submitcontentblock.md new file mode 100644 index 0000000..9899b77 --- /dev/null +++ b/docs/models/submitcontentblock.md @@ -0,0 +1,9 @@ +# SubmitContentBlock + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| `until` | *OptionalNullable[float]* | :heavy_minus_sign: | The timestamp until which they are blocked if the author is suspended. | +| `reason` | *OptionalNullable[str]* | :heavy_minus_sign: | The moderators reason why the author was blocked or suspended. | \ No newline at end of file diff --git a/docs/models/submitcontentbrandimpersonation.md b/docs/models/submitcontentbrandimpersonation.md new file mode 100644 index 0000000..8fa7291 --- /dev/null +++ b/docs/models/submitcontentbrandimpersonation.md @@ -0,0 +1,9 @@ +# SubmitContentBrandImpersonation + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------ | ------------------------------------ | ------------------------------------ | ------------------------------------ | +| `brand` | *str* | :heavy_check_mark: | N/A | +| `method` | [models.Method](../models/method.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/submitcontentclientaction.md b/docs/models/submitcontentclientaction.md new file mode 100644 index 0000000..4780afa --- /dev/null +++ b/docs/models/submitcontentclientaction.md @@ -0,0 +1,13 @@ +# SubmitContentClientAction + +A recommendation from your own client-side flagging (e.g. a banned-IP list or a third-party tool). Feeds the rules engine and can escalate or override the recommended action. Does not change whether our analysis flagged the content. + + +## Fields + +| Field | Type | Required | Description | +| ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| `action` | [models.ActionRequest](../models/actionrequest.md) | :heavy_check_mark: | Your recommendation for the content: allow, review, or reject. | +| `behavior` | [Optional[models.SubmitContentBehavior]](../models/submitcontentbehavior.md) | :heavy_minus_sign: | How your recommendation combines with ours. Defaults to 'escalate', which only applies it when stricter than ours; 'override' replaces ours outright. | +| `source` | *Optional[str]* | :heavy_minus_sign: | Where your recommendation came from, e.g. "banned-ip". | +| `reason` | *Optional[str]* | :heavy_minus_sign: | A human-readable explanation for your recommendation. | \ No newline at end of file diff --git a/docs/models/submitcontentlabel.md b/docs/models/submitcontentlabel.md new file mode 100644 index 0000000..618a65d --- /dev/null +++ b/docs/models/submitcontentlabel.md @@ -0,0 +1,11 @@ +# SubmitContentLabel + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `id` | *str* | :heavy_check_mark: | N/A | +| `probability` | *float* | :heavy_check_mark: | N/A | +| `flagged` | *bool* | :heavy_check_mark: | N/A | +| `shadow_flagged` | *Optional[bool]* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/submitcontentmatch.md b/docs/models/submitcontentmatch.md new file mode 100644 index 0000000..48a4370 --- /dev/null +++ b/docs/models/submitcontentmatch.md @@ -0,0 +1,13 @@ +# SubmitContentMatch + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `probability` | *float* | :heavy_check_mark: | N/A | +| `match` | *str* | :heavy_check_mark: | N/A | +| `span` | List[*int*] | :heavy_check_mark: | N/A | +| `reasons` | List[*str*] | :heavy_minus_sign: | Stable codes explaining why a URL was flagged (URL Risk only). | +| `signals` | [Optional[models.SignalsResponse]](../models/signalsresponse.md) | :heavy_minus_sign: | Observable properties of a URL (URL Risk only). Absent for allow/block list matches. | +| `entity_type` | *Optional[str]* | :heavy_minus_sign: | Sub-type of the entity match — e.g. the NER key (email, phone, name, …) for PII matches. Absent for URL Risk and wordlist matches where the type is already encoded in the parent label. | \ No newline at end of file diff --git a/docs/models/submitcontentmetatype.md b/docs/models/submitcontentmetatype.md new file mode 100644 index 0000000..e30f7f8 --- /dev/null +++ b/docs/models/submitcontentmetatype.md @@ -0,0 +1,23 @@ +# SubmitContentMetaType + +The meta type of content being moderated + +## Example Usage + +```python +from moderation_api.models import SubmitContentMetaType +value: SubmitContentMetaType = "profile" +``` + + +## Values + +- `"profile"` +- `"message"` +- `"post"` +- `"comment"` +- `"event"` +- `"product"` +- `"review"` +- `"voice"` +- `"other"` diff --git a/docs/models/submitcontentobject.md b/docs/models/submitcontentobject.md new file mode 100644 index 0000000..69677f0 --- /dev/null +++ b/docs/models/submitcontentobject.md @@ -0,0 +1,11 @@ +# SubmitContentObject + +Object + + +## Fields + +| Field | Type | Required | Description | +| ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | +| `type` | *Literal["object"]* | :heavy_check_mark: | N/A | +| `data` | Dict[str, [models.ContentLeafRequest](../models/contentleafrequest.md)] | :heavy_check_mark: | Values in the object. Can be mixed content types. | \ No newline at end of file diff --git a/docs/models/submitcontentrecommendationaction.md b/docs/models/submitcontentrecommendationaction.md new file mode 100644 index 0000000..3f3b530 --- /dev/null +++ b/docs/models/submitcontentrecommendationaction.md @@ -0,0 +1,21 @@ +# SubmitContentRecommendationAction + +The action to take based on the recommendation + +## Example Usage + +```python +from moderation_api.models import SubmitContentRecommendationAction + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: SubmitContentRecommendationAction = "review" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"review"` +- `"allow"` +- `"reject"` diff --git a/docs/models/submitcontentrequest.md b/docs/models/submitcontentrequest.md new file mode 100644 index 0000000..20b0139 --- /dev/null +++ b/docs/models/submitcontentrequest.md @@ -0,0 +1,18 @@ +# SubmitContentRequest + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `content` | [models.ContentRequest](../models/contentrequest.md) | :heavy_check_mark: | The content sent for moderation | +| `timestamp` | *Optional[float]* | :heavy_minus_sign: | Unix timestamp (in milliseconds) of when the content was created. Use if content is not submitted in real-time. | +| `channel` | *Optional[str]* | :heavy_minus_sign: | Provide a channel ID or key. Will use the project's default channel if not provided. | +| `content_id` | *Optional[str]* | :heavy_minus_sign: | The unique ID of the content in your database. | +| `meta_type` | [Optional[models.SubmitContentMetaType]](../models/submitcontentmetatype.md) | :heavy_minus_sign: | The meta type of content being moderated | +| `author_id` | *Optional[str]* | :heavy_minus_sign: | The author of the content. | +| `conversation_id` | *Optional[str]* | :heavy_minus_sign: | For example the ID of a chat room or a post | +| `metadata` | Dict[str, *Any*] | :heavy_minus_sign: | Any metadata you want to store with the content | +| `client_action` | [Optional[models.SubmitContentClientAction]](../models/submitcontentclientaction.md) | :heavy_minus_sign: | A recommendation from your own client-side flagging (e.g. a banned-IP list or a third-party tool). Feeds the rules engine and can escalate or override the recommended action. Does not change whether our analysis flagged the content. | +| `do_not_store` | *Optional[bool]* | :heavy_minus_sign: | Do not store the content. The content won't enter the review queue | +| `policies` | List[[models.PolicyRequest](../models/policyrequest.md)] | :heavy_minus_sign: | (Enterprise) override the channel policies for this moderation request only. | \ No newline at end of file diff --git a/docs/models/submitcontentresponse.md b/docs/models/submitcontentresponse.md new file mode 100644 index 0000000..f3b68aa --- /dev/null +++ b/docs/models/submitcontentresponse.md @@ -0,0 +1,18 @@ +# SubmitContentResponse + +Successful response + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `content` | [models.ContentResponse](../models/contentresponse.md) | :heavy_check_mark: | Potentially modified content. | +| `author` | [Nullable[models.Author]](../models/author.md) | :heavy_check_mark: | The author of the content if your account has authors enabled. Requires you to send authorId when submitting content. | +| `evaluation` | [models.Evaluation](../models/evaluation.md) | :heavy_check_mark: | The evaluation of the content after running the channel policies. | +| `recommendation` | [models.Recommendation](../models/recommendation.md) | :heavy_check_mark: | The recommendation for the content based on the evaluation. | +| `casebook` | [Nullable[models.Casebook]](../models/casebook.md) | :heavy_check_mark: | What your casebook — the record of your past moderation decisions — found for this content, or null when it had nothing close enough to say, when the matching cases disagreed, or when casebook lookups are not switched on for this channel. Reports what the casebook found; whether it decided the outcome is shown in `recommendation`, where a higher-priority rule may have settled the item first. | +| `policies` | List[[models.PolicyResponse](../models/policyresponse.md)] | :heavy_check_mark: | Results of all policies in the channel. Sorted by highest probability. | +| `insights` | List[[models.Insight](../models/insight.md)] | :heavy_check_mark: | Results of all insights enabled in the channel. | +| `meta` | [models.Meta](../models/meta.md) | :heavy_check_mark: | Metadata about the moderation request | +| `errors` | List[[models.Error](../models/error.md)] | :heavy_minus_sign: | Policies that had errors | \ No newline at end of file diff --git a/docs/models/submitcontenttrustlevel.md b/docs/models/submitcontenttrustlevel.md new file mode 100644 index 0000000..2025266 --- /dev/null +++ b/docs/models/submitcontenttrustlevel.md @@ -0,0 +1,9 @@ +# SubmitContentTrustLevel + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- | +| `level` | *float* | :heavy_check_mark: | Author trust level (-1, 0, 1, 2, 3, or 4) | +| `manual` | *bool* | :heavy_check_mark: | True if the trust level was set manually by a moderator | \ No newline at end of file diff --git a/docs/models/textrequest.md b/docs/models/textrequest.md new file mode 100644 index 0000000..580a6ac --- /dev/null +++ b/docs/models/textrequest.md @@ -0,0 +1,11 @@ +# TextRequest + +Text + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `type` | *Literal["text"]* | :heavy_check_mark: | N/A | +| `text` | *str* | :heavy_check_mark: | The content text | \ No newline at end of file diff --git a/docs/models/textresponse.md b/docs/models/textresponse.md new file mode 100644 index 0000000..d90040c --- /dev/null +++ b/docs/models/textresponse.md @@ -0,0 +1,11 @@ +# TextResponse + +Text + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `type` | *Literal["text"]* | :heavy_check_mark: | N/A | +| `text` | *str* | :heavy_check_mark: | The content text | \ No newline at end of file diff --git a/docs/models/threat.md b/docs/models/threat.md new file mode 100644 index 0000000..af7e0d4 --- /dev/null +++ b/docs/models/threat.md @@ -0,0 +1,10 @@ +# Threat + + +## Fields + +| Field | Type | Required | Description | +| ------------------- | ------------------- | ------------------- | ------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["threat"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/tobacco.md b/docs/models/tobacco.md new file mode 100644 index 0000000..5670a7b --- /dev/null +++ b/docs/models/tobacco.md @@ -0,0 +1,10 @@ +# Tobacco + + +## Fields + +| Field | Type | Required | Description | +| -------------------- | -------------------- | -------------------- | -------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["tobacco"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/topaction.md b/docs/models/topaction.md new file mode 100644 index 0000000..77d1be2 --- /dev/null +++ b/docs/models/topaction.md @@ -0,0 +1,10 @@ +# TopAction + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------ | ------------------------------------ | ------------------------------------ | ------------------------------------ | +| `action_id` | *str* | :heavy_check_mark: | Most used action by this reviewer | +| `action_name` | *str* | :heavy_check_mark: | Name of the most used action | +| `count` | *float* | :heavy_check_mark: | Number of times this action was used | \ No newline at end of file diff --git a/docs/models/topic.md b/docs/models/topic.md new file mode 100644 index 0000000..a2e3253 --- /dev/null +++ b/docs/models/topic.md @@ -0,0 +1,9 @@ +# Topic + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `id` | *str* | :heavy_check_mark: | N/A | +| `label` | *str* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/topreviewer.md b/docs/models/topreviewer.md new file mode 100644 index 0000000..d2b695f --- /dev/null +++ b/docs/models/topreviewer.md @@ -0,0 +1,13 @@ +# TopReviewer + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- | +| `user_id` | *str* | :heavy_check_mark: | ID of the reviewer | +| `name` | *str* | :heavy_check_mark: | Name of the reviewer | +| `review_count` | *float* | :heavy_check_mark: | Number of items reviewed | +| `average_time_per_review` | *float* | :heavy_check_mark: | Average review time in milliseconds | +| `accuracy_score` | *Optional[float]* | :heavy_minus_sign: | Optional accuracy score based on review quality metrics | +| `top_actions` | List[[models.TopAction](../models/topaction.md)] | :heavy_check_mark: | Most common actions taken by this reviewer | \ No newline at end of file diff --git a/docs/models/toxicity.md b/docs/models/toxicity.md new file mode 100644 index 0000000..c1c20b0 --- /dev/null +++ b/docs/models/toxicity.md @@ -0,0 +1,10 @@ +# Toxicity + + +## Fields + +| Field | Type | Required | Description | +| --------------------- | --------------------- | --------------------- | --------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["toxicity"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/transcriptionquality.md b/docs/models/transcriptionquality.md new file mode 100644 index 0000000..29f3949 --- /dev/null +++ b/docs/models/transcriptionquality.md @@ -0,0 +1,21 @@ +# TranscriptionQuality + +Trade-off between transcription speed and accuracy. + +## Example Usage + +```python +from moderation_api.models import TranscriptionQuality + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: TranscriptionQuality = "SPEED" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"SPEED"` +- `"BALANCED"` +- `"ACCURACY"` diff --git a/docs/models/trends.md b/docs/models/trends.md new file mode 100644 index 0000000..d9cf8da --- /dev/null +++ b/docs/models/trends.md @@ -0,0 +1,9 @@ +# Trends + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- | +| `daily_review_counts` | List[[models.DailyReviewCount](../models/dailyreviewcount.md)] | :heavy_check_mark: | N/A | +| `flagged_content_trends` | List[[models.FlaggedContentTrend](../models/flaggedcontenttrend.md)] | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/trustlevel.md b/docs/models/trustlevel.md new file mode 100644 index 0000000..363851b --- /dev/null +++ b/docs/models/trustlevel.md @@ -0,0 +1,9 @@ +# TrustLevel + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- | +| `level` | *float* | :heavy_check_mark: | Author trust level (-1, 0, 1, 2, 3, or 4) | +| `manual` | *bool* | :heavy_check_mark: | True if the trust level was set manually by a moderator | \ No newline at end of file diff --git a/docs/models/unicodespoofing.md b/docs/models/unicodespoofing.md new file mode 100644 index 0000000..ca39e29 --- /dev/null +++ b/docs/models/unicodespoofing.md @@ -0,0 +1,11 @@ +# UnicodeSpoofing + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["unicode_spoofing"]* | :heavy_check_mark: | N/A | +| `signals` | Dict[str, [models.SignalsRequest](../models/signalsrequest.md)] | :heavy_minus_sign: | Per-signal flag toggles. Omitted spoofing signals are enabled; encoding_damage defaults to off because decode damage (U+FFFD) marks a broken pipeline, not an attack. A disabled signal is still detected and reported as a label, but does not by itself flag the policy. | \ No newline at end of file diff --git a/docs/models/unresolvequeueitemrequest.md b/docs/models/unresolvequeueitemrequest.md new file mode 100644 index 0000000..5d38e4d --- /dev/null +++ b/docs/models/unresolvequeueitemrequest.md @@ -0,0 +1,10 @@ +# UnresolveQueueItemRequest + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The queue ID | +| `item_id` | *str* | :heavy_check_mark: | The item ID to unresolve | +| `body` | [models.UnresolveQueueItemRequestBody](../models/unresolvequeueitemrequestbody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/unresolvequeueitemrequestbody.md b/docs/models/unresolvequeueitemrequestbody.md new file mode 100644 index 0000000..5ef20d2 --- /dev/null +++ b/docs/models/unresolvequeueitemrequestbody.md @@ -0,0 +1,8 @@ +# UnresolveQueueItemRequestBody + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------- | ---------------------------------------- | ---------------------------------------- | ---------------------------------------- | +| `comment` | *Optional[str]* | :heavy_minus_sign: | Optional reason for unresolving the item | \ No newline at end of file diff --git a/docs/models/unresolvequeueitemresponse.md b/docs/models/unresolvequeueitemresponse.md new file mode 100644 index 0000000..6f969f5 --- /dev/null +++ b/docs/models/unresolvequeueitemresponse.md @@ -0,0 +1,12 @@ +# UnresolveQueueItemResponse + +Successful response + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------- | -------------------------------------- | -------------------------------------- | -------------------------------------- | +| `success` | *bool* | :heavy_check_mark: | N/A | +| `status` | *str* | :heavy_check_mark: | New status of the item | +| `unresolved_at` | *str* | :heavy_check_mark: | Timestamp when the item was unresolved | \ No newline at end of file diff --git a/docs/models/updateactionpositionrequest.md b/docs/models/updateactionpositionrequest.md new file mode 100644 index 0000000..8acb535 --- /dev/null +++ b/docs/models/updateactionpositionrequest.md @@ -0,0 +1,17 @@ +# UpdateActionPositionRequest + +Show the action in all queues, selected queues or no queues (to use via API only). + +## Example Usage + +```python +from moderation_api.models import UpdateActionPositionRequest +value: UpdateActionPositionRequest = "ALL_QUEUES" +``` + + +## Values + +- `"ALL_QUEUES"` +- `"SOME_QUEUES"` +- `"HIDDEN"` diff --git a/docs/models/updateactionpositionresponse.md b/docs/models/updateactionpositionresponse.md new file mode 100644 index 0000000..14cdc08 --- /dev/null +++ b/docs/models/updateactionpositionresponse.md @@ -0,0 +1,21 @@ +# UpdateActionPositionResponse + +Show the action in all queues, selected queues or no queues (to use via API only). + +## Example Usage + +```python +from moderation_api.models import UpdateActionPositionResponse + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: UpdateActionPositionResponse = "ALL_QUEUES" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"ALL_QUEUES"` +- `"SOME_QUEUES"` +- `"HIDDEN"` diff --git a/docs/models/updateactionpossiblevaluerequest.md b/docs/models/updateactionpossiblevaluerequest.md new file mode 100644 index 0000000..621b767 --- /dev/null +++ b/docs/models/updateactionpossiblevaluerequest.md @@ -0,0 +1,8 @@ +# UpdateActionPossibleValueRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------------ | ------------------------ | ------------------------ | ------------------------ | +| `value` | *str* | :heavy_check_mark: | The value of the action. | \ No newline at end of file diff --git a/docs/models/updateactionpossiblevalueresponse.md b/docs/models/updateactionpossiblevalueresponse.md new file mode 100644 index 0000000..e19a551 --- /dev/null +++ b/docs/models/updateactionpossiblevalueresponse.md @@ -0,0 +1,8 @@ +# UpdateActionPossibleValueResponse + + +## Fields + +| Field | Type | Required | Description | +| ------------------------ | ------------------------ | ------------------------ | ------------------------ | +| `value` | *str* | :heavy_check_mark: | The value of the action. | \ No newline at end of file diff --git a/docs/models/updateactionqueuebehaviourrequest.md b/docs/models/updateactionqueuebehaviourrequest.md new file mode 100644 index 0000000..fb9fc4f --- /dev/null +++ b/docs/models/updateactionqueuebehaviourrequest.md @@ -0,0 +1,17 @@ +# UpdateActionQueueBehaviourRequest + +Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. + +## Example Usage + +```python +from moderation_api.models import UpdateActionQueueBehaviourRequest +value: UpdateActionQueueBehaviourRequest = "REMOVE" +``` + + +## Values + +- `"REMOVE"` +- `"ADD"` +- `"NO_CHANGE"` diff --git a/docs/models/updateactionqueuebehaviourresponse.md b/docs/models/updateactionqueuebehaviourresponse.md new file mode 100644 index 0000000..9eed080 --- /dev/null +++ b/docs/models/updateactionqueuebehaviourresponse.md @@ -0,0 +1,21 @@ +# UpdateActionQueueBehaviourResponse + +Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. + +## Example Usage + +```python +from moderation_api.models import UpdateActionQueueBehaviourResponse + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: UpdateActionQueueBehaviourResponse = "REMOVE" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"REMOVE"` +- `"ADD"` +- `"NO_CHANGE"` diff --git a/docs/models/updateactionrequest.md b/docs/models/updateactionrequest.md new file mode 100644 index 0000000..ffb7a86 --- /dev/null +++ b/docs/models/updateactionrequest.md @@ -0,0 +1,9 @@ +# UpdateActionRequest + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the action to update. | +| `body` | [models.UpdateActionRequestBody](../models/updateactionrequestbody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/updateactionrequestbody.md b/docs/models/updateactionrequestbody.md new file mode 100644 index 0000000..119d8fd --- /dev/null +++ b/docs/models/updateactionrequestbody.md @@ -0,0 +1,18 @@ +# UpdateActionRequestBody + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `key` | *OptionalNullable[str]* | :heavy_minus_sign: | User defined key of the action. | +| `name` | *Optional[str]* | :heavy_minus_sign: | The name of the action. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The description of the action. | +| `type` | [OptionalNullable[models.UpdateActionTypeRequest]](../models/updateactiontyperequest.md) | :heavy_minus_sign: | The type of the action. | +| `built_in` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the action is a built-in action or a custom one. | +| `queue_behaviour` | [Optional[models.UpdateActionQueueBehaviourRequest]](../models/updateactionqueuebehaviourrequest.md) | :heavy_minus_sign: | Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. | +| `filter_in_queue_ids` | List[*str*] | :heavy_minus_sign: | The IDs of the queues the action is available in. | +| `position` | [Optional[models.UpdateActionPositionRequest]](../models/updateactionpositionrequest.md) | :heavy_minus_sign: | Show the action in all queues, selected queues or no queues (to use via API only). | +| `possible_values` | List[[models.UpdateActionPossibleValueRequest](../models/updateactionpossiblevaluerequest.md)] | :heavy_minus_sign: | The possible values of the action. The user will be prompted to select one of these values when executing the action. | +| `value_required` | *Optional[bool]* | :heavy_minus_sign: | Whether the action requires a value to be executed. | +| `free_text` | *Optional[bool]* | :heavy_minus_sign: | Whether the action allows any text to be entered as a value or if it must be one of the possible values. | \ No newline at end of file diff --git a/docs/models/updateactionresponse.md b/docs/models/updateactionresponse.md new file mode 100644 index 0000000..9665301 --- /dev/null +++ b/docs/models/updateactionresponse.md @@ -0,0 +1,22 @@ +# UpdateActionResponse + +Action updated successfully + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the action. | +| `key` | *OptionalNullable[str]* | :heavy_minus_sign: | User defined key of the action. | +| `created_at` | *str* | :heavy_check_mark: | The date the action was created. | +| `name` | *str* | :heavy_check_mark: | The name of the action. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The description of the action. | +| `type` | [OptionalNullable[models.UpdateActionTypeResponse]](../models/updateactiontyperesponse.md) | :heavy_minus_sign: | The type of the action. | +| `built_in` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the action is a built-in action or a custom one. | +| `queue_behaviour` | [Optional[models.UpdateActionQueueBehaviourResponse]](../models/updateactionqueuebehaviourresponse.md) | :heavy_minus_sign: | Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. | +| `filter_in_queue_ids` | List[*str*] | :heavy_check_mark: | The IDs of the queues the action is available in. | +| `position` | [Optional[models.UpdateActionPositionResponse]](../models/updateactionpositionresponse.md) | :heavy_minus_sign: | Show the action in all queues, selected queues or no queues (to use via API only). | +| `possible_values` | List[[models.UpdateActionPossibleValueResponse](../models/updateactionpossiblevalueresponse.md)] | :heavy_check_mark: | The possible values of the action. The user will be prompted to select one of these values when executing the action. | +| `value_required` | *Optional[bool]* | :heavy_minus_sign: | Whether the action requires a value to be executed. | +| `free_text` | *Optional[bool]* | :heavy_minus_sign: | Whether the action allows any text to be entered as a value or if it must be one of the possible values. | \ No newline at end of file diff --git a/docs/models/updateactiontyperequest.md b/docs/models/updateactiontyperequest.md new file mode 100644 index 0000000..91750db --- /dev/null +++ b/docs/models/updateactiontyperequest.md @@ -0,0 +1,22 @@ +# UpdateActionTypeRequest + +## Example Usage + +```python +from moderation_api.models import UpdateActionTypeRequest +value: UpdateActionTypeRequest = "AUTHOR_BLOCK" +``` + + +## Values + +- `"AUTHOR_BLOCK"` +- `"AUTHOR_BLOCK_TEMP"` +- `"AUTHOR_UNBLOCK"` +- `"AUTHOR_DELETE"` +- `"AUTHOR_REPORT"` +- `"AUTHOR_WARN"` +- `"AUTHOR_CUSTOM"` +- `"ITEM_REJECT"` +- `"ITEM_ALLOW"` +- `"ITEM_CUSTOM"` diff --git a/docs/models/updateactiontyperesponse.md b/docs/models/updateactiontyperesponse.md new file mode 100644 index 0000000..d0e07bb --- /dev/null +++ b/docs/models/updateactiontyperesponse.md @@ -0,0 +1,26 @@ +# UpdateActionTypeResponse + +## Example Usage + +```python +from moderation_api.models import UpdateActionTypeResponse + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: UpdateActionTypeResponse = "AUTHOR_BLOCK" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"AUTHOR_BLOCK"` +- `"AUTHOR_BLOCK_TEMP"` +- `"AUTHOR_UNBLOCK"` +- `"AUTHOR_DELETE"` +- `"AUTHOR_REPORT"` +- `"AUTHOR_WARN"` +- `"AUTHOR_CUSTOM"` +- `"ITEM_REJECT"` +- `"ITEM_ALLOW"` +- `"ITEM_CUSTOM"` diff --git a/docs/models/updateauthormetadata.md b/docs/models/updateauthormetadata.md new file mode 100644 index 0000000..6732861 --- /dev/null +++ b/docs/models/updateauthormetadata.md @@ -0,0 +1,14 @@ +# UpdateAuthorMetadata + +Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process. + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------- | --------------------------------------------- | --------------------------------------------- | --------------------------------------------- | +| `email_verified` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the author's email is verified | +| `phone_verified` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the author's phone number is verified | +| `identity_verified` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the author's identity is verified | +| `is_paying_customer` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the author is a paying customer | +| `__pydantic_extra__` | Dict[str, *Any*] | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/updateauthorrequest.md b/docs/models/updateauthorrequest.md new file mode 100644 index 0000000..06fa54e --- /dev/null +++ b/docs/models/updateauthorrequest.md @@ -0,0 +1,9 @@ +# UpdateAuthorRequest + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | Either external ID or the ID assigned by moderation API. | +| `body` | [models.UpdateAuthorRequestBody](../models/updateauthorrequestbody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/updateauthorrequestbody.md b/docs/models/updateauthorrequestbody.md new file mode 100644 index 0000000..ee1269c --- /dev/null +++ b/docs/models/updateauthorrequestbody.md @@ -0,0 +1,16 @@ +# UpdateAuthorRequestBody + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| `profile_picture` | *OptionalNullable[str]* | :heavy_minus_sign: | URL of the author's profile picture | +| `external_link` | *OptionalNullable[str]* | :heavy_minus_sign: | URL of the author's external profile | +| `name` | *OptionalNullable[str]* | :heavy_minus_sign: | Author name or identifier | +| `email` | *OptionalNullable[str]* | :heavy_minus_sign: | Author email address | +| `company` | *OptionalNullable[str]* | :heavy_minus_sign: | The author's company or organization | +| `metadata` | [Optional[models.UpdateAuthorMetadata]](../models/updateauthormetadata.md) | :heavy_minus_sign: | Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process. | +| `first_seen` | *Optional[float]* | :heavy_minus_sign: | Timestamp when author first appeared | +| `last_seen` | *Optional[float]* | :heavy_minus_sign: | Timestamp of last activity | +| `manual_trust_level` | *OptionalNullable[float]* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/updatechannelcontenttype.md b/docs/models/updatechannelcontenttype.md new file mode 100644 index 0000000..d5ebc68 --- /dev/null +++ b/docs/models/updatechannelcontenttype.md @@ -0,0 +1,23 @@ +# UpdateChannelContentType + +The kind of content the channel moderates. + +## Example Usage + +```python +from moderation_api.models import UpdateChannelContentType +value: UpdateChannelContentType = "profile" +``` + + +## Values + +- `"profile"` +- `"message"` +- `"post"` +- `"comment"` +- `"event"` +- `"product"` +- `"review"` +- `"voice"` +- `"other"` diff --git a/docs/models/updatechannelflaggingmode.md b/docs/models/updatechannelflaggingmode.md new file mode 100644 index 0000000..4887d4b --- /dev/null +++ b/docs/models/updatechannelflaggingmode.md @@ -0,0 +1,16 @@ +# UpdateChannelFlaggingMode + +`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it. + +## Example Usage + +```python +from moderation_api.models import UpdateChannelFlaggingMode +value: UpdateChannelFlaggingMode = "DRY_RUN" +``` + + +## Values + +- `"DRY_RUN"` +- `"ACTIVE"` diff --git a/docs/models/updatechannelrequest.md b/docs/models/updatechannelrequest.md new file mode 100644 index 0000000..01970a4 --- /dev/null +++ b/docs/models/updatechannelrequest.md @@ -0,0 +1,9 @@ +# UpdateChannelRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | +| `id` | *str* | :heavy_check_mark: | The ID of the channel to update. | +| `body` | [models.UpdateChannelRequestBody](../models/updatechannelrequestbody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/updatechannelrequestbody.md b/docs/models/updatechannelrequestbody.md new file mode 100644 index 0000000..ac7751d --- /dev/null +++ b/docs/models/updatechannelrequestbody.md @@ -0,0 +1,23 @@ +# UpdateChannelRequestBody + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| `name` | *Optional[str]* | :heavy_minus_sign: | The channel's name. | +| `key` | *Optional[str]* | :heavy_minus_sign: | The key you pass as `channel` when you moderate content. Changing it breaks integrations that send the old key. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The channel's description. | +| `content_type` | [Optional[models.UpdateChannelContentType]](../models/updatechannelcontenttype.md) | :heavy_minus_sign: | The kind of content the channel moderates. | +| `flagging_mode` | [Optional[models.UpdateChannelFlaggingMode]](../models/updatechannelflaggingmode.md) | :heavy_minus_sign: | `ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it. | +| `context_enabled` | *Optional[bool]* | :heavy_minus_sign: | Whether earlier messages in the conversation are read when moderating a message. | +| `enable_image_moderation` | *Optional[bool]* | :heavy_minus_sign: | Whether images are moderated. | +| `enable_video_moderation` | *Optional[bool]* | :heavy_minus_sign: | Whether videos are moderated. | +| `enable_audio_moderation` | *Optional[bool]* | :heavy_minus_sign: | Whether audio is moderated. | +| `enable_voice_moderation` | *Optional[bool]* | :heavy_minus_sign: | Whether real-time voice streams are moderated. | +| `include_transcript_in_response` | *Optional[bool]* | :heavy_minus_sign: | Whether audio and video transcripts are returned in responses. | +| `transcription_quality` | [Optional[models.UpdateChannelTranscriptionQuality]](../models/updatechanneltranscriptionquality.md) | :heavy_minus_sign: | Trade-off between transcription speed and accuracy. | +| `unicode_spoof_detection_enabled` | *Optional[bool]* | :heavy_minus_sign: | Whether look-alike Unicode characters are normalized before moderation. | +| `frame_capture_interval` | *Optional[float]* | :heavy_minus_sign: | Seconds between frames captured from a video. | +| `max_frames_per_video` | *Optional[int]* | :heavy_minus_sign: | The most frames moderated per video. | +| `spread_frames` | *Optional[bool]* | :heavy_minus_sign: | Whether frames are spread evenly across the whole video. | \ No newline at end of file diff --git a/docs/models/updatechanneltranscriptionquality.md b/docs/models/updatechanneltranscriptionquality.md new file mode 100644 index 0000000..13a371a --- /dev/null +++ b/docs/models/updatechanneltranscriptionquality.md @@ -0,0 +1,17 @@ +# UpdateChannelTranscriptionQuality + +Trade-off between transcription speed and accuracy. + +## Example Usage + +```python +from moderation_api.models import UpdateChannelTranscriptionQuality +value: UpdateChannelTranscriptionQuality = "SPEED" +``` + + +## Values + +- `"SPEED"` +- `"BALANCED"` +- `"ACCURACY"` diff --git a/docs/models/updateprojectglobalflaggingmode.md b/docs/models/updateprojectglobalflaggingmode.md new file mode 100644 index 0000000..4297830 --- /dev/null +++ b/docs/models/updateprojectglobalflaggingmode.md @@ -0,0 +1,16 @@ +# UpdateProjectGlobalFlaggingMode + +`ACTIVE` flags content. `DRY_RUN` evaluates without flagging. + +## Example Usage + +```python +from moderation_api.models import UpdateProjectGlobalFlaggingMode +value: UpdateProjectGlobalFlaggingMode = "DRY_RUN" +``` + + +## Values + +- `"DRY_RUN"` +- `"ACTIVE"` diff --git a/docs/models/updateprojectrequest.md b/docs/models/updateprojectrequest.md new file mode 100644 index 0000000..99fce9c --- /dev/null +++ b/docs/models/updateprojectrequest.md @@ -0,0 +1,9 @@ +# UpdateProjectRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | +| `id` | *str* | :heavy_check_mark: | The ID of the project to update. | +| `body` | [models.UpdateProjectRequestBody](../models/updateprojectrequestbody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/updateprojectrequestbody.md b/docs/models/updateprojectrequestbody.md new file mode 100644 index 0000000..ae981da --- /dev/null +++ b/docs/models/updateprojectrequestbody.md @@ -0,0 +1,14 @@ +# UpdateProjectRequestBody + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| `name` | *Optional[str]* | :heavy_minus_sign: | The project's name. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The project's description. | +| `domain` | *OptionalNullable[str]* | :heavy_minus_sign: | The website or app the project moderates content for. Setting it on a project without context writes one. | +| `context` | *OptionalNullable[str]* | :heavy_minus_sign: | What the platform is and who uses it. | +| `global_flagging_mode` | [Optional[models.UpdateProjectGlobalFlaggingMode]](../models/updateprojectglobalflaggingmode.md) | :heavy_minus_sign: | `ACTIVE` flags content. `DRY_RUN` evaluates without flagging. | +| `enable_request_logging` | *Optional[bool]* | :heavy_minus_sign: | Whether moderated content is stored and shown in the dashboard. | +| `casebook_enabled` | *Optional[bool]* | :heavy_minus_sign: | Whether review queue decisions are recorded in the casebook. | \ No newline at end of file diff --git a/docs/models/updatewebhookeventtyperequest.md b/docs/models/updatewebhookeventtyperequest.md new file mode 100644 index 0000000..912f96a --- /dev/null +++ b/docs/models/updatewebhookeventtyperequest.md @@ -0,0 +1,23 @@ +# UpdateWebhookEventTypeRequest + +## Example Usage + +```python +from moderation_api.models import UpdateWebhookEventTypeRequest +value: UpdateWebhookEventTypeRequest = "QUEUE_ITEM_NEW" +``` + + +## Values + +- `"QUEUE_ITEM_NEW"` +- `"QUEUE_ITEM_COMPLETED"` +- `"QUEUE_ITEM_ACTION"` +- `"QUEUE_ITEM_REJECTED"` +- `"QUEUE_ITEM_ALLOWED"` +- `"AUTHOR_BLOCKED"` +- `"AUTHOR_UNBLOCKED"` +- `"AUTHOR_SUSPENDED"` +- `"AUTHOR_UPDATED"` +- `"AUTHOR_TRUST_LEVEL_CHANGED"` +- `"AUTHOR_ACTION"` diff --git a/docs/models/updatewebhookeventtyperesponse.md b/docs/models/updatewebhookeventtyperesponse.md new file mode 100644 index 0000000..99226b2 --- /dev/null +++ b/docs/models/updatewebhookeventtyperesponse.md @@ -0,0 +1,27 @@ +# UpdateWebhookEventTypeResponse + +## Example Usage + +```python +from moderation_api.models import UpdateWebhookEventTypeResponse + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: UpdateWebhookEventTypeResponse = "QUEUE_ITEM_NEW" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"QUEUE_ITEM_NEW"` +- `"QUEUE_ITEM_COMPLETED"` +- `"QUEUE_ITEM_ACTION"` +- `"QUEUE_ITEM_REJECTED"` +- `"QUEUE_ITEM_ALLOWED"` +- `"AUTHOR_BLOCKED"` +- `"AUTHOR_UNBLOCKED"` +- `"AUTHOR_SUSPENDED"` +- `"AUTHOR_UPDATED"` +- `"AUTHOR_TRUST_LEVEL_CHANGED"` +- `"AUTHOR_ACTION"` diff --git a/docs/models/updatewebhookpayloadversion.md b/docs/models/updatewebhookpayloadversion.md new file mode 100644 index 0000000..f307e26 --- /dev/null +++ b/docs/models/updatewebhookpayloadversion.md @@ -0,0 +1,20 @@ +# UpdateWebhookPayloadVersion + +Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API. + +## Example Usage + +```python +from moderation_api.models import UpdateWebhookPayloadVersion + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: UpdateWebhookPayloadVersion = "V1" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"V1"` +- `"V2"` diff --git a/docs/models/updatewebhookrequest.md b/docs/models/updatewebhookrequest.md new file mode 100644 index 0000000..6d184d2 --- /dev/null +++ b/docs/models/updatewebhookrequest.md @@ -0,0 +1,9 @@ +# UpdateWebhookRequest + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | +| `id` | *str* | :heavy_check_mark: | The ID of the webhook to update. | +| `body` | [models.UpdateWebhookRequestBody](../models/updatewebhookrequestbody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/updatewebhookrequestbody.md b/docs/models/updatewebhookrequestbody.md new file mode 100644 index 0000000..e7ec702 --- /dev/null +++ b/docs/models/updatewebhookrequestbody.md @@ -0,0 +1,11 @@ +# UpdateWebhookRequestBody + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | +| `name` | *Optional[str]* | :heavy_minus_sign: | The webhook's name, used to identify it in the dashboard | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The webhook's description | +| `url` | *Optional[str]* | :heavy_minus_sign: | The webhook's URL. We'll call this URL when an event occurs. | +| `event_types` | List[[models.UpdateWebhookEventTypeRequest](../models/updatewebhookeventtyperequest.md)] | :heavy_minus_sign: | Event types this webhook subscribes to. One webhook URL receives all events you list here. | \ No newline at end of file diff --git a/docs/models/updatewebhookresponse.md b/docs/models/updatewebhookresponse.md new file mode 100644 index 0000000..d0ab547 --- /dev/null +++ b/docs/models/updatewebhookresponse.md @@ -0,0 +1,16 @@ +# UpdateWebhookResponse + +Webhook updated successfully + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the webhook. | +| `name` | *str* | :heavy_check_mark: | The webhook's name. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The webhook's description. | +| `url` | *str* | :heavy_check_mark: | The URL we call when a subscribed event occurs. | +| `event_types` | List[[models.UpdateWebhookEventTypeResponse](../models/updatewebhookeventtyperesponse.md)] | :heavy_check_mark: | Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead. | +| `payload_version` | [models.UpdateWebhookPayloadVersion](../models/updatewebhookpayloadversion.md) | :heavy_check_mark: | Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API. | +| `created_at` | *str* | :heavy_check_mark: | The date the webhook was created. | \ No newline at end of file diff --git a/docs/models/updatewordlistrequest.md b/docs/models/updatewordlistrequest.md new file mode 100644 index 0000000..f27528d --- /dev/null +++ b/docs/models/updatewordlistrequest.md @@ -0,0 +1,9 @@ +# UpdateWordlistRequest + + +## Fields + +| Field | Type | Required | Description | +| -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | ID of the wordlist to update | +| `body` | [models.UpdateWordlistRequestBody](../models/updatewordlistrequestbody.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/updatewordlistrequestbody.md b/docs/models/updatewordlistrequestbody.md new file mode 100644 index 0000000..42bb174 --- /dev/null +++ b/docs/models/updatewordlistrequestbody.md @@ -0,0 +1,12 @@ +# UpdateWordlistRequestBody + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | +| `name` | *Optional[str]* | :heavy_minus_sign: | New name for the wordlist | +| `key` | *Optional[str]* | :heavy_minus_sign: | New key for the wordlist | +| `description` | *Optional[str]* | :heavy_minus_sign: | New description for the wordlist | +| `words` | List[*str*] | :heavy_minus_sign: | New words for the wordlist. Replace the existing words with these new ones. Duplicate words will be ignored. | +| `strict` | *Optional[bool]* | :heavy_minus_sign: | Deprecated. Now using threshold in project settings. | \ No newline at end of file diff --git a/docs/models/updatewordlistresponse.md b/docs/models/updatewordlistresponse.md new file mode 100644 index 0000000..20a1761 --- /dev/null +++ b/docs/models/updatewordlistresponse.md @@ -0,0 +1,16 @@ +# UpdateWordlistResponse + +Wordlist updated successfully + + +## Fields + +| Field | Type | Required | Description | +| ----------------------------- | ----------------------------- | ----------------------------- | ----------------------------- | +| `id` | *str* | :heavy_check_mark: | ID of the wordlist | +| `name` | *Nullable[str]* | :heavy_check_mark: | N/A | +| `words` | List[*str*] | :heavy_check_mark: | Words in the wordlist | +| `created_at` | *str* | :heavy_check_mark: | Creation date of the wordlist | +| `organization_id` | *str* | :heavy_check_mark: | ID of the organization | +| `user_id` | *Nullable[str]* | :heavy_check_mark: | N/A | +| `strict` | *bool* | :heavy_check_mark: | Strict mode | \ No newline at end of file diff --git a/docs/models/urlmasking.md b/docs/models/urlmasking.md new file mode 100644 index 0000000..102ec1b --- /dev/null +++ b/docs/models/urlmasking.md @@ -0,0 +1,9 @@ +# URLMasking + + +## Fields + +| Field | Type | Required | Description | +| ----------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------- | +| `id` | *Literal["url"]* | :heavy_check_mark: | N/A | +| `entities` | Dict[str, [models.Entities2](../models/entities2.md)] | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/urlrisk.md b/docs/models/urlrisk.md new file mode 100644 index 0000000..cfefbeb --- /dev/null +++ b/docs/models/urlrisk.md @@ -0,0 +1,13 @@ +# URLRisk + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["url_risk"]* | :heavy_check_mark: | N/A | +| `allowlist_wordlist_ids` | List[*str*] | :heavy_minus_sign: | IDs of wordlists whose entries are treated as allowed URL domains. Matches short-circuit the risk model and are never flagged. | +| `blocklist_wordlist_ids` | List[*str*] | :heavy_minus_sign: | IDs of wordlists whose entries are treated as blocked URL domains. Matches short-circuit the risk model and are always flagged. Blocklists take precedence over allowlists. | +| `flag_link_shorteners` | *Optional[bool]* | :heavy_minus_sign: | When true, any URL detected as a free link shortener (bit.ly, t.co, tinyurl, etc.) is always flagged regardless of risk score. Allowlist matches still win. | \ No newline at end of file diff --git a/docs/models/utils/retryconfig.md b/docs/models/utils/retryconfig.md new file mode 100644 index 0000000..69dd549 --- /dev/null +++ b/docs/models/utils/retryconfig.md @@ -0,0 +1,24 @@ +# RetryConfig + +Allows customizing the default retry configuration. Only usable with methods that mention they support retries. + +## Fields + +| Name | Type | Description | Example | +| ------------------------- | ----------------------------------- | --------------------------------------- | --------- | +| `strategy` | `*str*` | The retry strategy to use. | `backoff` | +| `backoff` | [BackoffStrategy](#backoffstrategy) | Configuration for the backoff strategy. | | +| `retry_connection_errors` | `*bool*` | Whether to retry on connection errors. | `true` | + +## BackoffStrategy + +The backoff strategy allows retrying a request with an exponential backoff between each retry. + +### Fields + +| Name | Type | Description | Example | +| ------------------ | --------- | ----------------------------------------- | -------- | +| `initial_interval` | `*int*` | The initial interval in milliseconds. | `500` | +| `max_interval` | `*int*` | The maximum interval in milliseconds. | `60000` | +| `exponent` | `*float*` | The exponent to use for the backoff. | `1.5` | +| `max_elapsed_time` | `*int*` | The maximum elapsed time in milliseconds. | `300000` | \ No newline at end of file diff --git a/docs/models/value.md b/docs/models/value.md new file mode 100644 index 0000000..94fc3f3 --- /dev/null +++ b/docs/models/value.md @@ -0,0 +1,19 @@ +# Value + +## Example Usage + +```python +from moderation_api.models import Value + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: Value = "positive" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"positive"` +- `"neutral"` +- `"negative"` diff --git a/docs/models/verdict.md b/docs/models/verdict.md new file mode 100644 index 0000000..80f1a05 --- /dev/null +++ b/docs/models/verdict.md @@ -0,0 +1,20 @@ +# Verdict + +The ruling your past decisions point to for this content. + +## Example Usage + +```python +from moderation_api.models import Verdict + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: Verdict = "allow" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"allow"` +- `"reject"` diff --git a/docs/models/videorequest.md b/docs/models/videorequest.md new file mode 100644 index 0000000..53a55fa --- /dev/null +++ b/docs/models/videorequest.md @@ -0,0 +1,11 @@ +# VideoRequest + +Video + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------- | --------------------------------- | --------------------------------- | --------------------------------- | +| `type` | *Literal["video"]* | :heavy_check_mark: | N/A | +| `url` | *str* | :heavy_check_mark: | A public URL of the video content | \ No newline at end of file diff --git a/docs/models/videoresponse.md b/docs/models/videoresponse.md new file mode 100644 index 0000000..fcd1b01 --- /dev/null +++ b/docs/models/videoresponse.md @@ -0,0 +1,11 @@ +# VideoResponse + +Video + + +## Fields + +| Field | Type | Required | Description | +| --------------------------------- | --------------------------------- | --------------------------------- | --------------------------------- | +| `type` | *Literal["video"]* | :heavy_check_mark: | N/A | +| `url` | *str* | :heavy_check_mark: | A public URL of the video content | \ No newline at end of file diff --git a/docs/models/violence.md b/docs/models/violence.md new file mode 100644 index 0000000..6aae20d --- /dev/null +++ b/docs/models/violence.md @@ -0,0 +1,10 @@ +# Violence + + +## Fields + +| Field | Type | Required | Description | +| --------------------- | --------------------- | --------------------- | --------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["violence"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/weapons.md b/docs/models/weapons.md new file mode 100644 index 0000000..bf34342 --- /dev/null +++ b/docs/models/weapons.md @@ -0,0 +1,10 @@ +# Weapons + + +## Fields + +| Field | Type | Required | Description | +| -------------------- | -------------------- | -------------------- | -------------------- | +| `flag` | *bool* | :heavy_check_mark: | N/A | +| `threshold` | *Optional[float]* | :heavy_minus_sign: | N/A | +| `id` | *Literal["weapons"]* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/models/withinunit.md b/docs/models/withinunit.md new file mode 100644 index 0000000..67ba327 --- /dev/null +++ b/docs/models/withinunit.md @@ -0,0 +1,22 @@ +# WithinUnit + +## Example Usage + +```python +from moderation_api.models import WithinUnit + +# Open enum: unrecognized values are captured as UnrecognizedStr +value: WithinUnit = "MINUTES" +``` + + +## Values + +This is an open enum. Unrecognized values will not fail type checks. + +- `"MINUTES"` +- `"HOURS"` +- `"DAYS"` +- `"WEEKS"` +- `"MONTHS"` +- `"YEARS"` diff --git a/docs/sdks/account/README.md b/docs/sdks/account/README.md new file mode 100644 index 0000000..aa86408 --- /dev/null +++ b/docs/sdks/account/README.md @@ -0,0 +1,48 @@ +# Account + +## Overview + +### Available Operations + +* [retrieve](#retrieve) - Get account details + +## retrieve + +Get account details + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.account.retrieve() + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.GetAccountResponse](../../models/getaccountresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.AuthenticationError | 401 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | \ No newline at end of file diff --git a/docs/sdks/actions/README.md b/docs/sdks/actions/README.md new file mode 100644 index 0000000..b79e070 --- /dev/null +++ b/docs/sdks/actions/README.md @@ -0,0 +1,311 @@ +# Actions + +## Overview + +### Available Operations + +* [list](#list) - List moderation actions +* [create](#create) - Create an action +* [retrieve](#retrieve) - Get an action +* [update](#update) - Update an action +* [delete](#delete) - Delete an action +* [execute](#execute) - Execute moderation action + +## list + +List the moderation actions of the project. The list is paginated. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.actions.list(page=1, page_size=20) + + while res is not None: + # Handle items + + res = res.next() + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `queue_id` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `page` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `page_size` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.ListActionsResponse](../../models/listactionsresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.InternalServerError | 500 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## create + +Create an action. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.actions.create(name="Suspend for 24 hours", key="suspend-24h", type_="AUTHOR_BLOCK_TEMP", built_in=False, queue_behaviour="REMOVE", position="ALL_QUEUES", value_required=False, free_text=False) + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `name` | *str* | :heavy_check_mark: | The name of the action. | +| `key` | *OptionalNullable[str]* | :heavy_minus_sign: | User defined key of the action. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The description of the action. | +| `type` | [OptionalNullable[models.CreateActionTypeRequest]](../../models/createactiontyperequest.md) | :heavy_minus_sign: | The type of the action. | +| `built_in` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the action is a built-in action or a custom one. | +| `queue_behaviour` | [Optional[models.CreateActionQueueBehaviourRequest]](../../models/createactionqueuebehaviourrequest.md) | :heavy_minus_sign: | Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. | +| `filter_in_queue_ids` | List[*str*] | :heavy_minus_sign: | The IDs of the queues the action is available in. | +| `position` | [Optional[models.CreateActionPositionRequest]](../../models/createactionpositionrequest.md) | :heavy_minus_sign: | Show the action in all queues, selected queues or no queues (to use via API only). | +| `possible_values` | List[[models.CreateActionPossibleValueRequest](../../models/createactionpossiblevaluerequest.md)] | :heavy_minus_sign: | The possible values of the action. The user will be prompted to select one of these values when executing the action. | +| `value_required` | *Optional[bool]* | :heavy_minus_sign: | Whether the action requires a value to be executed. | +| `free_text` | *Optional[bool]* | :heavy_minus_sign: | Whether the action allows any text to be entered as a value or if it must be one of the possible values. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.CreateActionResponse](../../models/createactionresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.ConflictError | 409 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## retrieve + +Get an action by ID. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.actions.retrieve(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the action to get. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.GetActionResponse](../../models/getactionresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## update + +Update an action. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.actions.update(id="", built_in=False, queue_behaviour="NO_CHANGE", position="ALL_QUEUES", value_required=False, free_text=False) + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the action to update. | +| `key` | *OptionalNullable[str]* | :heavy_minus_sign: | User defined key of the action. | +| `name` | *Optional[str]* | :heavy_minus_sign: | The name of the action. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The description of the action. | +| `type` | [OptionalNullable[models.UpdateActionTypeRequest]](../../models/updateactiontyperequest.md) | :heavy_minus_sign: | The type of the action. | +| `built_in` | *OptionalNullable[bool]* | :heavy_minus_sign: | Whether the action is a built-in action or a custom one. | +| `queue_behaviour` | [Optional[models.UpdateActionQueueBehaviourRequest]](../../models/updateactionqueuebehaviourrequest.md) | :heavy_minus_sign: | Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. | +| `filter_in_queue_ids` | List[*str*] | :heavy_minus_sign: | The IDs of the queues the action is available in. | +| `position` | [Optional[models.UpdateActionPositionRequest]](../../models/updateactionpositionrequest.md) | :heavy_minus_sign: | Show the action in all queues, selected queues or no queues (to use via API only). | +| `possible_values` | List[[models.UpdateActionPossibleValueRequest](../../models/updateactionpossiblevaluerequest.md)] | :heavy_minus_sign: | The possible values of the action. The user will be prompted to select one of these values when executing the action. | +| `value_required` | *Optional[bool]* | :heavy_minus_sign: | Whether the action requires a value to be executed. | +| `free_text` | *Optional[bool]* | :heavy_minus_sign: | Whether the action allows any text to be entered as a value or if it must be one of the possible values. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.UpdateActionResponse](../../models/updateactionresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.ConflictError | 409 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## delete + +Delete an action and all of its webhooks. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.actions.delete(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the action to delete. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.DeleteActionResponse](../../models/deleteactionresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## execute + +Execute a moderation action on one or more content items. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.actions.execute(action_key="suspend-24h", author_ids=[ + "user-1092", + ]) + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| --------------------------------------------------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| `action_key` | *str* | :heavy_check_mark: | ID or key of the action to execute | +| `content_ids` | List[*str*] | :heavy_minus_sign: | IDs of the content items to apply the action to. Provide this or authorIds. | +| `author_ids` | List[*str*] | :heavy_minus_sign: | IDs of the authors to apply the action to. Provide this or contentIds. | +| `value` | *Optional[str]* | :heavy_minus_sign: | Optional value to provide with the action | +| `queue_id` | *Optional[str]* | :heavy_minus_sign: | Optional queue ID if the action is queue-specific | +| `duration` | *Optional[float]* | :heavy_minus_sign: | Optional duration in milliseconds for actions with timeouts | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.ExecuteActionResponse](../../models/executeactionresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | \ No newline at end of file diff --git a/docs/sdks/authors/README.md b/docs/sdks/authors/README.md new file mode 100644 index 0000000..6b87903 --- /dev/null +++ b/docs/sdks/authors/README.md @@ -0,0 +1,265 @@ +# Authors + +## Overview + +### Available Operations + +* [list](#list) - List authors +* [create](#create) - Create a new author +* [retrieve](#retrieve) - Get author details +* [update](#update) - Update author details +* [delete](#delete) - Delete an author + +## list + +List authors with their activity metrics and reputation. The list is paginated. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.authors.list(request={}) + + while res is not None: + # Handle items + + res = res.next() + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `request` | [models.ListAuthorsRequest](../../models/listauthorsrequest.md) | :heavy_check_mark: | The request object to use for the request. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.ListAuthorsResponse](../../models/listauthorsresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.InternalServerError | 500 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## create + +Create a new author. Typically not needed as authors are created automatically when content is moderated. + +### Example Usage + + +```python +from moderation_api import ModerationAPI, models +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.authors.create(external_id="user-1092", name="Jamie Rivera", email="jamie@example.com", metadata=models.CreateAuthorMetadata( + email_verified=True, + is_paying_customer=False, + )) + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| `external_id` | *str* | :heavy_check_mark: | External ID of the user, typically the ID of the author in your database. | +| `profile_picture` | *OptionalNullable[str]* | :heavy_minus_sign: | URL of the author's profile picture | +| `external_link` | *OptionalNullable[str]* | :heavy_minus_sign: | URL of the author's external profile | +| `name` | *OptionalNullable[str]* | :heavy_minus_sign: | Author name or identifier | +| `email` | *OptionalNullable[str]* | :heavy_minus_sign: | Author email address | +| `company` | *OptionalNullable[str]* | :heavy_minus_sign: | The author's company or organization | +| `metadata` | [Optional[models.CreateAuthorMetadata]](../../models/createauthormetadata.md) | :heavy_minus_sign: | Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process. | +| `first_seen` | *Optional[float]* | :heavy_minus_sign: | Timestamp when author first appeared | +| `last_seen` | *Optional[float]* | :heavy_minus_sign: | Timestamp of last activity | +| `manual_trust_level` | *OptionalNullable[float]* | :heavy_minus_sign: | N/A | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.PublicAuthor](../../models/publicauthor.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.InternalServerError | 500 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## retrieve + +Get detailed information about a specific author including historical data and analysis + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.authors.retrieve(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | Either external ID or the ID assigned by moderation API. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.PublicAuthor](../../models/publicauthor.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.InternalServerError | 500 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## update + +Update the details of a specific author + +### Example Usage + + +```python +from moderation_api import ModerationAPI, models +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.authors.update(id="", metadata=models.UpdateAuthorMetadata( + identity_verified=True, + ), manual_trust_level=2.0) + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | Either external ID or the ID assigned by moderation API. | +| `profile_picture` | *OptionalNullable[str]* | :heavy_minus_sign: | URL of the author's profile picture | +| `external_link` | *OptionalNullable[str]* | :heavy_minus_sign: | URL of the author's external profile | +| `name` | *OptionalNullable[str]* | :heavy_minus_sign: | Author name or identifier | +| `email` | *OptionalNullable[str]* | :heavy_minus_sign: | Author email address | +| `company` | *OptionalNullable[str]* | :heavy_minus_sign: | The author's company or organization | +| `metadata` | [Optional[models.UpdateAuthorMetadata]](../../models/updateauthormetadata.md) | :heavy_minus_sign: | Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process. | +| `first_seen` | *Optional[float]* | :heavy_minus_sign: | Timestamp when author first appeared | +| `last_seen` | *Optional[float]* | :heavy_minus_sign: | Timestamp of last activity | +| `manual_trust_level` | *OptionalNullable[float]* | :heavy_minus_sign: | N/A | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.PublicAuthor](../../models/publicauthor.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.InternalServerError | 500 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## delete + +Delete a specific author. This resets the author: status, blocks, trust level, metrics and action history are removed. The author is created again with a clean record the next time content is moderated for the same ID. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.authors.delete(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | Either external ID or the ID assigned by moderation API. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.DeleteAuthorResponse](../../models/deleteauthorresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.InternalServerError | 500 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | \ No newline at end of file diff --git a/docs/sdks/channels/README.md b/docs/sdks/channels/README.md new file mode 100644 index 0000000..0288190 --- /dev/null +++ b/docs/sdks/channels/README.md @@ -0,0 +1,306 @@ +# Channels + +## Overview + +### Available Operations + +* [list](#list) - List channels +* [create](#create) - Create a channel +* [retrieve](#retrieve) - Get a channel +* [update](#update) - Update a channel +* [delete](#delete) - Delete a channel +* [duplicate](#duplicate) - Duplicate a channel + +## list + +List the channels in a project. The list is paginated. Needs the `channels:read` permission. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.channels.list(project_id="", page=1, page_size=20) + + while res is not None: + # Handle items + + res = res.next() + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `project_id` | *str* | :heavy_check_mark: | The ID of the project. | +| `page` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `page_size` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.ListChannelsResponse](../../models/listchannelsresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## create + +Create a channel in a project, with the default rules. The project's first channel becomes its default. Needs the `channels:write` permission. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.channels.create(project_id="", name="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| `project_id` | *str* | :heavy_check_mark: | The ID of the project to add the channel to. | +| `name` | *str* | :heavy_check_mark: | The channel's name. | +| `key` | *Optional[str]* | :heavy_minus_sign: | The key you pass as `channel` when you moderate content. Defaults to the name in lowercase with dashes. Must be unique in the project. | +| `description` | *Optional[str]* | :heavy_minus_sign: | The channel's description. | +| `content_type` | [Optional[models.CreateChannelContentType]](../../models/createchannelcontenttype.md) | :heavy_minus_sign: | The kind of content the channel moderates. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.Channel](../../models/channel.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## retrieve + +Get a channel by ID. Needs the `channels:read` permission. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.channels.retrieve(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the channel. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.Channel](../../models/channel.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## update + +Update a channel. Fields you leave out keep their value. Needs the `channels:write` permission. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.channels.update(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the channel to update. | +| `name` | *Optional[str]* | :heavy_minus_sign: | The channel's name. | +| `key` | *Optional[str]* | :heavy_minus_sign: | The key you pass as `channel` when you moderate content. Changing it breaks integrations that send the old key. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The channel's description. | +| `content_type` | [Optional[models.UpdateChannelContentType]](../../models/updatechannelcontenttype.md) | :heavy_minus_sign: | The kind of content the channel moderates. | +| `flagging_mode` | [Optional[models.UpdateChannelFlaggingMode]](../../models/updatechannelflaggingmode.md) | :heavy_minus_sign: | `ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it. | +| `context_enabled` | *Optional[bool]* | :heavy_minus_sign: | Whether earlier messages in the conversation are read when moderating a message. | +| `enable_image_moderation` | *Optional[bool]* | :heavy_minus_sign: | Whether images are moderated. | +| `enable_video_moderation` | *Optional[bool]* | :heavy_minus_sign: | Whether videos are moderated. | +| `enable_audio_moderation` | *Optional[bool]* | :heavy_minus_sign: | Whether audio is moderated. | +| `enable_voice_moderation` | *Optional[bool]* | :heavy_minus_sign: | Whether real-time voice streams are moderated. | +| `include_transcript_in_response` | *Optional[bool]* | :heavy_minus_sign: | Whether audio and video transcripts are returned in responses. | +| `transcription_quality` | [Optional[models.UpdateChannelTranscriptionQuality]](../../models/updatechanneltranscriptionquality.md) | :heavy_minus_sign: | Trade-off between transcription speed and accuracy. | +| `unicode_spoof_detection_enabled` | *Optional[bool]* | :heavy_minus_sign: | Whether look-alike Unicode characters are normalized before moderation. | +| `frame_capture_interval` | *Optional[float]* | :heavy_minus_sign: | Seconds between frames captured from a video. | +| `max_frames_per_video` | *Optional[int]* | :heavy_minus_sign: | The most frames moderated per video. | +| `spread_frames` | *Optional[bool]* | :heavy_minus_sign: | Whether frames are spread evenly across the whole video. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.Channel](../../models/channel.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## delete + +Delete a channel. Its key is freed for a new channel. When it was the default, the oldest remaining channel becomes the default. A channel that still moderates a Slack workspace must be disconnected first. Needs the `channels:write` permission. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.channels.delete(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the channel. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.DeleteChannelResponse](../../models/deletechannelresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## duplicate + +Copy a channel in its project, with every setting, policy, rule and wordlist. Integrations (Discord, Slack, WordPress) are not copied. Needs the `channels:write` permission. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.channels.duplicate(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the channel to copy. | +| `name` | *Optional[str]* | :heavy_minus_sign: | Name of the copy. Defaults to the original name with "(copy)". | +| `key` | *Optional[str]* | :heavy_minus_sign: | Key of the copy. Defaults to the original key with a timestamp. Must be unique in the project. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.Channel](../../models/channel.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | \ No newline at end of file diff --git a/docs/sdks/contentsdk/README.md b/docs/sdks/contentsdk/README.md new file mode 100644 index 0000000..3526843 --- /dev/null +++ b/docs/sdks/contentsdk/README.md @@ -0,0 +1,65 @@ +# Content + +## Overview + +### Available Operations + +* [submit](#submit) + +## submit + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.content.submit(content={ + "type": "text", + "text": "Hey, check out my profile for a deal!", + }, content_id="message-48213", author_id="user-1092", conversation_id="room-general", metadata={ + "platform": "ios", + }) + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `content` | [models.ContentRequest](../../models/contentrequest.md) | :heavy_check_mark: | The content sent for moderation | +| `timestamp` | *Optional[float]* | :heavy_minus_sign: | Unix timestamp (in milliseconds) of when the content was created. Use if content is not submitted in real-time. | +| `channel` | *Optional[str]* | :heavy_minus_sign: | Provide a channel ID or key. Will use the project's default channel if not provided. | +| `content_id` | *Optional[str]* | :heavy_minus_sign: | The unique ID of the content in your database. | +| `meta_type` | [Optional[models.SubmitContentMetaType]](../../models/submitcontentmetatype.md) | :heavy_minus_sign: | The meta type of content being moderated | +| `author_id` | *Optional[str]* | :heavy_minus_sign: | The author of the content. | +| `conversation_id` | *Optional[str]* | :heavy_minus_sign: | For example the ID of a chat room or a post | +| `metadata` | Dict[str, *Any*] | :heavy_minus_sign: | Any metadata you want to store with the content | +| `client_action` | [Optional[models.SubmitContentClientAction]](../../models/submitcontentclientaction.md) | :heavy_minus_sign: | A recommendation from your own client-side flagging (e.g. a banned-IP list or a third-party tool). Feeds the rules engine and can escalate or override the recommended action. Does not change whether our analysis flagged the content. | +| `do_not_store` | *Optional[bool]* | :heavy_minus_sign: | Do not store the content. The content won't enter the review queue | +| `policies` | List[[models.PolicyRequest](../../models/policyrequest.md)] | :heavy_minus_sign: | (Enterprise) override the channel policies for this moderation request only. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.SubmitContentResponse](../../models/submitcontentresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.InternalServerError | 500 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | \ No newline at end of file diff --git a/docs/sdks/items/README.md b/docs/sdks/items/README.md new file mode 100644 index 0000000..b32d8e2 --- /dev/null +++ b/docs/sdks/items/README.md @@ -0,0 +1,162 @@ +# Queue.Items + +## Overview + +### Available Operations + +* [list](#list) - Get queue items +* [resolve](#resolve) - Resolve a queue item +* [unresolve](#unresolve) - Unresolve a queue item + +## list + +Get paginated list of items in a moderation queue with filtering options + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.queue.items.list(id="", page=1, page_size=20, sort_direction="desc") + + while res is not None: + # Handle items + + res = res.next() + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The queue ID | +| `page` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `page_size` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `sort_field` | [Optional[models.SortField]](../../models/sortfield.md) | :heavy_minus_sign: | N/A | +| `sort_direction` | [Optional[models.ListQueueItemsSortDirection]](../../models/listqueueitemssortdirection.md) | :heavy_minus_sign: | Sort direction | +| `conversation_ids` | List[*str*] | :heavy_minus_sign: | N/A | +| `labels` | List[*str*] | :heavy_minus_sign: | N/A | +| `after_date` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `before_date` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `include_resolved` | *Optional[bool]* | :heavy_minus_sign: | N/A | +| `author_id` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `filtered_action_ids` | List[*str*] | :heavy_minus_sign: | N/A | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.ListQueueItemsResponse](../../models/listqueueitemsresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.InternalServerError | 500 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## resolve + +Mark a queue item as resolved with a specific moderation action + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.queue.items.resolve(id="", item_id="", comment="Reviewed: no violation.") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The queue ID | +| `item_id` | *str* | :heavy_check_mark: | The item ID to resolve | +| `comment` | *Optional[str]* | :heavy_minus_sign: | Optional comment | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.ResolveQueueItemResponse](../../models/resolvequeueitemresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.InternalServerError | 500 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## unresolve + +Mark a previously resolved queue item as unresolved/pending + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.queue.items.unresolve(id="", item_id="", comment="Reopened after an appeal.") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The queue ID | +| `item_id` | *str* | :heavy_check_mark: | The item ID to unresolve | +| `comment` | *Optional[str]* | :heavy_minus_sign: | Optional reason for unresolving the item | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.UnresolveQueueItemResponse](../../models/unresolvequeueitemresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.InternalServerError | 500 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | \ No newline at end of file diff --git a/docs/sdks/projects/README.md b/docs/sdks/projects/README.md new file mode 100644 index 0000000..76e771a --- /dev/null +++ b/docs/sdks/projects/README.md @@ -0,0 +1,292 @@ +# Projects + +## Overview + +### Available Operations + +* [list](#list) - List projects +* [create](#create) - Create a project +* [retrieve](#retrieve) - Get a project +* [update](#update) - Update a project +* [delete](#delete) - Delete a project +* [duplicate](#duplicate) - Duplicate a project + +## list + +List the projects in the organization. The list is paginated. Needs the `projects:read` permission. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.projects.list(page=1, page_size=20) + + while res is not None: + # Handle items + + res = res.next() + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `page` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `page_size` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.ListProjectsResponse](../../models/listprojectsresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## create + +Create a project with one channel, a review queue inbox and a secret API key. Give a domain or context and the project suggests policies for the platform. Needs the `projects:write` permission and a key that reaches all projects. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.projects.create(name="", used_by_minors=False) + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| `name` | *str* | :heavy_check_mark: | The project's name. | +| `domain` | *Optional[str]* | :heavy_minus_sign: | The website or app the project moderates content for. Used to write the context when none is given. | +| `context` | *Optional[str]* | :heavy_minus_sign: | What the platform is and who uses it. | +| `brief` | *Optional[str]* | :heavy_minus_sign: | What you moderate, the problems you see and your guidelines. | +| `used_by_minors` | *Optional[bool]* | :heavy_minus_sign: | Whether children use the platform. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.ProjectWithKey](../../models/projectwithkey.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## retrieve + +Get a project by ID. Needs the `projects:read` permission. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.projects.retrieve(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the project. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.Project](../../models/project.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## update + +Update a project. Fields you leave out keep their value. Needs the `projects:write` permission. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.projects.update(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the project to update. | +| `name` | *Optional[str]* | :heavy_minus_sign: | The project's name. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The project's description. | +| `domain` | *OptionalNullable[str]* | :heavy_minus_sign: | The website or app the project moderates content for. Setting it on a project without context writes one. | +| `context` | *OptionalNullable[str]* | :heavy_minus_sign: | What the platform is and who uses it. | +| `global_flagging_mode` | [Optional[models.UpdateProjectGlobalFlaggingMode]](../../models/updateprojectglobalflaggingmode.md) | :heavy_minus_sign: | `ACTIVE` flags content. `DRY_RUN` evaluates without flagging. | +| `enable_request_logging` | *Optional[bool]* | :heavy_minus_sign: | Whether moderated content is stored and shown in the dashboard. | +| `casebook_enabled` | *Optional[bool]* | :heavy_minus_sign: | Whether review queue decisions are recorded in the casebook. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.Project](../../models/project.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## delete + +Delete a project with everything in it: channels, queues, API keys and stored content. This can't be undone. The project's API keys stop working at once. Needs the `projects:write` permission. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.projects.delete(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the project. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.DeleteProjectResponse](../../models/deleteprojectresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## duplicate + +Copy a project's configuration into a new project: its settings, every channel with its policies, rules and wordlists, its moderation actions and its review queues. The copy gets its own secret API key. Webhooks, integrations, API keys and content are not copied. Needs the `projects:write` permission and a key that reaches all projects. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.projects.duplicate(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the project to copy. | +| `name` | *Optional[str]* | :heavy_minus_sign: | Name of the copy. Defaults to the original name with "(copy)". | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.ProjectWithKey](../../models/projectwithkey.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | \ No newline at end of file diff --git a/docs/sdks/queuesdk/README.md b/docs/sdks/queuesdk/README.md new file mode 100644 index 0000000..2bdacfd --- /dev/null +++ b/docs/sdks/queuesdk/README.md @@ -0,0 +1,101 @@ +# Queue + +## Overview + +### Available Operations + +* [retrieve](#retrieve) - Get a queue +* [get_stats](#get_stats) - Get queue statistics + +## retrieve + +Get a queue + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.queue.retrieve(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The queue ID | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.GetQueueResponse](../../models/getqueueresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.InternalServerError | 500 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## get_stats + +Get detailed statistics about a moderation queue including review times, action counts, and trends + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.queue.get_stats(id="", within_days=30) + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The queue ID | +| `within_days` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.GetQueueStatsResponse](../../models/getqueuestatsresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.InternalServerError | 500 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | \ No newline at end of file diff --git a/docs/sdks/webhooks/README.md b/docs/sdks/webhooks/README.md new file mode 100644 index 0000000..9998a49 --- /dev/null +++ b/docs/sdks/webhooks/README.md @@ -0,0 +1,239 @@ +# Webhooks + +## Overview + +### Available Operations + +* [list](#list) - List webhooks +* [create](#create) - Create a webhook +* [retrieve](#retrieve) - Get a webhook +* [update](#update) - Update a webhook +* [delete](#delete) - Delete a webhook + +## list + +List the webhooks of the project. The list is paginated. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.webhooks.list(page=1, page_size=20) + + while res is not None: + # Handle items + + res = res.next() + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `page` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `page_size` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.ListWebhooksResponse](../../models/listwebhooksresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.AuthenticationError | 401 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## create + +Create a webhook subscribed to one or more event types. Deliveries use the v2 envelope and are signed with the project signing secret (see the signing secret endpoint). + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.webhooks.create(name="Moderation events", url="https://example.com/webhooks/moderation", event_types=[ + "QUEUE_ITEM_REJECTED", + "AUTHOR_BLOCKED", + ]) + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| `name` | *str* | :heavy_check_mark: | The webhook's name, used to identify it in the dashboard | +| `url` | *str* | :heavy_check_mark: | The webhook's URL. We'll call this URL when an event occurs. | +| `event_types` | List[[models.CreateWebhookEventTypeRequest](../../models/createwebhookeventtyperequest.md)] | :heavy_check_mark: | Event types this webhook subscribes to. One webhook URL receives all events you list here. | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The webhook's description | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.CreateWebhookResponse](../../models/createwebhookresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## retrieve + +Get a webhook by ID. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.webhooks.retrieve(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the webhook to get. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.GetWebhookResponse](../../models/getwebhookresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.AuthenticationError | 401 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## update + +Update a webhook. Legacy v1 webhooks are read-only: delete them and create a new webhook instead. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.webhooks.update(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the webhook to update. | +| `name` | *Optional[str]* | :heavy_minus_sign: | The webhook's name, used to identify it in the dashboard | +| `description` | *OptionalNullable[str]* | :heavy_minus_sign: | The webhook's description | +| `url` | *Optional[str]* | :heavy_minus_sign: | The webhook's URL. We'll call this URL when an event occurs. | +| `event_types` | List[[models.UpdateWebhookEventTypeRequest](../../models/updatewebhookeventtyperequest.md)] | :heavy_minus_sign: | Event types this webhook subscribes to. One webhook URL receives all events you list here. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.UpdateWebhookResponse](../../models/updatewebhookresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## delete + +Delete a webhook. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.webhooks.delete(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | The ID of the webhook to delete. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.DeleteWebhookResponse](../../models/deletewebhookresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.AuthenticationError | 401 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | \ No newline at end of file diff --git a/docs/sdks/webhooksecret/README.md b/docs/sdks/webhooksecret/README.md new file mode 100644 index 0000000..1c2a7bc --- /dev/null +++ b/docs/sdks/webhooksecret/README.md @@ -0,0 +1,48 @@ +# WebhookSecret + +## Overview + +### Available Operations + +* [retrieve](#retrieve) - Get the webhook signing secret + +## retrieve + +Get the signing secret used to sign webhook deliveries for this project, creating one if none exists yet. Verify deliveries by comparing the `modapi-signature` header to HMAC-SHA256(raw request body, secret) hex-encoded. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.webhook_secret.retrieve() + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.GetWebhookSecretResponse](../../models/getwebhooksecretresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.AuthenticationError | 401 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | \ No newline at end of file diff --git a/docs/sdks/wordlist/README.md b/docs/sdks/wordlist/README.md new file mode 100644 index 0000000..5cf1560 --- /dev/null +++ b/docs/sdks/wordlist/README.md @@ -0,0 +1,200 @@ +# Wordlist + +## Overview + +### Available Operations + +* [list](#list) - List wordlists +* [retrieve](#retrieve) - Get wordlist +* [update](#update) - Update wordlist +* [get_embedding_status](#get_embedding_status) - Get embedding status + +## list + +List the wordlists of the organization. The list is paginated. + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.wordlist.list(page=1, page_size=20) + + while res is not None: + # Handle items + + res = res.next() + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `page` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `page_size` | *Optional[int]* | :heavy_minus_sign: | N/A | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.ListWordlistsResponse](../../models/listwordlistsresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.InternalServerError | 500 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## retrieve + +Get a specific wordlist by ID + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.wordlist.retrieve(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | ID of the wordlist to get | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.GetWordlistResponse](../../models/getwordlistresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.InternalServerError | 500 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## update + +Update a wordlist + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.wordlist.update(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | +| `id` | *str* | :heavy_check_mark: | ID of the wordlist to update | +| `name` | *Optional[str]* | :heavy_minus_sign: | New name for the wordlist | +| `key` | *Optional[str]* | :heavy_minus_sign: | New key for the wordlist | +| `description` | *Optional[str]* | :heavy_minus_sign: | New description for the wordlist | +| `words` | List[*str*] | :heavy_minus_sign: | New words for the wordlist. Replace the existing words with these new ones. Duplicate words will be ignored. | +| `strict` | *Optional[bool]* | :heavy_minus_sign: | Deprecated. Now using threshold in project settings. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.UpdateWordlistResponse](../../models/updatewordlistresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## get_embedding_status + +Get the current embedding progress status for a wordlist + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.wordlist.get_embedding_status(id="") + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | ID of the wordlist to check embedding status for | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.GetWordlistEmbeddingStatusResponse](../../models/getwordlistembeddingstatusresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.PermissionDeniedError | 403 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.InternalServerError | 500 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | \ No newline at end of file diff --git a/docs/sdks/words/README.md b/docs/sdks/words/README.md new file mode 100644 index 0000000..4fba2d8 --- /dev/null +++ b/docs/sdks/words/README.md @@ -0,0 +1,105 @@ +# Wordlist.Words + +## Overview + +### Available Operations + +* [add](#add) - Add words to wordlist +* [remove](#remove) - Remove words from wordlist + +## add + +Add words to an existing wordlist + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.wordlist.words.add(id="", words=[ + "free money", + "click here", + ]) + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | ID of the wordlist to add words to | +| `words` | List[*str*] | :heavy_check_mark: | Array of words to add to the wordlist. Duplicate words will be ignored. | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.AddWordlistWordsResponse](../../models/addwordlistwordsresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | + +## remove + +Remove words from an existing wordlist + +### Example Usage + + +```python +from moderation_api import ModerationAPI +import os + + +with ModerationAPI( + secret_key=os.getenv("MODAPI_SECRET_KEY", ""), +) as ma_client: + + res = ma_client.wordlist.words.remove(id="", words=[ + "", + "", + "", + ]) + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `id` | *str* | :heavy_check_mark: | ID of the wordlist to remove words from | +| `words` | List[*str*] | :heavy_check_mark: | Array of words to remove from the wordlist | +| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. | + +### Response + +**[models.RemoveWordlistWordsResponse](../../models/removewordlistwordsresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| -------------------------------- | -------------------------------- | -------------------------------- | +| errors.BadRequestError | 400 | application/json | +| errors.AuthenticationError | 401 | application/json | +| errors.NotFoundError | 404 | application/json | +| errors.RateLimitError | 429 | application/json | +| errors.ModerationAPIDefaultError | 4XX, 5XX | \*/\* | \ No newline at end of file diff --git a/examples/.keep b/examples/.keep deleted file mode 100644 index d8c73e9..0000000 --- a/examples/.keep +++ /dev/null @@ -1,4 +0,0 @@ -File generated from our OpenAPI spec by Stainless. - -This directory can be used to store example files demonstrating usage of this SDK. -It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. \ No newline at end of file diff --git a/py.typed b/py.typed new file mode 100644 index 0000000..3e38f1a --- /dev/null +++ b/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561. The package enables type hints. diff --git a/pylintrc b/pylintrc new file mode 100644 index 0000000..c00200c --- /dev/null +++ b/pylintrc @@ -0,0 +1,663 @@ +[MAIN] + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint +# in a server-like mode. +clear-cache-post-run=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, +# it can't be used as an escape character. +ignore-paths= + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked and +# will not be imported (useful for modules/projects where namespaces are +# manipulated during runtime and thus existing member attributes cannot be +# deduced by static analysis). It supports qualified module names, as well as +# Unix pattern matching. +ignored-modules= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.10 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots=src + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +#attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +attr-rgx=[^\W\d][^\W]*|__.*__$ + +# Bad variable names which should always be refused, separated by a comma. +bad-names= + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _, + e, + id + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type alias names. If left empty, type +# alias names will be checked with the set naming style. +typealias-rgx=.* + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + asyncSetUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=25 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=builtins.BaseException,builtins.Exception + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow explicit reexports by alias from a package __init__. +allow-reexport-from-package=no + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-implicit-booleaness-not-comparison-to-string, + use-implicit-booleaness-not-comparison-to-zero, + use-symbolic-message-instead, + trailing-whitespace, + line-too-long, + missing-class-docstring, + missing-module-docstring, + missing-function-docstring, + too-many-instance-attributes, + wrong-import-order, + too-many-arguments, + broad-exception-raised, + too-few-public-methods, + too-many-branches, + duplicate-code, + trailing-newlines, + too-many-public-methods, + too-many-locals, + too-many-lines, + using-constant-test, + too-many-statements, + cyclic-import, + too-many-nested-blocks, + too-many-boolean-expressions, + no-else-raise, + bare-except, + broad-exception-caught, + fixme, + relative-beyond-top-level, + consider-using-with, + wildcard-import, + unused-wildcard-import, + too-many-return-statements, + redefined-builtin + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable= + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are: text, parseable, colorized, +# json2 (improved json format), json (old json format) and msvs (visual +# studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. No available dictionaries : You need to install +# both the python package and the system dependency for enchant to work. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins=id,object,input,dir + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index d67f054..9a82639 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,255 +1,55 @@ [project] name = "moderation_api" -version = "1.42.0" -description = "The official Python library for the moderation-api API" -dynamic = ["readme"] -license = "Apache-2.0" -authors = [ -{ name = "Moderation API", email = "support@moderationapi.com" }, -] - +version = "2.0.0" +description = "The official Python library for the Moderation API" +authors = [{ name = "Moderation API" },] +readme = "README-PYPI.md" +requires-python = ">=3.10" dependencies = [ - "httpx>=0.23.0, <1", - "pydantic>=1.9.0, <3", - "typing-extensions>=4.14, <5", - "anyio>=3.5.0, <5", - "distro>=1.7.0, <2", - "sniffio", -] - -requires-python = ">= 3.9" -classifiers = [ - "Typing :: Typed", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", - "Operating System :: OS Independent", - "Operating System :: POSIX", - "Operating System :: MacOS", - "Operating System :: POSIX :: Linux", - "Operating System :: Microsoft :: Windows", - "Topic :: Software Development :: Libraries :: Python Modules", - "License :: OSI Approved :: Apache Software License" -] - -[project.urls] -Homepage = "https://github.com/moderation-api/sdk-python" -Repository = "https://github.com/moderation-api/sdk-python" - -[project.optional-dependencies] -aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"] - -[tool.uv] -managed = true -required-version = ">=0.9" -conflicts = [ - [ - { group = "pydantic-v1" }, - { group = "pydantic-v2" }, - ], + "httpcore >=1.0.9", + "httpx >=0.28.1", + "jsonpath-python >=1.0.6", + "pydantic >=2.11.2", ] +urls.repository = "https://github.com/moderation-api/sdk-python" +license = { text = "Apache-2.0" } [dependency-groups] -# version pins are in uv.lock dev = [ - "pyright==1.1.399", - "mypy==1.17", - "respx", - "pytest", - "pytest-asyncio", - "ruff", - "time-machine", - "dirty-equals>=0.6.0", - "importlib-metadata>=6.7.0", - "rich>=13.7.1", - "pytest-xdist>=3.6.1", -] -pydantic-v1 = [ - "pydantic>=1.9.0,<2", -] -pydantic-v2 = [ - "pydantic~=2.0 ; python_full_version < '3.14'", - "pydantic~=2.12 ; python_full_version >= '3.14'", + "mypy ==1.15.0", + "pylint ==3.2.3", + "pyright ==1.1.398", ] -[build-system] -requires = ["hatchling==1.26.3", "hatch-fancy-pypi-readme"] -build-backend = "hatchling.build" +[tool.setuptools.packages.find] +where = ["src"] -[tool.hatch.build] -include = [ - "src/*" -] - -[tool.hatch.build.targets.wheel] -packages = ["src/moderation_api"] - -[tool.hatch.build.targets.sdist] -# Basically everything except hidden files/directories (such as .github, .devcontainers, .python-version, etc) -include = [ - "/*.toml", - "/*.json", - "/*.lock", - "/*.md", - "/mypy.ini", - "/noxfile.py", - "bin/*", - "examples/*", - "src/*", - "tests/*", -] - -[tool.hatch.metadata.hooks.fancy-pypi-readme] -content-type = "text/markdown" +[tool.setuptools.package-data] +"*" = ["py.typed"] -[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] -path = "README.md" - -[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]] -# replace relative links with absolute links -pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)' -replacement = '[\1](https://github.com/moderation-api/sdk-python/tree/main/\g<2>)' +[build-system] +requires = ["setuptools>=80", "wheel"] +build-backend = "setuptools.build_meta" [tool.pytest.ini_options] -testpaths = ["tests"] -addopts = "--tb=short -n auto" -xfail_strict = true -asyncio_mode = "auto" -asyncio_default_fixture_loop_scope = "session" -filterwarnings = [ - "error" -] - -[tool.pyright] -# this enables practically every flag given by pyright. -# there are a couple of flags that are still disabled by -# default in strict mode as they are experimental and niche. -typeCheckingMode = "strict" -pythonVersion = "3.9" - -exclude = [ - "_dev", - ".venv", - ".nox", - ".git", -] - -reportImplicitOverride = true -reportOverlappingOverload = false - -reportImportCycles = false -reportPrivateUsage = false +asyncio_default_fixture_loop_scope = "function" +pythonpath = ["src"] [tool.mypy] -pretty = true -show_error_codes = true - -# Exclude _files.py because mypy isn't smart enough to apply -# the correct type narrowing and as this is an internal module -# it's fine to just use Pyright. -# -# We also exclude our `tests` as mypy doesn't always infer -# types correctly and Pyright will still catch any type errors. -exclude = ["src/moderation_api/_files.py", "_dev/.*.py", "tests/.*"] - -strict_equality = true -implicit_reexport = true -check_untyped_defs = true -no_implicit_optional = true - -warn_return_any = true -warn_unreachable = true -warn_unused_configs = true - -# Turn these options off as it could cause conflicts -# with the Pyright options. -warn_unused_ignores = false -warn_redundant_casts = false - -disallow_any_generics = true -disallow_untyped_defs = true -disallow_untyped_calls = true -disallow_subclassing_any = true -disallow_incomplete_defs = true -disallow_untyped_decorators = true -cache_fine_grained = true - -# By default, mypy reports an error if you assign a value to the result -# of a function call that doesn't return anything. We do this in our test -# cases: -# ``` -# result = ... -# assert result is None -# ``` -# Changing this codegen to make mypy happy would increase complexity -# and would not be worth it. -disable_error_code = "func-returns-value,overload-cannot-match" +disable_error_code = "misc" +explicit_package_bases = true +mypy_path = "src" -# https://github.com/python/mypy/issues/12162 [[tool.mypy.overrides]] -module = "black.files.*" -ignore_errors = true +module = "typing_inspect" ignore_missing_imports = true +[[tool.mypy.overrides]] +module = "jsonpath" +ignore_missing_imports = true -[tool.ruff] -line-length = 120 -output-format = "grouped" -target-version = "py38" - -[tool.ruff.format] -docstring-code-format = true - -[tool.ruff.lint] -select = [ - # isort - "I", - # bugbear rules - "B", - # remove unused imports - "F401", - # check for missing future annotations - "FA102", - # bare except statements - "E722", - # unused arguments - "ARG", - # print statements - "T201", - "T203", - # misuse of typing.TYPE_CHECKING - "TC004", - # import rules - "TID251", -] -ignore = [ - # mutable defaults - "B006", -] -unfixable = [ - # disable auto fix for print statements - "T201", - "T203", -] - -extend-safe-fixes = ["FA102"] - -[tool.ruff.lint.flake8-tidy-imports.banned-api] -"functools.lru_cache".msg = "This function does not retain type information for the wrapped function's arguments; The `lru_cache` function from `_utils` should be used instead" +[tool.pyright] +venvPath = "." +venv = ".venv" -[tool.ruff.lint.isort] -length-sort = true -length-sort-straight = true -combine-as-imports = true -extra-standard-library = ["typing_extensions"] -known-first-party = ["moderation_api", "tests"] -[tool.ruff.lint.per-file-ignores] -"bin/**.py" = ["T201", "T203"] -"scripts/**.py" = ["T201", "T203"] -"tests/**.py" = ["T201", "T203"] -"examples/**.py" = ["T201", "T203"] diff --git a/release-please-config.json b/release-please-config.json deleted file mode 100644 index e07cb3e..0000000 --- a/release-please-config.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "packages": { - ".": {} - }, - "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", - "include-v-in-tag": true, - "include-component-in-tag": false, - "versioning": "prerelease", - "prerelease": true, - "bump-minor-pre-major": true, - "bump-patch-for-minor-pre-major": false, - "pull-request-header": "Automated Release PR", - "pull-request-title-pattern": "release: ${version}", - "changelog-sections": [ - { - "type": "feat", - "section": "Features" - }, - { - "type": "fix", - "section": "Bug Fixes" - }, - { - "type": "perf", - "section": "Performance Improvements" - }, - { - "type": "revert", - "section": "Reverts" - }, - { - "type": "chore", - "section": "Chores" - }, - { - "type": "docs", - "section": "Documentation" - }, - { - "type": "style", - "section": "Styles" - }, - { - "type": "refactor", - "section": "Refactors" - }, - { - "type": "test", - "section": "Tests", - "hidden": true - }, - { - "type": "build", - "section": "Build System" - }, - { - "type": "ci", - "section": "Continuous Integration", - "hidden": true - } - ], - "release-type": "python", - "extra-files": [ - "src/moderation_api/_version.py" - ] -} \ No newline at end of file diff --git a/requirements-dev.lock b/requirements-dev.lock deleted file mode 100644 index 3d2356d..0000000 --- a/requirements-dev.lock +++ /dev/null @@ -1,110 +0,0 @@ -# This file was autogenerated by uv via the following command: -# uv export -o requirements-dev.lock --no-hashes --e . -annotated-types==0.7.0 - # via pydantic -anyio==4.12.1 - # via - # httpx - # moderation-api -backports-asyncio-runner==1.2.0 ; python_full_version < '3.11' - # via pytest-asyncio -certifi==2026.1.4 - # via - # httpcore - # httpx -colorama==0.4.6 ; sys_platform == 'win32' - # via pytest -dirty-equals==0.11 -distro==1.9.0 - # via moderation-api -exceptiongroup==1.3.1 ; python_full_version < '3.11' - # via - # anyio - # pytest -execnet==2.1.2 - # via pytest-xdist -h11==0.16.0 - # via httpcore -httpcore==1.0.9 - # via httpx -httpx==0.28.1 - # via - # moderation-api - # respx -idna==3.11 - # via - # anyio - # httpx -importlib-metadata==8.7.1 -iniconfig==2.1.0 ; python_full_version < '3.10' - # via pytest -iniconfig==2.3.0 ; python_full_version >= '3.10' - # via pytest -markdown-it-py==3.0.0 ; python_full_version < '3.10' - # via rich -markdown-it-py==4.0.0 ; python_full_version >= '3.10' - # via rich -mdurl==0.1.2 - # via markdown-it-py -mypy==1.17.0 -mypy-extensions==1.1.0 - # via mypy -nodeenv==1.10.0 - # via pyright -packaging==25.0 - # via pytest -pathspec==1.0.3 - # via mypy -pluggy==1.6.0 - # via pytest -pydantic==2.12.5 - # via moderation-api -pydantic-core==2.41.5 - # via pydantic -pygments==2.19.2 - # via - # pytest - # rich -pyright==1.1.399 -pytest==8.4.2 ; python_full_version < '3.10' - # via - # pytest-asyncio - # pytest-xdist -pytest==9.0.2 ; python_full_version >= '3.10' - # via - # pytest-asyncio - # pytest-xdist -pytest-asyncio==1.2.0 ; python_full_version < '3.10' -pytest-asyncio==1.3.0 ; python_full_version >= '3.10' -pytest-xdist==3.8.0 -python-dateutil==2.9.0.post0 ; python_full_version < '3.10' - # via time-machine -respx==0.22.0 -rich==14.2.0 -ruff==0.14.13 -six==1.17.0 ; python_full_version < '3.10' - # via python-dateutil -sniffio==1.3.1 - # via moderation-api -time-machine==2.19.0 ; python_full_version < '3.10' -time-machine==3.2.0 ; python_full_version >= '3.10' -tomli==2.4.0 ; python_full_version < '3.11' - # via - # mypy - # pytest -typing-extensions==4.15.0 - # via - # anyio - # exceptiongroup - # moderation-api - # mypy - # pydantic - # pydantic-core - # pyright - # pytest-asyncio - # typing-inspection -typing-inspection==0.4.2 - # via pydantic -zipp==3.23.0 - # via importlib-metadata diff --git a/scripts/bootstrap b/scripts/bootstrap deleted file mode 100755 index 5a23841..0000000 --- a/scripts/bootstrap +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then - brew bundle check >/dev/null 2>&1 || { - echo -n "==> Install Homebrew dependencies? (y/N): " - read -r response - case "$response" in - [yY][eE][sS]|[yY]) - brew bundle - ;; - *) - ;; - esac - echo - } -fi - -echo "==> Installing Python…" -uv python install - -echo "==> Installing Python dependencies…" -uv sync --all-extras - -echo "==> Exporting Python dependencies…" -# note: `--no-hashes` is required because of https://github.com/pypa/pip/issues/4995 -uv export -o requirements-dev.lock --no-hashes diff --git a/scripts/format b/scripts/format deleted file mode 100755 index c8e1f69..0000000 --- a/scripts/format +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -echo "==> Running ruff" -uv run ruff format -uv run ruff check --fix . -# run formatting again to fix any inconsistencies when imports are stripped -uv run ruff format - -echo "==> Formatting docs" -uv run python scripts/utils/ruffen-docs.py README.md $(find . -type f -name api.md) diff --git a/scripts/lint b/scripts/lint deleted file mode 100755 index 803d0aa..0000000 --- a/scripts/lint +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -if [ "$1" = "--fix" ]; then - echo "==> Running ruff with --fix" - uv run ruff check . --fix -else - echo "==> Running ruff" - uv run ruff check . -fi - -echo "==> Running pyright" -uv run pyright -p . - -echo "==> Running mypy" -uv run mypy . - -echo "==> Making sure it imports" -uv run python -c 'import moderation_api' diff --git a/scripts/prepare_readme.py b/scripts/prepare_readme.py new file mode 100644 index 0000000..73f87f9 --- /dev/null +++ b/scripts/prepare_readme.py @@ -0,0 +1,35 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +import re +import shutil + +try: + with open("README.md", "r", encoding="utf-8") as rh: + readme_contents = rh.read() + GITHUB_URL = "https://github.com/moderation-api/sdk-python" + GITHUB_URL = ( + GITHUB_URL[: -len(".git")] if GITHUB_URL.endswith(".git") else GITHUB_URL + ) + REPO_SUBDIR = "" + # links on PyPI should have absolute URLs + readme_contents = re.sub( + r"(\[[^\]]+\]\()((?!https?:)[^\)]+)(\))", + lambda m: m.group(1) + + GITHUB_URL + + "/blob/master/" + + REPO_SUBDIR + + m.group(2) + + m.group(3), + readme_contents, + ) + + with open("README-PYPI.md", "w", encoding="utf-8") as wh: + wh.write(readme_contents) +except Exception as e: + try: + print("Failed to rewrite README.md to README-PYPI.md, copying original instead") + print(e) + shutil.copyfile("README.md", "README-PYPI.md") + except Exception as ie: + print("Failed to copy README.md to README-PYPI.md") + print(ie) diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100644 index 0000000..c35748f --- /dev/null +++ b/scripts/publish.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +uv run python scripts/prepare_readme.py + +uv build +uv publish --token $PYPI_TOKEN diff --git a/scripts/test b/scripts/test deleted file mode 100755 index fe50ebb..0000000 --- a/scripts/test +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - - - -export DEFER_PYDANTIC_BUILD=false - -# Note that we need to specify the patch version here so that uv -# won't use unstable (alpha, beta, rc) releases for the tests -PY_VERSION_MIN=">=3.9.0" -PY_VERSION_MAX=">=3.14.0" - -function run_tests() { - echo "==> Running tests with Pydantic v2" - uv run --isolated --all-extras pytest "$@" - - # Skip Pydantic v1 tests on latest Python (not supported) - if [[ "$UV_PYTHON" != "$PY_VERSION_MAX" ]]; then - echo "==> Running tests with Pydantic v1" - uv run --isolated --all-extras --group=pydantic-v1 pytest "$@" - fi -} - -# If UV_PYTHON is already set in the environment, just run the command once -if [[ -n "$UV_PYTHON" ]]; then - run_tests "$@" -else - # If UV_PYTHON is not set, run the command for min and max versions - - echo "==> Running tests for Python $PY_VERSION_MIN" - UV_PYTHON="$PY_VERSION_MIN" run_tests "$@" - - echo "==> Running tests for Python $PY_VERSION_MAX" - UV_PYTHON="$PY_VERSION_MAX" run_tests "$@" -fi diff --git a/scripts/utils/ruffen-docs.py b/scripts/utils/ruffen-docs.py deleted file mode 100644 index 0cf2bd2..0000000 --- a/scripts/utils/ruffen-docs.py +++ /dev/null @@ -1,167 +0,0 @@ -# fork of https://github.com/asottile/blacken-docs adapted for ruff -from __future__ import annotations - -import re -import sys -import argparse -import textwrap -import contextlib -import subprocess -from typing import Match, Optional, Sequence, Generator, NamedTuple, cast - -MD_RE = re.compile( - r"(?P^(?P *)```\s*python\n)" r"(?P.*?)" r"(?P^(?P=indent)```\s*$)", - re.DOTALL | re.MULTILINE, -) -MD_PYCON_RE = re.compile( - r"(?P^(?P *)```\s*pycon\n)" r"(?P.*?)" r"(?P^(?P=indent)```.*$)", - re.DOTALL | re.MULTILINE, -) -PYCON_PREFIX = ">>> " -PYCON_CONTINUATION_PREFIX = "..." -PYCON_CONTINUATION_RE = re.compile( - rf"^{re.escape(PYCON_CONTINUATION_PREFIX)}( |$)", -) -DEFAULT_LINE_LENGTH = 100 - - -class CodeBlockError(NamedTuple): - offset: int - exc: Exception - - -def format_str( - src: str, -) -> tuple[str, Sequence[CodeBlockError]]: - errors: list[CodeBlockError] = [] - - @contextlib.contextmanager - def _collect_error(match: Match[str]) -> Generator[None, None, None]: - try: - yield - except Exception as e: - errors.append(CodeBlockError(match.start(), e)) - - def _md_match(match: Match[str]) -> str: - code = textwrap.dedent(match["code"]) - with _collect_error(match): - code = format_code_block(code) - code = textwrap.indent(code, match["indent"]) - return f"{match['before']}{code}{match['after']}" - - def _pycon_match(match: Match[str]) -> str: - code = "" - fragment = cast(Optional[str], None) - - def finish_fragment() -> None: - nonlocal code - nonlocal fragment - - if fragment is not None: - with _collect_error(match): - fragment = format_code_block(fragment) - fragment_lines = fragment.splitlines() - code += f"{PYCON_PREFIX}{fragment_lines[0]}\n" - for line in fragment_lines[1:]: - # Skip blank lines to handle Black adding a blank above - # functions within blocks. A blank line would end the REPL - # continuation prompt. - # - # >>> if True: - # ... def f(): - # ... pass - # ... - if line: - code += f"{PYCON_CONTINUATION_PREFIX} {line}\n" - if fragment_lines[-1].startswith(" "): - code += f"{PYCON_CONTINUATION_PREFIX}\n" - fragment = None - - indentation = None - for line in match["code"].splitlines(): - orig_line, line = line, line.lstrip() - if indentation is None and line: - indentation = len(orig_line) - len(line) - continuation_match = PYCON_CONTINUATION_RE.match(line) - if continuation_match and fragment is not None: - fragment += line[continuation_match.end() :] + "\n" - else: - finish_fragment() - if line.startswith(PYCON_PREFIX): - fragment = line[len(PYCON_PREFIX) :] + "\n" - else: - code += orig_line[indentation:] + "\n" - finish_fragment() - return code - - def _md_pycon_match(match: Match[str]) -> str: - code = _pycon_match(match) - code = textwrap.indent(code, match["indent"]) - return f"{match['before']}{code}{match['after']}" - - src = MD_RE.sub(_md_match, src) - src = MD_PYCON_RE.sub(_md_pycon_match, src) - return src, errors - - -def format_code_block(code: str) -> str: - return subprocess.check_output( - [ - sys.executable, - "-m", - "ruff", - "format", - "--stdin-filename=script.py", - f"--line-length={DEFAULT_LINE_LENGTH}", - ], - encoding="utf-8", - input=code, - ) - - -def format_file( - filename: str, - skip_errors: bool, -) -> int: - with open(filename, encoding="UTF-8") as f: - contents = f.read() - new_contents, errors = format_str(contents) - for error in errors: - lineno = contents[: error.offset].count("\n") + 1 - print(f"{filename}:{lineno}: code block parse error {error.exc}") - if errors and not skip_errors: - return 1 - if contents != new_contents: - print(f"{filename}: Rewriting...") - with open(filename, "w", encoding="UTF-8") as f: - f.write(new_contents) - return 0 - else: - return 0 - - -def main(argv: Sequence[str] | None = None) -> int: - parser = argparse.ArgumentParser() - parser.add_argument( - "-l", - "--line-length", - type=int, - default=DEFAULT_LINE_LENGTH, - ) - parser.add_argument( - "-S", - "--skip-string-normalization", - action="store_true", - ) - parser.add_argument("-E", "--skip-errors", action="store_true") - parser.add_argument("filenames", nargs="*") - args = parser.parse_args(argv) - - retv = 0 - for filename in args.filenames: - retv |= format_file(filename, skip_errors=args.skip_errors) - return retv - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh deleted file mode 100755 index c284e0c..0000000 --- a/scripts/utils/upload-artifact.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -set -exuo pipefail - -FILENAME=$(basename dist/*.whl) - -RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \ - -H "Authorization: Bearer $AUTH" \ - -H "Content-Type: application/json") - -SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') - -if [[ "$SIGNED_URL" == "null" ]]; then - echo -e "\033[31mFailed to get signed URL.\033[0m" - exit 1 -fi - -UPLOAD_RESPONSE=$(curl -v -X PUT \ - -H "Content-Type: binary/octet-stream" \ - --data-binary "@dist/$FILENAME" "$SIGNED_URL" 2>&1) - -if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then - echo -e "\033[32mUploaded build to Stainless storage.\033[0m" - echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/moderation-api-python/$SHA/$FILENAME'\033[0m" -else - echo -e "\033[31mFailed to upload artifact.\033[0m" - exit 1 -fi diff --git a/src/moderation_api/__init__.py b/src/moderation_api/__init__.py index 5b03cc8..833c68c 100644 --- a/src/moderation_api/__init__.py +++ b/src/moderation_api/__init__.py @@ -1,102 +1,17 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" -import typing as _t - -from . import types -from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given -from ._utils import file_from_path -from ._client import ( - Client, - Stream, - Timeout, - Transport, - AsyncClient, - AsyncStream, - ModerationAPI, - RequestOptions, - AsyncModerationAPI, -) -from ._models import BaseModel -from ._version import __title__, __version__ -from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse -from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS -from ._exceptions import ( - APIError, - ConflictError, - NotFoundError, - APIStatusError, - RateLimitError, - APITimeoutError, - BadRequestError, - APIConnectionError, - ModerationAPIError, - AuthenticationError, - InternalServerError, - PermissionDeniedError, - UnprocessableEntityError, - APIResponseValidationError, +from ._version import ( + __title__, + __version__, + __openapi_doc_version__, + __gen_version__, + __user_agent__, ) -from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient -from ._utils._logs import setup_logging as _setup_logging - -__all__ = [ - "types", - "__version__", - "__title__", - "NoneType", - "Transport", - "ProxiesTypes", - "NotGiven", - "NOT_GIVEN", - "not_given", - "Omit", - "omit", - "ModerationAPIError", - "APIError", - "APIStatusError", - "APITimeoutError", - "APIConnectionError", - "APIResponseValidationError", - "BadRequestError", - "AuthenticationError", - "PermissionDeniedError", - "NotFoundError", - "ConflictError", - "UnprocessableEntityError", - "RateLimitError", - "InternalServerError", - "Timeout", - "RequestOptions", - "Client", - "AsyncClient", - "Stream", - "AsyncStream", - "ModerationAPI", - "AsyncModerationAPI", - "file_from_path", - "BaseModel", - "DEFAULT_TIMEOUT", - "DEFAULT_MAX_RETRIES", - "DEFAULT_CONNECTION_LIMITS", - "DefaultHttpxClient", - "DefaultAsyncHttpxClient", - "DefaultAioHttpClient", -] - -if not _t.TYPE_CHECKING: - from ._utils._resources_proxy import resources as resources +from .sdk import * +from .sdkconfiguration import * -_setup_logging() -# Update the __module__ attribute for exported symbols so that -# error messages point to this module instead of the module -# it was originally defined in, e.g. -# moderation_api._exceptions.NotFoundError -> moderation_api.NotFoundError -__locals = locals() -for __name in __all__: - if not __name.startswith("__"): - try: - __locals[__name].__module__ = "moderation_api" - except (TypeError, AttributeError): - # Some of our exported symbols are builtins which we can't set attributes for. - pass +VERSION: str = __version__ +OPENAPI_DOC_VERSION = __openapi_doc_version__ +SPEAKEASY_GENERATOR_VERSION = __gen_version__ +USER_AGENT = __user_agent__ diff --git a/src/moderation_api/_base_client.py b/src/moderation_api/_base_client.py deleted file mode 100644 index 5e263d4..0000000 --- a/src/moderation_api/_base_client.py +++ /dev/null @@ -1,2131 +0,0 @@ -from __future__ import annotations - -import sys -import json -import time -import uuid -import email -import asyncio -import inspect -import logging -import platform -import warnings -import email.utils -from types import TracebackType -from random import random -from typing import ( - TYPE_CHECKING, - Any, - Dict, - Type, - Union, - Generic, - Mapping, - TypeVar, - Iterable, - Iterator, - Optional, - Generator, - AsyncIterator, - cast, - overload, -) -from typing_extensions import Literal, override, get_origin - -import anyio -import httpx -import distro -import pydantic -from httpx import URL -from pydantic import PrivateAttr - -from . import _exceptions -from ._qs import Querystring -from ._files import to_httpx_files, async_to_httpx_files -from ._types import ( - Body, - Omit, - Query, - Headers, - Timeout, - NotGiven, - ResponseT, - AnyMapping, - PostParser, - BinaryTypes, - RequestFiles, - HttpxSendArgs, - RequestOptions, - AsyncBinaryTypes, - HttpxRequestFiles, - ModelBuilderProtocol, - not_given, -) -from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping -from ._compat import PYDANTIC_V1, model_copy, model_dump -from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type -from ._response import ( - APIResponse, - BaseAPIResponse, - AsyncAPIResponse, - extract_response_type, -) -from ._constants import ( - DEFAULT_TIMEOUT, - MAX_RETRY_DELAY, - DEFAULT_MAX_RETRIES, - INITIAL_RETRY_DELAY, - RAW_RESPONSE_HEADER, - OVERRIDE_CAST_TO_HEADER, - DEFAULT_CONNECTION_LIMITS, -) -from ._streaming import Stream, SSEDecoder, AsyncStream, SSEBytesDecoder -from ._exceptions import ( - APIStatusError, - APITimeoutError, - APIConnectionError, - APIResponseValidationError, -) -from ._utils._json import openapi_dumps - -log: logging.Logger = logging.getLogger(__name__) - -# TODO: make base page type vars covariant -SyncPageT = TypeVar("SyncPageT", bound="BaseSyncPage[Any]") -AsyncPageT = TypeVar("AsyncPageT", bound="BaseAsyncPage[Any]") - - -_T = TypeVar("_T") -_T_co = TypeVar("_T_co", covariant=True) - -_StreamT = TypeVar("_StreamT", bound=Stream[Any]) -_AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any]) - -if TYPE_CHECKING: - from httpx._config import ( - DEFAULT_TIMEOUT_CONFIG, # pyright: ignore[reportPrivateImportUsage] - ) - - HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG -else: - try: - from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT - except ImportError: - # taken from https://github.com/encode/httpx/blob/3ba5fe0d7ac70222590e759c31442b1cab263791/httpx/_config.py#L366 - HTTPX_DEFAULT_TIMEOUT = Timeout(5.0) - - -class PageInfo: - """Stores the necessary information to build the request to retrieve the next page. - - Either `url` or `params` must be set. - """ - - url: URL | NotGiven - params: Query | NotGiven - json: Body | NotGiven - - @overload - def __init__( - self, - *, - url: URL, - ) -> None: ... - - @overload - def __init__( - self, - *, - params: Query, - ) -> None: ... - - @overload - def __init__( - self, - *, - json: Body, - ) -> None: ... - - def __init__( - self, - *, - url: URL | NotGiven = not_given, - json: Body | NotGiven = not_given, - params: Query | NotGiven = not_given, - ) -> None: - self.url = url - self.json = json - self.params = params - - @override - def __repr__(self) -> str: - if self.url: - return f"{self.__class__.__name__}(url={self.url})" - if self.json: - return f"{self.__class__.__name__}(json={self.json})" - return f"{self.__class__.__name__}(params={self.params})" - - -class BasePage(GenericModel, Generic[_T]): - """ - Defines the core interface for pagination. - - Type Args: - ModelT: The pydantic model that represents an item in the response. - - Methods: - has_next_page(): Check if there is another page available - next_page_info(): Get the necessary information to make a request for the next page - """ - - _options: FinalRequestOptions = PrivateAttr() - _model: Type[_T] = PrivateAttr() - - def has_next_page(self) -> bool: - items = self._get_page_items() - if not items: - return False - return self.next_page_info() is not None - - def next_page_info(self) -> Optional[PageInfo]: ... - - def _get_page_items(self) -> Iterable[_T]: # type: ignore[empty-body] - ... - - def _params_from_url(self, url: URL) -> httpx.QueryParams: - # TODO: do we have to preprocess params here? - return httpx.QueryParams(cast(Any, self._options.params)).merge(url.params) - - def _info_to_options(self, info: PageInfo) -> FinalRequestOptions: - options = model_copy(self._options) - options._strip_raw_response_header() - - if not isinstance(info.params, NotGiven): - options.params = {**options.params, **info.params} - return options - - if not isinstance(info.url, NotGiven): - params = self._params_from_url(info.url) - url = info.url.copy_with(params=params) - options.params = dict(url.params) - options.url = str(url) - return options - - if not isinstance(info.json, NotGiven): - if not is_mapping(info.json): - raise TypeError("Pagination is only supported with mappings") - - if not options.json_data: - options.json_data = {**info.json} - else: - if not is_mapping(options.json_data): - raise TypeError("Pagination is only supported with mappings") - - options.json_data = {**options.json_data, **info.json} - return options - - raise ValueError("Unexpected PageInfo state") - - -class BaseSyncPage(BasePage[_T], Generic[_T]): - _client: SyncAPIClient = pydantic.PrivateAttr() - - def _set_private_attributes( - self, - client: SyncAPIClient, - model: Type[_T], - options: FinalRequestOptions, - ) -> None: - if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: - self.__pydantic_private__ = {} - - self._model = model - self._client = client - self._options = options - - # Pydantic uses a custom `__iter__` method to support casting BaseModels - # to dictionaries. e.g. dict(model). - # As we want to support `for item in page`, this is inherently incompatible - # with the default pydantic behaviour. It is not possible to support both - # use cases at once. Fortunately, this is not a big deal as all other pydantic - # methods should continue to work as expected as there is an alternative method - # to cast a model to a dictionary, model.dict(), which is used internally - # by pydantic. - def __iter__(self) -> Iterator[_T]: # type: ignore - for page in self.iter_pages(): - for item in page._get_page_items(): - yield item - - def iter_pages(self: SyncPageT) -> Iterator[SyncPageT]: - page = self - while True: - yield page - if page.has_next_page(): - page = page.get_next_page() - else: - return - - def get_next_page(self: SyncPageT) -> SyncPageT: - info = self.next_page_info() - if not info: - raise RuntimeError( - "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`." - ) - - options = self._info_to_options(info) - return self._client._request_api_list(self._model, page=self.__class__, options=options) - - -class AsyncPaginator(Generic[_T, AsyncPageT]): - def __init__( - self, - client: AsyncAPIClient, - options: FinalRequestOptions, - page_cls: Type[AsyncPageT], - model: Type[_T], - ) -> None: - self._model = model - self._client = client - self._options = options - self._page_cls = page_cls - - def __await__(self) -> Generator[Any, None, AsyncPageT]: - return self._get_page().__await__() - - async def _get_page(self) -> AsyncPageT: - def _parser(resp: AsyncPageT) -> AsyncPageT: - resp._set_private_attributes( - model=self._model, - options=self._options, - client=self._client, - ) - return resp - - self._options.post_parser = _parser - - return await self._client.request(self._page_cls, self._options) - - async def __aiter__(self) -> AsyncIterator[_T]: - # https://github.com/microsoft/pyright/issues/3464 - page = cast( - AsyncPageT, - await self, # type: ignore - ) - async for item in page: - yield item - - -class BaseAsyncPage(BasePage[_T], Generic[_T]): - _client: AsyncAPIClient = pydantic.PrivateAttr() - - def _set_private_attributes( - self, - model: Type[_T], - client: AsyncAPIClient, - options: FinalRequestOptions, - ) -> None: - if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: - self.__pydantic_private__ = {} - - self._model = model - self._client = client - self._options = options - - async def __aiter__(self) -> AsyncIterator[_T]: - async for page in self.iter_pages(): - for item in page._get_page_items(): - yield item - - async def iter_pages(self: AsyncPageT) -> AsyncIterator[AsyncPageT]: - page = self - while True: - yield page - if page.has_next_page(): - page = await page.get_next_page() - else: - return - - async def get_next_page(self: AsyncPageT) -> AsyncPageT: - info = self.next_page_info() - if not info: - raise RuntimeError( - "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`." - ) - - options = self._info_to_options(info) - return await self._client._request_api_list(self._model, page=self.__class__, options=options) - - -_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient]) -_DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]]) - - -class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]): - _client: _HttpxClientT - _version: str - _base_url: URL - max_retries: int - timeout: Union[float, Timeout, None] - _strict_response_validation: bool - _idempotency_header: str | None - _default_stream_cls: type[_DefaultStreamT] | None = None - - def __init__( - self, - *, - version: str, - base_url: str | URL, - _strict_response_validation: bool, - max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None = DEFAULT_TIMEOUT, - custom_headers: Mapping[str, str] | None = None, - custom_query: Mapping[str, object] | None = None, - ) -> None: - self._version = version - self._base_url = self._enforce_trailing_slash(URL(base_url)) - self.max_retries = max_retries - self.timeout = timeout - self._custom_headers = custom_headers or {} - self._custom_query = custom_query or {} - self._strict_response_validation = _strict_response_validation - self._idempotency_header = None - self._platform: Platform | None = None - - if max_retries is None: # pyright: ignore[reportUnnecessaryComparison] - raise TypeError( - "max_retries cannot be None. If you want to disable retries, pass `0`; if you want unlimited retries, pass `math.inf` or a very high number; if you want the default behavior, pass `moderation_api.DEFAULT_MAX_RETRIES`" - ) - - def _enforce_trailing_slash(self, url: URL) -> URL: - if url.raw_path.endswith(b"/"): - return url - return url.copy_with(raw_path=url.raw_path + b"/") - - def _make_status_error_from_response( - self, - response: httpx.Response, - ) -> APIStatusError: - if response.is_closed and not response.is_stream_consumed: - # We can't read the response body as it has been closed - # before it was read. This can happen if an event hook - # raises a status error. - body = None - err_msg = f"Error code: {response.status_code}" - else: - err_text = response.text.strip() - body = err_text - - try: - body = json.loads(err_text) - err_msg = f"Error code: {response.status_code} - {body}" - except Exception: - err_msg = err_text or f"Error code: {response.status_code}" - - return self._make_status_error(err_msg, body=body, response=response) - - def _make_status_error( - self, - err_msg: str, - *, - body: object, - response: httpx.Response, - ) -> _exceptions.APIStatusError: - raise NotImplementedError() - - def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0) -> httpx.Headers: - custom_headers = options.headers or {} - headers_dict = _merge_mappings(self.default_headers, custom_headers) - self._validate_headers(headers_dict, custom_headers) - - # headers are case-insensitive while dictionaries are not. - headers = httpx.Headers(headers_dict) - - idempotency_header = self._idempotency_header - if idempotency_header and options.idempotency_key and idempotency_header not in headers: - headers[idempotency_header] = options.idempotency_key - - # Don't set these headers if they were already set or removed by the caller. We check - # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case. - lower_custom_headers = [header.lower() for header in custom_headers] - if "x-stainless-retry-count" not in lower_custom_headers: - headers["x-stainless-retry-count"] = str(retries_taken) - if "x-stainless-read-timeout" not in lower_custom_headers: - timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout - if isinstance(timeout, Timeout): - timeout = timeout.read - if timeout is not None: - headers["x-stainless-read-timeout"] = str(timeout) - - return headers - - def _prepare_url(self, url: str) -> URL: - """ - Merge a URL argument together with any 'base_url' on the client, - to create the URL used for the outgoing request. - """ - # Copied from httpx's `_merge_url` method. - merge_url = URL(url) - if merge_url.is_relative_url: - merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/") - return self.base_url.copy_with(raw_path=merge_raw_path) - - return merge_url - - def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder: - return SSEDecoder() - - def _build_request( - self, - options: FinalRequestOptions, - *, - retries_taken: int = 0, - ) -> httpx.Request: - if log.isEnabledFor(logging.DEBUG): - log.debug( - "Request options: %s", - model_dump( - options, - exclude_unset=True, - # Pydantic v1 can't dump every type we support in content, so we exclude it for now. - exclude={ - "content", - } - if PYDANTIC_V1 - else {}, - ), - ) - kwargs: dict[str, Any] = {} - - json_data = options.json_data - if options.extra_json is not None: - if json_data is None: - json_data = cast(Body, options.extra_json) - elif is_mapping(json_data): - json_data = _merge_mappings(json_data, options.extra_json) - else: - raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`") - - headers = self._build_headers(options, retries_taken=retries_taken) - params = _merge_mappings(self.default_query, options.params) - content_type = headers.get("Content-Type") - files = options.files - - # If the given Content-Type header is multipart/form-data then it - # has to be removed so that httpx can generate the header with - # additional information for us as it has to be in this form - # for the server to be able to correctly parse the request: - # multipart/form-data; boundary=---abc-- - if content_type is not None and content_type.startswith("multipart/form-data"): - if "boundary" not in content_type: - # only remove the header if the boundary hasn't been explicitly set - # as the caller doesn't want httpx to come up with their own boundary - headers.pop("Content-Type") - - # As we are now sending multipart/form-data instead of application/json - # we need to tell httpx to use it, https://www.python-httpx.org/advanced/clients/#multipart-file-encoding - if json_data: - if not is_dict(json_data): - raise TypeError( - f"Expected query input to be a dictionary for multipart requests but got {type(json_data)} instead." - ) - kwargs["data"] = self._serialize_multipartform(json_data) - - # httpx determines whether or not to send a "multipart/form-data" - # request based on the truthiness of the "files" argument. - # This gets around that issue by generating a dict value that - # evaluates to true. - # - # https://github.com/encode/httpx/discussions/2399#discussioncomment-3814186 - if not files: - files = cast(HttpxRequestFiles, ForceMultipartDict()) - - prepared_url = self._prepare_url(options.url) - # preserve hard-coded query params from the url - if params and prepared_url.query: - params = {**dict(prepared_url.params.items()), **params} - prepared_url = prepared_url.copy_with(raw_path=prepared_url.raw_path.split(b"?", 1)[0]) - if "_" in prepared_url.host: - # work around https://github.com/encode/httpx/discussions/2880 - kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} - - is_body_allowed = options.method.lower() != "get" - - if is_body_allowed: - if options.content is not None and json_data is not None: - raise TypeError("Passing both `content` and `json_data` is not supported") - if options.content is not None and files is not None: - raise TypeError("Passing both `content` and `files` is not supported") - if options.content is not None: - kwargs["content"] = options.content - elif isinstance(json_data, bytes): - kwargs["content"] = json_data - elif not files: - # Don't set content when JSON is sent as multipart/form-data, - # since httpx's content param overrides other body arguments - kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None - kwargs["files"] = files - else: - headers.pop("Content-Type", None) - kwargs.pop("data", None) - - # TODO: report this error to httpx - return self._client.build_request( # pyright: ignore[reportUnknownMemberType] - headers=headers, - timeout=self.timeout if isinstance(options.timeout, NotGiven) else options.timeout, - method=options.method, - url=prepared_url, - # the `Query` type that we use is incompatible with qs' - # `Params` type as it needs to be typed as `Mapping[str, object]` - # so that passing a `TypedDict` doesn't cause an error. - # https://github.com/microsoft/pyright/issues/3526#event-6715453066 - params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None, - **kwargs, - ) - - def _serialize_multipartform(self, data: Mapping[object, object]) -> dict[str, object]: - items = self.qs.stringify_items( - # TODO: type ignore is required as stringify_items is well typed but we can't be - # well typed without heavy validation. - data, # type: ignore - array_format="brackets", - ) - serialized: dict[str, object] = {} - for key, value in items: - existing = serialized.get(key) - - if not existing: - serialized[key] = value - continue - - # If a value has already been set for this key then that - # means we're sending data like `array[]=[1, 2, 3]` and we - # need to tell httpx that we want to send multiple values with - # the same key which is done by using a list or a tuple. - # - # Note: 2d arrays should never result in the same key at both - # levels so it's safe to assume that if the value is a list, - # it was because we changed it to be a list. - if is_list(existing): - existing.append(value) - else: - serialized[key] = [existing, value] - - return serialized - - def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalRequestOptions) -> type[ResponseT]: - if not is_given(options.headers): - return cast_to - - # make a copy of the headers so we don't mutate user-input - headers = dict(options.headers) - - # we internally support defining a temporary header to override the - # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response` - # see _response.py for implementation details - override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, not_given) - if is_given(override_cast_to): - options.headers = headers - return cast(Type[ResponseT], override_cast_to) - - return cast_to - - def _should_stream_response_body(self, request: httpx.Request) -> bool: - return request.headers.get(RAW_RESPONSE_HEADER) == "stream" # type: ignore[no-any-return] - - def _process_response_data( - self, - *, - data: object, - cast_to: type[ResponseT], - response: httpx.Response, - ) -> ResponseT: - if data is None: - return cast(ResponseT, None) - - if cast_to is object: - return cast(ResponseT, data) - - try: - if inspect.isclass(cast_to) and issubclass(cast_to, ModelBuilderProtocol): - return cast(ResponseT, cast_to.build(response=response, data=data)) - - if self._strict_response_validation: - return cast(ResponseT, validate_type(type_=cast_to, value=data)) - - return cast(ResponseT, construct_type(type_=cast_to, value=data)) - except pydantic.ValidationError as err: - raise APIResponseValidationError(response=response, body=data) from err - - @property - def qs(self) -> Querystring: - return Querystring() - - @property - def custom_auth(self) -> httpx.Auth | None: - return None - - @property - def auth_headers(self) -> dict[str, str]: - return {} - - @property - def default_headers(self) -> dict[str, str | Omit]: - return { - "Accept": "application/json", - "Content-Type": "application/json", - "User-Agent": self.user_agent, - **self.platform_headers(), - **self.auth_headers, - **self._custom_headers, - } - - @property - def default_query(self) -> dict[str, object]: - return { - **self._custom_query, - } - - def _validate_headers( - self, - headers: Headers, # noqa: ARG002 - custom_headers: Headers, # noqa: ARG002 - ) -> None: - """Validate the given default headers and custom headers. - - Does nothing by default. - """ - return - - @property - def user_agent(self) -> str: - return f"{self.__class__.__name__}/Python {self._version}" - - @property - def base_url(self) -> URL: - return self._base_url - - @base_url.setter - def base_url(self, url: URL | str) -> None: - self._base_url = self._enforce_trailing_slash(url if isinstance(url, URL) else URL(url)) - - def platform_headers(self) -> Dict[str, str]: - # the actual implementation is in a separate `lru_cache` decorated - # function because adding `lru_cache` to methods will leak memory - # https://github.com/python/cpython/issues/88476 - return platform_headers(self._version, platform=self._platform) - - def _parse_retry_after_header(self, response_headers: Optional[httpx.Headers] = None) -> float | None: - """Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified. - - About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After - See also https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax - """ - if response_headers is None: - return None - - # First, try the non-standard `retry-after-ms` header for milliseconds, - # which is more precise than integer-seconds `retry-after` - try: - retry_ms_header = response_headers.get("retry-after-ms", None) - return float(retry_ms_header) / 1000 - except (TypeError, ValueError): - pass - - # Next, try parsing `retry-after` header as seconds (allowing nonstandard floats). - retry_header = response_headers.get("retry-after") - try: - # note: the spec indicates that this should only ever be an integer - # but if someone sends a float there's no reason for us to not respect it - return float(retry_header) - except (TypeError, ValueError): - pass - - # Last, try parsing `retry-after` as a date. - retry_date_tuple = email.utils.parsedate_tz(retry_header) - if retry_date_tuple is None: - return None - - retry_date = email.utils.mktime_tz(retry_date_tuple) - return float(retry_date - time.time()) - - def _calculate_retry_timeout( - self, - remaining_retries: int, - options: FinalRequestOptions, - response_headers: Optional[httpx.Headers] = None, - ) -> float: - max_retries = options.get_max_retries(self.max_retries) - - # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says. - retry_after = self._parse_retry_after_header(response_headers) - if retry_after is not None and 0 < retry_after <= 60: - return retry_after - - # Also cap retry count to 1000 to avoid any potential overflows with `pow` - nb_retries = min(max_retries - remaining_retries, 1000) - - # Apply exponential backoff, but not more than the max. - sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY) - - # Apply some jitter, plus-or-minus half a second. - jitter = 1 - 0.25 * random() - timeout = sleep_seconds * jitter - return timeout if timeout >= 0 else 0 - - def _should_retry(self, response: httpx.Response) -> bool: - # Note: this is not a standard header - should_retry_header = response.headers.get("x-should-retry") - - # If the server explicitly says whether or not to retry, obey. - if should_retry_header == "true": - log.debug("Retrying as header `x-should-retry` is set to `true`") - return True - if should_retry_header == "false": - log.debug("Not retrying as header `x-should-retry` is set to `false`") - return False - - # Retry on request timeouts. - if response.status_code == 408: - log.debug("Retrying due to status code %i", response.status_code) - return True - - # Retry on lock timeouts. - if response.status_code == 409: - log.debug("Retrying due to status code %i", response.status_code) - return True - - # Retry on rate limits. - if response.status_code == 429: - log.debug("Retrying due to status code %i", response.status_code) - return True - - # Retry internal errors. - if response.status_code >= 500: - log.debug("Retrying due to status code %i", response.status_code) - return True - - log.debug("Not retrying") - return False - - def _idempotency_key(self) -> str: - return f"stainless-python-retry-{uuid.uuid4()}" - - -class _DefaultHttpxClient(httpx.Client): - def __init__(self, **kwargs: Any) -> None: - kwargs.setdefault("timeout", DEFAULT_TIMEOUT) - kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) - kwargs.setdefault("follow_redirects", True) - super().__init__(**kwargs) - - -if TYPE_CHECKING: - DefaultHttpxClient = httpx.Client - """An alias to `httpx.Client` that provides the same defaults that this SDK - uses internally. - - This is useful because overriding the `http_client` with your own instance of - `httpx.Client` will result in httpx's defaults being used, not ours. - """ -else: - DefaultHttpxClient = _DefaultHttpxClient - - -class SyncHttpxClientWrapper(DefaultHttpxClient): - def __del__(self) -> None: - if self.is_closed: - return - - try: - self.close() - except Exception: - pass - - -class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]): - _client: httpx.Client - _default_stream_cls: type[Stream[Any]] | None = None - - def __init__( - self, - *, - version: str, - base_url: str | URL, - max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = not_given, - http_client: httpx.Client | None = None, - custom_headers: Mapping[str, str] | None = None, - custom_query: Mapping[str, object] | None = None, - _strict_response_validation: bool, - ) -> None: - if not is_given(timeout): - # if the user passed in a custom http client with a non-default - # timeout set then we use that timeout. - # - # note: there is an edge case here where the user passes in a client - # where they've explicitly set the timeout to match the default timeout - # as this check is structural, meaning that we'll think they didn't - # pass in a timeout and will ignore it - if http_client and http_client.timeout != HTTPX_DEFAULT_TIMEOUT: - timeout = http_client.timeout - else: - timeout = DEFAULT_TIMEOUT - - if http_client is not None and not isinstance(http_client, httpx.Client): # pyright: ignore[reportUnnecessaryIsInstance] - raise TypeError( - f"Invalid `http_client` argument; Expected an instance of `httpx.Client` but got {type(http_client)}" - ) - - super().__init__( - version=version, - # cast to a valid type because mypy doesn't understand our type narrowing - timeout=cast(Timeout, timeout), - base_url=base_url, - max_retries=max_retries, - custom_query=custom_query, - custom_headers=custom_headers, - _strict_response_validation=_strict_response_validation, - ) - self._client = http_client or SyncHttpxClientWrapper( - base_url=base_url, - # cast to a valid type because mypy doesn't understand our type narrowing - timeout=cast(Timeout, timeout), - ) - - def is_closed(self) -> bool: - return self._client.is_closed - - def close(self) -> None: - """Close the underlying HTTPX client. - - The client will *not* be usable after this. - """ - # If an error is thrown while constructing a client, self._client - # may not be present - if hasattr(self, "_client"): - self._client.close() - - def __enter__(self: _T) -> _T: - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - self.close() - - def _prepare_options( - self, - options: FinalRequestOptions, # noqa: ARG002 - ) -> FinalRequestOptions: - """Hook for mutating the given options""" - return options - - def _prepare_request( - self, - request: httpx.Request, # noqa: ARG002 - ) -> None: - """This method is used as a callback for mutating the `Request` object - after it has been constructed. - This is useful for cases where you want to add certain headers based off of - the request properties, e.g. `url`, `method` etc. - """ - return None - - @overload - def request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: Literal[True], - stream_cls: Type[_StreamT], - ) -> _StreamT: ... - - @overload - def request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: Literal[False] = False, - ) -> ResponseT: ... - - @overload - def request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: bool = False, - stream_cls: Type[_StreamT] | None = None, - ) -> ResponseT | _StreamT: ... - - def request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: bool = False, - stream_cls: type[_StreamT] | None = None, - ) -> ResponseT | _StreamT: - cast_to = self._maybe_override_cast_to(cast_to, options) - - # create a copy of the options we were given so that if the - # options are mutated later & we then retry, the retries are - # given the original options - input_options = model_copy(options) - if input_options.idempotency_key is None and input_options.method.lower() != "get": - # ensure the idempotency key is reused between requests - input_options.idempotency_key = self._idempotency_key() - - response: httpx.Response | None = None - max_retries = input_options.get_max_retries(self.max_retries) - - retries_taken = 0 - for retries_taken in range(max_retries + 1): - options = model_copy(input_options) - options = self._prepare_options(options) - - remaining_retries = max_retries - retries_taken - request = self._build_request(options, retries_taken=retries_taken) - self._prepare_request(request) - - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth - - if options.follow_redirects is not None: - kwargs["follow_redirects"] = options.follow_redirects - - log.debug("Sending HTTP Request: %s %s", request.method, request.url) - - response = None - try: - response = self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) - - if remaining_retries > 0: - self._sleep_for_retry( - retries_taken=retries_taken, - max_retries=max_retries, - options=input_options, - response=None, - ) - continue - - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) - - if remaining_retries > 0: - self._sleep_for_retry( - retries_taken=retries_taken, - max_retries=max_retries, - options=input_options, - response=None, - ) - continue - - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err - - log.debug( - 'HTTP Response: %s %s "%i %s" %s', - request.method, - request.url, - response.status_code, - response.reason_phrase, - response.headers, - ) - - try: - response.raise_for_status() - except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code - log.debug("Encountered httpx.HTTPStatusError", exc_info=True) - - if remaining_retries > 0 and self._should_retry(err.response): - err.response.close() - self._sleep_for_retry( - retries_taken=retries_taken, - max_retries=max_retries, - options=input_options, - response=response, - ) - continue - - # If the response is streamed then we need to explicitly read the response - # to completion before attempting to access the response text. - if not err.response.is_closed: - err.response.read() - - log.debug("Re-raising status error") - raise self._make_status_error_from_response(err.response) from None - - break - - assert response is not None, "could not resolve response (should never happen)" - return self._process_response( - cast_to=cast_to, - options=options, - response=response, - stream=stream, - stream_cls=stream_cls, - retries_taken=retries_taken, - ) - - def _sleep_for_retry( - self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None - ) -> None: - remaining_retries = max_retries - retries_taken - if remaining_retries == 1: - log.debug("1 retry left") - else: - log.debug("%i retries left", remaining_retries) - - timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) - log.info("Retrying request to %s in %f seconds", options.url, timeout) - - time.sleep(timeout) - - def _process_response( - self, - *, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - response: httpx.Response, - stream: bool, - stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, - retries_taken: int = 0, - ) -> ResponseT: - origin = get_origin(cast_to) or cast_to - - if ( - inspect.isclass(origin) - and issubclass(origin, BaseAPIResponse) - # we only want to actually return the custom BaseAPIResponse class if we're - # returning the raw response, or if we're not streaming SSE, as if we're streaming - # SSE then `cast_to` doesn't actively reflect the type we need to parse into - and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) - ): - if not issubclass(origin, APIResponse): - raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}") - - response_cls = cast("type[BaseAPIResponse[Any]]", cast_to) - return cast( - ResponseT, - response_cls( - raw=response, - client=self, - cast_to=extract_response_type(response_cls), - stream=stream, - stream_cls=stream_cls, - options=options, - retries_taken=retries_taken, - ), - ) - - if cast_to == httpx.Response: - return cast(ResponseT, response) - - api_response = APIResponse( - raw=response, - client=self, - cast_to=cast("type[ResponseT]", cast_to), # pyright: ignore[reportUnnecessaryCast] - stream=stream, - stream_cls=stream_cls, - options=options, - retries_taken=retries_taken, - ) - if bool(response.request.headers.get(RAW_RESPONSE_HEADER)): - return cast(ResponseT, api_response) - - return api_response.parse() - - def _request_api_list( - self, - model: Type[object], - page: Type[SyncPageT], - options: FinalRequestOptions, - ) -> SyncPageT: - def _parser(resp: SyncPageT) -> SyncPageT: - resp._set_private_attributes( - client=self, - model=model, - options=options, - ) - return resp - - options.post_parser = _parser - - return self.request(page, options, stream=False) - - @overload - def get( - self, - path: str, - *, - cast_to: Type[ResponseT], - options: RequestOptions = {}, - stream: Literal[False] = False, - ) -> ResponseT: ... - - @overload - def get( - self, - path: str, - *, - cast_to: Type[ResponseT], - options: RequestOptions = {}, - stream: Literal[True], - stream_cls: type[_StreamT], - ) -> _StreamT: ... - - @overload - def get( - self, - path: str, - *, - cast_to: Type[ResponseT], - options: RequestOptions = {}, - stream: bool, - stream_cls: type[_StreamT] | None = None, - ) -> ResponseT | _StreamT: ... - - def get( - self, - path: str, - *, - cast_to: Type[ResponseT], - options: RequestOptions = {}, - stream: bool = False, - stream_cls: type[_StreamT] | None = None, - ) -> ResponseT | _StreamT: - opts = FinalRequestOptions.construct(method="get", url=path, **options) - # cast is required because mypy complains about returning Any even though - # it understands the type variables - return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)) - - @overload - def post( - self, - path: str, - *, - cast_to: Type[ResponseT], - body: Body | None = None, - content: BinaryTypes | None = None, - options: RequestOptions = {}, - files: RequestFiles | None = None, - stream: Literal[False] = False, - ) -> ResponseT: ... - - @overload - def post( - self, - path: str, - *, - cast_to: Type[ResponseT], - body: Body | None = None, - content: BinaryTypes | None = None, - options: RequestOptions = {}, - files: RequestFiles | None = None, - stream: Literal[True], - stream_cls: type[_StreamT], - ) -> _StreamT: ... - - @overload - def post( - self, - path: str, - *, - cast_to: Type[ResponseT], - body: Body | None = None, - content: BinaryTypes | None = None, - options: RequestOptions = {}, - files: RequestFiles | None = None, - stream: bool, - stream_cls: type[_StreamT] | None = None, - ) -> ResponseT | _StreamT: ... - - def post( - self, - path: str, - *, - cast_to: Type[ResponseT], - body: Body | None = None, - content: BinaryTypes | None = None, - options: RequestOptions = {}, - files: RequestFiles | None = None, - stream: bool = False, - stream_cls: type[_StreamT] | None = None, - ) -> ResponseT | _StreamT: - if body is not None and content is not None: - raise TypeError("Passing both `body` and `content` is not supported") - if files is not None and content is not None: - raise TypeError("Passing both `files` and `content` is not supported") - if isinstance(body, bytes): - warnings.warn( - "Passing raw bytes as `body` is deprecated and will be removed in a future version. " - "Please pass raw bytes via the `content` parameter instead.", - DeprecationWarning, - stacklevel=2, - ) - opts = FinalRequestOptions.construct( - method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options - ) - return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)) - - def patch( - self, - path: str, - *, - cast_to: Type[ResponseT], - body: Body | None = None, - content: BinaryTypes | None = None, - files: RequestFiles | None = None, - options: RequestOptions = {}, - ) -> ResponseT: - if body is not None and content is not None: - raise TypeError("Passing both `body` and `content` is not supported") - if files is not None and content is not None: - raise TypeError("Passing both `files` and `content` is not supported") - if isinstance(body, bytes): - warnings.warn( - "Passing raw bytes as `body` is deprecated and will be removed in a future version. " - "Please pass raw bytes via the `content` parameter instead.", - DeprecationWarning, - stacklevel=2, - ) - opts = FinalRequestOptions.construct( - method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options - ) - return self.request(cast_to, opts) - - def put( - self, - path: str, - *, - cast_to: Type[ResponseT], - body: Body | None = None, - content: BinaryTypes | None = None, - files: RequestFiles | None = None, - options: RequestOptions = {}, - ) -> ResponseT: - if body is not None and content is not None: - raise TypeError("Passing both `body` and `content` is not supported") - if files is not None and content is not None: - raise TypeError("Passing both `files` and `content` is not supported") - if isinstance(body, bytes): - warnings.warn( - "Passing raw bytes as `body` is deprecated and will be removed in a future version. " - "Please pass raw bytes via the `content` parameter instead.", - DeprecationWarning, - stacklevel=2, - ) - opts = FinalRequestOptions.construct( - method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options - ) - return self.request(cast_to, opts) - - def delete( - self, - path: str, - *, - cast_to: Type[ResponseT], - body: Body | None = None, - content: BinaryTypes | None = None, - options: RequestOptions = {}, - ) -> ResponseT: - if body is not None and content is not None: - raise TypeError("Passing both `body` and `content` is not supported") - if isinstance(body, bytes): - warnings.warn( - "Passing raw bytes as `body` is deprecated and will be removed in a future version. " - "Please pass raw bytes via the `content` parameter instead.", - DeprecationWarning, - stacklevel=2, - ) - opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) - return self.request(cast_to, opts) - - def get_api_list( - self, - path: str, - *, - model: Type[object], - page: Type[SyncPageT], - body: Body | None = None, - options: RequestOptions = {}, - method: str = "get", - ) -> SyncPageT: - opts = FinalRequestOptions.construct(method=method, url=path, json_data=body, **options) - return self._request_api_list(model, page, opts) - - -class _DefaultAsyncHttpxClient(httpx.AsyncClient): - def __init__(self, **kwargs: Any) -> None: - kwargs.setdefault("timeout", DEFAULT_TIMEOUT) - kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) - kwargs.setdefault("follow_redirects", True) - super().__init__(**kwargs) - - -try: - import httpx_aiohttp -except ImportError: - - class _DefaultAioHttpClient(httpx.AsyncClient): - def __init__(self, **_kwargs: Any) -> None: - raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra") -else: - - class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore - def __init__(self, **kwargs: Any) -> None: - kwargs.setdefault("timeout", DEFAULT_TIMEOUT) - kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) - kwargs.setdefault("follow_redirects", True) - - super().__init__(**kwargs) - - -if TYPE_CHECKING: - DefaultAsyncHttpxClient = httpx.AsyncClient - """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK - uses internally. - - This is useful because overriding the `http_client` with your own instance of - `httpx.AsyncClient` will result in httpx's defaults being used, not ours. - """ - - DefaultAioHttpClient = httpx.AsyncClient - """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`.""" -else: - DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient - DefaultAioHttpClient = _DefaultAioHttpClient - - -class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient): - def __del__(self) -> None: - if self.is_closed: - return - - try: - # TODO(someday): support non asyncio runtimes here - asyncio.get_running_loop().create_task(self.aclose()) - except Exception: - pass - - -class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]): - _client: httpx.AsyncClient - _default_stream_cls: type[AsyncStream[Any]] | None = None - - def __init__( - self, - *, - version: str, - base_url: str | URL, - _strict_response_validation: bool, - max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = not_given, - http_client: httpx.AsyncClient | None = None, - custom_headers: Mapping[str, str] | None = None, - custom_query: Mapping[str, object] | None = None, - ) -> None: - if not is_given(timeout): - # if the user passed in a custom http client with a non-default - # timeout set then we use that timeout. - # - # note: there is an edge case here where the user passes in a client - # where they've explicitly set the timeout to match the default timeout - # as this check is structural, meaning that we'll think they didn't - # pass in a timeout and will ignore it - if http_client and http_client.timeout != HTTPX_DEFAULT_TIMEOUT: - timeout = http_client.timeout - else: - timeout = DEFAULT_TIMEOUT - - if http_client is not None and not isinstance(http_client, httpx.AsyncClient): # pyright: ignore[reportUnnecessaryIsInstance] - raise TypeError( - f"Invalid `http_client` argument; Expected an instance of `httpx.AsyncClient` but got {type(http_client)}" - ) - - super().__init__( - version=version, - base_url=base_url, - # cast to a valid type because mypy doesn't understand our type narrowing - timeout=cast(Timeout, timeout), - max_retries=max_retries, - custom_query=custom_query, - custom_headers=custom_headers, - _strict_response_validation=_strict_response_validation, - ) - self._client = http_client or AsyncHttpxClientWrapper( - base_url=base_url, - # cast to a valid type because mypy doesn't understand our type narrowing - timeout=cast(Timeout, timeout), - ) - - def is_closed(self) -> bool: - return self._client.is_closed - - async def close(self) -> None: - """Close the underlying HTTPX client. - - The client will *not* be usable after this. - """ - await self._client.aclose() - - async def __aenter__(self: _T) -> _T: - return self - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - await self.close() - - async def _prepare_options( - self, - options: FinalRequestOptions, # noqa: ARG002 - ) -> FinalRequestOptions: - """Hook for mutating the given options""" - return options - - async def _prepare_request( - self, - request: httpx.Request, # noqa: ARG002 - ) -> None: - """This method is used as a callback for mutating the `Request` object - after it has been constructed. - This is useful for cases where you want to add certain headers based off of - the request properties, e.g. `url`, `method` etc. - """ - return None - - @overload - async def request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: Literal[False] = False, - ) -> ResponseT: ... - - @overload - async def request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: Literal[True], - stream_cls: type[_AsyncStreamT], - ) -> _AsyncStreamT: ... - - @overload - async def request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: bool, - stream_cls: type[_AsyncStreamT] | None = None, - ) -> ResponseT | _AsyncStreamT: ... - - async def request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: bool = False, - stream_cls: type[_AsyncStreamT] | None = None, - ) -> ResponseT | _AsyncStreamT: - if self._platform is None: - # `get_platform` can make blocking IO calls so we - # execute it earlier while we are in an async context - self._platform = await asyncify(get_platform)() - - cast_to = self._maybe_override_cast_to(cast_to, options) - - # create a copy of the options we were given so that if the - # options are mutated later & we then retry, the retries are - # given the original options - input_options = model_copy(options) - if input_options.idempotency_key is None and input_options.method.lower() != "get": - # ensure the idempotency key is reused between requests - input_options.idempotency_key = self._idempotency_key() - - response: httpx.Response | None = None - max_retries = input_options.get_max_retries(self.max_retries) - - retries_taken = 0 - for retries_taken in range(max_retries + 1): - options = model_copy(input_options) - options = await self._prepare_options(options) - - remaining_retries = max_retries - retries_taken - request = self._build_request(options, retries_taken=retries_taken) - await self._prepare_request(request) - - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth - - if options.follow_redirects is not None: - kwargs["follow_redirects"] = options.follow_redirects - - log.debug("Sending HTTP Request: %s %s", request.method, request.url) - - response = None - try: - response = await self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) - - if remaining_retries > 0: - await self._sleep_for_retry( - retries_taken=retries_taken, - max_retries=max_retries, - options=input_options, - response=None, - ) - continue - - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) - - if remaining_retries > 0: - await self._sleep_for_retry( - retries_taken=retries_taken, - max_retries=max_retries, - options=input_options, - response=None, - ) - continue - - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err - - log.debug( - 'HTTP Response: %s %s "%i %s" %s', - request.method, - request.url, - response.status_code, - response.reason_phrase, - response.headers, - ) - - try: - response.raise_for_status() - except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code - log.debug("Encountered httpx.HTTPStatusError", exc_info=True) - - if remaining_retries > 0 and self._should_retry(err.response): - await err.response.aclose() - await self._sleep_for_retry( - retries_taken=retries_taken, - max_retries=max_retries, - options=input_options, - response=response, - ) - continue - - # If the response is streamed then we need to explicitly read the response - # to completion before attempting to access the response text. - if not err.response.is_closed: - await err.response.aread() - - log.debug("Re-raising status error") - raise self._make_status_error_from_response(err.response) from None - - break - - assert response is not None, "could not resolve response (should never happen)" - return await self._process_response( - cast_to=cast_to, - options=options, - response=response, - stream=stream, - stream_cls=stream_cls, - retries_taken=retries_taken, - ) - - async def _sleep_for_retry( - self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None - ) -> None: - remaining_retries = max_retries - retries_taken - if remaining_retries == 1: - log.debug("1 retry left") - else: - log.debug("%i retries left", remaining_retries) - - timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) - log.info("Retrying request to %s in %f seconds", options.url, timeout) - - await anyio.sleep(timeout) - - async def _process_response( - self, - *, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - response: httpx.Response, - stream: bool, - stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, - retries_taken: int = 0, - ) -> ResponseT: - origin = get_origin(cast_to) or cast_to - - if ( - inspect.isclass(origin) - and issubclass(origin, BaseAPIResponse) - # we only want to actually return the custom BaseAPIResponse class if we're - # returning the raw response, or if we're not streaming SSE, as if we're streaming - # SSE then `cast_to` doesn't actively reflect the type we need to parse into - and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) - ): - if not issubclass(origin, AsyncAPIResponse): - raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}") - - response_cls = cast("type[BaseAPIResponse[Any]]", cast_to) - return cast( - "ResponseT", - response_cls( - raw=response, - client=self, - cast_to=extract_response_type(response_cls), - stream=stream, - stream_cls=stream_cls, - options=options, - retries_taken=retries_taken, - ), - ) - - if cast_to == httpx.Response: - return cast(ResponseT, response) - - api_response = AsyncAPIResponse( - raw=response, - client=self, - cast_to=cast("type[ResponseT]", cast_to), # pyright: ignore[reportUnnecessaryCast] - stream=stream, - stream_cls=stream_cls, - options=options, - retries_taken=retries_taken, - ) - if bool(response.request.headers.get(RAW_RESPONSE_HEADER)): - return cast(ResponseT, api_response) - - return await api_response.parse() - - def _request_api_list( - self, - model: Type[_T], - page: Type[AsyncPageT], - options: FinalRequestOptions, - ) -> AsyncPaginator[_T, AsyncPageT]: - return AsyncPaginator(client=self, options=options, page_cls=page, model=model) - - @overload - async def get( - self, - path: str, - *, - cast_to: Type[ResponseT], - options: RequestOptions = {}, - stream: Literal[False] = False, - ) -> ResponseT: ... - - @overload - async def get( - self, - path: str, - *, - cast_to: Type[ResponseT], - options: RequestOptions = {}, - stream: Literal[True], - stream_cls: type[_AsyncStreamT], - ) -> _AsyncStreamT: ... - - @overload - async def get( - self, - path: str, - *, - cast_to: Type[ResponseT], - options: RequestOptions = {}, - stream: bool, - stream_cls: type[_AsyncStreamT] | None = None, - ) -> ResponseT | _AsyncStreamT: ... - - async def get( - self, - path: str, - *, - cast_to: Type[ResponseT], - options: RequestOptions = {}, - stream: bool = False, - stream_cls: type[_AsyncStreamT] | None = None, - ) -> ResponseT | _AsyncStreamT: - opts = FinalRequestOptions.construct(method="get", url=path, **options) - return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls) - - @overload - async def post( - self, - path: str, - *, - cast_to: Type[ResponseT], - body: Body | None = None, - content: AsyncBinaryTypes | None = None, - files: RequestFiles | None = None, - options: RequestOptions = {}, - stream: Literal[False] = False, - ) -> ResponseT: ... - - @overload - async def post( - self, - path: str, - *, - cast_to: Type[ResponseT], - body: Body | None = None, - content: AsyncBinaryTypes | None = None, - files: RequestFiles | None = None, - options: RequestOptions = {}, - stream: Literal[True], - stream_cls: type[_AsyncStreamT], - ) -> _AsyncStreamT: ... - - @overload - async def post( - self, - path: str, - *, - cast_to: Type[ResponseT], - body: Body | None = None, - content: AsyncBinaryTypes | None = None, - files: RequestFiles | None = None, - options: RequestOptions = {}, - stream: bool, - stream_cls: type[_AsyncStreamT] | None = None, - ) -> ResponseT | _AsyncStreamT: ... - - async def post( - self, - path: str, - *, - cast_to: Type[ResponseT], - body: Body | None = None, - content: AsyncBinaryTypes | None = None, - files: RequestFiles | None = None, - options: RequestOptions = {}, - stream: bool = False, - stream_cls: type[_AsyncStreamT] | None = None, - ) -> ResponseT | _AsyncStreamT: - if body is not None and content is not None: - raise TypeError("Passing both `body` and `content` is not supported") - if files is not None and content is not None: - raise TypeError("Passing both `files` and `content` is not supported") - if isinstance(body, bytes): - warnings.warn( - "Passing raw bytes as `body` is deprecated and will be removed in a future version. " - "Please pass raw bytes via the `content` parameter instead.", - DeprecationWarning, - stacklevel=2, - ) - opts = FinalRequestOptions.construct( - method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options - ) - return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls) - - async def patch( - self, - path: str, - *, - cast_to: Type[ResponseT], - body: Body | None = None, - content: AsyncBinaryTypes | None = None, - files: RequestFiles | None = None, - options: RequestOptions = {}, - ) -> ResponseT: - if body is not None and content is not None: - raise TypeError("Passing both `body` and `content` is not supported") - if files is not None and content is not None: - raise TypeError("Passing both `files` and `content` is not supported") - if isinstance(body, bytes): - warnings.warn( - "Passing raw bytes as `body` is deprecated and will be removed in a future version. " - "Please pass raw bytes via the `content` parameter instead.", - DeprecationWarning, - stacklevel=2, - ) - opts = FinalRequestOptions.construct( - method="patch", - url=path, - json_data=body, - content=content, - files=await async_to_httpx_files(files), - **options, - ) - return await self.request(cast_to, opts) - - async def put( - self, - path: str, - *, - cast_to: Type[ResponseT], - body: Body | None = None, - content: AsyncBinaryTypes | None = None, - files: RequestFiles | None = None, - options: RequestOptions = {}, - ) -> ResponseT: - if body is not None and content is not None: - raise TypeError("Passing both `body` and `content` is not supported") - if files is not None and content is not None: - raise TypeError("Passing both `files` and `content` is not supported") - if isinstance(body, bytes): - warnings.warn( - "Passing raw bytes as `body` is deprecated and will be removed in a future version. " - "Please pass raw bytes via the `content` parameter instead.", - DeprecationWarning, - stacklevel=2, - ) - opts = FinalRequestOptions.construct( - method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options - ) - return await self.request(cast_to, opts) - - async def delete( - self, - path: str, - *, - cast_to: Type[ResponseT], - body: Body | None = None, - content: AsyncBinaryTypes | None = None, - options: RequestOptions = {}, - ) -> ResponseT: - if body is not None and content is not None: - raise TypeError("Passing both `body` and `content` is not supported") - if isinstance(body, bytes): - warnings.warn( - "Passing raw bytes as `body` is deprecated and will be removed in a future version. " - "Please pass raw bytes via the `content` parameter instead.", - DeprecationWarning, - stacklevel=2, - ) - opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) - return await self.request(cast_to, opts) - - def get_api_list( - self, - path: str, - *, - model: Type[_T], - page: Type[AsyncPageT], - body: Body | None = None, - options: RequestOptions = {}, - method: str = "get", - ) -> AsyncPaginator[_T, AsyncPageT]: - opts = FinalRequestOptions.construct(method=method, url=path, json_data=body, **options) - return self._request_api_list(model, page, opts) - - -def make_request_options( - *, - query: Query | None = None, - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - idempotency_key: str | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - post_parser: PostParser | NotGiven = not_given, -) -> RequestOptions: - """Create a dict of type RequestOptions without keys of NotGiven values.""" - options: RequestOptions = {} - if extra_headers is not None: - options["headers"] = extra_headers - - if extra_body is not None: - options["extra_json"] = cast(AnyMapping, extra_body) - - if query is not None: - options["params"] = query - - if extra_query is not None: - options["params"] = {**options.get("params", {}), **extra_query} - - if not isinstance(timeout, NotGiven): - options["timeout"] = timeout - - if idempotency_key is not None: - options["idempotency_key"] = idempotency_key - - if is_given(post_parser): - # internal - options["post_parser"] = post_parser # type: ignore - - return options - - -class ForceMultipartDict(Dict[str, None]): - def __bool__(self) -> bool: - return True - - -class OtherPlatform: - def __init__(self, name: str) -> None: - self.name = name - - @override - def __str__(self) -> str: - return f"Other:{self.name}" - - -Platform = Union[ - OtherPlatform, - Literal[ - "MacOS", - "Linux", - "Windows", - "FreeBSD", - "OpenBSD", - "iOS", - "Android", - "Unknown", - ], -] - - -def get_platform() -> Platform: - try: - system = platform.system().lower() - platform_name = platform.platform().lower() - except Exception: - return "Unknown" - - if "iphone" in platform_name or "ipad" in platform_name: - # Tested using Python3IDE on an iPhone 11 and Pythonista on an iPad 7 - # system is Darwin and platform_name is a string like: - # - Darwin-21.6.0-iPhone12,1-64bit - # - Darwin-21.6.0-iPad7,11-64bit - return "iOS" - - if system == "darwin": - return "MacOS" - - if system == "windows": - return "Windows" - - if "android" in platform_name: - # Tested using Pydroid 3 - # system is Linux and platform_name is a string like 'Linux-5.10.81-android12-9-00001-geba40aecb3b7-ab8534902-aarch64-with-libc' - return "Android" - - if system == "linux": - # https://distro.readthedocs.io/en/latest/#distro.id - distro_id = distro.id() - if distro_id == "freebsd": - return "FreeBSD" - - if distro_id == "openbsd": - return "OpenBSD" - - return "Linux" - - if platform_name: - return OtherPlatform(platform_name) - - return "Unknown" - - -@lru_cache(maxsize=None) -def platform_headers(version: str, *, platform: Platform | None) -> Dict[str, str]: - return { - "X-Stainless-Lang": "python", - "X-Stainless-Package-Version": version, - "X-Stainless-OS": str(platform or get_platform()), - "X-Stainless-Arch": str(get_architecture()), - "X-Stainless-Runtime": get_python_runtime(), - "X-Stainless-Runtime-Version": get_python_version(), - } - - -class OtherArch: - def __init__(self, name: str) -> None: - self.name = name - - @override - def __str__(self) -> str: - return f"other:{self.name}" - - -Arch = Union[OtherArch, Literal["x32", "x64", "arm", "arm64", "unknown"]] - - -def get_python_runtime() -> str: - try: - return platform.python_implementation() - except Exception: - return "unknown" - - -def get_python_version() -> str: - try: - return platform.python_version() - except Exception: - return "unknown" - - -def get_architecture() -> Arch: - try: - machine = platform.machine().lower() - except Exception: - return "unknown" - - if machine in ("arm64", "aarch64"): - return "arm64" - - # TODO: untested - if machine == "arm": - return "arm" - - if machine == "x86_64": - return "x64" - - # TODO: untested - if sys.maxsize <= 2**32: - return "x32" - - if machine: - return OtherArch(machine) - - return "unknown" - - -def _merge_mappings( - obj1: Mapping[_T_co, Union[_T, Omit]], - obj2: Mapping[_T_co, Union[_T, Omit]], -) -> Dict[_T_co, _T]: - """Merge two mappings of the same type, removing any values that are instances of `Omit`. - - In cases with duplicate keys the second mapping takes precedence. - """ - merged = {**obj1, **obj2} - return {key: value for key, value in merged.items() if not isinstance(value, Omit)} diff --git a/src/moderation_api/_client.py b/src/moderation_api/_client.py deleted file mode 100644 index 6bdd9f5..0000000 --- a/src/moderation_api/_client.py +++ /dev/null @@ -1,775 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import TYPE_CHECKING, Any, Mapping -from typing_extensions import Self, override - -import httpx - -from . import _exceptions -from ._qs import Querystring -from ._types import ( - Omit, - Timeout, - NotGiven, - Transport, - ProxiesTypes, - RequestOptions, - not_given, -) -from ._utils import ( - is_given, - is_mapping_t, - get_async_library, -) -from ._compat import cached_property -from ._version import __version__ -from ._streaming import Stream as Stream, AsyncStream as AsyncStream -from ._exceptions import APIStatusError, ModerationAPIError -from ._base_client import ( - DEFAULT_MAX_RETRIES, - SyncAPIClient, - AsyncAPIClient, -) - -if TYPE_CHECKING: - from .resources import auth, queue, account, actions, authors, content, webhooks, wordlist, webhook_secret - from .resources.auth import AuthResource, AsyncAuthResource - from .resources.account import AccountResource, AsyncAccountResource - from .resources.authors import AuthorsResource, AsyncAuthorsResource - from .resources.content import ContentResource, AsyncContentResource - from .resources.webhooks import WebhooksResource, AsyncWebhooksResource - from .resources.queue.queue import QueueResource, AsyncQueueResource - from .resources.webhook_secret import WebhookSecretResource, AsyncWebhookSecretResource - from .resources.actions.actions import ActionsResource, AsyncActionsResource - from .resources.wordlist.wordlist import WordlistResource, AsyncWordlistResource - -__all__ = [ - "Timeout", - "Transport", - "ProxiesTypes", - "RequestOptions", - "ModerationAPI", - "AsyncModerationAPI", - "Client", - "AsyncClient", -] - - -class ModerationAPI(SyncAPIClient): - # client options - secret_key: str - - def __init__( - self, - *, - secret_key: str | None = None, - base_url: str | httpx.URL | None = None, - timeout: float | Timeout | None | NotGiven = not_given, - max_retries: int = DEFAULT_MAX_RETRIES, - default_headers: Mapping[str, str] | None = None, - default_query: Mapping[str, object] | None = None, - # Configure a custom httpx client. - # We provide a `DefaultHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`. - # See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details. - http_client: httpx.Client | None = None, - # Enable or disable schema validation for data returned by the API. - # When enabled an error APIResponseValidationError is raised - # if the API responds with invalid data for the expected schema. - # - # This parameter may be removed or changed in the future. - # If you rely on this feature, please open a GitHub issue - # outlining your use-case to help us decide if it should be - # part of our public interface in the future. - _strict_response_validation: bool = False, - ) -> None: - """Construct a new synchronous ModerationAPI client instance. - - This automatically infers the `secret_key` argument from the `MODAPI_SECRET_KEY` environment variable if it is not provided. - """ - if secret_key is None: - secret_key = os.environ.get("MODAPI_SECRET_KEY") - if secret_key is None: - raise ModerationAPIError( - "The secret_key client option must be set either by passing secret_key to the client or by setting the MODAPI_SECRET_KEY environment variable" - ) - self.secret_key = secret_key - - if base_url is None: - base_url = os.environ.get("MODERATION_API_BASE_URL") - self._base_url_overridden = base_url is not None - if base_url is None: - base_url = f"https://api.moderationapi.com/v1" - - custom_headers_env = os.environ.get("MODERATION_API_CUSTOM_HEADERS") - if custom_headers_env is not None: - parsed: dict[str, str] = {} - for line in custom_headers_env.split("\n"): - colon = line.find(":") - if colon >= 0: - parsed[line[:colon].strip()] = line[colon + 1 :].strip() - default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})} - - super().__init__( - version=__version__, - base_url=base_url, - max_retries=max_retries, - timeout=timeout, - http_client=http_client, - custom_headers=default_headers, - custom_query=default_query, - _strict_response_validation=_strict_response_validation, - ) - - @cached_property - def authors(self) -> AuthorsResource: - from .resources.authors import AuthorsResource - - return AuthorsResource(self) - - @cached_property - def queue(self) -> QueueResource: - from .resources.queue import QueueResource - - return QueueResource(self) - - @cached_property - def actions(self) -> ActionsResource: - from .resources.actions import ActionsResource - - return ActionsResource(self) - - @cached_property - def content(self) -> ContentResource: - from .resources.content import ContentResource - - return ContentResource(self) - - @cached_property - def account(self) -> AccountResource: - from .resources.account import AccountResource - - return AccountResource(self) - - @cached_property - def auth(self) -> AuthResource: - from .resources.auth import AuthResource - - return AuthResource(self) - - @cached_property - def wordlist(self) -> WordlistResource: - from .resources.wordlist import WordlistResource - - return WordlistResource(self) - - @cached_property - def webhooks(self) -> WebhooksResource: - from .resources.webhooks import WebhooksResource - - return WebhooksResource(self) - - @cached_property - def webhook_secret(self) -> WebhookSecretResource: - from .resources.webhook_secret import WebhookSecretResource - - return WebhookSecretResource(self) - - @cached_property - def with_raw_response(self) -> ModerationAPIWithRawResponse: - return ModerationAPIWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> ModerationAPIWithStreamedResponse: - return ModerationAPIWithStreamedResponse(self) - - @property - @override - def qs(self) -> Querystring: - return Querystring(array_format="comma") - - @property - @override - def auth_headers(self) -> dict[str, str]: - secret_key = self.secret_key - return {"Authorization": f"Bearer {secret_key}"} - - @property - @override - def default_headers(self) -> dict[str, str | Omit]: - return { - **super().default_headers, - "X-Stainless-Async": "false", - **self._custom_headers, - } - - def copy( - self, - *, - secret_key: str | None = None, - base_url: str | httpx.URL | None = None, - timeout: float | Timeout | None | NotGiven = not_given, - http_client: httpx.Client | None = None, - max_retries: int | NotGiven = not_given, - default_headers: Mapping[str, str] | None = None, - set_default_headers: Mapping[str, str] | None = None, - default_query: Mapping[str, object] | None = None, - set_default_query: Mapping[str, object] | None = None, - _extra_kwargs: Mapping[str, Any] = {}, - ) -> Self: - """ - Create a new client instance re-using the same options given to the current client with optional overriding. - """ - if default_headers is not None and set_default_headers is not None: - raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive") - - if default_query is not None and set_default_query is not None: - raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive") - - headers = self._custom_headers - if default_headers is not None: - headers = {**headers, **default_headers} - elif set_default_headers is not None: - headers = set_default_headers - - params = self._custom_query - if default_query is not None: - params = {**params, **default_query} - elif set_default_query is not None: - params = set_default_query - - http_client = http_client or self._client - client = self.__class__( - secret_key=secret_key or self.secret_key, - base_url=base_url or self.base_url, - timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, - http_client=http_client, - max_retries=max_retries if is_given(max_retries) else self.max_retries, - default_headers=headers, - default_query=params, - **_extra_kwargs, - ) - client._base_url_overridden = self._base_url_overridden or base_url is not None - return client - - # Alias for `copy` for nicer inline usage, e.g. - # client.with_options(timeout=10).foo.create(...) - with_options = copy - - @override - def _make_status_error( - self, - err_msg: str, - *, - body: object, - response: httpx.Response, - ) -> APIStatusError: - if response.status_code == 400: - return _exceptions.BadRequestError(err_msg, response=response, body=body) - - if response.status_code == 401: - return _exceptions.AuthenticationError(err_msg, response=response, body=body) - - if response.status_code == 403: - return _exceptions.PermissionDeniedError(err_msg, response=response, body=body) - - if response.status_code == 404: - return _exceptions.NotFoundError(err_msg, response=response, body=body) - - if response.status_code == 409: - return _exceptions.ConflictError(err_msg, response=response, body=body) - - if response.status_code == 422: - return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body) - - if response.status_code == 429: - return _exceptions.RateLimitError(err_msg, response=response, body=body) - - if response.status_code >= 500: - return _exceptions.InternalServerError(err_msg, response=response, body=body) - return APIStatusError(err_msg, response=response, body=body) - - -class AsyncModerationAPI(AsyncAPIClient): - # client options - secret_key: str - - def __init__( - self, - *, - secret_key: str | None = None, - base_url: str | httpx.URL | None = None, - timeout: float | Timeout | None | NotGiven = not_given, - max_retries: int = DEFAULT_MAX_RETRIES, - default_headers: Mapping[str, str] | None = None, - default_query: Mapping[str, object] | None = None, - # Configure a custom httpx client. - # We provide a `DefaultAsyncHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`. - # See the [httpx documentation](https://www.python-httpx.org/api/#asyncclient) for more details. - http_client: httpx.AsyncClient | None = None, - # Enable or disable schema validation for data returned by the API. - # When enabled an error APIResponseValidationError is raised - # if the API responds with invalid data for the expected schema. - # - # This parameter may be removed or changed in the future. - # If you rely on this feature, please open a GitHub issue - # outlining your use-case to help us decide if it should be - # part of our public interface in the future. - _strict_response_validation: bool = False, - ) -> None: - """Construct a new async AsyncModerationAPI client instance. - - This automatically infers the `secret_key` argument from the `MODAPI_SECRET_KEY` environment variable if it is not provided. - """ - if secret_key is None: - secret_key = os.environ.get("MODAPI_SECRET_KEY") - if secret_key is None: - raise ModerationAPIError( - "The secret_key client option must be set either by passing secret_key to the client or by setting the MODAPI_SECRET_KEY environment variable" - ) - self.secret_key = secret_key - - if base_url is None: - base_url = os.environ.get("MODERATION_API_BASE_URL") - self._base_url_overridden = base_url is not None - if base_url is None: - base_url = f"https://api.moderationapi.com/v1" - - custom_headers_env = os.environ.get("MODERATION_API_CUSTOM_HEADERS") - if custom_headers_env is not None: - parsed: dict[str, str] = {} - for line in custom_headers_env.split("\n"): - colon = line.find(":") - if colon >= 0: - parsed[line[:colon].strip()] = line[colon + 1 :].strip() - default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})} - - super().__init__( - version=__version__, - base_url=base_url, - max_retries=max_retries, - timeout=timeout, - http_client=http_client, - custom_headers=default_headers, - custom_query=default_query, - _strict_response_validation=_strict_response_validation, - ) - - @cached_property - def authors(self) -> AsyncAuthorsResource: - from .resources.authors import AsyncAuthorsResource - - return AsyncAuthorsResource(self) - - @cached_property - def queue(self) -> AsyncQueueResource: - from .resources.queue import AsyncQueueResource - - return AsyncQueueResource(self) - - @cached_property - def actions(self) -> AsyncActionsResource: - from .resources.actions import AsyncActionsResource - - return AsyncActionsResource(self) - - @cached_property - def content(self) -> AsyncContentResource: - from .resources.content import AsyncContentResource - - return AsyncContentResource(self) - - @cached_property - def account(self) -> AsyncAccountResource: - from .resources.account import AsyncAccountResource - - return AsyncAccountResource(self) - - @cached_property - def auth(self) -> AsyncAuthResource: - from .resources.auth import AsyncAuthResource - - return AsyncAuthResource(self) - - @cached_property - def wordlist(self) -> AsyncWordlistResource: - from .resources.wordlist import AsyncWordlistResource - - return AsyncWordlistResource(self) - - @cached_property - def webhooks(self) -> AsyncWebhooksResource: - from .resources.webhooks import AsyncWebhooksResource - - return AsyncWebhooksResource(self) - - @cached_property - def webhook_secret(self) -> AsyncWebhookSecretResource: - from .resources.webhook_secret import AsyncWebhookSecretResource - - return AsyncWebhookSecretResource(self) - - @cached_property - def with_raw_response(self) -> AsyncModerationAPIWithRawResponse: - return AsyncModerationAPIWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncModerationAPIWithStreamedResponse: - return AsyncModerationAPIWithStreamedResponse(self) - - @property - @override - def qs(self) -> Querystring: - return Querystring(array_format="comma") - - @property - @override - def auth_headers(self) -> dict[str, str]: - secret_key = self.secret_key - return {"Authorization": f"Bearer {secret_key}"} - - @property - @override - def default_headers(self) -> dict[str, str | Omit]: - return { - **super().default_headers, - "X-Stainless-Async": f"async:{get_async_library()}", - **self._custom_headers, - } - - def copy( - self, - *, - secret_key: str | None = None, - base_url: str | httpx.URL | None = None, - timeout: float | Timeout | None | NotGiven = not_given, - http_client: httpx.AsyncClient | None = None, - max_retries: int | NotGiven = not_given, - default_headers: Mapping[str, str] | None = None, - set_default_headers: Mapping[str, str] | None = None, - default_query: Mapping[str, object] | None = None, - set_default_query: Mapping[str, object] | None = None, - _extra_kwargs: Mapping[str, Any] = {}, - ) -> Self: - """ - Create a new client instance re-using the same options given to the current client with optional overriding. - """ - if default_headers is not None and set_default_headers is not None: - raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive") - - if default_query is not None and set_default_query is not None: - raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive") - - headers = self._custom_headers - if default_headers is not None: - headers = {**headers, **default_headers} - elif set_default_headers is not None: - headers = set_default_headers - - params = self._custom_query - if default_query is not None: - params = {**params, **default_query} - elif set_default_query is not None: - params = set_default_query - - http_client = http_client or self._client - client = self.__class__( - secret_key=secret_key or self.secret_key, - base_url=base_url or self.base_url, - timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, - http_client=http_client, - max_retries=max_retries if is_given(max_retries) else self.max_retries, - default_headers=headers, - default_query=params, - **_extra_kwargs, - ) - client._base_url_overridden = self._base_url_overridden or base_url is not None - return client - - # Alias for `copy` for nicer inline usage, e.g. - # client.with_options(timeout=10).foo.create(...) - with_options = copy - - @override - def _make_status_error( - self, - err_msg: str, - *, - body: object, - response: httpx.Response, - ) -> APIStatusError: - if response.status_code == 400: - return _exceptions.BadRequestError(err_msg, response=response, body=body) - - if response.status_code == 401: - return _exceptions.AuthenticationError(err_msg, response=response, body=body) - - if response.status_code == 403: - return _exceptions.PermissionDeniedError(err_msg, response=response, body=body) - - if response.status_code == 404: - return _exceptions.NotFoundError(err_msg, response=response, body=body) - - if response.status_code == 409: - return _exceptions.ConflictError(err_msg, response=response, body=body) - - if response.status_code == 422: - return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body) - - if response.status_code == 429: - return _exceptions.RateLimitError(err_msg, response=response, body=body) - - if response.status_code >= 500: - return _exceptions.InternalServerError(err_msg, response=response, body=body) - return APIStatusError(err_msg, response=response, body=body) - - -class ModerationAPIWithRawResponse: - _client: ModerationAPI - - def __init__(self, client: ModerationAPI) -> None: - self._client = client - - @cached_property - def authors(self) -> authors.AuthorsResourceWithRawResponse: - from .resources.authors import AuthorsResourceWithRawResponse - - return AuthorsResourceWithRawResponse(self._client.authors) - - @cached_property - def queue(self) -> queue.QueueResourceWithRawResponse: - from .resources.queue import QueueResourceWithRawResponse - - return QueueResourceWithRawResponse(self._client.queue) - - @cached_property - def actions(self) -> actions.ActionsResourceWithRawResponse: - from .resources.actions import ActionsResourceWithRawResponse - - return ActionsResourceWithRawResponse(self._client.actions) - - @cached_property - def content(self) -> content.ContentResourceWithRawResponse: - from .resources.content import ContentResourceWithRawResponse - - return ContentResourceWithRawResponse(self._client.content) - - @cached_property - def account(self) -> account.AccountResourceWithRawResponse: - from .resources.account import AccountResourceWithRawResponse - - return AccountResourceWithRawResponse(self._client.account) - - @cached_property - def auth(self) -> auth.AuthResourceWithRawResponse: - from .resources.auth import AuthResourceWithRawResponse - - return AuthResourceWithRawResponse(self._client.auth) - - @cached_property - def wordlist(self) -> wordlist.WordlistResourceWithRawResponse: - from .resources.wordlist import WordlistResourceWithRawResponse - - return WordlistResourceWithRawResponse(self._client.wordlist) - - @cached_property - def webhooks(self) -> webhooks.WebhooksResourceWithRawResponse: - from .resources.webhooks import WebhooksResourceWithRawResponse - - return WebhooksResourceWithRawResponse(self._client.webhooks) - - @cached_property - def webhook_secret(self) -> webhook_secret.WebhookSecretResourceWithRawResponse: - from .resources.webhook_secret import WebhookSecretResourceWithRawResponse - - return WebhookSecretResourceWithRawResponse(self._client.webhook_secret) - - -class AsyncModerationAPIWithRawResponse: - _client: AsyncModerationAPI - - def __init__(self, client: AsyncModerationAPI) -> None: - self._client = client - - @cached_property - def authors(self) -> authors.AsyncAuthorsResourceWithRawResponse: - from .resources.authors import AsyncAuthorsResourceWithRawResponse - - return AsyncAuthorsResourceWithRawResponse(self._client.authors) - - @cached_property - def queue(self) -> queue.AsyncQueueResourceWithRawResponse: - from .resources.queue import AsyncQueueResourceWithRawResponse - - return AsyncQueueResourceWithRawResponse(self._client.queue) - - @cached_property - def actions(self) -> actions.AsyncActionsResourceWithRawResponse: - from .resources.actions import AsyncActionsResourceWithRawResponse - - return AsyncActionsResourceWithRawResponse(self._client.actions) - - @cached_property - def content(self) -> content.AsyncContentResourceWithRawResponse: - from .resources.content import AsyncContentResourceWithRawResponse - - return AsyncContentResourceWithRawResponse(self._client.content) - - @cached_property - def account(self) -> account.AsyncAccountResourceWithRawResponse: - from .resources.account import AsyncAccountResourceWithRawResponse - - return AsyncAccountResourceWithRawResponse(self._client.account) - - @cached_property - def auth(self) -> auth.AsyncAuthResourceWithRawResponse: - from .resources.auth import AsyncAuthResourceWithRawResponse - - return AsyncAuthResourceWithRawResponse(self._client.auth) - - @cached_property - def wordlist(self) -> wordlist.AsyncWordlistResourceWithRawResponse: - from .resources.wordlist import AsyncWordlistResourceWithRawResponse - - return AsyncWordlistResourceWithRawResponse(self._client.wordlist) - - @cached_property - def webhooks(self) -> webhooks.AsyncWebhooksResourceWithRawResponse: - from .resources.webhooks import AsyncWebhooksResourceWithRawResponse - - return AsyncWebhooksResourceWithRawResponse(self._client.webhooks) - - @cached_property - def webhook_secret(self) -> webhook_secret.AsyncWebhookSecretResourceWithRawResponse: - from .resources.webhook_secret import AsyncWebhookSecretResourceWithRawResponse - - return AsyncWebhookSecretResourceWithRawResponse(self._client.webhook_secret) - - -class ModerationAPIWithStreamedResponse: - _client: ModerationAPI - - def __init__(self, client: ModerationAPI) -> None: - self._client = client - - @cached_property - def authors(self) -> authors.AuthorsResourceWithStreamingResponse: - from .resources.authors import AuthorsResourceWithStreamingResponse - - return AuthorsResourceWithStreamingResponse(self._client.authors) - - @cached_property - def queue(self) -> queue.QueueResourceWithStreamingResponse: - from .resources.queue import QueueResourceWithStreamingResponse - - return QueueResourceWithStreamingResponse(self._client.queue) - - @cached_property - def actions(self) -> actions.ActionsResourceWithStreamingResponse: - from .resources.actions import ActionsResourceWithStreamingResponse - - return ActionsResourceWithStreamingResponse(self._client.actions) - - @cached_property - def content(self) -> content.ContentResourceWithStreamingResponse: - from .resources.content import ContentResourceWithStreamingResponse - - return ContentResourceWithStreamingResponse(self._client.content) - - @cached_property - def account(self) -> account.AccountResourceWithStreamingResponse: - from .resources.account import AccountResourceWithStreamingResponse - - return AccountResourceWithStreamingResponse(self._client.account) - - @cached_property - def auth(self) -> auth.AuthResourceWithStreamingResponse: - from .resources.auth import AuthResourceWithStreamingResponse - - return AuthResourceWithStreamingResponse(self._client.auth) - - @cached_property - def wordlist(self) -> wordlist.WordlistResourceWithStreamingResponse: - from .resources.wordlist import WordlistResourceWithStreamingResponse - - return WordlistResourceWithStreamingResponse(self._client.wordlist) - - @cached_property - def webhooks(self) -> webhooks.WebhooksResourceWithStreamingResponse: - from .resources.webhooks import WebhooksResourceWithStreamingResponse - - return WebhooksResourceWithStreamingResponse(self._client.webhooks) - - @cached_property - def webhook_secret(self) -> webhook_secret.WebhookSecretResourceWithStreamingResponse: - from .resources.webhook_secret import WebhookSecretResourceWithStreamingResponse - - return WebhookSecretResourceWithStreamingResponse(self._client.webhook_secret) - - -class AsyncModerationAPIWithStreamedResponse: - _client: AsyncModerationAPI - - def __init__(self, client: AsyncModerationAPI) -> None: - self._client = client - - @cached_property - def authors(self) -> authors.AsyncAuthorsResourceWithStreamingResponse: - from .resources.authors import AsyncAuthorsResourceWithStreamingResponse - - return AsyncAuthorsResourceWithStreamingResponse(self._client.authors) - - @cached_property - def queue(self) -> queue.AsyncQueueResourceWithStreamingResponse: - from .resources.queue import AsyncQueueResourceWithStreamingResponse - - return AsyncQueueResourceWithStreamingResponse(self._client.queue) - - @cached_property - def actions(self) -> actions.AsyncActionsResourceWithStreamingResponse: - from .resources.actions import AsyncActionsResourceWithStreamingResponse - - return AsyncActionsResourceWithStreamingResponse(self._client.actions) - - @cached_property - def content(self) -> content.AsyncContentResourceWithStreamingResponse: - from .resources.content import AsyncContentResourceWithStreamingResponse - - return AsyncContentResourceWithStreamingResponse(self._client.content) - - @cached_property - def account(self) -> account.AsyncAccountResourceWithStreamingResponse: - from .resources.account import AsyncAccountResourceWithStreamingResponse - - return AsyncAccountResourceWithStreamingResponse(self._client.account) - - @cached_property - def auth(self) -> auth.AsyncAuthResourceWithStreamingResponse: - from .resources.auth import AsyncAuthResourceWithStreamingResponse - - return AsyncAuthResourceWithStreamingResponse(self._client.auth) - - @cached_property - def wordlist(self) -> wordlist.AsyncWordlistResourceWithStreamingResponse: - from .resources.wordlist import AsyncWordlistResourceWithStreamingResponse - - return AsyncWordlistResourceWithStreamingResponse(self._client.wordlist) - - @cached_property - def webhooks(self) -> webhooks.AsyncWebhooksResourceWithStreamingResponse: - from .resources.webhooks import AsyncWebhooksResourceWithStreamingResponse - - return AsyncWebhooksResourceWithStreamingResponse(self._client.webhooks) - - @cached_property - def webhook_secret(self) -> webhook_secret.AsyncWebhookSecretResourceWithStreamingResponse: - from .resources.webhook_secret import AsyncWebhookSecretResourceWithStreamingResponse - - return AsyncWebhookSecretResourceWithStreamingResponse(self._client.webhook_secret) - - -Client = ModerationAPI - -AsyncClient = AsyncModerationAPI diff --git a/src/moderation_api/_compat.py b/src/moderation_api/_compat.py deleted file mode 100644 index e6690a4..0000000 --- a/src/moderation_api/_compat.py +++ /dev/null @@ -1,226 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload -from datetime import date, datetime -from typing_extensions import Self, Literal, TypedDict - -import pydantic -from pydantic.fields import FieldInfo - -from ._types import IncEx, StrBytesIntFloat - -_T = TypeVar("_T") -_ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel) - -# --------------- Pydantic v2, v3 compatibility --------------- - -# Pyright incorrectly reports some of our functions as overriding a method when they don't -# pyright: reportIncompatibleMethodOverride=false - -PYDANTIC_V1 = pydantic.VERSION.startswith("1.") - -if TYPE_CHECKING: - - def parse_date(value: date | StrBytesIntFloat) -> date: # noqa: ARG001 - ... - - def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: # noqa: ARG001 - ... - - def get_args(t: type[Any]) -> tuple[Any, ...]: # noqa: ARG001 - ... - - def is_union(tp: type[Any] | None) -> bool: # noqa: ARG001 - ... - - def get_origin(t: type[Any]) -> type[Any] | None: # noqa: ARG001 - ... - - def is_literal_type(type_: type[Any]) -> bool: # noqa: ARG001 - ... - - def is_typeddict(type_: type[Any]) -> bool: # noqa: ARG001 - ... - -else: - # v1 re-exports - if PYDANTIC_V1: - from pydantic.typing import ( - get_args as get_args, - is_union as is_union, - get_origin as get_origin, - is_typeddict as is_typeddict, - is_literal_type as is_literal_type, - ) - from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime - else: - from ._utils import ( - get_args as get_args, - is_union as is_union, - get_origin as get_origin, - parse_date as parse_date, - is_typeddict as is_typeddict, - parse_datetime as parse_datetime, - is_literal_type as is_literal_type, - ) - - -# refactored config -if TYPE_CHECKING: - from pydantic import ConfigDict as ConfigDict -else: - if PYDANTIC_V1: - # TODO: provide an error message here? - ConfigDict = None - else: - from pydantic import ConfigDict as ConfigDict - - -# renamed methods / properties -def parse_obj(model: type[_ModelT], value: object) -> _ModelT: - if PYDANTIC_V1: - return cast(_ModelT, model.parse_obj(value)) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - else: - return model.model_validate(value) - - -def field_is_required(field: FieldInfo) -> bool: - if PYDANTIC_V1: - return field.required # type: ignore - return field.is_required() - - -def field_get_default(field: FieldInfo) -> Any: - value = field.get_default() - if PYDANTIC_V1: - return value - from pydantic_core import PydanticUndefined - - if value == PydanticUndefined: - return None - return value - - -def field_outer_type(field: FieldInfo) -> Any: - if PYDANTIC_V1: - return field.outer_type_ # type: ignore - return field.annotation - - -def get_model_config(model: type[pydantic.BaseModel]) -> Any: - if PYDANTIC_V1: - return model.__config__ # type: ignore - return model.model_config - - -def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]: - if PYDANTIC_V1: - return model.__fields__ # type: ignore - return model.model_fields - - -def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT: - if PYDANTIC_V1: - return model.copy(deep=deep) # type: ignore - return model.model_copy(deep=deep) - - -def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str: - if PYDANTIC_V1: - return model.json(indent=indent) # type: ignore - return model.model_dump_json(indent=indent) - - -class _ModelDumpKwargs(TypedDict, total=False): - by_alias: bool - - -def model_dump( - model: pydantic.BaseModel, - *, - exclude: IncEx | None = None, - exclude_unset: bool = False, - exclude_defaults: bool = False, - warnings: bool = True, - mode: Literal["json", "python"] = "python", - by_alias: bool | None = None, -) -> dict[str, Any]: - if (not PYDANTIC_V1) or hasattr(model, "model_dump"): - kwargs: _ModelDumpKwargs = {} - if by_alias is not None: - kwargs["by_alias"] = by_alias - return model.model_dump( - mode=mode, - exclude=exclude, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - # warnings are not supported in Pydantic v1 - warnings=True if PYDANTIC_V1 else warnings, - **kwargs, - ) - return cast( - "dict[str, Any]", - model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias) - ), - ) - - -def model_parse(model: type[_ModelT], data: Any) -> _ModelT: - if PYDANTIC_V1: - return model.parse_obj(data) # pyright: ignore[reportDeprecated] - return model.model_validate(data) - - -# generic models -if TYPE_CHECKING: - - class GenericModel(pydantic.BaseModel): ... - -else: - if PYDANTIC_V1: - import pydantic.generics - - class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... - else: - # there no longer needs to be a distinction in v2 but - # we still have to create our own subclass to avoid - # inconsistent MRO ordering errors - class GenericModel(pydantic.BaseModel): ... - - -# cached properties -if TYPE_CHECKING: - cached_property = property - - # we define a separate type (copied from typeshed) - # that represents that `cached_property` is `set`able - # at runtime, which differs from `@property`. - # - # this is a separate type as editors likely special case - # `@property` and we don't want to cause issues just to have - # more helpful internal types. - - class typed_cached_property(Generic[_T]): - func: Callable[[Any], _T] - attrname: str | None - - def __init__(self, func: Callable[[Any], _T]) -> None: ... - - @overload - def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ... - - @overload - def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: ... - - def __get__(self, instance: object, owner: type[Any] | None = None) -> _T | Self: - raise NotImplementedError() - - def __set_name__(self, owner: type[Any], name: str) -> None: ... - - # __set__ is not defined at runtime, but @cached_property is designed to be settable - def __set__(self, instance: object, value: _T) -> None: ... -else: - from functools import cached_property as cached_property - - typed_cached_property = cached_property diff --git a/src/moderation_api/_constants.py b/src/moderation_api/_constants.py deleted file mode 100644 index 6ddf2c7..0000000 --- a/src/moderation_api/_constants.py +++ /dev/null @@ -1,14 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import httpx - -RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response" -OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to" - -# default timeout is 1 minute -DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0) -DEFAULT_MAX_RETRIES = 2 -DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20) - -INITIAL_RETRY_DELAY = 0.5 -MAX_RETRY_DELAY = 8.0 diff --git a/src/moderation_api/_exceptions.py b/src/moderation_api/_exceptions.py deleted file mode 100644 index a1e921c..0000000 --- a/src/moderation_api/_exceptions.py +++ /dev/null @@ -1,108 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal - -import httpx - -__all__ = [ - "BadRequestError", - "AuthenticationError", - "PermissionDeniedError", - "NotFoundError", - "ConflictError", - "UnprocessableEntityError", - "RateLimitError", - "InternalServerError", -] - - -class ModerationAPIError(Exception): - pass - - -class APIError(ModerationAPIError): - message: str - request: httpx.Request - - body: object | None - """The API response body. - - If the API responded with a valid JSON structure then this property will be the - decoded result. - - If it isn't a valid JSON structure then this will be the raw response. - - If there was no response associated with this error then it will be `None`. - """ - - def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None: # noqa: ARG002 - super().__init__(message) - self.request = request - self.message = message - self.body = body - - -class APIResponseValidationError(APIError): - response: httpx.Response - status_code: int - - def __init__(self, response: httpx.Response, body: object | None, *, message: str | None = None) -> None: - super().__init__(message or "Data returned by API invalid for expected schema.", response.request, body=body) - self.response = response - self.status_code = response.status_code - - -class APIStatusError(APIError): - """Raised when an API response has a status code of 4xx or 5xx.""" - - response: httpx.Response - status_code: int - - def __init__(self, message: str, *, response: httpx.Response, body: object | None) -> None: - super().__init__(message, response.request, body=body) - self.response = response - self.status_code = response.status_code - - -class APIConnectionError(APIError): - def __init__(self, *, message: str = "Connection error.", request: httpx.Request) -> None: - super().__init__(message, request, body=None) - - -class APITimeoutError(APIConnectionError): - def __init__(self, request: httpx.Request) -> None: - super().__init__(message="Request timed out.", request=request) - - -class BadRequestError(APIStatusError): - status_code: Literal[400] = 400 # pyright: ignore[reportIncompatibleVariableOverride] - - -class AuthenticationError(APIStatusError): - status_code: Literal[401] = 401 # pyright: ignore[reportIncompatibleVariableOverride] - - -class PermissionDeniedError(APIStatusError): - status_code: Literal[403] = 403 # pyright: ignore[reportIncompatibleVariableOverride] - - -class NotFoundError(APIStatusError): - status_code: Literal[404] = 404 # pyright: ignore[reportIncompatibleVariableOverride] - - -class ConflictError(APIStatusError): - status_code: Literal[409] = 409 # pyright: ignore[reportIncompatibleVariableOverride] - - -class UnprocessableEntityError(APIStatusError): - status_code: Literal[422] = 422 # pyright: ignore[reportIncompatibleVariableOverride] - - -class RateLimitError(APIStatusError): - status_code: Literal[429] = 429 # pyright: ignore[reportIncompatibleVariableOverride] - - -class InternalServerError(APIStatusError): - pass diff --git a/src/moderation_api/_files.py b/src/moderation_api/_files.py deleted file mode 100644 index 76da9e0..0000000 --- a/src/moderation_api/_files.py +++ /dev/null @@ -1,173 +0,0 @@ -from __future__ import annotations - -import io -import os -import pathlib -from typing import Sequence, cast, overload -from typing_extensions import TypeVar, TypeGuard - -import anyio - -from ._types import ( - FileTypes, - FileContent, - RequestFiles, - HttpxFileTypes, - Base64FileInput, - HttpxFileContent, - HttpxRequestFiles, -) -from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t - -_T = TypeVar("_T") - - -def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]: - return isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike) - - -def is_file_content(obj: object) -> TypeGuard[FileContent]: - return ( - isinstance(obj, bytes) or isinstance(obj, tuple) or isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike) - ) - - -def assert_is_file_content(obj: object, *, key: str | None = None) -> None: - if not is_file_content(obj): - prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`" - raise RuntimeError( - f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead." - ) from None - - -@overload -def to_httpx_files(files: None) -> None: ... - - -@overload -def to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ... - - -def to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None: - if files is None: - return None - - if is_mapping_t(files): - files = {key: _transform_file(file) for key, file in files.items()} - elif is_sequence_t(files): - files = [(key, _transform_file(file)) for key, file in files] - else: - raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence") - - return files - - -def _transform_file(file: FileTypes) -> HttpxFileTypes: - if is_file_content(file): - if isinstance(file, os.PathLike): - path = pathlib.Path(file) - return (path.name, path.read_bytes()) - - return file - - if is_tuple_t(file): - return (file[0], read_file_content(file[1]), *file[2:]) - - raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") - - -def read_file_content(file: FileContent) -> HttpxFileContent: - if isinstance(file, os.PathLike): - return pathlib.Path(file).read_bytes() - return file - - -@overload -async def async_to_httpx_files(files: None) -> None: ... - - -@overload -async def async_to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ... - - -async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None: - if files is None: - return None - - if is_mapping_t(files): - files = {key: await _async_transform_file(file) for key, file in files.items()} - elif is_sequence_t(files): - files = [(key, await _async_transform_file(file)) for key, file in files] - else: - raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence") - - return files - - -async def _async_transform_file(file: FileTypes) -> HttpxFileTypes: - if is_file_content(file): - if isinstance(file, os.PathLike): - path = anyio.Path(file) - return (path.name, await path.read_bytes()) - - return file - - if is_tuple_t(file): - return (file[0], await async_read_file_content(file[1]), *file[2:]) - - raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") - - -async def async_read_file_content(file: FileContent) -> HttpxFileContent: - if isinstance(file, os.PathLike): - return await anyio.Path(file).read_bytes() - - return file - - -def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T: - """Copy only the containers along the given paths. - - Used to guard against mutation by extract_files without copying the entire structure. - Only dicts and lists that lie on a path are copied; everything else - is returned by reference. - - For example, given paths=[["foo", "files", "file"]] and the structure: - { - "foo": { - "bar": {"baz": {}}, - "files": {"file": } - } - } - The root dict, "foo", and "files" are copied (they lie on the path). - "bar" and "baz" are returned by reference (off the path). - """ - return _deepcopy_with_paths(item, paths, 0) - - -def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T: - if not paths: - return item - if is_mapping(item): - key_to_paths: dict[str, list[Sequence[str]]] = {} - for path in paths: - if index < len(path): - key_to_paths.setdefault(path[index], []).append(path) - - # if no path continues through this mapping, it won't be mutated and copying it is redundant - if not key_to_paths: - return item - - result = dict(item) - for key, subpaths in key_to_paths.items(): - if key in result: - result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1) - return cast(_T, result) - if is_list(item): - array_paths = [path for path in paths if index < len(path) and path[index] == ""] - - # if no path expects a list here, nothing will be mutated inside it - return by reference - if not array_paths: - return cast(_T, item) - return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item]) - return item diff --git a/src/moderation_api/_hooks/__init__.py b/src/moderation_api/_hooks/__init__.py new file mode 100644 index 0000000..e763be4 --- /dev/null +++ b/src/moderation_api/_hooks/__init__.py @@ -0,0 +1,4 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .sdkhooks import * +from .types import * diff --git a/src/moderation_api/_hooks/sdkhooks.py b/src/moderation_api/_hooks/sdkhooks.py new file mode 100644 index 0000000..5e44c57 --- /dev/null +++ b/src/moderation_api/_hooks/sdkhooks.py @@ -0,0 +1,74 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +import httpx +from .types import ( + SDKInitHook, + BeforeRequestContext, + BeforeRequestHook, + AfterSuccessContext, + AfterSuccessHook, + AfterErrorContext, + AfterErrorHook, + Hooks, +) +from typing import List, Optional, Tuple +from moderation_api.sdkconfiguration import SDKConfiguration + + +class SDKHooks(Hooks): + def __init__(self) -> None: + self.sdk_init_hooks: List[SDKInitHook] = [] + self.before_request_hooks: List[BeforeRequestHook] = [] + self.after_success_hooks: List[AfterSuccessHook] = [] + self.after_error_hooks: List[AfterErrorHook] = [] + + def register_sdk_init_hook(self, hook: SDKInitHook) -> None: + self.sdk_init_hooks.append(hook) + + def register_before_request_hook(self, hook: BeforeRequestHook) -> None: + self.before_request_hooks.append(hook) + + def register_after_success_hook(self, hook: AfterSuccessHook) -> None: + self.after_success_hooks.append(hook) + + def register_after_error_hook(self, hook: AfterErrorHook) -> None: + self.after_error_hooks.append(hook) + + def sdk_init(self, config: SDKConfiguration) -> SDKConfiguration: + for hook in self.sdk_init_hooks: + config = hook.sdk_init(config) + return config + + def before_request( + self, hook_ctx: BeforeRequestContext, request: httpx.Request + ) -> httpx.Request: + for hook in self.before_request_hooks: + out = hook.before_request(hook_ctx, request) + if isinstance(out, Exception): + raise out + request = out + + return request + + def after_success( + self, hook_ctx: AfterSuccessContext, response: httpx.Response + ) -> httpx.Response: + for hook in self.after_success_hooks: + out = hook.after_success(hook_ctx, response) + if isinstance(out, Exception): + raise out + response = out + return response + + def after_error( + self, + hook_ctx: AfterErrorContext, + response: Optional[httpx.Response], + error: Optional[Exception], + ) -> Tuple[Optional[httpx.Response], Optional[Exception]]: + for hook in self.after_error_hooks: + result = hook.after_error(hook_ctx, response, error) + if isinstance(result, Exception): + raise result + response, error = result + return response, error diff --git a/src/moderation_api/_hooks/types.py b/src/moderation_api/_hooks/types.py new file mode 100644 index 0000000..781396d --- /dev/null +++ b/src/moderation_api/_hooks/types.py @@ -0,0 +1,124 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from abc import ABC, abstractmethod +import httpx +from moderation_api.sdkconfiguration import SDKConfiguration +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + + +class HookContext: + config: SDKConfiguration + base_url: str + operation_id: str + oauth2_scopes: Optional[List[str]] = None + security_source: Optional[Union[Any, Callable[[], Any]]] = None + tags: Optional[List[str]] = None + extensions: Optional[Dict[str, Any]] = None + + def __init__( + self, + config: SDKConfiguration, + base_url: str, + operation_id: str, + oauth2_scopes: Optional[List[str]], + security_source: Optional[Union[Any, Callable[[], Any]]], + tags: Optional[List[str]], + extensions: Optional[Dict[str, Any]], + ): + self.config = config + self.base_url = base_url + self.operation_id = operation_id + self.oauth2_scopes = oauth2_scopes + self.security_source = security_source + self.tags = tags + self.extensions = extensions + + +class BeforeRequestContext(HookContext): + def __init__(self, hook_ctx: HookContext): + super().__init__( + hook_ctx.config, + hook_ctx.base_url, + hook_ctx.operation_id, + hook_ctx.oauth2_scopes, + hook_ctx.security_source, + hook_ctx.tags, + hook_ctx.extensions, + ) + + +class AfterSuccessContext(HookContext): + def __init__(self, hook_ctx: HookContext): + super().__init__( + hook_ctx.config, + hook_ctx.base_url, + hook_ctx.operation_id, + hook_ctx.oauth2_scopes, + hook_ctx.security_source, + hook_ctx.tags, + hook_ctx.extensions, + ) + + +class AfterErrorContext(HookContext): + def __init__(self, hook_ctx: HookContext): + super().__init__( + hook_ctx.config, + hook_ctx.base_url, + hook_ctx.operation_id, + hook_ctx.oauth2_scopes, + hook_ctx.security_source, + hook_ctx.tags, + hook_ctx.extensions, + ) + + +class SDKInitHook(ABC): + @abstractmethod + def sdk_init(self, config: SDKConfiguration) -> SDKConfiguration: + pass + + +class BeforeRequestHook(ABC): + @abstractmethod + def before_request( + self, hook_ctx: BeforeRequestContext, request: httpx.Request + ) -> Union[httpx.Request, Exception]: + pass + + +class AfterSuccessHook(ABC): + @abstractmethod + def after_success( + self, hook_ctx: AfterSuccessContext, response: httpx.Response + ) -> Union[httpx.Response, Exception]: + pass + + +class AfterErrorHook(ABC): + @abstractmethod + def after_error( + self, + hook_ctx: AfterErrorContext, + response: Optional[httpx.Response], + error: Optional[Exception], + ) -> Union[Tuple[Optional[httpx.Response], Optional[Exception]], Exception]: + pass + + +class Hooks(ABC): + @abstractmethod + def register_sdk_init_hook(self, hook: SDKInitHook): + pass + + @abstractmethod + def register_before_request_hook(self, hook: BeforeRequestHook): + pass + + @abstractmethod + def register_after_success_hook(self, hook: AfterSuccessHook): + pass + + @abstractmethod + def register_after_error_hook(self, hook: AfterErrorHook): + pass diff --git a/src/moderation_api/_models.py b/src/moderation_api/_models.py deleted file mode 100644 index 8c5ab26..0000000 --- a/src/moderation_api/_models.py +++ /dev/null @@ -1,952 +0,0 @@ -from __future__ import annotations - -import os -import inspect -import weakref -from typing import ( - IO, - TYPE_CHECKING, - Any, - Type, - Union, - Generic, - TypeVar, - Callable, - Iterable, - Optional, - AsyncIterable, - cast, -) -from datetime import date, datetime -from typing_extensions import ( - List, - Unpack, - Literal, - ClassVar, - Protocol, - Required, - Annotated, - ParamSpec, - TypeAlias, - TypedDict, - TypeGuard, - final, - override, - runtime_checkable, -) - -import pydantic -from pydantic.fields import FieldInfo - -from ._types import ( - Body, - IncEx, - Query, - ModelT, - Headers, - Timeout, - NotGiven, - AnyMapping, - HttpxRequestFiles, -) -from ._utils import ( - PropertyInfo, - is_list, - is_given, - json_safe, - lru_cache, - is_mapping, - parse_date, - coerce_boolean, - parse_datetime, - strip_not_given, - extract_type_arg, - is_annotated_type, - is_type_alias_type, - strip_annotated_type, -) -from ._compat import ( - PYDANTIC_V1, - ConfigDict, - GenericModel as BaseGenericModel, - get_args, - is_union, - parse_obj, - get_origin, - is_literal_type, - get_model_config, - get_model_fields, - field_get_default, -) -from ._constants import RAW_RESPONSE_HEADER - -if TYPE_CHECKING: - from pydantic import GetCoreSchemaHandler, ValidatorFunctionWrapHandler - from pydantic_core import CoreSchema, core_schema - from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema -else: - try: - from pydantic_core import CoreSchema, core_schema - except ImportError: - CoreSchema = None - core_schema = None - -__all__ = ["BaseModel", "GenericModel"] - -_T = TypeVar("_T") -_BaseModelT = TypeVar("_BaseModelT", bound="BaseModel") - -P = ParamSpec("P") - - -@runtime_checkable -class _ConfigProtocol(Protocol): - allow_population_by_field_name: bool - - -class BaseModel(pydantic.BaseModel): - if PYDANTIC_V1: - - @property - @override - def model_fields_set(self) -> set[str]: - # a forwards-compat shim for pydantic v2 - return self.__fields_set__ # type: ignore - - class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] - extra: Any = pydantic.Extra.allow # type: ignore - else: - model_config: ClassVar[ConfigDict] = ConfigDict( - extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true")) - ) - - def to_dict( - self, - *, - mode: Literal["json", "python"] = "python", - use_api_names: bool = True, - exclude_unset: bool = True, - exclude_defaults: bool = False, - exclude_none: bool = False, - warnings: bool = True, - ) -> dict[str, object]: - """Recursively generate a dictionary representation of the model, optionally specifying which fields to include or exclude. - - By default, fields that were not set by the API will not be included, - and keys will match the API response, *not* the property names from the model. - - For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property, - the output will use the `"fooBar"` key (unless `use_api_names=False` is passed). - - Args: - mode: - If mode is 'json', the dictionary will only contain JSON serializable types. e.g. `datetime` will be turned into a string, `"2024-3-22T18:11:19.117000Z"`. - If mode is 'python', the dictionary may contain any Python objects. e.g. `datetime(2024, 3, 22)` - - use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`. - exclude_unset: Whether to exclude fields that have not been explicitly set. - exclude_defaults: Whether to exclude fields that are set to their default value from the output. - exclude_none: Whether to exclude fields that have a value of `None` from the output. - warnings: Whether to log warnings when invalid fields are encountered. This is only supported in Pydantic v2. - """ - return self.model_dump( - mode=mode, - by_alias=use_api_names, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - warnings=warnings, - ) - - def to_json( - self, - *, - indent: int | None = 2, - use_api_names: bool = True, - exclude_unset: bool = True, - exclude_defaults: bool = False, - exclude_none: bool = False, - warnings: bool = True, - ) -> str: - """Generates a JSON string representing this model as it would be received from or sent to the API (but with indentation). - - By default, fields that were not set by the API will not be included, - and keys will match the API response, *not* the property names from the model. - - For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property, - the output will use the `"fooBar"` key (unless `use_api_names=False` is passed). - - Args: - indent: Indentation to use in the JSON output. If `None` is passed, the output will be compact. Defaults to `2` - use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`. - exclude_unset: Whether to exclude fields that have not been explicitly set. - exclude_defaults: Whether to exclude fields that have the default value. - exclude_none: Whether to exclude fields that have a value of `None`. - warnings: Whether to show any warnings that occurred during serialization. This is only supported in Pydantic v2. - """ - return self.model_dump_json( - indent=indent, - by_alias=use_api_names, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - warnings=warnings, - ) - - @override - def __str__(self) -> str: - # mypy complains about an invalid self arg - return f"{self.__repr_name__()}({self.__repr_str__(', ')})" # type: ignore[misc] - - # Override the 'construct' method in a way that supports recursive parsing without validation. - # Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836. - @classmethod - @override - def construct( # pyright: ignore[reportIncompatibleMethodOverride] - __cls: Type[ModelT], - _fields_set: set[str] | None = None, - **values: object, - ) -> ModelT: - m = __cls.__new__(__cls) - fields_values: dict[str, object] = {} - - config = get_model_config(__cls) - populate_by_name = ( - config.allow_population_by_field_name - if isinstance(config, _ConfigProtocol) - else config.get("populate_by_name") - ) - - if _fields_set is None: - _fields_set = set() - - model_fields = get_model_fields(__cls) - for name, field in model_fields.items(): - key = field.alias - if key is None or (key not in values and populate_by_name): - key = name - - if key in values: - fields_values[name] = _construct_field(value=values[key], field=field, key=key) - _fields_set.add(name) - else: - fields_values[name] = field_get_default(field) - - extra_field_type = _get_extra_fields_type(__cls) - - _extra = {} - for key, value in values.items(): - if key not in model_fields: - parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value - - if PYDANTIC_V1: - _fields_set.add(key) - fields_values[key] = parsed - else: - _extra[key] = parsed - - object.__setattr__(m, "__dict__", fields_values) - - if PYDANTIC_V1: - # init_private_attributes() does not exist in v2 - m._init_private_attributes() # type: ignore - - # copied from Pydantic v1's `construct()` method - object.__setattr__(m, "__fields_set__", _fields_set) - else: - # these properties are copied from Pydantic's `model_construct()` method - object.__setattr__(m, "__pydantic_private__", None) - object.__setattr__(m, "__pydantic_extra__", _extra) - object.__setattr__(m, "__pydantic_fields_set__", _fields_set) - - return m - - if not TYPE_CHECKING: - # type checkers incorrectly complain about this assignment - # because the type signatures are technically different - # although not in practice - model_construct = construct - - if PYDANTIC_V1: - # we define aliases for some of the new pydantic v2 methods so - # that we can just document these methods without having to specify - # a specific pydantic version as some users may not know which - # pydantic version they are currently using - - @override - def model_dump( - self, - *, - mode: Literal["json", "python"] | str = "python", - include: IncEx | None = None, - exclude: IncEx | None = None, - context: Any | None = None, - by_alias: bool | None = None, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, - exclude_computed_fields: bool = False, - round_trip: bool = False, - warnings: bool | Literal["none", "warn", "error"] = True, - fallback: Callable[[Any], Any] | None = None, - serialize_as_any: bool = False, - ) -> dict[str, Any]: - """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump - - Generate a dictionary representation of the model, optionally specifying which fields to include or exclude. - - Args: - mode: The mode in which `to_python` should run. - If mode is 'json', the output will only contain JSON serializable types. - If mode is 'python', the output may contain non-JSON-serializable Python objects. - include: A set of fields to include in the output. - exclude: A set of fields to exclude from the output. - context: Additional context to pass to the serializer. - by_alias: Whether to use the field's alias in the dictionary key if defined. - exclude_unset: Whether to exclude fields that have not been explicitly set. - exclude_defaults: Whether to exclude fields that are set to their default value. - exclude_none: Whether to exclude fields that have a value of `None`. - exclude_computed_fields: Whether to exclude computed fields. - While this can be useful for round-tripping, it is usually recommended to use the dedicated - `round_trip` parameter instead. - round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T]. - warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, - "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. - fallback: A function to call when an unknown value is encountered. If not provided, - a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. - serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. - - Returns: - A dictionary representation of the model. - """ - if mode not in {"json", "python"}: - raise ValueError("mode must be either 'json' or 'python'") - if round_trip != False: - raise ValueError("round_trip is only supported in Pydantic v2") - if warnings != True: - raise ValueError("warnings is only supported in Pydantic v2") - if context is not None: - raise ValueError("context is only supported in Pydantic v2") - if serialize_as_any != False: - raise ValueError("serialize_as_any is only supported in Pydantic v2") - if fallback is not None: - raise ValueError("fallback is only supported in Pydantic v2") - if exclude_computed_fields != False: - raise ValueError("exclude_computed_fields is only supported in Pydantic v2") - dumped = super().dict( # pyright: ignore[reportDeprecated] - include=include, - exclude=exclude, - by_alias=by_alias if by_alias is not None else False, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - ) - - return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped - - @override - def model_dump_json( - self, - *, - indent: int | None = None, - ensure_ascii: bool = False, - include: IncEx | None = None, - exclude: IncEx | None = None, - context: Any | None = None, - by_alias: bool | None = None, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, - exclude_computed_fields: bool = False, - round_trip: bool = False, - warnings: bool | Literal["none", "warn", "error"] = True, - fallback: Callable[[Any], Any] | None = None, - serialize_as_any: bool = False, - ) -> str: - """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json - - Generates a JSON representation of the model using Pydantic's `to_json` method. - - Args: - indent: Indentation to use in the JSON output. If None is passed, the output will be compact. - include: Field(s) to include in the JSON output. Can take either a string or set of strings. - exclude: Field(s) to exclude from the JSON output. Can take either a string or set of strings. - by_alias: Whether to serialize using field aliases. - exclude_unset: Whether to exclude fields that have not been explicitly set. - exclude_defaults: Whether to exclude fields that have the default value. - exclude_none: Whether to exclude fields that have a value of `None`. - round_trip: Whether to use serialization/deserialization between JSON and class instance. - warnings: Whether to show any warnings that occurred during serialization. - - Returns: - A JSON string representation of the model. - """ - if round_trip != False: - raise ValueError("round_trip is only supported in Pydantic v2") - if warnings != True: - raise ValueError("warnings is only supported in Pydantic v2") - if context is not None: - raise ValueError("context is only supported in Pydantic v2") - if serialize_as_any != False: - raise ValueError("serialize_as_any is only supported in Pydantic v2") - if fallback is not None: - raise ValueError("fallback is only supported in Pydantic v2") - if ensure_ascii != False: - raise ValueError("ensure_ascii is only supported in Pydantic v2") - if exclude_computed_fields != False: - raise ValueError("exclude_computed_fields is only supported in Pydantic v2") - return super().json( # type: ignore[reportDeprecated] - indent=indent, - include=include, - exclude=exclude, - by_alias=by_alias if by_alias is not None else False, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - ) - - -class _EagerIterable(list[_T], Generic[_T]): - """ - Accepts any Iterable[T] input (including generators), consumes it - eagerly, and validates all items upfront. - - Validation preserves the original container type where possible - (e.g. a set[T] stays a set[T]). Serialization (model_dump / JSON) - always emits a list — round-tripping through model_dump() will not - restore the original container type. - """ - - @classmethod - def __get_pydantic_core_schema__( - cls, - source_type: Any, - handler: GetCoreSchemaHandler, - ) -> CoreSchema: - (item_type,) = get_args(source_type) or (Any,) - item_schema: CoreSchema = handler.generate_schema(item_type) - list_of_items_schema: CoreSchema = core_schema.list_schema(item_schema) - - return core_schema.no_info_wrap_validator_function( - cls._validate, - list_of_items_schema, - serialization=core_schema.plain_serializer_function_ser_schema( - cls._serialize, - info_arg=False, - ), - ) - - @staticmethod - def _validate(v: Iterable[_T], handler: "ValidatorFunctionWrapHandler") -> Any: - original_type: type[Any] = type(v) - - # Normalize to list so list_schema can validate each item - if isinstance(v, list): - items: list[_T] = v - else: - try: - items = list(v) - except TypeError as e: - raise TypeError("Value is not iterable") from e - - # Validate items against the inner schema - validated: list[_T] = handler(items) - - # Reconstruct original container type - if original_type is list: - return validated - # str(list) produces the list's repr, not a string built from items, - # so skip reconstruction for str and its subclasses. - if issubclass(original_type, str): - return validated - try: - return original_type(validated) - except (TypeError, ValueError): - # If the type cannot be reconstructed, just return the validated list - return validated - - @staticmethod - def _serialize(v: Iterable[_T]) -> list[_T]: - """Always serialize as a list so Pydantic's JSON encoder is happy.""" - if isinstance(v, list): - return v - return list(v) - - -EagerIterable: TypeAlias = Annotated[Iterable[_T], _EagerIterable] - - -def _construct_field(value: object, field: FieldInfo, key: str) -> object: - if value is None: - return field_get_default(field) - - if PYDANTIC_V1: - type_ = cast(type, field.outer_type_) # type: ignore - else: - type_ = field.annotation # type: ignore - - if type_ is None: - raise RuntimeError(f"Unexpected field type is None for {key}") - - return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None)) - - -def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None: - if PYDANTIC_V1: - # TODO - return None - - schema = cls.__pydantic_core_schema__ - if schema["type"] == "model": - fields = schema["schema"] - if fields["type"] == "model-fields": - extras = fields.get("extras_schema") - if extras and "cls" in extras: - # mypy can't narrow the type - return extras["cls"] # type: ignore[no-any-return] - - return None - - -def is_basemodel(type_: type) -> bool: - """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`""" - if is_union(type_): - for variant in get_args(type_): - if is_basemodel(variant): - return True - - return False - - return is_basemodel_type(type_) - - -def is_basemodel_type(type_: type) -> TypeGuard[type[BaseModel] | type[GenericModel]]: - origin = get_origin(type_) or type_ - if not inspect.isclass(origin): - return False - return issubclass(origin, BaseModel) or issubclass(origin, GenericModel) - - -def build( - base_model_cls: Callable[P, _BaseModelT], - *args: P.args, - **kwargs: P.kwargs, -) -> _BaseModelT: - """Construct a BaseModel class without validation. - - This is useful for cases where you need to instantiate a `BaseModel` - from an API response as this provides type-safe params which isn't supported - by helpers like `construct_type()`. - - ```py - build(MyModel, my_field_a="foo", my_field_b=123) - ``` - """ - if args: - raise TypeError( - "Received positional arguments which are not supported; Keyword arguments must be used instead", - ) - - return cast(_BaseModelT, construct_type(type_=base_model_cls, value=kwargs)) - - -def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T: - """Loose coercion to the expected type with construction of nested values. - - Note: the returned value from this function is not guaranteed to match the - given type. - """ - return cast(_T, construct_type(value=value, type_=type_)) - - -def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object: - """Loose coercion to the expected type with construction of nested values. - - If the given value does not match the expected type then it is returned as-is. - """ - - # store a reference to the original type we were given before we extract any inner - # types so that we can properly resolve forward references in `TypeAliasType` annotations - original_type = None - - # we allow `object` as the input type because otherwise, passing things like - # `Literal['value']` will be reported as a type error by type checkers - type_ = cast("type[object]", type_) - if is_type_alias_type(type_): - original_type = type_ # type: ignore[unreachable] - type_ = type_.__value__ # type: ignore[unreachable] - - # unwrap `Annotated[T, ...]` -> `T` - if metadata is not None and len(metadata) > 0: - meta: tuple[Any, ...] = tuple(metadata) - elif is_annotated_type(type_): - meta = get_args(type_)[1:] - type_ = extract_type_arg(type_, 0) - else: - meta = tuple() - - # we need to use the origin class for any types that are subscripted generics - # e.g. Dict[str, object] - origin = get_origin(type_) or type_ - args = get_args(type_) - - if is_union(origin): - try: - return validate_type(type_=cast("type[object]", original_type or type_), value=value) - except Exception: - pass - - # if the type is a discriminated union then we want to construct the right variant - # in the union, even if the data doesn't match exactly, otherwise we'd break code - # that relies on the constructed class types, e.g. - # - # class FooType: - # kind: Literal['foo'] - # value: str - # - # class BarType: - # kind: Literal['bar'] - # value: int - # - # without this block, if the data we get is something like `{'kind': 'bar', 'value': 'foo'}` then - # we'd end up constructing `FooType` when it should be `BarType`. - discriminator = _build_discriminated_union_meta(union=type_, meta_annotations=meta) - if discriminator and is_mapping(value): - variant_value = value.get(discriminator.field_alias_from or discriminator.field_name) - if variant_value and isinstance(variant_value, str): - variant_type = discriminator.mapping.get(variant_value) - if variant_type: - return construct_type(type_=variant_type, value=value) - - # if the data is not valid, use the first variant that doesn't fail while deserializing - for variant in args: - try: - return construct_type(value=value, type_=variant) - except Exception: - continue - - raise RuntimeError(f"Could not convert data into a valid instance of {type_}") - - if origin == dict: - if not is_mapping(value): - return value - - _, items_type = get_args(type_) # Dict[_, items_type] - return {key: construct_type(value=item, type_=items_type) for key, item in value.items()} - - if ( - not is_literal_type(type_) - and inspect.isclass(origin) - and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel)) - ): - if is_list(value): - return [cast(Any, type_).construct(**entry) if is_mapping(entry) else entry for entry in value] - - if is_mapping(value): - if issubclass(type_, BaseModel): - return type_.construct(**value) # type: ignore[arg-type] - - return cast(Any, type_).construct(**value) - - if origin == list: - if not is_list(value): - return value - - inner_type = args[0] # List[inner_type] - return [construct_type(value=entry, type_=inner_type) for entry in value] - - if origin == float: - if isinstance(value, int): - coerced = float(value) - if coerced != value: - return value - return coerced - - return value - - if type_ == datetime: - try: - return parse_datetime(value) # type: ignore - except Exception: - return value - - if type_ == date: - try: - return parse_date(value) # type: ignore - except Exception: - return value - - return value - - -@runtime_checkable -class CachedDiscriminatorType(Protocol): - __discriminator__: DiscriminatorDetails - - -DISCRIMINATOR_CACHE: weakref.WeakKeyDictionary[type, DiscriminatorDetails] = weakref.WeakKeyDictionary() - - -class DiscriminatorDetails: - field_name: str - """The name of the discriminator field in the variant class, e.g. - - ```py - class Foo(BaseModel): - type: Literal['foo'] - ``` - - Will result in field_name='type' - """ - - field_alias_from: str | None - """The name of the discriminator field in the API response, e.g. - - ```py - class Foo(BaseModel): - type: Literal['foo'] = Field(alias='type_from_api') - ``` - - Will result in field_alias_from='type_from_api' - """ - - mapping: dict[str, type] - """Mapping of discriminator value to variant type, e.g. - - {'foo': FooVariant, 'bar': BarVariant} - """ - - def __init__( - self, - *, - mapping: dict[str, type], - discriminator_field: str, - discriminator_alias: str | None, - ) -> None: - self.mapping = mapping - self.field_name = discriminator_field - self.field_alias_from = discriminator_alias - - -def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None: - cached = DISCRIMINATOR_CACHE.get(union) - if cached is not None: - return cached - - discriminator_field_name: str | None = None - - for annotation in meta_annotations: - if isinstance(annotation, PropertyInfo) and annotation.discriminator is not None: - discriminator_field_name = annotation.discriminator - break - - if not discriminator_field_name: - return None - - mapping: dict[str, type] = {} - discriminator_alias: str | None = None - - for variant in get_args(union): - variant = strip_annotated_type(variant) - if is_basemodel_type(variant): - if PYDANTIC_V1: - field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - if not field_info: - continue - - # Note: if one variant defines an alias then they all should - discriminator_alias = field_info.alias - - if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): - for entry in get_args(annotation): - if isinstance(entry, str): - mapping[entry] = variant - else: - field = _extract_field_schema_pv2(variant, discriminator_field_name) - if not field: - continue - - # Note: if one variant defines an alias then they all should - discriminator_alias = field.get("serialization_alias") - - field_schema = field["schema"] - - if field_schema["type"] == "literal": - for entry in cast("LiteralSchema", field_schema)["expected"]: - if isinstance(entry, str): - mapping[entry] = variant - - if not mapping: - return None - - details = DiscriminatorDetails( - mapping=mapping, - discriminator_field=discriminator_field_name, - discriminator_alias=discriminator_alias, - ) - DISCRIMINATOR_CACHE.setdefault(union, details) - return details - - -def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None: - schema = model.__pydantic_core_schema__ - if schema["type"] == "definitions": - schema = schema["schema"] - - if schema["type"] != "model": - return None - - schema = cast("ModelSchema", schema) - fields_schema = schema["schema"] - if fields_schema["type"] != "model-fields": - return None - - fields_schema = cast("ModelFieldsSchema", fields_schema) - field = fields_schema["fields"].get(field_name) - if not field: - return None - - return cast("ModelField", field) # pyright: ignore[reportUnnecessaryCast] - - -def validate_type(*, type_: type[_T], value: object) -> _T: - """Strict validation that the given value matches the expected type""" - if inspect.isclass(type_) and issubclass(type_, pydantic.BaseModel): - return cast(_T, parse_obj(type_, value)) - - return cast(_T, _validate_non_model_type(type_=type_, value=value)) - - -def set_pydantic_config(typ: Any, config: pydantic.ConfigDict) -> None: - """Add a pydantic config for the given type. - - Note: this is a no-op on Pydantic v1. - """ - setattr(typ, "__pydantic_config__", config) # noqa: B010 - - -# our use of subclassing here causes weirdness for type checkers, -# so we just pretend that we don't subclass -if TYPE_CHECKING: - GenericModel = BaseModel -else: - - class GenericModel(BaseGenericModel, BaseModel): - pass - - -if not PYDANTIC_V1: - from pydantic import TypeAdapter as _TypeAdapter - - _CachedTypeAdapter = cast("TypeAdapter[object]", lru_cache(maxsize=None)(_TypeAdapter)) - - if TYPE_CHECKING: - from pydantic import TypeAdapter - else: - TypeAdapter = _CachedTypeAdapter - - def _validate_non_model_type(*, type_: type[_T], value: object) -> _T: - return TypeAdapter(type_).validate_python(value) - -elif not TYPE_CHECKING: # TODO: condition is weird - - class RootModel(GenericModel, Generic[_T]): - """Used as a placeholder to easily convert runtime types to a Pydantic format - to provide validation. - - For example: - ```py - validated = RootModel[int](__root__="5").__root__ - # validated: 5 - ``` - """ - - __root__: _T - - def _validate_non_model_type(*, type_: type[_T], value: object) -> _T: - model = _create_pydantic_model(type_).validate(value) - return cast(_T, model.__root__) - - def _create_pydantic_model(type_: _T) -> Type[RootModel[_T]]: - return RootModel[type_] # type: ignore - - -class FinalRequestOptionsInput(TypedDict, total=False): - method: Required[str] - url: Required[str] - params: Query - headers: Headers - max_retries: int - timeout: float | Timeout | None - files: HttpxRequestFiles | None - idempotency_key: str - content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] - json_data: Body - extra_json: AnyMapping - follow_redirects: bool - - -@final -class FinalRequestOptions(pydantic.BaseModel): - method: str - url: str - params: Query = {} - headers: Union[Headers, NotGiven] = NotGiven() - max_retries: Union[int, NotGiven] = NotGiven() - timeout: Union[float, Timeout, None, NotGiven] = NotGiven() - files: Union[HttpxRequestFiles, None] = None - idempotency_key: Union[str, None] = None - post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() - follow_redirects: Union[bool, None] = None - - content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None - # It should be noted that we cannot use `json` here as that would override - # a BaseModel method in an incompatible fashion. - json_data: Union[Body, None] = None - extra_json: Union[AnyMapping, None] = None - - if PYDANTIC_V1: - - class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] - arbitrary_types_allowed: bool = True - else: - model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) - - def get_max_retries(self, max_retries: int) -> int: - if isinstance(self.max_retries, NotGiven): - return max_retries - return self.max_retries - - def _strip_raw_response_header(self) -> None: - if not is_given(self.headers): - return - - if self.headers.get(RAW_RESPONSE_HEADER): - self.headers = {**self.headers} - self.headers.pop(RAW_RESPONSE_HEADER) - - # override the `construct` method so that we can run custom transformations. - # this is necessary as we don't want to do any actual runtime type checking - # (which means we can't use validators) but we do want to ensure that `NotGiven` - # values are not present - # - # type ignore required because we're adding explicit types to `**values` - @classmethod - def construct( # type: ignore - cls, - _fields_set: set[str] | None = None, - **values: Unpack[FinalRequestOptionsInput], - ) -> FinalRequestOptions: - kwargs: dict[str, Any] = { - # we unconditionally call `strip_not_given` on any value - # as it will just ignore any non-mapping types - key: strip_not_given(value) - for key, value in values.items() - } - if PYDANTIC_V1: - return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated] - return super().model_construct(_fields_set, **kwargs) - - if not TYPE_CHECKING: - # type checkers incorrectly complain about this assignment - model_construct = construct diff --git a/src/moderation_api/_qs.py b/src/moderation_api/_qs.py deleted file mode 100644 index 4127c19..0000000 --- a/src/moderation_api/_qs.py +++ /dev/null @@ -1,149 +0,0 @@ -from __future__ import annotations - -from typing import Any, List, Tuple, Union, Mapping, TypeVar -from urllib.parse import parse_qs, urlencode -from typing_extensions import get_args - -from ._types import NotGiven, ArrayFormat, NestedFormat, not_given -from ._utils import flatten - -_T = TypeVar("_T") - -PrimitiveData = Union[str, int, float, bool, None] -# this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"] -# https://github.com/microsoft/pyright/issues/3555 -Data = Union[PrimitiveData, List[Any], Tuple[Any], "Mapping[str, Any]"] -Params = Mapping[str, Data] - - -class Querystring: - array_format: ArrayFormat - nested_format: NestedFormat - - def __init__( - self, - *, - array_format: ArrayFormat = "repeat", - nested_format: NestedFormat = "brackets", - ) -> None: - self.array_format = array_format - self.nested_format = nested_format - - def parse(self, query: str) -> Mapping[str, object]: - # Note: custom format syntax is not supported yet - return parse_qs(query) - - def stringify( - self, - params: Params, - *, - array_format: ArrayFormat | NotGiven = not_given, - nested_format: NestedFormat | NotGiven = not_given, - ) -> str: - return urlencode( - self.stringify_items( - params, - array_format=array_format, - nested_format=nested_format, - ) - ) - - def stringify_items( - self, - params: Params, - *, - array_format: ArrayFormat | NotGiven = not_given, - nested_format: NestedFormat | NotGiven = not_given, - ) -> list[tuple[str, str]]: - opts = Options( - qs=self, - array_format=array_format, - nested_format=nested_format, - ) - return flatten([self._stringify_item(key, value, opts) for key, value in params.items()]) - - def _stringify_item( - self, - key: str, - value: Data, - opts: Options, - ) -> list[tuple[str, str]]: - if isinstance(value, Mapping): - items: list[tuple[str, str]] = [] - nested_format = opts.nested_format - for subkey, subvalue in value.items(): - items.extend( - self._stringify_item( - # TODO: error if unknown format - f"{key}.{subkey}" if nested_format == "dots" else f"{key}[{subkey}]", - subvalue, - opts, - ) - ) - return items - - if isinstance(value, (list, tuple)): - array_format = opts.array_format - if array_format == "comma": - return [ - ( - key, - ",".join(self._primitive_value_to_str(item) for item in value if item is not None), - ), - ] - elif array_format == "repeat": - items = [] - for item in value: - items.extend(self._stringify_item(key, item, opts)) - return items - elif array_format == "indices": - items = [] - for i, item in enumerate(value): - items.extend(self._stringify_item(f"{key}[{i}]", item, opts)) - return items - elif array_format == "brackets": - items = [] - key = key + "[]" - for item in value: - items.extend(self._stringify_item(key, item, opts)) - return items - else: - raise NotImplementedError( - f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}" - ) - - serialised = self._primitive_value_to_str(value) - if not serialised: - return [] - return [(key, serialised)] - - def _primitive_value_to_str(self, value: PrimitiveData) -> str: - # copied from httpx - if value is True: - return "true" - elif value is False: - return "false" - elif value is None: - return "" - return str(value) - - -_qs = Querystring() -parse = _qs.parse -stringify = _qs.stringify -stringify_items = _qs.stringify_items - - -class Options: - array_format: ArrayFormat - nested_format: NestedFormat - - def __init__( - self, - qs: Querystring = _qs, - *, - array_format: ArrayFormat | NotGiven = not_given, - nested_format: NestedFormat | NotGiven = not_given, - ) -> None: - self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format - self.nested_format = qs.nested_format if isinstance(nested_format, NotGiven) else nested_format diff --git a/src/moderation_api/_resource.py b/src/moderation_api/_resource.py deleted file mode 100644 index a44113b..0000000 --- a/src/moderation_api/_resource.py +++ /dev/null @@ -1,43 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import time -from typing import TYPE_CHECKING - -import anyio - -if TYPE_CHECKING: - from ._client import ModerationAPI, AsyncModerationAPI - - -class SyncAPIResource: - _client: ModerationAPI - - def __init__(self, client: ModerationAPI) -> None: - self._client = client - self._get = client.get - self._post = client.post - self._patch = client.patch - self._put = client.put - self._delete = client.delete - self._get_api_list = client.get_api_list - - def _sleep(self, seconds: float) -> None: - time.sleep(seconds) - - -class AsyncAPIResource: - _client: AsyncModerationAPI - - def __init__(self, client: AsyncModerationAPI) -> None: - self._client = client - self._get = client.get - self._post = client.post - self._patch = client.patch - self._put = client.put - self._delete = client.delete - self._get_api_list = client.get_api_list - - async def _sleep(self, seconds: float) -> None: - await anyio.sleep(seconds) diff --git a/src/moderation_api/_response.py b/src/moderation_api/_response.py deleted file mode 100644 index 053cfef..0000000 --- a/src/moderation_api/_response.py +++ /dev/null @@ -1,835 +0,0 @@ -from __future__ import annotations - -import os -import inspect -import logging -import datetime -import functools -from types import TracebackType -from typing import ( - TYPE_CHECKING, - Any, - Union, - Generic, - TypeVar, - Callable, - Iterator, - AsyncIterator, - cast, - overload, -) -from typing_extensions import Awaitable, ParamSpec, override, get_origin - -import anyio -import httpx -import pydantic - -from ._types import NoneType -from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type, extract_type_var_from_base -from ._models import BaseModel, is_basemodel -from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER -from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type -from ._exceptions import ModerationAPIError, APIResponseValidationError - -if TYPE_CHECKING: - from ._models import FinalRequestOptions - from ._base_client import BaseClient - - -P = ParamSpec("P") -R = TypeVar("R") -_T = TypeVar("_T") -_APIResponseT = TypeVar("_APIResponseT", bound="APIResponse[Any]") -_AsyncAPIResponseT = TypeVar("_AsyncAPIResponseT", bound="AsyncAPIResponse[Any]") - -log: logging.Logger = logging.getLogger(__name__) - - -class BaseAPIResponse(Generic[R]): - _cast_to: type[R] - _client: BaseClient[Any, Any] - _parsed_by_type: dict[type[Any], Any] - _is_sse_stream: bool - _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None - _options: FinalRequestOptions - - http_response: httpx.Response - - retries_taken: int - """The number of retries made. If no retries happened this will be `0`""" - - def __init__( - self, - *, - raw: httpx.Response, - cast_to: type[R], - client: BaseClient[Any, Any], - stream: bool, - stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, - options: FinalRequestOptions, - retries_taken: int = 0, - ) -> None: - self._cast_to = cast_to - self._client = client - self._parsed_by_type = {} - self._is_sse_stream = stream - self._stream_cls = stream_cls - self._options = options - self.http_response = raw - self.retries_taken = retries_taken - - @property - def headers(self) -> httpx.Headers: - return self.http_response.headers - - @property - def http_request(self) -> httpx.Request: - """Returns the httpx Request instance associated with the current response.""" - return self.http_response.request - - @property - def status_code(self) -> int: - return self.http_response.status_code - - @property - def url(self) -> httpx.URL: - """Returns the URL for which the request was made.""" - return self.http_response.url - - @property - def method(self) -> str: - return self.http_request.method - - @property - def http_version(self) -> str: - return self.http_response.http_version - - @property - def elapsed(self) -> datetime.timedelta: - """The time taken for the complete request/response cycle to complete.""" - return self.http_response.elapsed - - @property - def is_closed(self) -> bool: - """Whether or not the response body has been closed. - - If this is False then there is response data that has not been read yet. - You must either fully consume the response body or call `.close()` - before discarding the response to prevent resource leaks. - """ - return self.http_response.is_closed - - @override - def __repr__(self) -> str: - return ( - f"<{self.__class__.__name__} [{self.status_code} {self.http_response.reason_phrase}] type={self._cast_to}>" - ) - - def _parse(self, *, to: type[_T] | None = None) -> R | _T: - cast_to = to if to is not None else self._cast_to - - # unwrap `TypeAlias('Name', T)` -> `T` - if is_type_alias_type(cast_to): - cast_to = cast_to.__value__ # type: ignore[unreachable] - - # unwrap `Annotated[T, ...]` -> `T` - if cast_to and is_annotated_type(cast_to): - cast_to = extract_type_arg(cast_to, 0) - - origin = get_origin(cast_to) or cast_to - - if self._is_sse_stream: - if to: - if not is_stream_class_type(to): - raise TypeError(f"Expected custom parse type to be a subclass of {Stream} or {AsyncStream}") - - return cast( - _T, - to( - cast_to=extract_stream_chunk_type( - to, - failure_message="Expected custom stream type to be passed with a type argument, e.g. Stream[ChunkType]", - ), - response=self.http_response, - client=cast(Any, self._client), - options=self._options, - ), - ) - - if self._stream_cls: - return cast( - R, - self._stream_cls( - cast_to=extract_stream_chunk_type(self._stream_cls), - response=self.http_response, - client=cast(Any, self._client), - options=self._options, - ), - ) - - stream_cls = cast("type[Stream[Any]] | type[AsyncStream[Any]] | None", self._client._default_stream_cls) - if stream_cls is None: - raise MissingStreamClassError() - - return cast( - R, - stream_cls( - cast_to=cast_to, - response=self.http_response, - client=cast(Any, self._client), - options=self._options, - ), - ) - - if cast_to is NoneType: - return cast(R, None) - - response = self.http_response - if cast_to == str: - return cast(R, response.text) - - if cast_to == bytes: - return cast(R, response.content) - - if cast_to == int: - return cast(R, int(response.text)) - - if cast_to == float: - return cast(R, float(response.text)) - - if cast_to == bool: - return cast(R, response.text.lower() == "true") - - if origin == APIResponse: - raise RuntimeError("Unexpected state - cast_to is `APIResponse`") - - if inspect.isclass(origin) and issubclass(origin, httpx.Response): - # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response - # and pass that class to our request functions. We cannot change the variance to be either - # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct - # the response class ourselves but that is something that should be supported directly in httpx - # as it would be easy to incorrectly construct the Response object due to the multitude of arguments. - if cast_to != httpx.Response: - raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`") - return cast(R, response) - - if ( - inspect.isclass( - origin # pyright: ignore[reportUnknownArgumentType] - ) - and not issubclass(origin, BaseModel) - and issubclass(origin, pydantic.BaseModel) - ): - raise TypeError( - "Pydantic models must subclass our base model type, e.g. `from moderation_api import BaseModel`" - ) - - if ( - cast_to is not object - and not origin is list - and not origin is dict - and not origin is Union - and not issubclass(origin, BaseModel) - ): - raise RuntimeError( - f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}." - ) - - # split is required to handle cases where additional information is included - # in the response, e.g. application/json; charset=utf-8 - content_type, *_ = response.headers.get("content-type", "*").split(";") - if not content_type.endswith("json"): - if is_basemodel(cast_to): - try: - data = response.json() - except Exception as exc: - log.debug("Could not read JSON from response data due to %s - %s", type(exc), exc) - else: - return self._client._process_response_data( - data=data, - cast_to=cast_to, # type: ignore - response=response, - ) - - if self._client._strict_response_validation: - raise APIResponseValidationError( - response=response, - message=f"Expected Content-Type response header to be `application/json` but received `{content_type}` instead.", - body=response.text, - ) - - # If the API responds with content that isn't JSON then we just return - # the (decoded) text without performing any parsing so that you can still - # handle the response however you need to. - return response.text # type: ignore - - data = response.json() - - return self._client._process_response_data( - data=data, - cast_to=cast_to, # type: ignore - response=response, - ) - - -class APIResponse(BaseAPIResponse[R]): - @overload - def parse(self, *, to: type[_T]) -> _T: ... - - @overload - def parse(self) -> R: ... - - def parse(self, *, to: type[_T] | None = None) -> R | _T: - """Returns the rich python representation of this response's data. - - For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`. - - You can customise the type that the response is parsed into through - the `to` argument, e.g. - - ```py - from moderation_api import BaseModel - - - class MyModel(BaseModel): - foo: str - - - obj = response.parse(to=MyModel) - print(obj.foo) - ``` - - We support parsing: - - `BaseModel` - - `dict` - - `list` - - `Union` - - `str` - - `int` - - `float` - - `httpx.Response` - """ - cache_key = to if to is not None else self._cast_to - cached = self._parsed_by_type.get(cache_key) - if cached is not None: - return cached # type: ignore[no-any-return] - - if not self._is_sse_stream: - self.read() - - parsed = self._parse(to=to) - if is_given(self._options.post_parser): - parsed = self._options.post_parser(parsed) - - self._parsed_by_type[cache_key] = parsed - return parsed - - def read(self) -> bytes: - """Read and return the binary response content.""" - try: - return self.http_response.read() - except httpx.StreamConsumed as exc: - # The default error raised by httpx isn't very - # helpful in our case so we re-raise it with - # a different error message. - raise StreamAlreadyConsumed() from exc - - def text(self) -> str: - """Read and decode the response content into a string.""" - self.read() - return self.http_response.text - - def json(self) -> object: - """Read and decode the JSON response content.""" - self.read() - return self.http_response.json() - - def close(self) -> None: - """Close the response and release the connection. - - Automatically called if the response body is read to completion. - """ - self.http_response.close() - - def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]: - """ - A byte-iterator over the decoded response content. - - This automatically handles gzip, deflate and brotli encoded responses. - """ - for chunk in self.http_response.iter_bytes(chunk_size): - yield chunk - - def iter_text(self, chunk_size: int | None = None) -> Iterator[str]: - """A str-iterator over the decoded response content - that handles both gzip, deflate, etc but also detects the content's - string encoding. - """ - for chunk in self.http_response.iter_text(chunk_size): - yield chunk - - def iter_lines(self) -> Iterator[str]: - """Like `iter_text()` but will only yield chunks for each line""" - for chunk in self.http_response.iter_lines(): - yield chunk - - -class AsyncAPIResponse(BaseAPIResponse[R]): - @overload - async def parse(self, *, to: type[_T]) -> _T: ... - - @overload - async def parse(self) -> R: ... - - async def parse(self, *, to: type[_T] | None = None) -> R | _T: - """Returns the rich python representation of this response's data. - - For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`. - - You can customise the type that the response is parsed into through - the `to` argument, e.g. - - ```py - from moderation_api import BaseModel - - - class MyModel(BaseModel): - foo: str - - - obj = response.parse(to=MyModel) - print(obj.foo) - ``` - - We support parsing: - - `BaseModel` - - `dict` - - `list` - - `Union` - - `str` - - `httpx.Response` - """ - cache_key = to if to is not None else self._cast_to - cached = self._parsed_by_type.get(cache_key) - if cached is not None: - return cached # type: ignore[no-any-return] - - if not self._is_sse_stream: - await self.read() - - parsed = self._parse(to=to) - if is_given(self._options.post_parser): - parsed = self._options.post_parser(parsed) - - self._parsed_by_type[cache_key] = parsed - return parsed - - async def read(self) -> bytes: - """Read and return the binary response content.""" - try: - return await self.http_response.aread() - except httpx.StreamConsumed as exc: - # the default error raised by httpx isn't very - # helpful in our case so we re-raise it with - # a different error message - raise StreamAlreadyConsumed() from exc - - async def text(self) -> str: - """Read and decode the response content into a string.""" - await self.read() - return self.http_response.text - - async def json(self) -> object: - """Read and decode the JSON response content.""" - await self.read() - return self.http_response.json() - - async def close(self) -> None: - """Close the response and release the connection. - - Automatically called if the response body is read to completion. - """ - await self.http_response.aclose() - - async def iter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]: - """ - A byte-iterator over the decoded response content. - - This automatically handles gzip, deflate and brotli encoded responses. - """ - async for chunk in self.http_response.aiter_bytes(chunk_size): - yield chunk - - async def iter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]: - """A str-iterator over the decoded response content - that handles both gzip, deflate, etc but also detects the content's - string encoding. - """ - async for chunk in self.http_response.aiter_text(chunk_size): - yield chunk - - async def iter_lines(self) -> AsyncIterator[str]: - """Like `iter_text()` but will only yield chunks for each line""" - async for chunk in self.http_response.aiter_lines(): - yield chunk - - -class BinaryAPIResponse(APIResponse[bytes]): - """Subclass of APIResponse providing helpers for dealing with binary data. - - Note: If you want to stream the response data instead of eagerly reading it - all at once then you should use `.with_streaming_response` when making - the API request, e.g. `.with_streaming_response.get_binary_response()` - """ - - def write_to_file( - self, - file: str | os.PathLike[str], - ) -> None: - """Write the output to the given file. - - Accepts a filename or any path-like object, e.g. pathlib.Path - - Note: if you want to stream the data to the file instead of writing - all at once then you should use `.with_streaming_response` when making - the API request, e.g. `.with_streaming_response.get_binary_response()` - """ - with open(file, mode="wb") as f: - for data in self.iter_bytes(): - f.write(data) - - -class AsyncBinaryAPIResponse(AsyncAPIResponse[bytes]): - """Subclass of APIResponse providing helpers for dealing with binary data. - - Note: If you want to stream the response data instead of eagerly reading it - all at once then you should use `.with_streaming_response` when making - the API request, e.g. `.with_streaming_response.get_binary_response()` - """ - - async def write_to_file( - self, - file: str | os.PathLike[str], - ) -> None: - """Write the output to the given file. - - Accepts a filename or any path-like object, e.g. pathlib.Path - - Note: if you want to stream the data to the file instead of writing - all at once then you should use `.with_streaming_response` when making - the API request, e.g. `.with_streaming_response.get_binary_response()` - """ - path = anyio.Path(file) - async with await path.open(mode="wb") as f: - async for data in self.iter_bytes(): - await f.write(data) - - -class StreamedBinaryAPIResponse(APIResponse[bytes]): - def stream_to_file( - self, - file: str | os.PathLike[str], - *, - chunk_size: int | None = None, - ) -> None: - """Streams the output to the given file. - - Accepts a filename or any path-like object, e.g. pathlib.Path - """ - with open(file, mode="wb") as f: - for data in self.iter_bytes(chunk_size): - f.write(data) - - -class AsyncStreamedBinaryAPIResponse(AsyncAPIResponse[bytes]): - async def stream_to_file( - self, - file: str | os.PathLike[str], - *, - chunk_size: int | None = None, - ) -> None: - """Streams the output to the given file. - - Accepts a filename or any path-like object, e.g. pathlib.Path - """ - path = anyio.Path(file) - async with await path.open(mode="wb") as f: - async for data in self.iter_bytes(chunk_size): - await f.write(data) - - -class MissingStreamClassError(TypeError): - def __init__(self) -> None: - super().__init__( - "The `stream` argument was set to `True` but the `stream_cls` argument was not given. See `moderation_api._streaming` for reference", - ) - - -class StreamAlreadyConsumed(ModerationAPIError): - """ - Attempted to read or stream content, but the content has already - been streamed. - - This can happen if you use a method like `.iter_lines()` and then attempt - to read th entire response body afterwards, e.g. - - ```py - response = await client.post(...) - async for line in response.iter_lines(): - ... # do something with `line` - - content = await response.read() - # ^ error - ``` - - If you want this behaviour you'll need to either manually accumulate the response - content or call `await response.read()` before iterating over the stream. - """ - - def __init__(self) -> None: - message = ( - "Attempted to read or stream some content, but the content has " - "already been streamed. " - "This could be due to attempting to stream the response " - "content more than once." - "\n\n" - "You can fix this by manually accumulating the response content while streaming " - "or by calling `.read()` before starting to stream." - ) - super().__init__(message) - - -class ResponseContextManager(Generic[_APIResponseT]): - """Context manager for ensuring that a request is not made - until it is entered and that the response will always be closed - when the context manager exits - """ - - def __init__(self, request_func: Callable[[], _APIResponseT]) -> None: - self._request_func = request_func - self.__response: _APIResponseT | None = None - - def __enter__(self) -> _APIResponseT: - self.__response = self._request_func() - return self.__response - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - if self.__response is not None: - self.__response.close() - - -class AsyncResponseContextManager(Generic[_AsyncAPIResponseT]): - """Context manager for ensuring that a request is not made - until it is entered and that the response will always be closed - when the context manager exits - """ - - def __init__(self, api_request: Awaitable[_AsyncAPIResponseT]) -> None: - self._api_request = api_request - self.__response: _AsyncAPIResponseT | None = None - - async def __aenter__(self) -> _AsyncAPIResponseT: - self.__response = await self._api_request - return self.__response - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - if self.__response is not None: - await self.__response.close() - - -def to_streamed_response_wrapper(func: Callable[P, R]) -> Callable[P, ResponseContextManager[APIResponse[R]]]: - """Higher order function that takes one of our bound API methods and wraps it - to support streaming and returning the raw `APIResponse` object directly. - """ - - @functools.wraps(func) - def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[APIResponse[R]]: - extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} - extra_headers[RAW_RESPONSE_HEADER] = "stream" - - kwargs["extra_headers"] = extra_headers - - make_request = functools.partial(func, *args, **kwargs) - - return ResponseContextManager(cast(Callable[[], APIResponse[R]], make_request)) - - return wrapped - - -def async_to_streamed_response_wrapper( - func: Callable[P, Awaitable[R]], -) -> Callable[P, AsyncResponseContextManager[AsyncAPIResponse[R]]]: - """Higher order function that takes one of our bound API methods and wraps it - to support streaming and returning the raw `APIResponse` object directly. - """ - - @functools.wraps(func) - def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[AsyncAPIResponse[R]]: - extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} - extra_headers[RAW_RESPONSE_HEADER] = "stream" - - kwargs["extra_headers"] = extra_headers - - make_request = func(*args, **kwargs) - - return AsyncResponseContextManager(cast(Awaitable[AsyncAPIResponse[R]], make_request)) - - return wrapped - - -def to_custom_streamed_response_wrapper( - func: Callable[P, object], - response_cls: type[_APIResponseT], -) -> Callable[P, ResponseContextManager[_APIResponseT]]: - """Higher order function that takes one of our bound API methods and an `APIResponse` class - and wraps the method to support streaming and returning the given response class directly. - - Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` - """ - - @functools.wraps(func) - def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[_APIResponseT]: - extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} - extra_headers[RAW_RESPONSE_HEADER] = "stream" - extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls - - kwargs["extra_headers"] = extra_headers - - make_request = functools.partial(func, *args, **kwargs) - - return ResponseContextManager(cast(Callable[[], _APIResponseT], make_request)) - - return wrapped - - -def async_to_custom_streamed_response_wrapper( - func: Callable[P, Awaitable[object]], - response_cls: type[_AsyncAPIResponseT], -) -> Callable[P, AsyncResponseContextManager[_AsyncAPIResponseT]]: - """Higher order function that takes one of our bound API methods and an `APIResponse` class - and wraps the method to support streaming and returning the given response class directly. - - Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` - """ - - @functools.wraps(func) - def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[_AsyncAPIResponseT]: - extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} - extra_headers[RAW_RESPONSE_HEADER] = "stream" - extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls - - kwargs["extra_headers"] = extra_headers - - make_request = func(*args, **kwargs) - - return AsyncResponseContextManager(cast(Awaitable[_AsyncAPIResponseT], make_request)) - - return wrapped - - -def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]]: - """Higher order function that takes one of our bound API methods and wraps it - to support returning the raw `APIResponse` object directly. - """ - - @functools.wraps(func) - def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]: - extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} - extra_headers[RAW_RESPONSE_HEADER] = "raw" - - kwargs["extra_headers"] = extra_headers - - return cast(APIResponse[R], func(*args, **kwargs)) - - return wrapped - - -def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[AsyncAPIResponse[R]]]: - """Higher order function that takes one of our bound API methods and wraps it - to support returning the raw `APIResponse` object directly. - """ - - @functools.wraps(func) - async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncAPIResponse[R]: - extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} - extra_headers[RAW_RESPONSE_HEADER] = "raw" - - kwargs["extra_headers"] = extra_headers - - return cast(AsyncAPIResponse[R], await func(*args, **kwargs)) - - return wrapped - - -def to_custom_raw_response_wrapper( - func: Callable[P, object], - response_cls: type[_APIResponseT], -) -> Callable[P, _APIResponseT]: - """Higher order function that takes one of our bound API methods and an `APIResponse` class - and wraps the method to support returning the given response class directly. - - Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` - """ - - @functools.wraps(func) - def wrapped(*args: P.args, **kwargs: P.kwargs) -> _APIResponseT: - extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} - extra_headers[RAW_RESPONSE_HEADER] = "raw" - extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls - - kwargs["extra_headers"] = extra_headers - - return cast(_APIResponseT, func(*args, **kwargs)) - - return wrapped - - -def async_to_custom_raw_response_wrapper( - func: Callable[P, Awaitable[object]], - response_cls: type[_AsyncAPIResponseT], -) -> Callable[P, Awaitable[_AsyncAPIResponseT]]: - """Higher order function that takes one of our bound API methods and an `APIResponse` class - and wraps the method to support returning the given response class directly. - - Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` - """ - - @functools.wraps(func) - def wrapped(*args: P.args, **kwargs: P.kwargs) -> Awaitable[_AsyncAPIResponseT]: - extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} - extra_headers[RAW_RESPONSE_HEADER] = "raw" - extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls - - kwargs["extra_headers"] = extra_headers - - return cast(Awaitable[_AsyncAPIResponseT], func(*args, **kwargs)) - - return wrapped - - -def extract_response_type(typ: type[BaseAPIResponse[Any]]) -> type: - """Given a type like `APIResponse[T]`, returns the generic type variable `T`. - - This also handles the case where a concrete subclass is given, e.g. - ```py - class MyResponse(APIResponse[bytes]): - ... - - extract_response_type(MyResponse) -> bytes - ``` - """ - return extract_type_var_from_base( - typ, - generic_bases=cast("tuple[type, ...]", (BaseAPIResponse, APIResponse, AsyncAPIResponse)), - index=0, - ) diff --git a/src/moderation_api/_streaming.py b/src/moderation_api/_streaming.py deleted file mode 100644 index 3bbba94..0000000 --- a/src/moderation_api/_streaming.py +++ /dev/null @@ -1,338 +0,0 @@ -# Note: initially copied from https://github.com/florimondmanca/httpx-sse/blob/master/src/httpx_sse/_decoders.py -from __future__ import annotations - -import json -import inspect -from types import TracebackType -from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast -from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable - -import httpx - -from ._utils import extract_type_var_from_base - -if TYPE_CHECKING: - from ._client import ModerationAPI, AsyncModerationAPI - from ._models import FinalRequestOptions - - -_T = TypeVar("_T") - - -class Stream(Generic[_T]): - """Provides the core interface to iterate over a synchronous stream response.""" - - response: httpx.Response - _options: Optional[FinalRequestOptions] = None - _decoder: SSEBytesDecoder - - def __init__( - self, - *, - cast_to: type[_T], - response: httpx.Response, - client: ModerationAPI, - options: Optional[FinalRequestOptions] = None, - ) -> None: - self.response = response - self._cast_to = cast_to - self._client = client - self._options = options - self._decoder = client._make_sse_decoder() - self._iterator = self.__stream__() - - def __next__(self) -> _T: - return self._iterator.__next__() - - def __iter__(self) -> Iterator[_T]: - for item in self._iterator: - yield item - - def _iter_events(self) -> Iterator[ServerSentEvent]: - yield from self._decoder.iter_bytes(self.response.iter_bytes()) - - def __stream__(self) -> Iterator[_T]: - cast_to = cast(Any, self._cast_to) - response = self.response - process_data = self._client._process_response_data - iterator = self._iter_events() - - try: - for sse in iterator: - yield process_data(data=sse.json(), cast_to=cast_to, response=response) - finally: - # Ensure the response is closed even if the consumer doesn't read all data - response.close() - - def __enter__(self) -> Self: - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - self.close() - - def close(self) -> None: - """ - Close the response and release the connection. - - Automatically called if the response body is read to completion. - """ - self.response.close() - - -class AsyncStream(Generic[_T]): - """Provides the core interface to iterate over an asynchronous stream response.""" - - response: httpx.Response - _options: Optional[FinalRequestOptions] = None - _decoder: SSEDecoder | SSEBytesDecoder - - def __init__( - self, - *, - cast_to: type[_T], - response: httpx.Response, - client: AsyncModerationAPI, - options: Optional[FinalRequestOptions] = None, - ) -> None: - self.response = response - self._cast_to = cast_to - self._client = client - self._options = options - self._decoder = client._make_sse_decoder() - self._iterator = self.__stream__() - - async def __anext__(self) -> _T: - return await self._iterator.__anext__() - - async def __aiter__(self) -> AsyncIterator[_T]: - async for item in self._iterator: - yield item - - async def _iter_events(self) -> AsyncIterator[ServerSentEvent]: - async for sse in self._decoder.aiter_bytes(self.response.aiter_bytes()): - yield sse - - async def __stream__(self) -> AsyncIterator[_T]: - cast_to = cast(Any, self._cast_to) - response = self.response - process_data = self._client._process_response_data - iterator = self._iter_events() - - try: - async for sse in iterator: - yield process_data(data=sse.json(), cast_to=cast_to, response=response) - finally: - # Ensure the response is closed even if the consumer doesn't read all data - await response.aclose() - - async def __aenter__(self) -> Self: - return self - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - await self.close() - - async def close(self) -> None: - """ - Close the response and release the connection. - - Automatically called if the response body is read to completion. - """ - await self.response.aclose() - - -class ServerSentEvent: - def __init__( - self, - *, - event: str | None = None, - data: str | None = None, - id: str | None = None, - retry: int | None = None, - ) -> None: - if data is None: - data = "" - - self._id = id - self._data = data - self._event = event or None - self._retry = retry - - @property - def event(self) -> str | None: - return self._event - - @property - def id(self) -> str | None: - return self._id - - @property - def retry(self) -> int | None: - return self._retry - - @property - def data(self) -> str: - return self._data - - def json(self) -> Any: - return json.loads(self.data) - - @override - def __repr__(self) -> str: - return f"ServerSentEvent(event={self.event}, data={self.data}, id={self.id}, retry={self.retry})" - - -class SSEDecoder: - _data: list[str] - _event: str | None - _retry: int | None - _last_event_id: str | None - - def __init__(self) -> None: - self._event = None - self._data = [] - self._last_event_id = None - self._retry = None - - def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]: - """Given an iterator that yields raw binary data, iterate over it & yield every event encountered""" - for chunk in self._iter_chunks(iterator): - # Split before decoding so splitlines() only uses \r and \n - for raw_line in chunk.splitlines(): - line = raw_line.decode("utf-8") - sse = self.decode(line) - if sse: - yield sse - - def _iter_chunks(self, iterator: Iterator[bytes]) -> Iterator[bytes]: - """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks""" - data = b"" - for chunk in iterator: - for line in chunk.splitlines(keepends=True): - data += line - if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")): - yield data - data = b"" - if data: - yield data - - async def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]: - """Given an iterator that yields raw binary data, iterate over it & yield every event encountered""" - async for chunk in self._aiter_chunks(iterator): - # Split before decoding so splitlines() only uses \r and \n - for raw_line in chunk.splitlines(): - line = raw_line.decode("utf-8") - sse = self.decode(line) - if sse: - yield sse - - async def _aiter_chunks(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[bytes]: - """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks""" - data = b"" - async for chunk in iterator: - for line in chunk.splitlines(keepends=True): - data += line - if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")): - yield data - data = b"" - if data: - yield data - - def decode(self, line: str) -> ServerSentEvent | None: - # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501 - - if not line: - if not self._event and not self._data and not self._last_event_id and self._retry is None: - return None - - sse = ServerSentEvent( - event=self._event, - data="\n".join(self._data), - id=self._last_event_id, - retry=self._retry, - ) - - # NOTE: as per the SSE spec, do not reset last_event_id. - self._event = None - self._data = [] - self._retry = None - - return sse - - if line.startswith(":"): - return None - - fieldname, _, value = line.partition(":") - - if value.startswith(" "): - value = value[1:] - - if fieldname == "event": - self._event = value - elif fieldname == "data": - self._data.append(value) - elif fieldname == "id": - if "\0" in value: - pass - else: - self._last_event_id = value - elif fieldname == "retry": - try: - self._retry = int(value) - except (TypeError, ValueError): - pass - else: - pass # Field is ignored. - - return None - - -@runtime_checkable -class SSEBytesDecoder(Protocol): - def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]: - """Given an iterator that yields raw binary data, iterate over it & yield every event encountered""" - ... - - def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]: - """Given an async iterator that yields raw binary data, iterate over it & yield every event encountered""" - ... - - -def is_stream_class_type(typ: type) -> TypeGuard[type[Stream[object]] | type[AsyncStream[object]]]: - """TypeGuard for determining whether or not the given type is a subclass of `Stream` / `AsyncStream`""" - origin = get_origin(typ) or typ - return inspect.isclass(origin) and issubclass(origin, (Stream, AsyncStream)) - - -def extract_stream_chunk_type( - stream_cls: type, - *, - failure_message: str | None = None, -) -> type: - """Given a type like `Stream[T]`, returns the generic type variable `T`. - - This also handles the case where a concrete subclass is given, e.g. - ```py - class MyStream(Stream[bytes]): - ... - - extract_stream_chunk_type(MyStream) -> bytes - ``` - """ - from ._base_client import Stream, AsyncStream - - return extract_type_var_from_base( - stream_cls, - index=0, - generic_bases=cast("tuple[type, ...]", (Stream, AsyncStream)), - failure_message=failure_message, - ) diff --git a/src/moderation_api/_types.py b/src/moderation_api/_types.py deleted file mode 100644 index 3fe257c..0000000 --- a/src/moderation_api/_types.py +++ /dev/null @@ -1,273 +0,0 @@ -from __future__ import annotations - -from os import PathLike -from typing import ( - IO, - TYPE_CHECKING, - Any, - Dict, - List, - Type, - Tuple, - Union, - Mapping, - TypeVar, - Callable, - Iterable, - Iterator, - Optional, - Sequence, - AsyncIterable, -) -from typing_extensions import ( - Set, - Literal, - Protocol, - TypeAlias, - TypedDict, - SupportsIndex, - overload, - override, - runtime_checkable, -) - -import httpx -import pydantic -from httpx import URL, Proxy, Timeout, Response, BaseTransport, AsyncBaseTransport - -if TYPE_CHECKING: - from ._models import BaseModel - from ._response import APIResponse, AsyncAPIResponse - -Transport = BaseTransport -AsyncTransport = AsyncBaseTransport -Query = Mapping[str, object] -Body = object -AnyMapping = Mapping[str, object] -ModelT = TypeVar("ModelT", bound=pydantic.BaseModel) -_T = TypeVar("_T") - -ArrayFormat = Literal["comma", "repeat", "indices", "brackets"] -NestedFormat = Literal["dots", "brackets"] - - -# Approximates httpx internal ProxiesTypes and RequestFiles types -# while adding support for `PathLike` instances -ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]] -ProxiesTypes = Union[str, Proxy, ProxiesDict] -if TYPE_CHECKING: - Base64FileInput = Union[IO[bytes], PathLike[str]] - FileContent = Union[IO[bytes], bytes, PathLike[str]] -else: - Base64FileInput = Union[IO[bytes], PathLike] - FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8. - - -# Used for sending raw binary data / streaming data in request bodies -# e.g. for file uploads without multipart encoding -BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]] -AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]] - -FileTypes = Union[ - # file (or bytes) - FileContent, - # (filename, file (or bytes)) - Tuple[Optional[str], FileContent], - # (filename, file (or bytes), content_type) - Tuple[Optional[str], FileContent, Optional[str]], - # (filename, file (or bytes), content_type, headers) - Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], -] -RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]] - -# duplicate of the above but without our custom file support -HttpxFileContent = Union[IO[bytes], bytes] -HttpxFileTypes = Union[ - # file (or bytes) - HttpxFileContent, - # (filename, file (or bytes)) - Tuple[Optional[str], HttpxFileContent], - # (filename, file (or bytes), content_type) - Tuple[Optional[str], HttpxFileContent, Optional[str]], - # (filename, file (or bytes), content_type, headers) - Tuple[Optional[str], HttpxFileContent, Optional[str], Mapping[str, str]], -] -HttpxRequestFiles = Union[Mapping[str, HttpxFileTypes], Sequence[Tuple[str, HttpxFileTypes]]] - -# Workaround to support (cast_to: Type[ResponseT]) -> ResponseT -# where ResponseT includes `None`. In order to support directly -# passing `None`, overloads would have to be defined for every -# method that uses `ResponseT` which would lead to an unacceptable -# amount of code duplication and make it unreadable. See _base_client.py -# for example usage. -# -# This unfortunately means that you will either have -# to import this type and pass it explicitly: -# -# from moderation_api import NoneType -# client.get('/foo', cast_to=NoneType) -# -# or build it yourself: -# -# client.get('/foo', cast_to=type(None)) -if TYPE_CHECKING: - NoneType: Type[None] -else: - NoneType = type(None) - - -class RequestOptions(TypedDict, total=False): - headers: Headers - max_retries: int - timeout: float | Timeout | None - params: Query - extra_json: AnyMapping - idempotency_key: str - follow_redirects: bool - - -# Sentinel class used until PEP 0661 is accepted -class NotGiven: - """ - For parameters with a meaningful None value, we need to distinguish between - the user explicitly passing None, and the user not passing the parameter at - all. - - User code shouldn't need to use not_given directly. - - For example: - - ```py - def create(timeout: Timeout | None | NotGiven = not_given): ... - - - create(timeout=1) # 1s timeout - create(timeout=None) # No timeout - create() # Default timeout behavior - ``` - """ - - def __bool__(self) -> Literal[False]: - return False - - @override - def __repr__(self) -> str: - return "NOT_GIVEN" - - -not_given = NotGiven() -# for backwards compatibility: -NOT_GIVEN = NotGiven() - - -class Omit: - """ - To explicitly omit something from being sent in a request, use `omit`. - - ```py - # as the default `Content-Type` header is `application/json` that will be sent - client.post("/upload/files", files={"file": b"my raw file content"}) - - # you can't explicitly override the header as it has to be dynamically generated - # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983' - client.post(..., headers={"Content-Type": "multipart/form-data"}) - - # instead you can remove the default `application/json` header by passing omit - client.post(..., headers={"Content-Type": omit}) - ``` - """ - - def __bool__(self) -> Literal[False]: - return False - - -omit = Omit() - - -@runtime_checkable -class ModelBuilderProtocol(Protocol): - @classmethod - def build( - cls: type[_T], - *, - response: Response, - data: object, - ) -> _T: ... - - -Headers = Mapping[str, Union[str, Omit]] - - -class HeadersLikeProtocol(Protocol): - def get(self, __key: str) -> str | None: ... - - -HeadersLike = Union[Headers, HeadersLikeProtocol] - -ResponseT = TypeVar( - "ResponseT", - bound=Union[ - object, - str, - None, - "BaseModel", - List[Any], - Dict[str, Any], - Response, - ModelBuilderProtocol, - "APIResponse[Any]", - "AsyncAPIResponse[Any]", - ], -) - -StrBytesIntFloat = Union[str, bytes, int, float] - -# Note: copied from Pydantic -# https://github.com/pydantic/pydantic/blob/6f31f8f68ef011f84357330186f603ff295312fd/pydantic/main.py#L79 -IncEx: TypeAlias = Union[Set[int], Set[str], Mapping[int, Union["IncEx", bool]], Mapping[str, Union["IncEx", bool]]] - -PostParser = Callable[[Any], Any] - - -@runtime_checkable -class InheritsGeneric(Protocol): - """Represents a type that has inherited from `Generic` - - The `__orig_bases__` property can be used to determine the resolved - type variable for a given base class. - """ - - __orig_bases__: tuple[_GenericAlias] - - -class _GenericAlias(Protocol): - __origin__: type[object] - - -class HttpxSendArgs(TypedDict, total=False): - auth: httpx.Auth - follow_redirects: bool - - -_T_co = TypeVar("_T_co", covariant=True) - - -if TYPE_CHECKING: - # This works because str.__contains__ does not accept object (either in typeshed or at runtime) - # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285 - # - # Note: index() and count() methods are intentionally omitted to allow pyright to properly - # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr. - class SequenceNotStr(Protocol[_T_co]): - @overload - def __getitem__(self, index: SupportsIndex, /) -> _T_co: ... - @overload - def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ... - def __contains__(self, value: object, /) -> bool: ... - def __len__(self) -> int: ... - def __iter__(self) -> Iterator[_T_co]: ... - def __reversed__(self) -> Iterator[_T_co]: ... -else: - # just point this to a normal `Sequence` at runtime to avoid having to special case - # deserializing our custom sequence type - SequenceNotStr = Sequence diff --git a/src/moderation_api/_utils/__init__.py b/src/moderation_api/_utils/__init__.py deleted file mode 100644 index 1c090e5..0000000 --- a/src/moderation_api/_utils/__init__.py +++ /dev/null @@ -1,64 +0,0 @@ -from ._path import path_template as path_template -from ._sync import asyncify as asyncify -from ._proxy import LazyProxy as LazyProxy -from ._utils import ( - flatten as flatten, - is_dict as is_dict, - is_list as is_list, - is_given as is_given, - is_tuple as is_tuple, - json_safe as json_safe, - lru_cache as lru_cache, - is_mapping as is_mapping, - is_tuple_t as is_tuple_t, - is_iterable as is_iterable, - is_sequence as is_sequence, - coerce_float as coerce_float, - is_mapping_t as is_mapping_t, - removeprefix as removeprefix, - removesuffix as removesuffix, - extract_files as extract_files, - is_sequence_t as is_sequence_t, - required_args as required_args, - coerce_boolean as coerce_boolean, - coerce_integer as coerce_integer, - file_from_path as file_from_path, - strip_not_given as strip_not_given, - get_async_library as get_async_library, - maybe_coerce_float as maybe_coerce_float, - get_required_header as get_required_header, - maybe_coerce_boolean as maybe_coerce_boolean, - maybe_coerce_integer as maybe_coerce_integer, -) -from ._compat import ( - get_args as get_args, - is_union as is_union, - get_origin as get_origin, - is_typeddict as is_typeddict, - is_literal_type as is_literal_type, -) -from ._typing import ( - is_list_type as is_list_type, - is_union_type as is_union_type, - extract_type_arg as extract_type_arg, - is_iterable_type as is_iterable_type, - is_required_type as is_required_type, - is_sequence_type as is_sequence_type, - is_annotated_type as is_annotated_type, - is_type_alias_type as is_type_alias_type, - strip_annotated_type as strip_annotated_type, - extract_type_var_from_base as extract_type_var_from_base, -) -from ._streams import consume_sync_iterator as consume_sync_iterator, consume_async_iterator as consume_async_iterator -from ._transform import ( - PropertyInfo as PropertyInfo, - transform as transform, - async_transform as async_transform, - maybe_transform as maybe_transform, - async_maybe_transform as async_maybe_transform, -) -from ._reflection import ( - function_has_argument as function_has_argument, - assert_signatures_in_sync as assert_signatures_in_sync, -) -from ._datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime diff --git a/src/moderation_api/_utils/_compat.py b/src/moderation_api/_utils/_compat.py deleted file mode 100644 index 2c70b29..0000000 --- a/src/moderation_api/_utils/_compat.py +++ /dev/null @@ -1,45 +0,0 @@ -from __future__ import annotations - -import sys -import typing_extensions -from typing import Any, Type, Union, Literal, Optional -from datetime import date, datetime -from typing_extensions import get_args as _get_args, get_origin as _get_origin - -from .._types import StrBytesIntFloat -from ._datetime_parse import parse_date as _parse_date, parse_datetime as _parse_datetime - -_LITERAL_TYPES = {Literal, typing_extensions.Literal} - - -def get_args(tp: type[Any]) -> tuple[Any, ...]: - return _get_args(tp) - - -def get_origin(tp: type[Any]) -> type[Any] | None: - return _get_origin(tp) - - -def is_union(tp: Optional[Type[Any]]) -> bool: - if sys.version_info < (3, 10): - return tp is Union # type: ignore[comparison-overlap] - else: - import types - - return tp is Union or tp is types.UnionType # type: ignore[comparison-overlap] - - -def is_typeddict(tp: Type[Any]) -> bool: - return typing_extensions.is_typeddict(tp) - - -def is_literal_type(tp: Type[Any]) -> bool: - return get_origin(tp) in _LITERAL_TYPES - - -def parse_date(value: Union[date, StrBytesIntFloat]) -> date: - return _parse_date(value) - - -def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: - return _parse_datetime(value) diff --git a/src/moderation_api/_utils/_datetime_parse.py b/src/moderation_api/_utils/_datetime_parse.py deleted file mode 100644 index 7cb9d9e..0000000 --- a/src/moderation_api/_utils/_datetime_parse.py +++ /dev/null @@ -1,136 +0,0 @@ -""" -This file contains code from https://github.com/pydantic/pydantic/blob/main/pydantic/v1/datetime_parse.py -without the Pydantic v1 specific errors. -""" - -from __future__ import annotations - -import re -from typing import Dict, Union, Optional -from datetime import date, datetime, timezone, timedelta - -from .._types import StrBytesIntFloat - -date_expr = r"(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})" -time_expr = ( - r"(?P\d{1,2}):(?P\d{1,2})" - r"(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?" - r"(?PZ|[+-]\d{2}(?::?\d{2})?)?$" -) - -date_re = re.compile(f"{date_expr}$") -datetime_re = re.compile(f"{date_expr}[T ]{time_expr}") - - -EPOCH = datetime(1970, 1, 1) -# if greater than this, the number is in ms, if less than or equal it's in seconds -# (in seconds this is 11th October 2603, in ms it's 20th August 1970) -MS_WATERSHED = int(2e10) -# slightly more than datetime.max in ns - (datetime.max - EPOCH).total_seconds() * 1e9 -MAX_NUMBER = int(3e20) - - -def _get_numeric(value: StrBytesIntFloat, native_expected_type: str) -> Union[None, int, float]: - if isinstance(value, (int, float)): - return value - try: - return float(value) - except ValueError: - return None - except TypeError: - raise TypeError(f"invalid type; expected {native_expected_type}, string, bytes, int or float") from None - - -def _from_unix_seconds(seconds: Union[int, float]) -> datetime: - if seconds > MAX_NUMBER: - return datetime.max - elif seconds < -MAX_NUMBER: - return datetime.min - - while abs(seconds) > MS_WATERSHED: - seconds /= 1000 - dt = EPOCH + timedelta(seconds=seconds) - return dt.replace(tzinfo=timezone.utc) - - -def _parse_timezone(value: Optional[str]) -> Union[None, int, timezone]: - if value == "Z": - return timezone.utc - elif value is not None: - offset_mins = int(value[-2:]) if len(value) > 3 else 0 - offset = 60 * int(value[1:3]) + offset_mins - if value[0] == "-": - offset = -offset - return timezone(timedelta(minutes=offset)) - else: - return None - - -def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: - """ - Parse a datetime/int/float/string and return a datetime.datetime. - - This function supports time zone offsets. When the input contains one, - the output uses a timezone with a fixed offset from UTC. - - Raise ValueError if the input is well formatted but not a valid datetime. - Raise ValueError if the input isn't well formatted. - """ - if isinstance(value, datetime): - return value - - number = _get_numeric(value, "datetime") - if number is not None: - return _from_unix_seconds(number) - - if isinstance(value, bytes): - value = value.decode() - - assert not isinstance(value, (float, int)) - - match = datetime_re.match(value) - if match is None: - raise ValueError("invalid datetime format") - - kw = match.groupdict() - if kw["microsecond"]: - kw["microsecond"] = kw["microsecond"].ljust(6, "0") - - tzinfo = _parse_timezone(kw.pop("tzinfo")) - kw_: Dict[str, Union[None, int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None} - kw_["tzinfo"] = tzinfo - - return datetime(**kw_) # type: ignore - - -def parse_date(value: Union[date, StrBytesIntFloat]) -> date: - """ - Parse a date/int/float/string and return a datetime.date. - - Raise ValueError if the input is well formatted but not a valid date. - Raise ValueError if the input isn't well formatted. - """ - if isinstance(value, date): - if isinstance(value, datetime): - return value.date() - else: - return value - - number = _get_numeric(value, "date") - if number is not None: - return _from_unix_seconds(number).date() - - if isinstance(value, bytes): - value = value.decode() - - assert not isinstance(value, (float, int)) - match = date_re.match(value) - if match is None: - raise ValueError("invalid date format") - - kw = {k: int(v) for k, v in match.groupdict().items()} - - try: - return date(**kw) - except ValueError: - raise ValueError("invalid date format") from None diff --git a/src/moderation_api/_utils/_json.py b/src/moderation_api/_utils/_json.py deleted file mode 100644 index 6058421..0000000 --- a/src/moderation_api/_utils/_json.py +++ /dev/null @@ -1,35 +0,0 @@ -import json -from typing import Any -from datetime import datetime -from typing_extensions import override - -import pydantic - -from .._compat import model_dump - - -def openapi_dumps(obj: Any) -> bytes: - """ - Serialize an object to UTF-8 encoded JSON bytes. - - Extends the standard json.dumps with support for additional types - commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc. - """ - return json.dumps( - obj, - cls=_CustomEncoder, - # Uses the same defaults as httpx's JSON serialization - ensure_ascii=False, - separators=(",", ":"), - allow_nan=False, - ).encode() - - -class _CustomEncoder(json.JSONEncoder): - @override - def default(self, o: Any) -> Any: - if isinstance(o, datetime): - return o.isoformat() - if isinstance(o, pydantic.BaseModel): - return model_dump(o, exclude_unset=True, mode="json", by_alias=True) - return super().default(o) diff --git a/src/moderation_api/_utils/_logs.py b/src/moderation_api/_utils/_logs.py deleted file mode 100644 index 7147c0d..0000000 --- a/src/moderation_api/_utils/_logs.py +++ /dev/null @@ -1,25 +0,0 @@ -import os -import logging - -logger: logging.Logger = logging.getLogger("moderation_api") -httpx_logger: logging.Logger = logging.getLogger("httpx") - - -def _basic_config() -> None: - # e.g. [2023-10-05 14:12:26 - moderation_api._base_client:818 - DEBUG] HTTP Request: POST http://127.0.0.1:4010/foo/bar "200 OK" - logging.basicConfig( - format="[%(asctime)s - %(name)s:%(lineno)d - %(levelname)s] %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - ) - - -def setup_logging() -> None: - env = os.environ.get("MODERATION_API_LOG") - if env == "debug": - _basic_config() - logger.setLevel(logging.DEBUG) - httpx_logger.setLevel(logging.DEBUG) - elif env == "info": - _basic_config() - logger.setLevel(logging.INFO) - httpx_logger.setLevel(logging.INFO) diff --git a/src/moderation_api/_utils/_path.py b/src/moderation_api/_utils/_path.py deleted file mode 100644 index 4d6e1e4..0000000 --- a/src/moderation_api/_utils/_path.py +++ /dev/null @@ -1,127 +0,0 @@ -from __future__ import annotations - -import re -from typing import ( - Any, - Mapping, - Callable, -) -from urllib.parse import quote - -# Matches '.' or '..' where each dot is either literal or percent-encoded (%2e / %2E). -_DOT_SEGMENT_RE = re.compile(r"^(?:\.|%2[eE]){1,2}$") - -_PLACEHOLDER_RE = re.compile(r"\{(\w+)\}") - - -def _quote_path_segment_part(value: str) -> str: - """Percent-encode `value` for use in a URI path segment. - - Considers characters not in `pchar` set from RFC 3986 §3.3 to be unsafe. - https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 - """ - # quote() already treats unreserved characters (letters, digits, and -._~) - # as safe, so we only need to add sub-delims, ':', and '@'. - # Notably, unlike the default `safe` for quote(), / is unsafe and must be quoted. - return quote(value, safe="!$&'()*+,;=:@") - - -def _quote_query_part(value: str) -> str: - """Percent-encode `value` for use in a URI query string. - - Considers &, = and characters not in `query` set from RFC 3986 §3.4 to be unsafe. - https://datatracker.ietf.org/doc/html/rfc3986#section-3.4 - """ - return quote(value, safe="!$'()*+,;:@/?") - - -def _quote_fragment_part(value: str) -> str: - """Percent-encode `value` for use in a URI fragment. - - Considers characters not in `fragment` set from RFC 3986 §3.5 to be unsafe. - https://datatracker.ietf.org/doc/html/rfc3986#section-3.5 - """ - return quote(value, safe="!$&'()*+,;=:@/?") - - -def _interpolate( - template: str, - values: Mapping[str, Any], - quoter: Callable[[str], str], -) -> str: - """Replace {name} placeholders in `template`, quoting each value with `quoter`. - - Placeholder names are looked up in `values`. - - Raises: - KeyError: If a placeholder is not found in `values`. - """ - # re.split with a capturing group returns alternating - # [text, name, text, name, ..., text] elements. - parts = _PLACEHOLDER_RE.split(template) - - for i in range(1, len(parts), 2): - name = parts[i] - if name not in values: - raise KeyError(f"a value for placeholder {{{name}}} was not provided") - val = values[name] - if val is None: - parts[i] = "null" - elif isinstance(val, bool): - parts[i] = "true" if val else "false" - else: - parts[i] = quoter(str(values[name])) - - return "".join(parts) - - -def path_template(template: str, /, **kwargs: Any) -> str: - """Interpolate {name} placeholders in `template` from keyword arguments. - - Args: - template: The template string containing {name} placeholders. - **kwargs: Keyword arguments to interpolate into the template. - - Returns: - The template with placeholders interpolated and percent-encoded. - - Safe characters for percent-encoding are dependent on the URI component. - Placeholders in path and fragment portions are percent-encoded where the `segment` - and `fragment` sets from RFC 3986 respectively are considered safe. - Placeholders in the query portion are percent-encoded where the `query` set from - RFC 3986 §3.3 is considered safe except for = and & characters. - - Raises: - KeyError: If a placeholder is not found in `kwargs`. - ValueError: If resulting path contains /./ or /../ segments (including percent-encoded dot-segments). - """ - # Split the template into path, query, and fragment portions. - fragment_template: str | None = None - query_template: str | None = None - - rest = template - if "#" in rest: - rest, fragment_template = rest.split("#", 1) - if "?" in rest: - rest, query_template = rest.split("?", 1) - path_template = rest - - # Interpolate each portion with the appropriate quoting rules. - path_result = _interpolate(path_template, kwargs, _quote_path_segment_part) - - # Reject dot-segments (. and ..) in the final assembled path. The check - # runs after interpolation so that adjacent placeholders or a mix of static - # text and placeholders that together form a dot-segment are caught. - # Also reject percent-encoded dot-segments to protect against incorrectly - # implemented normalization in servers/proxies. - for segment in path_result.split("/"): - if _DOT_SEGMENT_RE.match(segment): - raise ValueError(f"Constructed path {path_result!r} contains dot-segment {segment!r} which is not allowed") - - result = path_result - if query_template is not None: - result += "?" + _interpolate(query_template, kwargs, _quote_query_part) - if fragment_template is not None: - result += "#" + _interpolate(fragment_template, kwargs, _quote_fragment_part) - - return result diff --git a/src/moderation_api/_utils/_proxy.py b/src/moderation_api/_utils/_proxy.py deleted file mode 100644 index 0f239a3..0000000 --- a/src/moderation_api/_utils/_proxy.py +++ /dev/null @@ -1,65 +0,0 @@ -from __future__ import annotations - -from abc import ABC, abstractmethod -from typing import Generic, TypeVar, Iterable, cast -from typing_extensions import override - -T = TypeVar("T") - - -class LazyProxy(Generic[T], ABC): - """Implements data methods to pretend that an instance is another instance. - - This includes forwarding attribute access and other methods. - """ - - # Note: we have to special case proxies that themselves return proxies - # to support using a proxy as a catch-all for any random access, e.g. `proxy.foo.bar.baz` - - def __getattr__(self, attr: str) -> object: - proxied = self.__get_proxied__() - if isinstance(proxied, LazyProxy): - return proxied # pyright: ignore - return getattr(proxied, attr) - - @override - def __repr__(self) -> str: - proxied = self.__get_proxied__() - if isinstance(proxied, LazyProxy): - return proxied.__class__.__name__ - return repr(self.__get_proxied__()) - - @override - def __str__(self) -> str: - proxied = self.__get_proxied__() - if isinstance(proxied, LazyProxy): - return proxied.__class__.__name__ - return str(proxied) - - @override - def __dir__(self) -> Iterable[str]: - proxied = self.__get_proxied__() - if isinstance(proxied, LazyProxy): - return [] - return proxied.__dir__() - - @property # type: ignore - @override - def __class__(self) -> type: # pyright: ignore - try: - proxied = self.__get_proxied__() - except Exception: - return type(self) - if issubclass(type(proxied), LazyProxy): - return type(proxied) - return proxied.__class__ - - def __get_proxied__(self) -> T: - return self.__load__() - - def __as_proxied__(self) -> T: - """Helper method that returns the current proxy, typed as the loaded object""" - return cast(T, self) - - @abstractmethod - def __load__(self) -> T: ... diff --git a/src/moderation_api/_utils/_reflection.py b/src/moderation_api/_utils/_reflection.py deleted file mode 100644 index 89aa712..0000000 --- a/src/moderation_api/_utils/_reflection.py +++ /dev/null @@ -1,42 +0,0 @@ -from __future__ import annotations - -import inspect -from typing import Any, Callable - - -def function_has_argument(func: Callable[..., Any], arg_name: str) -> bool: - """Returns whether or not the given function has a specific parameter""" - sig = inspect.signature(func) - return arg_name in sig.parameters - - -def assert_signatures_in_sync( - source_func: Callable[..., Any], - check_func: Callable[..., Any], - *, - exclude_params: set[str] = set(), -) -> None: - """Ensure that the signature of the second function matches the first.""" - - check_sig = inspect.signature(check_func) - source_sig = inspect.signature(source_func) - - errors: list[str] = [] - - for name, source_param in source_sig.parameters.items(): - if name in exclude_params: - continue - - custom_param = check_sig.parameters.get(name) - if not custom_param: - errors.append(f"the `{name}` param is missing") - continue - - if custom_param.annotation != source_param.annotation: - errors.append( - f"types for the `{name}` param are do not match; source={repr(source_param.annotation)} checking={repr(custom_param.annotation)}" - ) - continue - - if errors: - raise AssertionError(f"{len(errors)} errors encountered when comparing signatures:\n\n" + "\n\n".join(errors)) diff --git a/src/moderation_api/_utils/_resources_proxy.py b/src/moderation_api/_utils/_resources_proxy.py deleted file mode 100644 index 4d5e424..0000000 --- a/src/moderation_api/_utils/_resources_proxy.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import annotations - -from typing import Any -from typing_extensions import override - -from ._proxy import LazyProxy - - -class ResourcesProxy(LazyProxy[Any]): - """A proxy for the `moderation_api.resources` module. - - This is used so that we can lazily import `moderation_api.resources` only when - needed *and* so that users can just import `moderation_api` and reference `moderation_api.resources` - """ - - @override - def __load__(self) -> Any: - import importlib - - mod = importlib.import_module("moderation_api.resources") - return mod - - -resources = ResourcesProxy().__as_proxied__() diff --git a/src/moderation_api/_utils/_streams.py b/src/moderation_api/_utils/_streams.py deleted file mode 100644 index f4a0208..0000000 --- a/src/moderation_api/_utils/_streams.py +++ /dev/null @@ -1,12 +0,0 @@ -from typing import Any -from typing_extensions import Iterator, AsyncIterator - - -def consume_sync_iterator(iterator: Iterator[Any]) -> None: - for _ in iterator: - ... - - -async def consume_async_iterator(iterator: AsyncIterator[Any]) -> None: - async for _ in iterator: - ... diff --git a/src/moderation_api/_utils/_sync.py b/src/moderation_api/_utils/_sync.py deleted file mode 100644 index f6027c1..0000000 --- a/src/moderation_api/_utils/_sync.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import annotations - -import asyncio -import functools -from typing import TypeVar, Callable, Awaitable -from typing_extensions import ParamSpec - -import anyio -import sniffio -import anyio.to_thread - -T_Retval = TypeVar("T_Retval") -T_ParamSpec = ParamSpec("T_ParamSpec") - - -async def to_thread( - func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs -) -> T_Retval: - if sniffio.current_async_library() == "asyncio": - return await asyncio.to_thread(func, *args, **kwargs) - - return await anyio.to_thread.run_sync( - functools.partial(func, *args, **kwargs), - ) - - -# inspired by `asyncer`, https://github.com/tiangolo/asyncer -def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: - """ - Take a blocking function and create an async one that receives the same - positional and keyword arguments. - - Usage: - - ```python - def blocking_func(arg1, arg2, kwarg1=None): - # blocking code - return result - - - result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1) - ``` - - ## Arguments - - `function`: a blocking regular callable (e.g. a function) - - ## Return - - An async function that takes the same positional and keyword arguments as the - original one, that when called runs the same original function in a thread worker - and returns the result. - """ - - async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval: - return await to_thread(function, *args, **kwargs) - - return wrapper diff --git a/src/moderation_api/_utils/_transform.py b/src/moderation_api/_utils/_transform.py deleted file mode 100644 index 5207549..0000000 --- a/src/moderation_api/_utils/_transform.py +++ /dev/null @@ -1,457 +0,0 @@ -from __future__ import annotations - -import io -import base64 -import pathlib -from typing import Any, Mapping, TypeVar, cast -from datetime import date, datetime -from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints - -import anyio -import pydantic - -from ._utils import ( - is_list, - is_given, - lru_cache, - is_mapping, - is_iterable, - is_sequence, -) -from .._files import is_base64_file_input -from ._compat import get_origin, is_typeddict -from ._typing import ( - is_list_type, - is_union_type, - extract_type_arg, - is_iterable_type, - is_required_type, - is_sequence_type, - is_annotated_type, - strip_annotated_type, -) - -_T = TypeVar("_T") - - -# TODO: support for drilling globals() and locals() -# TODO: ensure works correctly with forward references in all cases - - -PropertyFormat = Literal["iso8601", "base64", "custom"] - - -class PropertyInfo: - """Metadata class to be used in Annotated types to provide information about a given type. - - For example: - - class MyParams(TypedDict): - account_holder_name: Annotated[str, PropertyInfo(alias='accountHolderName')] - - This means that {'account_holder_name': 'Robert'} will be transformed to {'accountHolderName': 'Robert'} before being sent to the API. - """ - - alias: str | None - format: PropertyFormat | None - format_template: str | None - discriminator: str | None - - def __init__( - self, - *, - alias: str | None = None, - format: PropertyFormat | None = None, - format_template: str | None = None, - discriminator: str | None = None, - ) -> None: - self.alias = alias - self.format = format - self.format_template = format_template - self.discriminator = discriminator - - @override - def __repr__(self) -> str: - return f"{self.__class__.__name__}(alias='{self.alias}', format={self.format}, format_template='{self.format_template}', discriminator='{self.discriminator}')" - - -def maybe_transform( - data: object, - expected_type: object, -) -> Any | None: - """Wrapper over `transform()` that allows `None` to be passed. - - See `transform()` for more details. - """ - if data is None: - return None - return transform(data, expected_type) - - -# Wrapper over _transform_recursive providing fake types -def transform( - data: _T, - expected_type: object, -) -> _T: - """Transform dictionaries based off of type information from the given type, for example: - - ```py - class Params(TypedDict, total=False): - card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]] - - - transformed = transform({"card_id": ""}, Params) - # {'cardID': ''} - ``` - - Any keys / data that does not have type information given will be included as is. - - It should be noted that the transformations that this function does are not represented in the type system. - """ - transformed = _transform_recursive(data, annotation=cast(type, expected_type)) - return cast(_T, transformed) - - -@lru_cache(maxsize=8096) -def _get_annotated_type(type_: type) -> type | None: - """If the given type is an `Annotated` type then it is returned, if not `None` is returned. - - This also unwraps the type when applicable, e.g. `Required[Annotated[T, ...]]` - """ - if is_required_type(type_): - # Unwrap `Required[Annotated[T, ...]]` to `Annotated[T, ...]` - type_ = get_args(type_)[0] - - if is_annotated_type(type_): - return type_ - - return None - - -def _maybe_transform_key(key: str, type_: type) -> str: - """Transform the given `data` based on the annotations provided in `type_`. - - Note: this function only looks at `Annotated` types that contain `PropertyInfo` metadata. - """ - annotated_type = _get_annotated_type(type_) - if annotated_type is None: - # no `Annotated` definition for this type, no transformation needed - return key - - # ignore the first argument as it is the actual type - annotations = get_args(annotated_type)[1:] - for annotation in annotations: - if isinstance(annotation, PropertyInfo) and annotation.alias is not None: - return annotation.alias - - return key - - -def _no_transform_needed(annotation: type) -> bool: - return annotation == float or annotation == int - - -def _transform_recursive( - data: object, - *, - annotation: type, - inner_type: type | None = None, -) -> object: - """Transform the given data against the expected type. - - Args: - annotation: The direct type annotation given to the particular piece of data. - This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc - - inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type - is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in - the list can be transformed using the metadata from the container type. - - Defaults to the same value as the `annotation` argument. - """ - from .._compat import model_dump - - if inner_type is None: - inner_type = annotation - - stripped_type = strip_annotated_type(inner_type) - origin = get_origin(stripped_type) or stripped_type - if is_typeddict(stripped_type) and is_mapping(data): - return _transform_typeddict(data, stripped_type) - - if origin == dict and is_mapping(data): - items_type = get_args(stripped_type)[1] - return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} - - if ( - # List[T] - (is_list_type(stripped_type) and is_list(data)) - # Iterable[T] - or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) - # Sequence[T] - or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) - ): - # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually - # intended as an iterable, so we don't transform it. - if isinstance(data, dict): - return cast(object, data) - - inner_type = extract_type_arg(stripped_type, 0) - if _no_transform_needed(inner_type): - # for some types there is no need to transform anything, so we can get a small - # perf boost from skipping that work. - # - # but we still need to convert to a list to ensure the data is json-serializable - if is_list(data): - return data - return list(data) - - return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] - - if is_union_type(stripped_type): - # For union types we run the transformation against all subtypes to ensure that everything is transformed. - # - # TODO: there may be edge cases where the same normalized field name will transform to two different names - # in different subtypes. - for subtype in get_args(stripped_type): - data = _transform_recursive(data, annotation=annotation, inner_type=subtype) - return data - - if isinstance(data, pydantic.BaseModel): - return model_dump(data, exclude_unset=True, mode="json") - - annotated_type = _get_annotated_type(annotation) - if annotated_type is None: - return data - - # ignore the first argument as it is the actual type - annotations = get_args(annotated_type)[1:] - for annotation in annotations: - if isinstance(annotation, PropertyInfo) and annotation.format is not None: - return _format_data(data, annotation.format, annotation.format_template) - - return data - - -def _format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object: - if isinstance(data, (date, datetime)): - if format_ == "iso8601": - return data.isoformat() - - if format_ == "custom" and format_template is not None: - return data.strftime(format_template) - - if format_ == "base64" and is_base64_file_input(data): - binary: str | bytes | None = None - - if isinstance(data, pathlib.Path): - binary = data.read_bytes() - elif isinstance(data, io.IOBase): - binary = data.read() - - if isinstance(binary, str): # type: ignore[unreachable] - binary = binary.encode() - - if not isinstance(binary, bytes): - raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}") - - return base64.b64encode(binary).decode("ascii") - - return data - - -def _transform_typeddict( - data: Mapping[str, object], - expected_type: type, -) -> Mapping[str, object]: - result: dict[str, object] = {} - annotations = get_type_hints(expected_type, include_extras=True) - for key, value in data.items(): - if not is_given(value): - # we don't need to include omitted values here as they'll - # be stripped out before the request is sent anyway - continue - - type_ = annotations.get(key) - if type_ is None: - # we do not have a type annotation for this field, leave it as is - result[key] = value - else: - result[_maybe_transform_key(key, type_)] = _transform_recursive(value, annotation=type_) - return result - - -async def async_maybe_transform( - data: object, - expected_type: object, -) -> Any | None: - """Wrapper over `async_transform()` that allows `None` to be passed. - - See `async_transform()` for more details. - """ - if data is None: - return None - return await async_transform(data, expected_type) - - -async def async_transform( - data: _T, - expected_type: object, -) -> _T: - """Transform dictionaries based off of type information from the given type, for example: - - ```py - class Params(TypedDict, total=False): - card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]] - - - transformed = transform({"card_id": ""}, Params) - # {'cardID': ''} - ``` - - Any keys / data that does not have type information given will be included as is. - - It should be noted that the transformations that this function does are not represented in the type system. - """ - transformed = await _async_transform_recursive(data, annotation=cast(type, expected_type)) - return cast(_T, transformed) - - -async def _async_transform_recursive( - data: object, - *, - annotation: type, - inner_type: type | None = None, -) -> object: - """Transform the given data against the expected type. - - Args: - annotation: The direct type annotation given to the particular piece of data. - This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc - - inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type - is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in - the list can be transformed using the metadata from the container type. - - Defaults to the same value as the `annotation` argument. - """ - from .._compat import model_dump - - if inner_type is None: - inner_type = annotation - - stripped_type = strip_annotated_type(inner_type) - origin = get_origin(stripped_type) or stripped_type - if is_typeddict(stripped_type) and is_mapping(data): - return await _async_transform_typeddict(data, stripped_type) - - if origin == dict and is_mapping(data): - items_type = get_args(stripped_type)[1] - return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} - - if ( - # List[T] - (is_list_type(stripped_type) and is_list(data)) - # Iterable[T] - or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) - # Sequence[T] - or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) - ): - # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually - # intended as an iterable, so we don't transform it. - if isinstance(data, dict): - return cast(object, data) - - inner_type = extract_type_arg(stripped_type, 0) - if _no_transform_needed(inner_type): - # for some types there is no need to transform anything, so we can get a small - # perf boost from skipping that work. - # - # but we still need to convert to a list to ensure the data is json-serializable - if is_list(data): - return data - return list(data) - - return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] - - if is_union_type(stripped_type): - # For union types we run the transformation against all subtypes to ensure that everything is transformed. - # - # TODO: there may be edge cases where the same normalized field name will transform to two different names - # in different subtypes. - for subtype in get_args(stripped_type): - data = await _async_transform_recursive(data, annotation=annotation, inner_type=subtype) - return data - - if isinstance(data, pydantic.BaseModel): - return model_dump(data, exclude_unset=True, mode="json") - - annotated_type = _get_annotated_type(annotation) - if annotated_type is None: - return data - - # ignore the first argument as it is the actual type - annotations = get_args(annotated_type)[1:] - for annotation in annotations: - if isinstance(annotation, PropertyInfo) and annotation.format is not None: - return await _async_format_data(data, annotation.format, annotation.format_template) - - return data - - -async def _async_format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object: - if isinstance(data, (date, datetime)): - if format_ == "iso8601": - return data.isoformat() - - if format_ == "custom" and format_template is not None: - return data.strftime(format_template) - - if format_ == "base64" and is_base64_file_input(data): - binary: str | bytes | None = None - - if isinstance(data, pathlib.Path): - binary = await anyio.Path(data).read_bytes() - elif isinstance(data, io.IOBase): - binary = data.read() - - if isinstance(binary, str): # type: ignore[unreachable] - binary = binary.encode() - - if not isinstance(binary, bytes): - raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}") - - return base64.b64encode(binary).decode("ascii") - - return data - - -async def _async_transform_typeddict( - data: Mapping[str, object], - expected_type: type, -) -> Mapping[str, object]: - result: dict[str, object] = {} - annotations = get_type_hints(expected_type, include_extras=True) - for key, value in data.items(): - if not is_given(value): - # we don't need to include omitted values here as they'll - # be stripped out before the request is sent anyway - continue - - type_ = annotations.get(key) - if type_ is None: - # we do not have a type annotation for this field, leave it as is - result[key] = value - else: - result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_) - return result - - -@lru_cache(maxsize=8096) -def get_type_hints( - obj: Any, - globalns: dict[str, Any] | None = None, - localns: Mapping[str, Any] | None = None, - include_extras: bool = False, -) -> dict[str, Any]: - return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras) diff --git a/src/moderation_api/_utils/_typing.py b/src/moderation_api/_utils/_typing.py deleted file mode 100644 index 193109f..0000000 --- a/src/moderation_api/_utils/_typing.py +++ /dev/null @@ -1,156 +0,0 @@ -from __future__ import annotations - -import sys -import typing -import typing_extensions -from typing import Any, TypeVar, Iterable, cast -from collections import abc as _c_abc -from typing_extensions import ( - TypeIs, - Required, - Annotated, - get_args, - get_origin, -) - -from ._utils import lru_cache -from .._types import InheritsGeneric -from ._compat import is_union as _is_union - - -def is_annotated_type(typ: type) -> bool: - return get_origin(typ) == Annotated - - -def is_list_type(typ: type) -> bool: - return (get_origin(typ) or typ) == list - - -def is_sequence_type(typ: type) -> bool: - origin = get_origin(typ) or typ - return origin == typing_extensions.Sequence or origin == typing.Sequence or origin == _c_abc.Sequence - - -def is_iterable_type(typ: type) -> bool: - """If the given type is `typing.Iterable[T]`""" - origin = get_origin(typ) or typ - return origin == Iterable or origin == _c_abc.Iterable - - -def is_union_type(typ: type) -> bool: - return _is_union(get_origin(typ)) - - -def is_required_type(typ: type) -> bool: - return get_origin(typ) == Required - - -def is_typevar(typ: type) -> bool: - # type ignore is required because type checkers - # think this expression will always return False - return type(typ) == TypeVar # type: ignore - - -_TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,) -if sys.version_info >= (3, 12): - _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType) - - -def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]: - """Return whether the provided argument is an instance of `TypeAliasType`. - - ```python - type Int = int - is_type_alias_type(Int) - # > True - Str = TypeAliasType("Str", str) - is_type_alias_type(Str) - # > True - ``` - """ - return isinstance(tp, _TYPE_ALIAS_TYPES) - - -# Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]] -@lru_cache(maxsize=8096) -def strip_annotated_type(typ: type) -> type: - if is_required_type(typ) or is_annotated_type(typ): - return strip_annotated_type(cast(type, get_args(typ)[0])) - - return typ - - -def extract_type_arg(typ: type, index: int) -> type: - args = get_args(typ) - try: - return cast(type, args[index]) - except IndexError as err: - raise RuntimeError(f"Expected type {typ} to have a type argument at index {index} but it did not") from err - - -def extract_type_var_from_base( - typ: type, - *, - generic_bases: tuple[type, ...], - index: int, - failure_message: str | None = None, -) -> type: - """Given a type like `Foo[T]`, returns the generic type variable `T`. - - This also handles the case where a concrete subclass is given, e.g. - ```py - class MyResponse(Foo[bytes]): - ... - - extract_type_var(MyResponse, bases=(Foo,), index=0) -> bytes - ``` - - And where a generic subclass is given: - ```py - _T = TypeVar('_T') - class MyResponse(Foo[_T]): - ... - - extract_type_var(MyResponse[bytes], bases=(Foo,), index=0) -> bytes - ``` - """ - cls = cast(object, get_origin(typ) or typ) - if cls in generic_bases: # pyright: ignore[reportUnnecessaryContains] - # we're given the class directly - return extract_type_arg(typ, index) - - # if a subclass is given - # --- - # this is needed as __orig_bases__ is not present in the typeshed stubs - # because it is intended to be for internal use only, however there does - # not seem to be a way to resolve generic TypeVars for inherited subclasses - # without using it. - if isinstance(cls, InheritsGeneric): - target_base_class: Any | None = None - for base in cls.__orig_bases__: - if base.__origin__ in generic_bases: - target_base_class = base - break - - if target_base_class is None: - raise RuntimeError( - "Could not find the generic base class;\n" - "This should never happen;\n" - f"Does {cls} inherit from one of {generic_bases} ?" - ) - - extracted = extract_type_arg(target_base_class, index) - if is_typevar(extracted): - # If the extracted type argument is itself a type variable - # then that means the subclass itself is generic, so we have - # to resolve the type argument from the class itself, not - # the base class. - # - # Note: if there is more than 1 type argument, the subclass could - # change the ordering of the type arguments, this is not currently - # supported. - return extract_type_arg(typ, index) - - return extracted - - raise RuntimeError(failure_message or f"Could not resolve inner type variable at index {index} for {typ}") diff --git a/src/moderation_api/_utils/_utils.py b/src/moderation_api/_utils/_utils.py deleted file mode 100644 index 199cd23..0000000 --- a/src/moderation_api/_utils/_utils.py +++ /dev/null @@ -1,433 +0,0 @@ -from __future__ import annotations - -import os -import re -import inspect -import functools -from typing import ( - Any, - Tuple, - Mapping, - TypeVar, - Callable, - Iterable, - Sequence, - cast, - overload, -) -from pathlib import Path -from datetime import date, datetime -from typing_extensions import TypeGuard, get_args - -import sniffio - -from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike - -_T = TypeVar("_T") -_TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) -_MappingT = TypeVar("_MappingT", bound=Mapping[str, object]) -_SequenceT = TypeVar("_SequenceT", bound=Sequence[object]) -CallableT = TypeVar("CallableT", bound=Callable[..., Any]) - - -def flatten(t: Iterable[Iterable[_T]]) -> list[_T]: - return [item for sublist in t for item in sublist] - - -def extract_files( - # TODO: this needs to take Dict but variance issues..... - # create protocol type ? - query: Mapping[str, object], - *, - paths: Sequence[Sequence[str]], - array_format: ArrayFormat = "brackets", -) -> list[tuple[str, FileTypes]]: - """Recursively extract files from the given dictionary based on specified paths. - - A path may look like this ['foo', 'files', '', 'data']. - - ``array_format`` controls how ```` segments contribute to the emitted - field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and - ``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``). - - Note: this mutates the given dictionary. - """ - files: list[tuple[str, FileTypes]] = [] - for path in paths: - files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format)) - return files - - -def _array_suffix(array_format: ArrayFormat, array_index: int) -> str: - if array_format == "brackets": - return "[]" - if array_format == "indices": - return f"[{array_index}]" - if array_format == "repeat" or array_format == "comma": - # Both repeat the bare field name for each file part; there is no - # meaningful way to comma-join binary parts. - return "" - raise NotImplementedError( - f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}" - ) - - -def _extract_items( - obj: object, - path: Sequence[str], - *, - index: int, - flattened_key: str | None, - array_format: ArrayFormat, -) -> list[tuple[str, FileTypes]]: - try: - key = path[index] - except IndexError: - if not is_given(obj): - # no value was provided - we can safely ignore - return [] - - # cyclical import - from .._files import assert_is_file_content - - # We have exhausted the path, return the entry we found. - assert flattened_key is not None - - if is_list(obj): - files: list[tuple[str, FileTypes]] = [] - for array_index, entry in enumerate(obj): - suffix = _array_suffix(array_format, array_index) - emitted_key = (flattened_key + suffix) if flattened_key else suffix - assert_is_file_content(entry, key=emitted_key) - files.append((emitted_key, cast(FileTypes, entry))) - return files - - assert_is_file_content(obj, key=flattened_key) - return [(flattened_key, cast(FileTypes, obj))] - - index += 1 - if is_dict(obj): - try: - # Remove the field if there are no more dict keys in the path, - # only "" traversal markers or end. - if all(p == "" for p in path[index:]): - item = obj.pop(key) - else: - item = obj[key] - except KeyError: - # Key was not present in the dictionary, this is not indicative of an error - # as the given path may not point to a required field. We also do not want - # to enforce required fields as the API may differ from the spec in some cases. - return [] - if flattened_key is None: - flattened_key = key - else: - flattened_key += f"[{key}]" - return _extract_items( - item, - path, - index=index, - flattened_key=flattened_key, - array_format=array_format, - ) - elif is_list(obj): - if key != "": - return [] - - return flatten( - [ - _extract_items( - item, - path, - index=index, - flattened_key=( - (flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index) - ), - array_format=array_format, - ) - for array_index, item in enumerate(obj) - ] - ) - - # Something unexpected was passed, just ignore it. - return [] - - -def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]: - return not isinstance(obj, NotGiven) and not isinstance(obj, Omit) - - -# Type safe methods for narrowing types with TypeVars. -# The default narrowing for isinstance(obj, dict) is dict[unknown, unknown], -# however this cause Pyright to rightfully report errors. As we know we don't -# care about the contained types we can safely use `object` in its place. -# -# There are two separate functions defined, `is_*` and `is_*_t` for different use cases. -# `is_*` is for when you're dealing with an unknown input -# `is_*_t` is for when you're narrowing a known union type to a specific subset - - -def is_tuple(obj: object) -> TypeGuard[tuple[object, ...]]: - return isinstance(obj, tuple) - - -def is_tuple_t(obj: _TupleT | object) -> TypeGuard[_TupleT]: - return isinstance(obj, tuple) - - -def is_sequence(obj: object) -> TypeGuard[Sequence[object]]: - return isinstance(obj, Sequence) - - -def is_sequence_t(obj: _SequenceT | object) -> TypeGuard[_SequenceT]: - return isinstance(obj, Sequence) - - -def is_mapping(obj: object) -> TypeGuard[Mapping[str, object]]: - return isinstance(obj, Mapping) - - -def is_mapping_t(obj: _MappingT | object) -> TypeGuard[_MappingT]: - return isinstance(obj, Mapping) - - -def is_dict(obj: object) -> TypeGuard[dict[object, object]]: - return isinstance(obj, dict) - - -def is_list(obj: object) -> TypeGuard[list[object]]: - return isinstance(obj, list) - - -def is_iterable(obj: object) -> TypeGuard[Iterable[object]]: - return isinstance(obj, Iterable) - - -# copied from https://github.com/Rapptz/RoboDanny -def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str: - size = len(seq) - if size == 0: - return "" - - if size == 1: - return seq[0] - - if size == 2: - return f"{seq[0]} {final} {seq[1]}" - - return delim.join(seq[:-1]) + f" {final} {seq[-1]}" - - -def quote(string: str) -> str: - """Add single quotation marks around the given string. Does *not* do any escaping.""" - return f"'{string}'" - - -def required_args(*variants: Sequence[str]) -> Callable[[CallableT], CallableT]: - """Decorator to enforce a given set of arguments or variants of arguments are passed to the decorated function. - - Useful for enforcing runtime validation of overloaded functions. - - Example usage: - ```py - @overload - def foo(*, a: str) -> str: ... - - - @overload - def foo(*, b: bool) -> str: ... - - - # This enforces the same constraints that a static type checker would - # i.e. that either a or b must be passed to the function - @required_args(["a"], ["b"]) - def foo(*, a: str | None = None, b: bool | None = None) -> str: ... - ``` - """ - - def inner(func: CallableT) -> CallableT: - params = inspect.signature(func).parameters - positional = [ - name - for name, param in params.items() - if param.kind - in { - param.POSITIONAL_ONLY, - param.POSITIONAL_OR_KEYWORD, - } - ] - - @functools.wraps(func) - def wrapper(*args: object, **kwargs: object) -> object: - given_params: set[str] = set() - for i, _ in enumerate(args): - try: - given_params.add(positional[i]) - except IndexError: - raise TypeError( - f"{func.__name__}() takes {len(positional)} argument(s) but {len(args)} were given" - ) from None - - for key in kwargs.keys(): - given_params.add(key) - - for variant in variants: - matches = all((param in given_params for param in variant)) - if matches: - break - else: # no break - if len(variants) > 1: - variations = human_join( - ["(" + human_join([quote(arg) for arg in variant], final="and") + ")" for variant in variants] - ) - msg = f"Missing required arguments; Expected either {variations} arguments to be given" - else: - assert len(variants) > 0 - - # TODO: this error message is not deterministic - missing = list(set(variants[0]) - given_params) - if len(missing) > 1: - msg = f"Missing required arguments: {human_join([quote(arg) for arg in missing])}" - else: - msg = f"Missing required argument: {quote(missing[0])}" - raise TypeError(msg) - return func(*args, **kwargs) - - return wrapper # type: ignore - - return inner - - -_K = TypeVar("_K") -_V = TypeVar("_V") - - -@overload -def strip_not_given(obj: None) -> None: ... - - -@overload -def strip_not_given(obj: Mapping[_K, _V | NotGiven]) -> dict[_K, _V]: ... - - -@overload -def strip_not_given(obj: object) -> object: ... - - -def strip_not_given(obj: object | None) -> object: - """Remove all top-level keys where their values are instances of `NotGiven`""" - if obj is None: - return None - - if not is_mapping(obj): - return obj - - return {key: value for key, value in obj.items() if not isinstance(value, NotGiven)} - - -def coerce_integer(val: str) -> int: - return int(val, base=10) - - -def coerce_float(val: str) -> float: - return float(val) - - -def coerce_boolean(val: str) -> bool: - return val == "true" or val == "1" or val == "on" - - -def maybe_coerce_integer(val: str | None) -> int | None: - if val is None: - return None - return coerce_integer(val) - - -def maybe_coerce_float(val: str | None) -> float | None: - if val is None: - return None - return coerce_float(val) - - -def maybe_coerce_boolean(val: str | None) -> bool | None: - if val is None: - return None - return coerce_boolean(val) - - -def removeprefix(string: str, prefix: str) -> str: - """Remove a prefix from a string. - - Backport of `str.removeprefix` for Python < 3.9 - """ - if string.startswith(prefix): - return string[len(prefix) :] - return string - - -def removesuffix(string: str, suffix: str) -> str: - """Remove a suffix from a string. - - Backport of `str.removesuffix` for Python < 3.9 - """ - if string.endswith(suffix): - return string[: -len(suffix)] - return string - - -def file_from_path(path: str) -> FileTypes: - contents = Path(path).read_bytes() - file_name = os.path.basename(path) - return (file_name, contents) - - -def get_required_header(headers: HeadersLike, header: str) -> str: - lower_header = header.lower() - if is_mapping_t(headers): - # mypy doesn't understand the type narrowing here - for k, v in headers.items(): # type: ignore - if k.lower() == lower_header and isinstance(v, str): - return v - - # to deal with the case where the header looks like Stainless-Event-Id - intercaps_header = re.sub(r"([^\w])(\w)", lambda pat: pat.group(1) + pat.group(2).upper(), header.capitalize()) - - for normalized_header in [header, lower_header, header.upper(), intercaps_header]: - value = headers.get(normalized_header) - if value: - return value - - raise ValueError(f"Could not find {header} header") - - -def get_async_library() -> str: - try: - return sniffio.current_async_library() - except Exception: - return "false" - - -def lru_cache(*, maxsize: int | None = 128) -> Callable[[CallableT], CallableT]: - """A version of functools.lru_cache that retains the type signature - for the wrapped function arguments. - """ - wrapper = functools.lru_cache( # noqa: TID251 - maxsize=maxsize, - ) - return cast(Any, wrapper) # type: ignore[no-any-return] - - -def json_safe(data: object) -> object: - """Translates a mapping / sequence recursively in the same fashion - as `pydantic` v2's `model_dump(mode="json")`. - """ - if is_mapping(data): - return {json_safe(key): json_safe(value) for key, value in data.items()} - - if is_iterable(data) and not isinstance(data, (str, bytes, bytearray)): - return [json_safe(item) for item in data] - - if isinstance(data, (datetime, date)): - return data.isoformat() - - return data diff --git a/src/moderation_api/_version.py b/src/moderation_api/_version.py index 7969619..cd591e8 100644 --- a/src/moderation_api/_version.py +++ b/src/moderation_api/_version.py @@ -1,4 +1,15 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" -__title__ = "moderation_api" -__version__ = "1.42.0" # x-release-please-version +import importlib.metadata + +__title__: str = "moderation_api" +__version__: str = "2.0.0" +__openapi_doc_version__: str = "1.1.0" +__gen_version__: str = "internal" +__user_agent__: str = "speakeasy-sdk/python 2.0.0 internal 1.1.0 moderation_api" + +try: + if __package__ is not None: + __version__ = importlib.metadata.version(__package__) +except importlib.metadata.PackageNotFoundError: + pass diff --git a/src/moderation_api/account.py b/src/moderation_api/account.py new file mode 100644 index 0000000..9388ce9 --- /dev/null +++ b/src/moderation_api/account.py @@ -0,0 +1,195 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .basesdk import BaseSDK +from moderation_api import errors, models, utils +from moderation_api._hooks import HookContext +from moderation_api.types import OptionalNullable, UNSET +from moderation_api.utils import get_security_from_env +from moderation_api.utils.unmarshal_json_response import unmarshal_json_response +from typing import Any, Mapping, Optional + + +class Account(BaseSDK): + def retrieve( + self, + *, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.GetAccountResponse: + r"""Get account details + + Get account details + + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + req = self._build_request( + method="GET", + path="/account", + base_url=base_url, + url_variables=url_variables, + request=None, + request_body_required=False, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getAccount", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Account"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.GetAccountResponse, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def retrieve_async( + self, + *, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.GetAccountResponse: + r"""Get account details + + Get account details + + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + req = self._build_request_async( + method="GET", + path="/account", + base_url=base_url, + url_variables=url_variables, + request=None, + request_body_required=False, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getAccount", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Account"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.GetAccountResponse, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) diff --git a/src/moderation_api/actions.py b/src/moderation_api/actions.py new file mode 100644 index 0000000..50c21d4 --- /dev/null +++ b/src/moderation_api/actions.py @@ -0,0 +1,1629 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .basesdk import BaseSDK +from jsonpath import JSONPath +from moderation_api import errors, models, utils +from moderation_api._hooks import HookContext +from moderation_api.types import OptionalNullable, UNSET +from moderation_api.utils import get_security_from_env +from moderation_api.utils.unmarshal_json_response import unmarshal_json_response +from typing import Any, Awaitable, Dict, Iterable, List, Mapping, Optional, Union + + +class Actions(BaseSDK): + def list( + self, + *, + queue_id: Optional[str] = None, + page: Optional[int] = 1, + page_size: Optional[int] = 20, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> Optional[models.ListActionsResponse]: + r"""List moderation actions + + List the moderation actions of the project. The list is paginated. + + :param queue_id: + :param page: + :param page_size: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.ListActionsRequest( + queue_id=queue_id, + page=page, + page_size=page_size, + ) + + req = self._build_request( + method="GET", + path="/actions", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="listActions", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Actions"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + def next_func() -> Optional[models.ListActionsResponse]: + body = utils.unmarshal_json(http_res.text, Union[Dict[Any, Any], List[Any]]) + + page = request.page if isinstance(request.page, int) else 1 + next_page = page + 1 + + num_pages = JSONPath("$.pagination.totalPages").parse(body) + if len(num_pages) == 0 or num_pages[0] <= page: + return None + + if not http_res.text: + return None + results = JSONPath("$.data").parse(body) + if len(results) == 0 or len(results[0]) == 0: + return None + limit_ = request.page_size if isinstance(request.page_size, int) else 20 + if len(results[0]) < limit_: + return None + + return self.list( + queue_id=queue_id, + page=next_page, + page_size=page_size, + retries=retries, + server_url=server_url, + timeout_ms=timeout_ms, + http_headers=http_headers, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return models.ListActionsResponse( + result=unmarshal_json_response( + models.ListActionsResponseBody, http_res + ), + next=next_func, + ) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def list_async( + self, + *, + queue_id: Optional[str] = None, + page: Optional[int] = 1, + page_size: Optional[int] = 20, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> Optional[models.ListActionsResponse]: + r"""List moderation actions + + List the moderation actions of the project. The list is paginated. + + :param queue_id: + :param page: + :param page_size: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.ListActionsRequest( + queue_id=queue_id, + page=page, + page_size=page_size, + ) + + req = self._build_request_async( + method="GET", + path="/actions", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="listActions", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Actions"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + def next_func() -> Awaitable[Optional[models.ListActionsResponse]]: + body = utils.unmarshal_json(http_res.text, Union[Dict[Any, Any], List[Any]]) + + async def empty_result(): + return None + + page = request.page if isinstance(request.page, int) else 1 + next_page = page + 1 + + num_pages = JSONPath("$.pagination.totalPages").parse(body) + if len(num_pages) == 0 or num_pages[0] <= page: + return empty_result() + + if not http_res.text: + return empty_result() + results = JSONPath("$.data").parse(body) + if len(results) == 0 or len(results[0]) == 0: + return empty_result() + limit_ = request.page_size if isinstance(request.page_size, int) else 20 + if len(results[0]) < limit_: + return empty_result() + + return self.list_async( + queue_id=queue_id, + page=next_page, + page_size=page_size, + retries=retries, + server_url=server_url, + timeout_ms=timeout_ms, + http_headers=http_headers, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return models.ListActionsResponse( + result=unmarshal_json_response( + models.ListActionsResponseBody, http_res + ), + next=next_func, + ) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def create( + self, + *, + name: str, + key: OptionalNullable[str] = UNSET, + description: OptionalNullable[str] = UNSET, + type_: OptionalNullable[models.CreateActionTypeRequest] = UNSET, + built_in: OptionalNullable[bool] = False, + queue_behaviour: Optional[ + models.CreateActionQueueBehaviourRequest + ] = "NO_CHANGE", + filter_in_queue_ids: Optional[Iterable[str]] = None, + position: Optional[models.CreateActionPositionRequest] = "ALL_QUEUES", + possible_values: Optional[ + Union[ + Iterable[models.CreateActionPossibleValueRequest], + Iterable[models.CreateActionPossibleValueRequestTypedDict], + ] + ] = None, + value_required: Optional[bool] = False, + free_text: Optional[bool] = False, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.CreateActionResponse: + r"""Create an action + + Create an action. + + :param name: The name of the action. + :param key: User defined key of the action. + :param description: The description of the action. + :param type: The type of the action. + :param built_in: Whether the action is a built-in action or a custom one. + :param queue_behaviour: Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. + :param filter_in_queue_ids: The IDs of the queues the action is available in. + :param position: Show the action in all queues, selected queues or no queues (to use via API only). + :param possible_values: The possible values of the action. The user will be prompted to select one of these values when executing the action. + :param value_required: Whether the action requires a value to be executed. + :param free_text: Whether the action allows any text to be entered as a value or if it must be one of the possible values. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.CreateActionRequest( + key=key, + name=name, + description=description, + type=type_, + built_in=built_in, + queue_behaviour=queue_behaviour, + filter_in_queue_ids=utils.unmarshal( + filter_in_queue_ids, Optional[List[str]] + ), + position=position, + possible_values=utils.get_pydantic_model( + possible_values, Optional[List[models.CreateActionPossibleValueRequest]] + ), + value_required=value_required, + free_text=free_text, + ) + + req = self._build_request( + method="POST", + path="/actions", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request, False, False, "json", models.CreateActionRequest + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="createAction", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Actions"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.CreateActionResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "409", "application/json"): + response_data = unmarshal_json_response(errors.ConflictErrorData, http_res) + raise errors.ConflictError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def create_async( + self, + *, + name: str, + key: OptionalNullable[str] = UNSET, + description: OptionalNullable[str] = UNSET, + type_: OptionalNullable[models.CreateActionTypeRequest] = UNSET, + built_in: OptionalNullable[bool] = False, + queue_behaviour: Optional[ + models.CreateActionQueueBehaviourRequest + ] = "NO_CHANGE", + filter_in_queue_ids: Optional[Iterable[str]] = None, + position: Optional[models.CreateActionPositionRequest] = "ALL_QUEUES", + possible_values: Optional[ + Union[ + Iterable[models.CreateActionPossibleValueRequest], + Iterable[models.CreateActionPossibleValueRequestTypedDict], + ] + ] = None, + value_required: Optional[bool] = False, + free_text: Optional[bool] = False, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.CreateActionResponse: + r"""Create an action + + Create an action. + + :param name: The name of the action. + :param key: User defined key of the action. + :param description: The description of the action. + :param type: The type of the action. + :param built_in: Whether the action is a built-in action or a custom one. + :param queue_behaviour: Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. + :param filter_in_queue_ids: The IDs of the queues the action is available in. + :param position: Show the action in all queues, selected queues or no queues (to use via API only). + :param possible_values: The possible values of the action. The user will be prompted to select one of these values when executing the action. + :param value_required: Whether the action requires a value to be executed. + :param free_text: Whether the action allows any text to be entered as a value or if it must be one of the possible values. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.CreateActionRequest( + key=key, + name=name, + description=description, + type=type_, + built_in=built_in, + queue_behaviour=queue_behaviour, + filter_in_queue_ids=utils.unmarshal( + filter_in_queue_ids, Optional[List[str]] + ), + position=position, + possible_values=utils.get_pydantic_model( + possible_values, Optional[List[models.CreateActionPossibleValueRequest]] + ), + value_required=value_required, + free_text=free_text, + ) + + req = self._build_request_async( + method="POST", + path="/actions", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request, False, False, "json", models.CreateActionRequest + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="createAction", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Actions"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.CreateActionResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "409", "application/json"): + response_data = unmarshal_json_response(errors.ConflictErrorData, http_res) + raise errors.ConflictError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def retrieve( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.GetActionResponse: + r"""Get an action + + Get an action by ID. + + :param id: The ID of the action to get. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetActionRequest( + id=id, + ) + + req = self._build_request( + method="GET", + path="/actions/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getAction", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Actions"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.GetActionResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def retrieve_async( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.GetActionResponse: + r"""Get an action + + Get an action by ID. + + :param id: The ID of the action to get. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetActionRequest( + id=id, + ) + + req = self._build_request_async( + method="GET", + path="/actions/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getAction", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Actions"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.GetActionResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def update( + self, + *, + id: str, + key: OptionalNullable[str] = UNSET, + name: Optional[str] = None, + description: OptionalNullable[str] = UNSET, + type_: OptionalNullable[models.UpdateActionTypeRequest] = UNSET, + built_in: OptionalNullable[bool] = False, + queue_behaviour: Optional[ + models.UpdateActionQueueBehaviourRequest + ] = "NO_CHANGE", + filter_in_queue_ids: Optional[Iterable[str]] = None, + position: Optional[models.UpdateActionPositionRequest] = "ALL_QUEUES", + possible_values: Optional[ + Union[ + Iterable[models.UpdateActionPossibleValueRequest], + Iterable[models.UpdateActionPossibleValueRequestTypedDict], + ] + ] = None, + value_required: Optional[bool] = False, + free_text: Optional[bool] = False, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.UpdateActionResponse: + r"""Update an action + + Update an action. + + :param id: The ID of the action to update. + :param key: User defined key of the action. + :param name: The name of the action. + :param description: The description of the action. + :param type: The type of the action. + :param built_in: Whether the action is a built-in action or a custom one. + :param queue_behaviour: Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. + :param filter_in_queue_ids: The IDs of the queues the action is available in. + :param position: Show the action in all queues, selected queues or no queues (to use via API only). + :param possible_values: The possible values of the action. The user will be prompted to select one of these values when executing the action. + :param value_required: Whether the action requires a value to be executed. + :param free_text: Whether the action allows any text to be entered as a value or if it must be one of the possible values. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.UpdateActionRequest( + id=id, + body=models.UpdateActionRequestBody( + key=key, + name=name, + description=description, + type=type_, + built_in=built_in, + queue_behaviour=queue_behaviour, + filter_in_queue_ids=utils.unmarshal( + filter_in_queue_ids, Optional[List[str]] + ), + position=position, + possible_values=utils.get_pydantic_model( + possible_values, + Optional[List[models.UpdateActionPossibleValueRequest]], + ), + value_required=value_required, + free_text=free_text, + ), + ) + + req = self._build_request( + method="PUT", + path="/actions/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.UpdateActionRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="updateAction", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Actions"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.UpdateActionResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "409", "application/json"): + response_data = unmarshal_json_response(errors.ConflictErrorData, http_res) + raise errors.ConflictError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def update_async( + self, + *, + id: str, + key: OptionalNullable[str] = UNSET, + name: Optional[str] = None, + description: OptionalNullable[str] = UNSET, + type_: OptionalNullable[models.UpdateActionTypeRequest] = UNSET, + built_in: OptionalNullable[bool] = False, + queue_behaviour: Optional[ + models.UpdateActionQueueBehaviourRequest + ] = "NO_CHANGE", + filter_in_queue_ids: Optional[Iterable[str]] = None, + position: Optional[models.UpdateActionPositionRequest] = "ALL_QUEUES", + possible_values: Optional[ + Union[ + Iterable[models.UpdateActionPossibleValueRequest], + Iterable[models.UpdateActionPossibleValueRequestTypedDict], + ] + ] = None, + value_required: Optional[bool] = False, + free_text: Optional[bool] = False, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.UpdateActionResponse: + r"""Update an action + + Update an action. + + :param id: The ID of the action to update. + :param key: User defined key of the action. + :param name: The name of the action. + :param description: The description of the action. + :param type: The type of the action. + :param built_in: Whether the action is a built-in action or a custom one. + :param queue_behaviour: Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. + :param filter_in_queue_ids: The IDs of the queues the action is available in. + :param position: Show the action in all queues, selected queues or no queues (to use via API only). + :param possible_values: The possible values of the action. The user will be prompted to select one of these values when executing the action. + :param value_required: Whether the action requires a value to be executed. + :param free_text: Whether the action allows any text to be entered as a value or if it must be one of the possible values. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.UpdateActionRequest( + id=id, + body=models.UpdateActionRequestBody( + key=key, + name=name, + description=description, + type=type_, + built_in=built_in, + queue_behaviour=queue_behaviour, + filter_in_queue_ids=utils.unmarshal( + filter_in_queue_ids, Optional[List[str]] + ), + position=position, + possible_values=utils.get_pydantic_model( + possible_values, + Optional[List[models.UpdateActionPossibleValueRequest]], + ), + value_required=value_required, + free_text=free_text, + ), + ) + + req = self._build_request_async( + method="PUT", + path="/actions/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.UpdateActionRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="updateAction", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Actions"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.UpdateActionResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "409", "application/json"): + response_data = unmarshal_json_response(errors.ConflictErrorData, http_res) + raise errors.ConflictError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def delete( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.DeleteActionResponse: + r"""Delete an action + + Delete an action and all of its webhooks. + + :param id: The ID of the action to delete. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.DeleteActionRequest( + id=id, + ) + + req = self._build_request( + method="DELETE", + path="/actions/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="deleteAction", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Actions"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.DeleteActionResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def delete_async( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.DeleteActionResponse: + r"""Delete an action + + Delete an action and all of its webhooks. + + :param id: The ID of the action to delete. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.DeleteActionRequest( + id=id, + ) + + req = self._build_request_async( + method="DELETE", + path="/actions/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="deleteAction", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Actions"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.DeleteActionResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def execute( + self, + *, + action_key: str, + content_ids: Optional[Iterable[str]] = None, + author_ids: Optional[Iterable[str]] = None, + value: Optional[str] = None, + queue_id: Optional[str] = None, + duration: Optional[float] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.ExecuteActionResponse: + r"""Execute moderation action + + Execute a moderation action on one or more content items. + + :param action_key: ID or key of the action to execute + :param content_ids: IDs of the content items to apply the action to. Provide this or authorIds. + :param author_ids: IDs of the authors to apply the action to. Provide this or contentIds. + :param value: Optional value to provide with the action + :param queue_id: Optional queue ID if the action is queue-specific + :param duration: Optional duration in milliseconds for actions with timeouts + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.ExecuteActionRequest( + action_key=action_key, + content_ids=utils.unmarshal(content_ids, Optional[List[str]]), + author_ids=utils.unmarshal(author_ids, Optional[List[str]]), + value=value, + queue_id=queue_id, + duration=duration, + ) + + req = self._build_request( + method="POST", + path="/actions/execute", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request, False, False, "json", models.ExecuteActionRequest + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="executeAction", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Actions"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.ExecuteActionResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def execute_async( + self, + *, + action_key: str, + content_ids: Optional[Iterable[str]] = None, + author_ids: Optional[Iterable[str]] = None, + value: Optional[str] = None, + queue_id: Optional[str] = None, + duration: Optional[float] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.ExecuteActionResponse: + r"""Execute moderation action + + Execute a moderation action on one or more content items. + + :param action_key: ID or key of the action to execute + :param content_ids: IDs of the content items to apply the action to. Provide this or authorIds. + :param author_ids: IDs of the authors to apply the action to. Provide this or contentIds. + :param value: Optional value to provide with the action + :param queue_id: Optional queue ID if the action is queue-specific + :param duration: Optional duration in milliseconds for actions with timeouts + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.ExecuteActionRequest( + action_key=action_key, + content_ids=utils.unmarshal(content_ids, Optional[List[str]]), + author_ids=utils.unmarshal(author_ids, Optional[List[str]]), + value=value, + queue_id=queue_id, + duration=duration, + ) + + req = self._build_request_async( + method="POST", + path="/actions/execute", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request, False, False, "json", models.ExecuteActionRequest + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="executeAction", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Actions"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.ExecuteActionResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) diff --git a/src/moderation_api/authors.py b/src/moderation_api/authors.py new file mode 100644 index 0000000..62122be --- /dev/null +++ b/src/moderation_api/authors.py @@ -0,0 +1,1403 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .basesdk import BaseSDK +from jsonpath import JSONPath +from moderation_api import errors, models, utils +from moderation_api._hooks import HookContext +from moderation_api.types import BaseModel, OptionalNullable, UNSET +from moderation_api.utils import get_security_from_env +from moderation_api.utils.unmarshal_json_response import unmarshal_json_response +from typing import Any, Awaitable, Dict, List, Mapping, Optional, Union, cast + + +class Authors(BaseSDK): + def list( + self, + *, + request: Union[ + models.ListAuthorsRequest, models.ListAuthorsRequestTypedDict + ] = models.ListAuthorsRequest(), + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> Optional[models.ListAuthorsResponse]: + r"""List authors + + List authors with their activity metrics and reputation. The list is paginated. + + :param request: The request object to send. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + if not isinstance(request, BaseModel): + request = utils.unmarshal(request, models.ListAuthorsRequest) + request = cast(models.ListAuthorsRequest, request) + + req = self._build_request( + method="GET", + path="/authors", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="listAuthors", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Authors"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + def next_func() -> Optional[models.ListAuthorsResponse]: + body = utils.unmarshal_json(http_res.text, Union[Dict[Any, Any], List[Any]]) + + page = request.page if isinstance(request.page, int) else 1 + next_page = page + 1 + + num_pages = JSONPath("$.pagination.totalPages").parse(body) + if len(num_pages) == 0 or num_pages[0] <= page: + return None + + if not http_res.text: + return None + results = JSONPath("$.data").parse(body) + if len(results) == 0 or len(results[0]) == 0: + return None + limit_ = request.page_size if isinstance(request.page_size, int) else 20 + if len(results[0]) < limit_: + return None + + return self.list( + request=models.ListAuthorsRequest( + page=next_page, + page_size=request.page_size, + sort_by=request.sort_by, + sort_direction=request.sort_direction, + member_since_date=request.member_since_date, + last_active_date=request.last_active_date, + content_types=request.content_types, + ), + retries=retries, + server_url=server_url, + timeout_ms=timeout_ms, + http_headers=http_headers, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return models.ListAuthorsResponse( + result=unmarshal_json_response( + models.ListAuthorsResponseBody, http_res + ), + next=next_func, + ) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def list_async( + self, + *, + request: Union[ + models.ListAuthorsRequest, models.ListAuthorsRequestTypedDict + ] = models.ListAuthorsRequest(), + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> Optional[models.ListAuthorsResponse]: + r"""List authors + + List authors with their activity metrics and reputation. The list is paginated. + + :param request: The request object to send. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + if not isinstance(request, BaseModel): + request = utils.unmarshal(request, models.ListAuthorsRequest) + request = cast(models.ListAuthorsRequest, request) + + req = self._build_request_async( + method="GET", + path="/authors", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="listAuthors", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Authors"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + def next_func() -> Awaitable[Optional[models.ListAuthorsResponse]]: + body = utils.unmarshal_json(http_res.text, Union[Dict[Any, Any], List[Any]]) + + async def empty_result(): + return None + + page = request.page if isinstance(request.page, int) else 1 + next_page = page + 1 + + num_pages = JSONPath("$.pagination.totalPages").parse(body) + if len(num_pages) == 0 or num_pages[0] <= page: + return empty_result() + + if not http_res.text: + return empty_result() + results = JSONPath("$.data").parse(body) + if len(results) == 0 or len(results[0]) == 0: + return empty_result() + limit_ = request.page_size if isinstance(request.page_size, int) else 20 + if len(results[0]) < limit_: + return empty_result() + + return self.list_async( + request=models.ListAuthorsRequest( + page=next_page, + page_size=request.page_size, + sort_by=request.sort_by, + sort_direction=request.sort_direction, + member_since_date=request.member_since_date, + last_active_date=request.last_active_date, + content_types=request.content_types, + ), + retries=retries, + server_url=server_url, + timeout_ms=timeout_ms, + http_headers=http_headers, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return models.ListAuthorsResponse( + result=unmarshal_json_response( + models.ListAuthorsResponseBody, http_res + ), + next=next_func, + ) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def create( + self, + *, + external_id: str, + profile_picture: OptionalNullable[str] = UNSET, + external_link: OptionalNullable[str] = UNSET, + name: OptionalNullable[str] = UNSET, + email: OptionalNullable[str] = UNSET, + company: OptionalNullable[str] = UNSET, + metadata: Optional[ + Union[models.CreateAuthorMetadata, models.CreateAuthorMetadataTypedDict] + ] = None, + first_seen: Optional[float] = None, + last_seen: Optional[float] = None, + manual_trust_level: OptionalNullable[float] = UNSET, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.PublicAuthor: + r"""Create a new author + + Create a new author. Typically not needed as authors are created automatically when content is moderated. + + :param external_id: External ID of the user, typically the ID of the author in your database. + :param profile_picture: URL of the author's profile picture + :param external_link: URL of the author's external profile + :param name: Author name or identifier + :param email: Author email address + :param company: The author's company or organization + :param metadata: Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process. + :param first_seen: Timestamp when author first appeared + :param last_seen: Timestamp of last activity + :param manual_trust_level: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.CreateAuthorRequest( + profile_picture=profile_picture, + external_link=external_link, + name=name, + email=email, + company=company, + metadata=utils.get_pydantic_model( + metadata, Optional[models.CreateAuthorMetadata] + ), + first_seen=first_seen, + last_seen=last_seen, + manual_trust_level=manual_trust_level, + external_id=external_id, + ) + + req = self._build_request( + method="POST", + path="/authors", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request, False, False, "json", models.CreateAuthorRequest + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="createAuthor", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Authors"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.PublicAuthor, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def create_async( + self, + *, + external_id: str, + profile_picture: OptionalNullable[str] = UNSET, + external_link: OptionalNullable[str] = UNSET, + name: OptionalNullable[str] = UNSET, + email: OptionalNullable[str] = UNSET, + company: OptionalNullable[str] = UNSET, + metadata: Optional[ + Union[models.CreateAuthorMetadata, models.CreateAuthorMetadataTypedDict] + ] = None, + first_seen: Optional[float] = None, + last_seen: Optional[float] = None, + manual_trust_level: OptionalNullable[float] = UNSET, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.PublicAuthor: + r"""Create a new author + + Create a new author. Typically not needed as authors are created automatically when content is moderated. + + :param external_id: External ID of the user, typically the ID of the author in your database. + :param profile_picture: URL of the author's profile picture + :param external_link: URL of the author's external profile + :param name: Author name or identifier + :param email: Author email address + :param company: The author's company or organization + :param metadata: Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process. + :param first_seen: Timestamp when author first appeared + :param last_seen: Timestamp of last activity + :param manual_trust_level: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.CreateAuthorRequest( + profile_picture=profile_picture, + external_link=external_link, + name=name, + email=email, + company=company, + metadata=utils.get_pydantic_model( + metadata, Optional[models.CreateAuthorMetadata] + ), + first_seen=first_seen, + last_seen=last_seen, + manual_trust_level=manual_trust_level, + external_id=external_id, + ) + + req = self._build_request_async( + method="POST", + path="/authors", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request, False, False, "json", models.CreateAuthorRequest + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="createAuthor", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Authors"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.PublicAuthor, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def retrieve( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.PublicAuthor: + r"""Get author details + + Get detailed information about a specific author including historical data and analysis + + :param id: Either external ID or the ID assigned by moderation API. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetAuthorRequest( + id=id, + ) + + req = self._build_request( + method="GET", + path="/authors/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getAuthor", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Authors"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.PublicAuthor, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def retrieve_async( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.PublicAuthor: + r"""Get author details + + Get detailed information about a specific author including historical data and analysis + + :param id: Either external ID or the ID assigned by moderation API. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetAuthorRequest( + id=id, + ) + + req = self._build_request_async( + method="GET", + path="/authors/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getAuthor", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Authors"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.PublicAuthor, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def update( + self, + *, + id: str, + profile_picture: OptionalNullable[str] = UNSET, + external_link: OptionalNullable[str] = UNSET, + name: OptionalNullable[str] = UNSET, + email: OptionalNullable[str] = UNSET, + company: OptionalNullable[str] = UNSET, + metadata: Optional[ + Union[models.UpdateAuthorMetadata, models.UpdateAuthorMetadataTypedDict] + ] = None, + first_seen: Optional[float] = None, + last_seen: Optional[float] = None, + manual_trust_level: OptionalNullable[float] = UNSET, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.PublicAuthor: + r"""Update author details + + Update the details of a specific author + + :param id: Either external ID or the ID assigned by moderation API. + :param profile_picture: URL of the author's profile picture + :param external_link: URL of the author's external profile + :param name: Author name or identifier + :param email: Author email address + :param company: The author's company or organization + :param metadata: Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process. + :param first_seen: Timestamp when author first appeared + :param last_seen: Timestamp of last activity + :param manual_trust_level: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.UpdateAuthorRequest( + id=id, + body=models.UpdateAuthorRequestBody( + profile_picture=profile_picture, + external_link=external_link, + name=name, + email=email, + company=company, + metadata=utils.get_pydantic_model( + metadata, Optional[models.UpdateAuthorMetadata] + ), + first_seen=first_seen, + last_seen=last_seen, + manual_trust_level=manual_trust_level, + ), + ) + + req = self._build_request( + method="PUT", + path="/authors/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.UpdateAuthorRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="updateAuthor", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Authors"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.PublicAuthor, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def update_async( + self, + *, + id: str, + profile_picture: OptionalNullable[str] = UNSET, + external_link: OptionalNullable[str] = UNSET, + name: OptionalNullable[str] = UNSET, + email: OptionalNullable[str] = UNSET, + company: OptionalNullable[str] = UNSET, + metadata: Optional[ + Union[models.UpdateAuthorMetadata, models.UpdateAuthorMetadataTypedDict] + ] = None, + first_seen: Optional[float] = None, + last_seen: Optional[float] = None, + manual_trust_level: OptionalNullable[float] = UNSET, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.PublicAuthor: + r"""Update author details + + Update the details of a specific author + + :param id: Either external ID or the ID assigned by moderation API. + :param profile_picture: URL of the author's profile picture + :param external_link: URL of the author's external profile + :param name: Author name or identifier + :param email: Author email address + :param company: The author's company or organization + :param metadata: Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process. + :param first_seen: Timestamp when author first appeared + :param last_seen: Timestamp of last activity + :param manual_trust_level: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.UpdateAuthorRequest( + id=id, + body=models.UpdateAuthorRequestBody( + profile_picture=profile_picture, + external_link=external_link, + name=name, + email=email, + company=company, + metadata=utils.get_pydantic_model( + metadata, Optional[models.UpdateAuthorMetadata] + ), + first_seen=first_seen, + last_seen=last_seen, + manual_trust_level=manual_trust_level, + ), + ) + + req = self._build_request_async( + method="PUT", + path="/authors/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.UpdateAuthorRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="updateAuthor", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Authors"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.PublicAuthor, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def delete( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.DeleteAuthorResponse: + r"""Delete an author + + Delete a specific author. This resets the author: status, blocks, trust level, metrics and action history are removed. The author is created again with a clean record the next time content is moderated for the same ID. + + :param id: Either external ID or the ID assigned by moderation API. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.DeleteAuthorRequest( + id=id, + ) + + req = self._build_request( + method="DELETE", + path="/authors/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="deleteAuthor", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Authors"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.DeleteAuthorResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def delete_async( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.DeleteAuthorResponse: + r"""Delete an author + + Delete a specific author. This resets the author: status, blocks, trust level, metrics and action history are removed. The author is created again with a clean record the next time content is moderated for the same ID. + + :param id: Either external ID or the ID assigned by moderation API. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.DeleteAuthorRequest( + id=id, + ) + + req = self._build_request_async( + method="DELETE", + path="/authors/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="deleteAuthor", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Authors"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.DeleteAuthorResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) diff --git a/src/moderation_api/basesdk.py b/src/moderation_api/basesdk.py new file mode 100644 index 0000000..df4ddfd --- /dev/null +++ b/src/moderation_api/basesdk.py @@ -0,0 +1,396 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .sdkconfiguration import SDKConfiguration +import httpx +from moderation_api import errors, models, utils +from moderation_api._hooks import ( + AfterErrorContext, + AfterSuccessContext, + BeforeRequestContext, + HookContext, +) +from moderation_api.utils import ( + RetryConfig, + SerializedRequestBody, + get_body_content, + run_sync_in_thread, +) +from typing import Callable, List, Mapping, Optional, Tuple +from urllib.parse import parse_qs, urlparse + + +class BaseSDK: + sdk_configuration: SDKConfiguration + parent_ref: Optional[object] = None + """ + Reference to the root SDK instance, if any. This will prevent it from + being garbage collected while there are active streams. + """ + + def __init__( + self, + sdk_config: SDKConfiguration, + parent_ref: Optional[object] = None, + ) -> None: + self.sdk_configuration = sdk_config + self.parent_ref = parent_ref + + def _get_url(self, base_url, url_variables): + sdk_url, sdk_variables = self.sdk_configuration.get_server_details() + + if base_url is None: + base_url = sdk_url + + if url_variables is None: + url_variables = sdk_variables + + return utils.template_url(base_url, url_variables) + + def _build_request_async( + self, + method, + path, + base_url, + url_variables, + request, + request_body_required, + request_has_path_params, + request_has_query_params, + user_agent_header, + accept_header_value, + _globals=None, + security=None, + timeout_ms: Optional[int] = None, + get_serialized_body: Optional[ + Callable[[], Optional[SerializedRequestBody]] + ] = None, + url_override: Optional[str] = None, + http_headers: Optional[Mapping[str, str]] = None, + allow_empty_value: Optional[List[str]] = None, + allowed_fields: Optional[List[str]] = None, + ) -> httpx.Request: + client = self.sdk_configuration.async_client + return self._build_request_with_client( + client, + method, + path, + base_url, + url_variables, + request, + request_body_required, + request_has_path_params, + request_has_query_params, + user_agent_header, + accept_header_value, + _globals, + security, + timeout_ms, + get_serialized_body, + url_override, + http_headers, + allow_empty_value, + allowed_fields, + ) + + def _build_request( + self, + method, + path, + base_url, + url_variables, + request, + request_body_required, + request_has_path_params, + request_has_query_params, + user_agent_header, + accept_header_value, + _globals=None, + security=None, + timeout_ms: Optional[int] = None, + get_serialized_body: Optional[ + Callable[[], Optional[SerializedRequestBody]] + ] = None, + url_override: Optional[str] = None, + http_headers: Optional[Mapping[str, str]] = None, + allow_empty_value: Optional[List[str]] = None, + allowed_fields: Optional[List[str]] = None, + ) -> httpx.Request: + client = self.sdk_configuration.client + return self._build_request_with_client( + client, + method, + path, + base_url, + url_variables, + request, + request_body_required, + request_has_path_params, + request_has_query_params, + user_agent_header, + accept_header_value, + _globals, + security, + timeout_ms, + get_serialized_body, + url_override, + http_headers, + allow_empty_value, + allowed_fields, + ) + + def _build_request_with_client( + self, + client, + method, + path, + base_url, + url_variables, + request, + request_body_required, + request_has_path_params, + request_has_query_params, + user_agent_header, + accept_header_value, + _globals=None, + security=None, + timeout_ms: Optional[int] = None, + get_serialized_body: Optional[ + Callable[[], Optional[SerializedRequestBody]] + ] = None, + url_override: Optional[str] = None, + http_headers: Optional[Mapping[str, str]] = None, + allow_empty_value: Optional[List[str]] = None, + allowed_fields: Optional[List[str]] = None, + ) -> httpx.Request: + query_params = {} + + url = url_override + if url is None: + url = utils.generate_url( + self._get_url(base_url, url_variables), + path, + request if request_has_path_params else None, + _globals if request_has_path_params else None, + ) + + query_params = utils.get_query_params( + request if request_has_query_params else None, + _globals if request_has_query_params else None, + allow_empty_value, + ) + else: + # Pick up the query parameter from the override so they can be + # preserved when building the request later on (necessary as of + # httpx 0.28). + parsed_override = urlparse(str(url_override)) + query_params = parse_qs(parsed_override.query, keep_blank_values=True) + + headers = utils.get_headers(request, _globals) + headers["Accept"] = accept_header_value + headers[user_agent_header] = self.sdk_configuration.user_agent + + if security is not None: + if callable(security): + security = security() + security = utils.get_security_from_env(security, models.Security) + if security is not None: + security_headers, security_query_params = utils.get_security( + security, allowed_fields + ) + headers = {**headers, **security_headers} + query_params = {**query_params, **security_query_params} + + serialized_request_body = SerializedRequestBody() + if get_serialized_body is not None: + rb = get_serialized_body() + if request_body_required and rb is None: + raise ValueError("request body is required") + + if rb is not None: + serialized_request_body = rb + + if ( + serialized_request_body.media_type is not None + and serialized_request_body.media_type + not in ( + "multipart/form-data", + "multipart/mixed", + ) + ): + headers["content-type"] = serialized_request_body.media_type + + if http_headers is not None: + for header, value in http_headers.items(): + headers[header] = value + + timeout = timeout_ms / 1000 if timeout_ms is not None else None + + return client.build_request( + method, + url, + params=query_params, + content=serialized_request_body.content, + data=serialized_request_body.data, + files=serialized_request_body.files, + headers=headers, + timeout=timeout if timeout is not None else httpx.USE_CLIENT_DEFAULT, + ) + + def do_request( + self, + hook_ctx: HookContext, + request: httpx.Request, + is_error_status_code: Callable[[int], bool], + stream: bool = False, + retry_config: Optional[Tuple[RetryConfig, List[str]]] = None, + ) -> httpx.Response: + client = self.sdk_configuration.client + logger = self.sdk_configuration.debug_logger + + hooks = self.sdk_configuration.__dict__["_hooks"] + + def do(): + http_res = None + try: + req = hooks.before_request(BeforeRequestContext(hook_ctx), request) + if "timeout" in request.extensions and "timeout" not in req.extensions: + req.extensions["timeout"] = request.extensions["timeout"] + logger.debug( + "Request:\nMethod: %s\nURL: %s\nHeaders: %s\nBody: %s", + req.method, + req.url, + req.headers, + get_body_content(req), + ) + + if client is None: + raise ValueError("client is required") + + http_res = client.send(req, stream=stream) + except Exception as e: + _, e = hooks.after_error(AfterErrorContext(hook_ctx), None, e) + if e is not None: + logger.debug("Request Exception", exc_info=True) + raise e + + if http_res is None: + logger.debug("Raising no response SDK error") + raise errors.NoResponseError("No response received") + + logger.debug( + "Response:\nStatus Code: %s\nURL: %s\nHeaders: %s\nBody: %s", + http_res.status_code, + http_res.url, + http_res.headers, + "" if stream else http_res.text, + ) + + return http_res + + if retry_config is not None: + http_res = utils.retry(do, utils.Retries(retry_config[0], retry_config[1])) + else: + http_res = do() + + if is_error_status_code(http_res.status_code): + result, err = hooks.after_error(AfterErrorContext(hook_ctx), http_res, None) + if err is not None: + logger.debug("Request Exception", exc_info=True) + raise err + if result is not None: + http_res = result + else: + logger.debug("Raising unexpected SDK error") + raise errors.ModerationAPIDefaultError( + "Unexpected error occurred", http_res + ) + else: + http_res = hooks.after_success(AfterSuccessContext(hook_ctx), http_res) + + return http_res + + async def do_request_async( + self, + hook_ctx: HookContext, + request: httpx.Request, + is_error_status_code: Callable[[int], bool], + stream: bool = False, + retry_config: Optional[Tuple[RetryConfig, List[str]]] = None, + ) -> httpx.Response: + client = self.sdk_configuration.async_client + logger = self.sdk_configuration.debug_logger + + hooks = self.sdk_configuration.__dict__["_hooks"] + + async def do(): + http_res = None + try: + req = await run_sync_in_thread( + hooks.before_request, BeforeRequestContext(hook_ctx), request + ) + + if "timeout" in request.extensions and "timeout" not in req.extensions: + req.extensions["timeout"] = request.extensions["timeout"] + logger.debug( + "Request:\nMethod: %s\nURL: %s\nHeaders: %s\nBody: %s", + req.method, + req.url, + req.headers, + get_body_content(req), + ) + + if client is None: + raise ValueError("client is required") + + http_res = await client.send(req, stream=stream) + except Exception as e: + _, e = await run_sync_in_thread( + hooks.after_error, AfterErrorContext(hook_ctx), None, e + ) + + if e is not None: + logger.debug("Request Exception", exc_info=True) + raise e + + if http_res is None: + logger.debug("Raising no response SDK error") + raise errors.NoResponseError("No response received") + + logger.debug( + "Response:\nStatus Code: %s\nURL: %s\nHeaders: %s\nBody: %s", + http_res.status_code, + http_res.url, + http_res.headers, + "" if stream else http_res.text, + ) + + return http_res + + if retry_config is not None: + http_res = await utils.retry_async( + do, utils.Retries(retry_config[0], retry_config[1]) + ) + else: + http_res = await do() + + if is_error_status_code(http_res.status_code): + result, err = await run_sync_in_thread( + hooks.after_error, AfterErrorContext(hook_ctx), http_res, None + ) + + if err is not None: + logger.debug("Request Exception", exc_info=True) + raise err + if result is not None: + http_res = result + else: + logger.debug("Raising unexpected SDK error") + raise errors.ModerationAPIDefaultError( + "Unexpected error occurred", http_res + ) + else: + http_res = await run_sync_in_thread( + hooks.after_success, AfterSuccessContext(hook_ctx), http_res + ) + + return http_res diff --git a/src/moderation_api/channels.py b/src/moderation_api/channels.py new file mode 100644 index 0000000..57dc72d --- /dev/null +++ b/src/moderation_api/channels.py @@ -0,0 +1,1585 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .basesdk import BaseSDK +from jsonpath import JSONPath +from moderation_api import errors, models, utils +from moderation_api._hooks import HookContext +from moderation_api.types import OptionalNullable, UNSET +from moderation_api.utils import get_security_from_env +from moderation_api.utils.unmarshal_json_response import unmarshal_json_response +from typing import Any, Awaitable, Dict, List, Mapping, Optional, Union + + +class Channels(BaseSDK): + def list( + self, + *, + project_id: str, + page: Optional[int] = 1, + page_size: Optional[int] = 20, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> Optional[models.ListChannelsResponse]: + r"""List channels + + List the channels in a project. The list is paginated. Needs the `channels:read` permission. + + :param project_id: The ID of the project. + :param page: + :param page_size: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.ListChannelsRequest( + project_id=project_id, + page=page, + page_size=page_size, + ) + + req = self._build_request( + method="GET", + path="/projects/{projectId}/channels", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="listChannels", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Channels"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + def next_func() -> Optional[models.ListChannelsResponse]: + body = utils.unmarshal_json(http_res.text, Union[Dict[Any, Any], List[Any]]) + + page = request.page if isinstance(request.page, int) else 1 + next_page = page + 1 + + num_pages = JSONPath("$.pagination.totalPages").parse(body) + if len(num_pages) == 0 or num_pages[0] <= page: + return None + + if not http_res.text: + return None + results = JSONPath("$.data").parse(body) + if len(results) == 0 or len(results[0]) == 0: + return None + limit_ = request.page_size if isinstance(request.page_size, int) else 20 + if len(results[0]) < limit_: + return None + + return self.list( + project_id=project_id, + page=next_page, + page_size=page_size, + retries=retries, + server_url=server_url, + timeout_ms=timeout_ms, + http_headers=http_headers, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return models.ListChannelsResponse( + result=unmarshal_json_response( + models.ListChannelsResponseBody, http_res + ), + next=next_func, + ) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def list_async( + self, + *, + project_id: str, + page: Optional[int] = 1, + page_size: Optional[int] = 20, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> Optional[models.ListChannelsResponse]: + r"""List channels + + List the channels in a project. The list is paginated. Needs the `channels:read` permission. + + :param project_id: The ID of the project. + :param page: + :param page_size: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.ListChannelsRequest( + project_id=project_id, + page=page, + page_size=page_size, + ) + + req = self._build_request_async( + method="GET", + path="/projects/{projectId}/channels", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="listChannels", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Channels"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + def next_func() -> Awaitable[Optional[models.ListChannelsResponse]]: + body = utils.unmarshal_json(http_res.text, Union[Dict[Any, Any], List[Any]]) + + async def empty_result(): + return None + + page = request.page if isinstance(request.page, int) else 1 + next_page = page + 1 + + num_pages = JSONPath("$.pagination.totalPages").parse(body) + if len(num_pages) == 0 or num_pages[0] <= page: + return empty_result() + + if not http_res.text: + return empty_result() + results = JSONPath("$.data").parse(body) + if len(results) == 0 or len(results[0]) == 0: + return empty_result() + limit_ = request.page_size if isinstance(request.page_size, int) else 20 + if len(results[0]) < limit_: + return empty_result() + + return self.list_async( + project_id=project_id, + page=next_page, + page_size=page_size, + retries=retries, + server_url=server_url, + timeout_ms=timeout_ms, + http_headers=http_headers, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return models.ListChannelsResponse( + result=unmarshal_json_response( + models.ListChannelsResponseBody, http_res + ), + next=next_func, + ) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def create( + self, + *, + project_id: str, + name: str, + key: Optional[str] = None, + description: Optional[str] = None, + content_type: Optional[models.CreateChannelContentType] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.Channel: + r"""Create a channel + + Create a channel in a project, with the default rules. The project's first channel becomes its default. Needs the `channels:write` permission. + + :param project_id: The ID of the project to add the channel to. + :param name: The channel's name. + :param key: The key you pass as `channel` when you moderate content. Defaults to the name in lowercase with dashes. Must be unique in the project. + :param description: The channel's description. + :param content_type: The kind of content the channel moderates. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.CreateChannelRequest( + project_id=project_id, + body=models.CreateChannelRequestBody( + name=name, + key=key, + description=description, + content_type=content_type, + ), + ) + + req = self._build_request( + method="POST", + path="/projects/{projectId}/channels", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.CreateChannelRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="createChannel", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Channels"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.Channel, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def create_async( + self, + *, + project_id: str, + name: str, + key: Optional[str] = None, + description: Optional[str] = None, + content_type: Optional[models.CreateChannelContentType] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.Channel: + r"""Create a channel + + Create a channel in a project, with the default rules. The project's first channel becomes its default. Needs the `channels:write` permission. + + :param project_id: The ID of the project to add the channel to. + :param name: The channel's name. + :param key: The key you pass as `channel` when you moderate content. Defaults to the name in lowercase with dashes. Must be unique in the project. + :param description: The channel's description. + :param content_type: The kind of content the channel moderates. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.CreateChannelRequest( + project_id=project_id, + body=models.CreateChannelRequestBody( + name=name, + key=key, + description=description, + content_type=content_type, + ), + ) + + req = self._build_request_async( + method="POST", + path="/projects/{projectId}/channels", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.CreateChannelRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="createChannel", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Channels"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.Channel, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def retrieve( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.Channel: + r"""Get a channel + + Get a channel by ID. Needs the `channels:read` permission. + + :param id: The ID of the channel. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetChannelRequest( + id=id, + ) + + req = self._build_request( + method="GET", + path="/channels/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getChannel", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Channels"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.Channel, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def retrieve_async( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.Channel: + r"""Get a channel + + Get a channel by ID. Needs the `channels:read` permission. + + :param id: The ID of the channel. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetChannelRequest( + id=id, + ) + + req = self._build_request_async( + method="GET", + path="/channels/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getChannel", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Channels"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.Channel, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def update( + self, + *, + id: str, + name: Optional[str] = None, + key: Optional[str] = None, + description: OptionalNullable[str] = UNSET, + content_type: Optional[models.UpdateChannelContentType] = None, + flagging_mode: Optional[models.UpdateChannelFlaggingMode] = None, + context_enabled: Optional[bool] = None, + enable_image_moderation: Optional[bool] = None, + enable_video_moderation: Optional[bool] = None, + enable_audio_moderation: Optional[bool] = None, + enable_voice_moderation: Optional[bool] = None, + include_transcript_in_response: Optional[bool] = None, + transcription_quality: Optional[ + models.UpdateChannelTranscriptionQuality + ] = None, + unicode_spoof_detection_enabled: Optional[bool] = None, + frame_capture_interval: Optional[float] = None, + max_frames_per_video: Optional[int] = None, + spread_frames: Optional[bool] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.Channel: + r"""Update a channel + + Update a channel. Fields you leave out keep their value. Needs the `channels:write` permission. + + :param id: The ID of the channel to update. + :param name: The channel's name. + :param key: The key you pass as `channel` when you moderate content. Changing it breaks integrations that send the old key. + :param description: The channel's description. + :param content_type: The kind of content the channel moderates. + :param flagging_mode: `ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it. + :param context_enabled: Whether earlier messages in the conversation are read when moderating a message. + :param enable_image_moderation: Whether images are moderated. + :param enable_video_moderation: Whether videos are moderated. + :param enable_audio_moderation: Whether audio is moderated. + :param enable_voice_moderation: Whether real-time voice streams are moderated. + :param include_transcript_in_response: Whether audio and video transcripts are returned in responses. + :param transcription_quality: Trade-off between transcription speed and accuracy. + :param unicode_spoof_detection_enabled: Whether look-alike Unicode characters are normalized before moderation. + :param frame_capture_interval: Seconds between frames captured from a video. + :param max_frames_per_video: The most frames moderated per video. + :param spread_frames: Whether frames are spread evenly across the whole video. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.UpdateChannelRequest( + id=id, + body=models.UpdateChannelRequestBody( + name=name, + key=key, + description=description, + content_type=content_type, + flagging_mode=flagging_mode, + context_enabled=context_enabled, + enable_image_moderation=enable_image_moderation, + enable_video_moderation=enable_video_moderation, + enable_audio_moderation=enable_audio_moderation, + enable_voice_moderation=enable_voice_moderation, + include_transcript_in_response=include_transcript_in_response, + transcription_quality=transcription_quality, + unicode_spoof_detection_enabled=unicode_spoof_detection_enabled, + frame_capture_interval=frame_capture_interval, + max_frames_per_video=max_frames_per_video, + spread_frames=spread_frames, + ), + ) + + req = self._build_request( + method="PUT", + path="/channels/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.UpdateChannelRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="updateChannel", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Channels"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.Channel, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def update_async( + self, + *, + id: str, + name: Optional[str] = None, + key: Optional[str] = None, + description: OptionalNullable[str] = UNSET, + content_type: Optional[models.UpdateChannelContentType] = None, + flagging_mode: Optional[models.UpdateChannelFlaggingMode] = None, + context_enabled: Optional[bool] = None, + enable_image_moderation: Optional[bool] = None, + enable_video_moderation: Optional[bool] = None, + enable_audio_moderation: Optional[bool] = None, + enable_voice_moderation: Optional[bool] = None, + include_transcript_in_response: Optional[bool] = None, + transcription_quality: Optional[ + models.UpdateChannelTranscriptionQuality + ] = None, + unicode_spoof_detection_enabled: Optional[bool] = None, + frame_capture_interval: Optional[float] = None, + max_frames_per_video: Optional[int] = None, + spread_frames: Optional[bool] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.Channel: + r"""Update a channel + + Update a channel. Fields you leave out keep their value. Needs the `channels:write` permission. + + :param id: The ID of the channel to update. + :param name: The channel's name. + :param key: The key you pass as `channel` when you moderate content. Changing it breaks integrations that send the old key. + :param description: The channel's description. + :param content_type: The kind of content the channel moderates. + :param flagging_mode: `ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it. + :param context_enabled: Whether earlier messages in the conversation are read when moderating a message. + :param enable_image_moderation: Whether images are moderated. + :param enable_video_moderation: Whether videos are moderated. + :param enable_audio_moderation: Whether audio is moderated. + :param enable_voice_moderation: Whether real-time voice streams are moderated. + :param include_transcript_in_response: Whether audio and video transcripts are returned in responses. + :param transcription_quality: Trade-off between transcription speed and accuracy. + :param unicode_spoof_detection_enabled: Whether look-alike Unicode characters are normalized before moderation. + :param frame_capture_interval: Seconds between frames captured from a video. + :param max_frames_per_video: The most frames moderated per video. + :param spread_frames: Whether frames are spread evenly across the whole video. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.UpdateChannelRequest( + id=id, + body=models.UpdateChannelRequestBody( + name=name, + key=key, + description=description, + content_type=content_type, + flagging_mode=flagging_mode, + context_enabled=context_enabled, + enable_image_moderation=enable_image_moderation, + enable_video_moderation=enable_video_moderation, + enable_audio_moderation=enable_audio_moderation, + enable_voice_moderation=enable_voice_moderation, + include_transcript_in_response=include_transcript_in_response, + transcription_quality=transcription_quality, + unicode_spoof_detection_enabled=unicode_spoof_detection_enabled, + frame_capture_interval=frame_capture_interval, + max_frames_per_video=max_frames_per_video, + spread_frames=spread_frames, + ), + ) + + req = self._build_request_async( + method="PUT", + path="/channels/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.UpdateChannelRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="updateChannel", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Channels"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.Channel, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def delete( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.DeleteChannelResponse: + r"""Delete a channel + + Delete a channel. Its key is freed for a new channel. When it was the default, the oldest remaining channel becomes the default. A channel that still moderates a Slack workspace must be disconnected first. Needs the `channels:write` permission. + + :param id: The ID of the channel. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.DeleteChannelRequest( + id=id, + ) + + req = self._build_request( + method="DELETE", + path="/channels/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="deleteChannel", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Channels"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.DeleteChannelResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def delete_async( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.DeleteChannelResponse: + r"""Delete a channel + + Delete a channel. Its key is freed for a new channel. When it was the default, the oldest remaining channel becomes the default. A channel that still moderates a Slack workspace must be disconnected first. Needs the `channels:write` permission. + + :param id: The ID of the channel. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.DeleteChannelRequest( + id=id, + ) + + req = self._build_request_async( + method="DELETE", + path="/channels/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="deleteChannel", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Channels"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.DeleteChannelResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def duplicate( + self, + *, + id: str, + name: Optional[str] = None, + key: Optional[str] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.Channel: + r"""Duplicate a channel + + Copy a channel in its project, with every setting, policy, rule and wordlist. Integrations (Discord, Slack, WordPress) are not copied. Needs the `channels:write` permission. + + :param id: The ID of the channel to copy. + :param name: Name of the copy. Defaults to the original name with \"(copy)\". + :param key: Key of the copy. Defaults to the original key with a timestamp. Must be unique in the project. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.DuplicateChannelRequest( + id=id, + body=models.DuplicateChannelRequestBody( + name=name, + key=key, + ), + ) + + req = self._build_request( + method="POST", + path="/channels/{id}/duplicate", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.DuplicateChannelRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="duplicateChannel", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Channels"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.Channel, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def duplicate_async( + self, + *, + id: str, + name: Optional[str] = None, + key: Optional[str] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.Channel: + r"""Duplicate a channel + + Copy a channel in its project, with every setting, policy, rule and wordlist. Integrations (Discord, Slack, WordPress) are not copied. Needs the `channels:write` permission. + + :param id: The ID of the channel to copy. + :param name: Name of the copy. Defaults to the original name with \"(copy)\". + :param key: Key of the copy. Defaults to the original key with a timestamp. Must be unique in the project. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.DuplicateChannelRequest( + id=id, + body=models.DuplicateChannelRequestBody( + name=name, + key=key, + ), + ) + + req = self._build_request_async( + method="POST", + path="/channels/{id}/duplicate", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.DuplicateChannelRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="duplicateChannel", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Channels"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.Channel, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) diff --git a/src/moderation_api/content_sdk.py b/src/moderation_api/content_sdk.py new file mode 100644 index 0000000..f57c914 --- /dev/null +++ b/src/moderation_api/content_sdk.py @@ -0,0 +1,325 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .basesdk import BaseSDK +from moderation_api import errors, models, utils +from moderation_api._hooks import HookContext +from moderation_api.types import OptionalNullable, UNSET +from moderation_api.utils import get_security_from_env +from moderation_api.utils.unmarshal_json_response import unmarshal_json_response +from typing import Any, Dict, Iterable, List, Mapping, Optional, Union + + +class ContentSDK(BaseSDK): + def submit( + self, + *, + content: Union[models.ContentRequest, models.ContentRequestTypedDict], + timestamp: Optional[float] = None, + channel: Optional[str] = None, + content_id: Optional[str] = None, + meta_type: Optional[models.SubmitContentMetaType] = None, + author_id: Optional[str] = None, + conversation_id: Optional[str] = None, + metadata: Optional[Mapping[str, Any]] = None, + client_action: Optional[ + Union[ + models.SubmitContentClientAction, + models.SubmitContentClientActionTypedDict, + ] + ] = None, + do_not_store: Optional[bool] = None, + policies: Optional[ + Union[ + Iterable[models.PolicyRequest], Iterable[models.PolicyRequestTypedDict] + ] + ] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.SubmitContentResponse: + r""" + :param content: The content sent for moderation + :param timestamp: Unix timestamp (in milliseconds) of when the content was created. Use if content is not submitted in real-time. + :param channel: Provide a channel ID or key. Will use the project's default channel if not provided. + :param content_id: The unique ID of the content in your database. + :param meta_type: The meta type of content being moderated + :param author_id: The author of the content. + :param conversation_id: For example the ID of a chat room or a post + :param metadata: Any metadata you want to store with the content + :param client_action: A recommendation from your own client-side flagging (e.g. a banned-IP list or a third-party tool). Feeds the rules engine and can escalate or override the recommended action. Does not change whether our analysis flagged the content. + :param do_not_store: Do not store the content. The content won't enter the review queue + :param policies: (Enterprise) override the channel policies for this moderation request only. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.SubmitContentRequest( + content=utils.get_pydantic_model(content, models.ContentRequest), + timestamp=timestamp, + channel=channel, + content_id=content_id, + meta_type=meta_type, + author_id=author_id, + conversation_id=conversation_id, + metadata=utils.unmarshal(metadata, Optional[Dict[str, Any]]), + client_action=utils.get_pydantic_model( + client_action, Optional[models.SubmitContentClientAction] + ), + do_not_store=do_not_store, + policies=utils.get_pydantic_model( + policies, Optional[List[models.PolicyRequest]] + ), + ) + + req = self._build_request( + method="POST", + path="/moderate", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request, False, False, "json", models.SubmitContentRequest + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="submitContent", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Content"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.SubmitContentResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def submit_async( + self, + *, + content: Union[models.ContentRequest, models.ContentRequestTypedDict], + timestamp: Optional[float] = None, + channel: Optional[str] = None, + content_id: Optional[str] = None, + meta_type: Optional[models.SubmitContentMetaType] = None, + author_id: Optional[str] = None, + conversation_id: Optional[str] = None, + metadata: Optional[Mapping[str, Any]] = None, + client_action: Optional[ + Union[ + models.SubmitContentClientAction, + models.SubmitContentClientActionTypedDict, + ] + ] = None, + do_not_store: Optional[bool] = None, + policies: Optional[ + Union[ + Iterable[models.PolicyRequest], Iterable[models.PolicyRequestTypedDict] + ] + ] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.SubmitContentResponse: + r""" + :param content: The content sent for moderation + :param timestamp: Unix timestamp (in milliseconds) of when the content was created. Use if content is not submitted in real-time. + :param channel: Provide a channel ID or key. Will use the project's default channel if not provided. + :param content_id: The unique ID of the content in your database. + :param meta_type: The meta type of content being moderated + :param author_id: The author of the content. + :param conversation_id: For example the ID of a chat room or a post + :param metadata: Any metadata you want to store with the content + :param client_action: A recommendation from your own client-side flagging (e.g. a banned-IP list or a third-party tool). Feeds the rules engine and can escalate or override the recommended action. Does not change whether our analysis flagged the content. + :param do_not_store: Do not store the content. The content won't enter the review queue + :param policies: (Enterprise) override the channel policies for this moderation request only. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.SubmitContentRequest( + content=utils.get_pydantic_model(content, models.ContentRequest), + timestamp=timestamp, + channel=channel, + content_id=content_id, + meta_type=meta_type, + author_id=author_id, + conversation_id=conversation_id, + metadata=utils.unmarshal(metadata, Optional[Dict[str, Any]]), + client_action=utils.get_pydantic_model( + client_action, Optional[models.SubmitContentClientAction] + ), + do_not_store=do_not_store, + policies=utils.get_pydantic_model( + policies, Optional[List[models.PolicyRequest]] + ), + ) + + req = self._build_request_async( + method="POST", + path="/moderate", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request, False, False, "json", models.SubmitContentRequest + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="submitContent", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Content"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.SubmitContentResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) diff --git a/src/moderation_api/errors/__init__.py b/src/moderation_api/errors/__init__.py new file mode 100644 index 0000000..e5e033d --- /dev/null +++ b/src/moderation_api/errors/__init__.py @@ -0,0 +1,69 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .moderationapierror import ModerationAPIError +from typing import Any, TYPE_CHECKING + +from moderation_api.utils.dynamic_imports import lazy_getattr, lazy_dir + +if TYPE_CHECKING: + from .authenticationerror import AuthenticationError, AuthenticationErrorData + from .badrequesterror import BadRequestError, BadRequestErrorData + from .conflicterror import ConflictError, ConflictErrorData + from .internalservererror import InternalServerError, InternalServerErrorData + from .moderationapidefaulterror import ModerationAPIDefaultError + from .no_response_error import NoResponseError + from .notfounderror import NotFoundError, NotFoundErrorData + from .permissiondeniederror import PermissionDeniedError, PermissionDeniedErrorData + from .ratelimiterror import RateLimitError, RateLimitErrorData + from .responsevalidationerror import ResponseValidationError + +__all__ = [ + "AuthenticationError", + "AuthenticationErrorData", + "BadRequestError", + "BadRequestErrorData", + "ConflictError", + "ConflictErrorData", + "InternalServerError", + "InternalServerErrorData", + "ModerationAPIDefaultError", + "ModerationAPIError", + "NoResponseError", + "NotFoundError", + "NotFoundErrorData", + "PermissionDeniedError", + "PermissionDeniedErrorData", + "RateLimitError", + "RateLimitErrorData", + "ResponseValidationError", +] + +_dynamic_imports: dict[str, str] = { + "AuthenticationError": ".authenticationerror", + "AuthenticationErrorData": ".authenticationerror", + "BadRequestError": ".badrequesterror", + "BadRequestErrorData": ".badrequesterror", + "ConflictError": ".conflicterror", + "ConflictErrorData": ".conflicterror", + "InternalServerError": ".internalservererror", + "InternalServerErrorData": ".internalservererror", + "ModerationAPIDefaultError": ".moderationapidefaulterror", + "NoResponseError": ".no_response_error", + "NotFoundError": ".notfounderror", + "NotFoundErrorData": ".notfounderror", + "PermissionDeniedError": ".permissiondeniederror", + "PermissionDeniedErrorData": ".permissiondeniederror", + "RateLimitError": ".ratelimiterror", + "RateLimitErrorData": ".ratelimiterror", + "ResponseValidationError": ".responsevalidationerror", +} + + +def __getattr__(attr_name: str) -> Any: + return lazy_getattr( + attr_name, package=__package__, dynamic_imports=_dynamic_imports + ) + + +def __dir__(): + return lazy_dir(dynamic_imports=_dynamic_imports) diff --git a/src/moderation_api/errors/authenticationerror.py b/src/moderation_api/errors/authenticationerror.py new file mode 100644 index 0000000..0dcb636 --- /dev/null +++ b/src/moderation_api/errors/authenticationerror.py @@ -0,0 +1,61 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from dataclasses import dataclass, field +import httpx +from moderation_api.errors import ModerationAPIError +from moderation_api.models import ( + authenticationerror as models_authenticationerror, + errorissue as models_errorissue, +) +from moderation_api.types import BaseModel +from moderation_api.utils import validate_const +import pydantic +from pydantic.functional_validators import AfterValidator +from typing import List, Literal, Optional +from typing_extensions import Annotated + + +class AuthenticationErrorData(BaseModel): + code: models_authenticationerror.AuthenticationErrorCode + r"""Exactly what went wrong. Stable; safe to branch on. New codes may be added. + + - `unauthorized`: Authentication failed. + - `missing_api_key`: No API key in the `Authorization` header. + - `malformed_api_key`: The `Authorization` header does not contain a valid key format. + - `invalid_api_key`: The API key does not exist or was revoked. + """ + message: str + r"""Human-readable explanation.""" + request_id: str + r"""Identifies this request. Include it when you contact support.""" + doc_url: str + r"""Documentation for this `code`.""" + type: Annotated[ + Annotated[ + Literal["authentication_error"], + AfterValidator(validate_const("authentication_error")), + ], + pydantic.Field(alias="type"), + ] = "authentication_error" + r"""Broad error category. Stable; safe to switch on.""" + issues: Optional[List[models_errorissue.ErrorIssue]] = None + r"""Validation problems, when the request body is invalid.""" + + +@dataclass(unsafe_hash=True) +class AuthenticationError(ModerationAPIError): + r"""The API key is missing, invalid or revoked.""" + + data: AuthenticationErrorData = field(hash=False) + + def __init__( + self, + data: AuthenticationErrorData, + raw_response: httpx.Response, + body: Optional[str] = None, + ): + fallback = body or raw_response.text + message = str(data.message) or fallback + super().__init__(message, raw_response, body) + object.__setattr__(self, "data", data) diff --git a/src/moderation_api/errors/badrequesterror.py b/src/moderation_api/errors/badrequesterror.py new file mode 100644 index 0000000..46a0a05 --- /dev/null +++ b/src/moderation_api/errors/badrequesterror.py @@ -0,0 +1,59 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from dataclasses import dataclass, field +import httpx +from moderation_api.errors import ModerationAPIError +from moderation_api.models import ( + badrequesterror as models_badrequesterror, + errorissue as models_errorissue, +) +from moderation_api.types import BaseModel +from moderation_api.utils import validate_const +import pydantic +from pydantic.functional_validators import AfterValidator +from typing import List, Literal, Optional +from typing_extensions import Annotated + + +class BadRequestErrorData(BaseModel): + code: models_badrequesterror.BadRequestErrorCode + r"""Exactly what went wrong. Stable; safe to branch on. New codes may be added. + + - `invalid_request`: A parameter is invalid. `message` says which one. + - `validation_failed`: The request body does not match the schema. `issues` lists each problem with its `path`. + """ + message: str + r"""Human-readable explanation.""" + request_id: str + r"""Identifies this request. Include it when you contact support.""" + doc_url: str + r"""Documentation for this `code`.""" + type: Annotated[ + Annotated[ + Literal["invalid_request_error"], + AfterValidator(validate_const("invalid_request_error")), + ], + pydantic.Field(alias="type"), + ] = "invalid_request_error" + r"""Broad error category. Stable; safe to switch on.""" + issues: Optional[List[models_errorissue.ErrorIssue]] = None + r"""Validation problems, when the request body is invalid.""" + + +@dataclass(unsafe_hash=True) +class BadRequestError(ModerationAPIError): + r"""The request is malformed or a parameter is invalid.""" + + data: BadRequestErrorData = field(hash=False) + + def __init__( + self, + data: BadRequestErrorData, + raw_response: httpx.Response, + body: Optional[str] = None, + ): + fallback = body or raw_response.text + message = str(data.message) or fallback + super().__init__(message, raw_response, body) + object.__setattr__(self, "data", data) diff --git a/src/moderation_api/errors/conflicterror.py b/src/moderation_api/errors/conflicterror.py new file mode 100644 index 0000000..98c3edb --- /dev/null +++ b/src/moderation_api/errors/conflicterror.py @@ -0,0 +1,57 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from dataclasses import dataclass, field +import httpx +from moderation_api.errors import ModerationAPIError +from moderation_api.models import ( + conflicterror as models_conflicterror, + errorissue as models_errorissue, +) +from moderation_api.types import BaseModel +from moderation_api.utils import validate_const +import pydantic +from pydantic.functional_validators import AfterValidator +from typing import List, Literal, Optional +from typing_extensions import Annotated + + +class ConflictErrorData(BaseModel): + code: models_conflicterror.ConflictErrorCode + r"""Exactly what went wrong. Stable; safe to branch on. New codes may be added. + + - `resource_conflict`: The request conflicts with an existing resource. + """ + message: str + r"""Human-readable explanation.""" + request_id: str + r"""Identifies this request. Include it when you contact support.""" + doc_url: str + r"""Documentation for this `code`.""" + type: Annotated[ + Annotated[ + Literal["conflict_error"], AfterValidator(validate_const("conflict_error")) + ], + pydantic.Field(alias="type"), + ] = "conflict_error" + r"""Broad error category. Stable; safe to switch on.""" + issues: Optional[List[models_errorissue.ErrorIssue]] = None + r"""Validation problems, when the request body is invalid.""" + + +@dataclass(unsafe_hash=True) +class ConflictError(ModerationAPIError): + r"""The request conflicts with the current state of a resource.""" + + data: ConflictErrorData = field(hash=False) + + def __init__( + self, + data: ConflictErrorData, + raw_response: httpx.Response, + body: Optional[str] = None, + ): + fallback = body or raw_response.text + message = str(data.message) or fallback + super().__init__(message, raw_response, body) + object.__setattr__(self, "data", data) diff --git a/src/moderation_api/errors/internalservererror.py b/src/moderation_api/errors/internalservererror.py new file mode 100644 index 0000000..03b320e --- /dev/null +++ b/src/moderation_api/errors/internalservererror.py @@ -0,0 +1,55 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from dataclasses import dataclass, field +import httpx +from moderation_api.errors import ModerationAPIError +from moderation_api.models import ( + errorissue as models_errorissue, + internalservererror as models_internalservererror, +) +from moderation_api.types import BaseModel +from moderation_api.utils import validate_const +import pydantic +from pydantic.functional_validators import AfterValidator +from typing import List, Literal, Optional +from typing_extensions import Annotated + + +class InternalServerErrorData(BaseModel): + code: models_internalservererror.InternalServerErrorCode + r"""Exactly what went wrong. Stable; safe to branch on. New codes may be added. + + - `internal_error`: Something went wrong on our side. Retry with backoff. + """ + message: str + r"""Human-readable explanation.""" + request_id: str + r"""Identifies this request. Include it when you contact support.""" + doc_url: str + r"""Documentation for this `code`.""" + type: Annotated[ + Annotated[Literal["api_error"], AfterValidator(validate_const("api_error"))], + pydantic.Field(alias="type"), + ] = "api_error" + r"""Broad error category. Stable; safe to switch on.""" + issues: Optional[List[models_errorissue.ErrorIssue]] = None + r"""Validation problems, when the request body is invalid.""" + + +@dataclass(unsafe_hash=True) +class InternalServerError(ModerationAPIError): + r"""Something went wrong on our side. Retry with backoff.""" + + data: InternalServerErrorData = field(hash=False) + + def __init__( + self, + data: InternalServerErrorData, + raw_response: httpx.Response, + body: Optional[str] = None, + ): + fallback = body or raw_response.text + message = str(data.message) or fallback + super().__init__(message, raw_response, body) + object.__setattr__(self, "data", data) diff --git a/src/moderation_api/errors/moderationapidefaulterror.py b/src/moderation_api/errors/moderationapidefaulterror.py new file mode 100644 index 0000000..f6ecfb7 --- /dev/null +++ b/src/moderation_api/errors/moderationapidefaulterror.py @@ -0,0 +1,40 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +import httpx +from typing import Optional +from dataclasses import dataclass + +from moderation_api.errors import ModerationAPIError + +MAX_MESSAGE_LEN = 10_000 + + +@dataclass(unsafe_hash=True) +class ModerationAPIDefaultError(ModerationAPIError): + """The fallback error class if no more specific error class is matched.""" + + def __init__( + self, message: str, raw_response: httpx.Response, body: Optional[str] = None + ): + body_display = body or raw_response.text or '""' + + if message: + message += ": " + message += f"Status {raw_response.status_code}" + + headers = raw_response.headers + content_type = headers.get("content-type", '""') + if content_type != "application/json": + if " " in content_type: + content_type = f'"{content_type}"' + message += f" Content-Type {content_type}" + + if len(body_display) > MAX_MESSAGE_LEN: + truncated = body_display[:MAX_MESSAGE_LEN] + remaining = len(body_display) - MAX_MESSAGE_LEN + body_display = f"{truncated}...and {remaining} more chars" + + message += f". Body: {body_display}" + message = message.strip() + + super().__init__(message, raw_response, body) diff --git a/src/moderation_api/errors/moderationapierror.py b/src/moderation_api/errors/moderationapierror.py new file mode 100644 index 0000000..f06d473 --- /dev/null +++ b/src/moderation_api/errors/moderationapierror.py @@ -0,0 +1,30 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +import httpx +from typing import Optional +from dataclasses import dataclass, field + + +@dataclass(unsafe_hash=True) +class ModerationAPIError(Exception): + """The base class for all HTTP error responses.""" + + message: str + status_code: int + body: str + headers: httpx.Headers = field(hash=False) + raw_response: httpx.Response = field(hash=False) + + def __init__( + self, message: str, raw_response: httpx.Response, body: Optional[str] = None + ): + object.__setattr__(self, "message", message) + object.__setattr__(self, "status_code", raw_response.status_code) + object.__setattr__( + self, "body", body if body is not None else raw_response.text + ) + object.__setattr__(self, "headers", raw_response.headers) + object.__setattr__(self, "raw_response", raw_response) + + def __str__(self): + return self.message diff --git a/src/moderation_api/errors/no_response_error.py b/src/moderation_api/errors/no_response_error.py new file mode 100644 index 0000000..1deab64 --- /dev/null +++ b/src/moderation_api/errors/no_response_error.py @@ -0,0 +1,17 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from dataclasses import dataclass + + +@dataclass(unsafe_hash=True) +class NoResponseError(Exception): + """Error raised when no HTTP response is received from the server.""" + + message: str + + def __init__(self, message: str = "No response received"): + object.__setattr__(self, "message", message) + super().__init__(message) + + def __str__(self): + return self.message diff --git a/src/moderation_api/errors/notfounderror.py b/src/moderation_api/errors/notfounderror.py new file mode 100644 index 0000000..9d916fa --- /dev/null +++ b/src/moderation_api/errors/notfounderror.py @@ -0,0 +1,65 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from dataclasses import dataclass, field +import httpx +from moderation_api.errors import ModerationAPIError +from moderation_api.models import ( + errorissue as models_errorissue, + notfounderror as models_notfounderror, +) +from moderation_api.types import BaseModel +from moderation_api.utils import validate_const +import pydantic +from pydantic.functional_validators import AfterValidator +from typing import List, Literal, Optional +from typing_extensions import Annotated + + +class NotFoundErrorData(BaseModel): + code: models_notfounderror.NotFoundErrorCode + r"""Exactly what went wrong. Stable; safe to branch on. New codes may be added. + + - `resource_not_found`: The resource does not exist. + - `action_not_found`: The action does not exist. + - `author_not_found`: The author does not exist. + - `queue_not_found`: The review queue does not exist. + - `wordlist_not_found`: The wordlist does not exist. + - `webhook_not_found`: The webhook does not exist. + - `project_not_found`: The project does not exist. + - `channel_not_found`: The channel does not exist. + """ + message: str + r"""Human-readable explanation.""" + request_id: str + r"""Identifies this request. Include it when you contact support.""" + doc_url: str + r"""Documentation for this `code`.""" + type: Annotated[ + Annotated[ + Literal["not_found_error"], + AfterValidator(validate_const("not_found_error")), + ], + pydantic.Field(alias="type"), + ] = "not_found_error" + r"""Broad error category. Stable; safe to switch on.""" + issues: Optional[List[models_errorissue.ErrorIssue]] = None + r"""Validation problems, when the request body is invalid.""" + + +@dataclass(unsafe_hash=True) +class NotFoundError(ModerationAPIError): + r"""The resource does not exist or the key cannot see it.""" + + data: NotFoundErrorData = field(hash=False) + + def __init__( + self, + data: NotFoundErrorData, + raw_response: httpx.Response, + body: Optional[str] = None, + ): + fallback = body or raw_response.text + message = str(data.message) or fallback + super().__init__(message, raw_response, body) + object.__setattr__(self, "data", data) diff --git a/src/moderation_api/errors/permissiondeniederror.py b/src/moderation_api/errors/permissiondeniederror.py new file mode 100644 index 0000000..b929507 --- /dev/null +++ b/src/moderation_api/errors/permissiondeniederror.py @@ -0,0 +1,66 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from dataclasses import dataclass, field +import httpx +from moderation_api.errors import ModerationAPIError +from moderation_api.models import ( + errorissue as models_errorissue, + permissiondeniederror as models_permissiondeniederror, +) +from moderation_api.types import BaseModel +from moderation_api.utils import validate_const +import pydantic +from pydantic.functional_validators import AfterValidator +from typing import List, Literal, Optional +from typing_extensions import Annotated + + +class PermissionDeniedErrorData(BaseModel): + code: models_permissiondeniederror.PermissionDeniedErrorCode + r"""Exactly what went wrong. Stable; safe to branch on. New codes may be added. + + - `permission_denied`: The API key cannot perform this request. + - `account_disabled`: The organization account is disabled. + - `quota_exceeded`: The organization has used all API requests on its plan. + - `plan_feature_unavailable`: The current plan does not include this feature. + - `plan_limit_exceeded`: The request goes over a limit of the current plan. + - `project_key_required`: This endpoint needs a project API key, but an organization key was used. + - `organization_key_required`: This endpoint needs an organization API key, but a project key was used. + - `missing_key_permission`: The organization key does not have the permission this endpoint needs. + - `project_not_reachable`: The organization key is limited to other projects. + """ + message: str + r"""Human-readable explanation.""" + request_id: str + r"""Identifies this request. Include it when you contact support.""" + doc_url: str + r"""Documentation for this `code`.""" + type: Annotated[ + Annotated[ + Literal["permission_error"], + AfterValidator(validate_const("permission_error")), + ], + pydantic.Field(alias="type"), + ] = "permission_error" + r"""Broad error category. Stable; safe to switch on.""" + issues: Optional[List[models_errorissue.ErrorIssue]] = None + r"""Validation problems, when the request body is invalid.""" + + +@dataclass(unsafe_hash=True) +class PermissionDeniedError(ModerationAPIError): + r"""The API key cannot do this, or the plan limit for this resource is reached.""" + + data: PermissionDeniedErrorData = field(hash=False) + + def __init__( + self, + data: PermissionDeniedErrorData, + raw_response: httpx.Response, + body: Optional[str] = None, + ): + fallback = body or raw_response.text + message = str(data.message) or fallback + super().__init__(message, raw_response, body) + object.__setattr__(self, "data", data) diff --git a/src/moderation_api/errors/ratelimiterror.py b/src/moderation_api/errors/ratelimiterror.py new file mode 100644 index 0000000..2bc2e7b --- /dev/null +++ b/src/moderation_api/errors/ratelimiterror.py @@ -0,0 +1,58 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from dataclasses import dataclass, field +import httpx +from moderation_api.errors import ModerationAPIError +from moderation_api.models import ( + errorissue as models_errorissue, + ratelimiterror as models_ratelimiterror, +) +from moderation_api.types import BaseModel +from moderation_api.utils import validate_const +import pydantic +from pydantic.functional_validators import AfterValidator +from typing import List, Literal, Optional +from typing_extensions import Annotated + + +class RateLimitErrorData(BaseModel): + code: models_ratelimiterror.RateLimitErrorCode + r"""Exactly what went wrong. Stable; safe to branch on. New codes may be added. + + - `rate_limit_exceeded`: Too many requests. Wait for the `Retry-After` seconds. + """ + message: str + r"""Human-readable explanation.""" + request_id: str + r"""Identifies this request. Include it when you contact support.""" + doc_url: str + r"""Documentation for this `code`.""" + type: Annotated[ + Annotated[ + Literal["rate_limit_error"], + AfterValidator(validate_const("rate_limit_error")), + ], + pydantic.Field(alias="type"), + ] = "rate_limit_error" + r"""Broad error category. Stable; safe to switch on.""" + issues: Optional[List[models_errorissue.ErrorIssue]] = None + r"""Validation problems, when the request body is invalid.""" + + +@dataclass(unsafe_hash=True) +class RateLimitError(ModerationAPIError): + r"""Too many requests. Retry after the number of seconds in the `Retry-After` header.""" + + data: RateLimitErrorData = field(hash=False) + + def __init__( + self, + data: RateLimitErrorData, + raw_response: httpx.Response, + body: Optional[str] = None, + ): + fallback = body or raw_response.text + message = str(data.message) or fallback + super().__init__(message, raw_response, body) + object.__setattr__(self, "data", data) diff --git a/src/moderation_api/errors/responsevalidationerror.py b/src/moderation_api/errors/responsevalidationerror.py new file mode 100644 index 0000000..737222b --- /dev/null +++ b/src/moderation_api/errors/responsevalidationerror.py @@ -0,0 +1,27 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +import httpx +from typing import Optional +from dataclasses import dataclass + +from moderation_api.errors import ModerationAPIError + + +@dataclass(unsafe_hash=True) +class ResponseValidationError(ModerationAPIError): + """Error raised when there is a type mismatch between the response data and the expected Pydantic model.""" + + def __init__( + self, + message: str, + raw_response: httpx.Response, + cause: Exception, + body: Optional[str] = None, + ): + message = f"{message}: {cause}" + super().__init__(message, raw_response, body) + + @property + def cause(self): + """Normally the Pydantic ValidationError""" + return self.__cause__ diff --git a/src/moderation_api/httpclient.py b/src/moderation_api/httpclient.py new file mode 100644 index 0000000..89560b5 --- /dev/null +++ b/src/moderation_api/httpclient.py @@ -0,0 +1,125 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +# pyright: reportReturnType = false +import asyncio +from typing_extensions import Protocol, runtime_checkable +import httpx +from typing import Any, Optional, Union + + +@runtime_checkable +class HttpClient(Protocol): + def send( + self, + request: httpx.Request, + *, + stream: bool = False, + auth: Union[ + httpx._types.AuthTypes, httpx._client.UseClientDefault, None + ] = httpx.USE_CLIENT_DEFAULT, + follow_redirects: Union[ + bool, httpx._client.UseClientDefault + ] = httpx.USE_CLIENT_DEFAULT, + ) -> httpx.Response: + pass + + def build_request( + self, + method: str, + url: httpx._types.URLTypes, + *, + content: Optional[httpx._types.RequestContent] = None, + data: Optional[httpx._types.RequestData] = None, + files: Optional[httpx._types.RequestFiles] = None, + json: Optional[Any] = None, + params: Optional[httpx._types.QueryParamTypes] = None, + headers: Optional[httpx._types.HeaderTypes] = None, + cookies: Optional[httpx._types.CookieTypes] = None, + timeout: Union[ + httpx._types.TimeoutTypes, httpx._client.UseClientDefault + ] = httpx.USE_CLIENT_DEFAULT, + extensions: Optional[httpx._types.RequestExtensions] = None, + ) -> httpx.Request: + pass + + def close(self) -> None: + pass + + +@runtime_checkable +class AsyncHttpClient(Protocol): + async def send( + self, + request: httpx.Request, + *, + stream: bool = False, + auth: Union[ + httpx._types.AuthTypes, httpx._client.UseClientDefault, None + ] = httpx.USE_CLIENT_DEFAULT, + follow_redirects: Union[ + bool, httpx._client.UseClientDefault + ] = httpx.USE_CLIENT_DEFAULT, + ) -> httpx.Response: + pass + + def build_request( + self, + method: str, + url: httpx._types.URLTypes, + *, + content: Optional[httpx._types.RequestContent] = None, + data: Optional[httpx._types.RequestData] = None, + files: Optional[httpx._types.RequestFiles] = None, + json: Optional[Any] = None, + params: Optional[httpx._types.QueryParamTypes] = None, + headers: Optional[httpx._types.HeaderTypes] = None, + cookies: Optional[httpx._types.CookieTypes] = None, + timeout: Union[ + httpx._types.TimeoutTypes, httpx._client.UseClientDefault + ] = httpx.USE_CLIENT_DEFAULT, + extensions: Optional[httpx._types.RequestExtensions] = None, + ) -> httpx.Request: + pass + + async def aclose(self) -> None: + pass + + +class ClientOwner(Protocol): + client: Union[HttpClient, None] + async_client: Union[AsyncHttpClient, None] + + +def close_clients( + owner: ClientOwner, + sync_client: Union[HttpClient, None], + sync_client_supplied: bool, + async_client: Union[AsyncHttpClient, None], + async_client_supplied: bool, +) -> None: + """ + A finalizer function that is meant to be used with weakref.finalize to close + httpx clients used by an SDK so that underlying resources can be garbage + collected. + """ + + # Unset the client/async_client properties so there are no more references + # to them from the owning SDK instance and they can be reaped. + owner.client = None + owner.async_client = None + if sync_client is not None and not sync_client_supplied: + try: + sync_client.close() + except Exception: + pass + + if async_client is not None and not async_client_supplied: + try: + loop = asyncio.get_running_loop() + asyncio.run_coroutine_threadsafe(async_client.aclose(), loop) + except RuntimeError: + try: + asyncio.run(async_client.aclose()) + except RuntimeError: + # best effort + pass diff --git a/src/moderation_api/items.py b/src/moderation_api/items.py new file mode 100644 index 0000000..f32fa4c --- /dev/null +++ b/src/moderation_api/items.py @@ -0,0 +1,905 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .basesdk import BaseSDK +from jsonpath import JSONPath +from moderation_api import errors, models, utils +from moderation_api._hooks import HookContext +from moderation_api.types import OptionalNullable, UNSET +from moderation_api.utils import get_security_from_env +from moderation_api.utils.unmarshal_json_response import unmarshal_json_response +from typing import Any, Awaitable, Dict, Iterable, List, Mapping, Optional, Union + + +class Items(BaseSDK): + def list( + self, + *, + id: str, + page: Optional[int] = 1, + page_size: Optional[int] = 20, + sort_field: Optional[models.SortField] = None, + sort_direction: Optional[models.ListQueueItemsSortDirection] = "desc", + conversation_ids: Optional[Iterable[str]] = None, + labels: Optional[Iterable[str]] = None, + after_date: Optional[str] = None, + before_date: Optional[str] = None, + include_resolved: Optional[bool] = None, + author_id: Optional[str] = None, + filtered_action_ids: Optional[Iterable[str]] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> Optional[models.ListQueueItemsResponse]: + r"""Get queue items + + Get paginated list of items in a moderation queue with filtering options + + :param id: The queue ID + :param page: + :param page_size: + :param sort_field: + :param sort_direction: Sort direction + :param conversation_ids: + :param labels: + :param after_date: + :param before_date: + :param include_resolved: + :param author_id: + :param filtered_action_ids: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.ListQueueItemsRequest( + id=id, + page=page, + page_size=page_size, + sort_field=sort_field, + sort_direction=sort_direction, + conversation_ids=utils.unmarshal(conversation_ids, Optional[List[str]]), + labels=utils.unmarshal(labels, Optional[List[str]]), + after_date=after_date, + before_date=before_date, + include_resolved=include_resolved, + author_id=author_id, + filtered_action_ids=utils.unmarshal( + filtered_action_ids, Optional[List[str]] + ), + ) + + req = self._build_request( + method="GET", + path="/queue/{id}/items", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="listQueueItems", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Review queues"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + def next_func() -> Optional[models.ListQueueItemsResponse]: + body = utils.unmarshal_json(http_res.text, Union[Dict[Any, Any], List[Any]]) + + page = request.page if isinstance(request.page, int) else 1 + next_page = page + 1 + + num_pages = JSONPath("$.pagination.totalPages").parse(body) + if len(num_pages) == 0 or num_pages[0] <= page: + return None + + if not http_res.text: + return None + results = JSONPath("$.data").parse(body) + if len(results) == 0 or len(results[0]) == 0: + return None + limit_ = request.page_size if isinstance(request.page_size, int) else 20 + if len(results[0]) < limit_: + return None + + return self.list( + id=id, + page=next_page, + page_size=page_size, + sort_field=sort_field, + sort_direction=sort_direction, + conversation_ids=conversation_ids, + labels=labels, + after_date=after_date, + before_date=before_date, + include_resolved=include_resolved, + author_id=author_id, + filtered_action_ids=filtered_action_ids, + retries=retries, + server_url=server_url, + timeout_ms=timeout_ms, + http_headers=http_headers, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return models.ListQueueItemsResponse( + result=unmarshal_json_response( + models.ListQueueItemsResponseBody, http_res + ), + next=next_func, + ) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def list_async( + self, + *, + id: str, + page: Optional[int] = 1, + page_size: Optional[int] = 20, + sort_field: Optional[models.SortField] = None, + sort_direction: Optional[models.ListQueueItemsSortDirection] = "desc", + conversation_ids: Optional[Iterable[str]] = None, + labels: Optional[Iterable[str]] = None, + after_date: Optional[str] = None, + before_date: Optional[str] = None, + include_resolved: Optional[bool] = None, + author_id: Optional[str] = None, + filtered_action_ids: Optional[Iterable[str]] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> Optional[models.ListQueueItemsResponse]: + r"""Get queue items + + Get paginated list of items in a moderation queue with filtering options + + :param id: The queue ID + :param page: + :param page_size: + :param sort_field: + :param sort_direction: Sort direction + :param conversation_ids: + :param labels: + :param after_date: + :param before_date: + :param include_resolved: + :param author_id: + :param filtered_action_ids: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.ListQueueItemsRequest( + id=id, + page=page, + page_size=page_size, + sort_field=sort_field, + sort_direction=sort_direction, + conversation_ids=utils.unmarshal(conversation_ids, Optional[List[str]]), + labels=utils.unmarshal(labels, Optional[List[str]]), + after_date=after_date, + before_date=before_date, + include_resolved=include_resolved, + author_id=author_id, + filtered_action_ids=utils.unmarshal( + filtered_action_ids, Optional[List[str]] + ), + ) + + req = self._build_request_async( + method="GET", + path="/queue/{id}/items", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="listQueueItems", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Review queues"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + def next_func() -> Awaitable[Optional[models.ListQueueItemsResponse]]: + body = utils.unmarshal_json(http_res.text, Union[Dict[Any, Any], List[Any]]) + + async def empty_result(): + return None + + page = request.page if isinstance(request.page, int) else 1 + next_page = page + 1 + + num_pages = JSONPath("$.pagination.totalPages").parse(body) + if len(num_pages) == 0 or num_pages[0] <= page: + return empty_result() + + if not http_res.text: + return empty_result() + results = JSONPath("$.data").parse(body) + if len(results) == 0 or len(results[0]) == 0: + return empty_result() + limit_ = request.page_size if isinstance(request.page_size, int) else 20 + if len(results[0]) < limit_: + return empty_result() + + return self.list_async( + id=id, + page=next_page, + page_size=page_size, + sort_field=sort_field, + sort_direction=sort_direction, + conversation_ids=conversation_ids, + labels=labels, + after_date=after_date, + before_date=before_date, + include_resolved=include_resolved, + author_id=author_id, + filtered_action_ids=filtered_action_ids, + retries=retries, + server_url=server_url, + timeout_ms=timeout_ms, + http_headers=http_headers, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return models.ListQueueItemsResponse( + result=unmarshal_json_response( + models.ListQueueItemsResponseBody, http_res + ), + next=next_func, + ) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def resolve( + self, + *, + id: str, + item_id: str, + comment: Optional[str] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.ResolveQueueItemResponse: + r"""Resolve a queue item + + Mark a queue item as resolved with a specific moderation action + + :param id: The queue ID + :param item_id: The item ID to resolve + :param comment: Optional comment + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.ResolveQueueItemRequest( + id=id, + item_id=item_id, + body=models.ResolveQueueItemRequestBody( + comment=comment, + ), + ) + + req = self._build_request( + method="POST", + path="/queue/{id}/items/{itemId}/resolve", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.ResolveQueueItemRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="resolveQueueItem", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Review queues"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.ResolveQueueItemResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def resolve_async( + self, + *, + id: str, + item_id: str, + comment: Optional[str] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.ResolveQueueItemResponse: + r"""Resolve a queue item + + Mark a queue item as resolved with a specific moderation action + + :param id: The queue ID + :param item_id: The item ID to resolve + :param comment: Optional comment + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.ResolveQueueItemRequest( + id=id, + item_id=item_id, + body=models.ResolveQueueItemRequestBody( + comment=comment, + ), + ) + + req = self._build_request_async( + method="POST", + path="/queue/{id}/items/{itemId}/resolve", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.ResolveQueueItemRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="resolveQueueItem", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Review queues"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.ResolveQueueItemResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def unresolve( + self, + *, + id: str, + item_id: str, + comment: Optional[str] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.UnresolveQueueItemResponse: + r"""Unresolve a queue item + + Mark a previously resolved queue item as unresolved/pending + + :param id: The queue ID + :param item_id: The item ID to unresolve + :param comment: Optional reason for unresolving the item + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.UnresolveQueueItemRequest( + id=id, + item_id=item_id, + body=models.UnresolveQueueItemRequestBody( + comment=comment, + ), + ) + + req = self._build_request( + method="POST", + path="/queue/{id}/items/{itemId}/unresolve", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.UnresolveQueueItemRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="unresolveQueueItem", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Review queues"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.UnresolveQueueItemResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def unresolve_async( + self, + *, + id: str, + item_id: str, + comment: Optional[str] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.UnresolveQueueItemResponse: + r"""Unresolve a queue item + + Mark a previously resolved queue item as unresolved/pending + + :param id: The queue ID + :param item_id: The item ID to unresolve + :param comment: Optional reason for unresolving the item + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.UnresolveQueueItemRequest( + id=id, + item_id=item_id, + body=models.UnresolveQueueItemRequestBody( + comment=comment, + ), + ) + + req = self._build_request_async( + method="POST", + path="/queue/{id}/items/{itemId}/unresolve", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.UnresolveQueueItemRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="unresolveQueueItem", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Review queues"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.UnresolveQueueItemResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) diff --git a/src/moderation_api/lib/.keep b/src/moderation_api/lib/.keep deleted file mode 100644 index 5e2c99f..0000000 --- a/src/moderation_api/lib/.keep +++ /dev/null @@ -1,4 +0,0 @@ -File generated from our OpenAPI spec by Stainless. - -This directory can be used to store custom files to expand the SDK. -It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. \ No newline at end of file diff --git a/src/moderation_api/models/__init__.py b/src/moderation_api/models/__init__.py new file mode 100644 index 0000000..79cfcc1 --- /dev/null +++ b/src/moderation_api/models/__init__.py @@ -0,0 +1,1624 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from typing import Any, TYPE_CHECKING + +from moderation_api.utils.dynamic_imports import lazy_getattr, lazy_dir + +if TYPE_CHECKING: + from .addwordlistwordsop import ( + AddWordlistWordsRequest, + AddWordlistWordsRequestBody, + AddWordlistWordsRequestBodyTypedDict, + AddWordlistWordsRequestTypedDict, + AddWordlistWordsResponse, + AddWordlistWordsResponseTypedDict, + ) + from .authenticationerror import AuthenticationErrorCode + from .badrequesterror import BadRequestErrorCode + from .channel import ( + Channel, + ChannelTypedDict, + ContentType, + FlaggingMode, + TranscriptionQuality, + ) + from .conflicterror import ConflictErrorCode + from .createactionop import ( + CreateActionPositionRequest, + CreateActionPositionResponse, + CreateActionPossibleValueRequest, + CreateActionPossibleValueRequestTypedDict, + CreateActionPossibleValueResponse, + CreateActionPossibleValueResponseTypedDict, + CreateActionQueueBehaviourRequest, + CreateActionQueueBehaviourResponse, + CreateActionRequest, + CreateActionRequestTypedDict, + CreateActionResponse, + CreateActionResponseTypedDict, + CreateActionTypeRequest, + CreateActionTypeResponse, + ) + from .createauthorop import ( + CreateAuthorMetadata, + CreateAuthorMetadataTypedDict, + CreateAuthorRequest, + CreateAuthorRequestTypedDict, + ) + from .createchannelop import ( + CreateChannelContentType, + CreateChannelRequest, + CreateChannelRequestBody, + CreateChannelRequestBodyTypedDict, + CreateChannelRequestTypedDict, + ) + from .createprojectop import CreateProjectRequest, CreateProjectRequestTypedDict + from .createwebhookop import ( + CreateWebhookEventTypeRequest, + CreateWebhookEventTypeResponse, + CreateWebhookPayloadVersion, + CreateWebhookRequest, + CreateWebhookRequestTypedDict, + CreateWebhookResponse, + CreateWebhookResponseTypedDict, + ) + from .deleteactionop import ( + DeleteActionRequest, + DeleteActionRequestTypedDict, + DeleteActionResponse, + DeleteActionResponseTypedDict, + ) + from .deleteauthorop import ( + DeleteAuthorRequest, + DeleteAuthorRequestTypedDict, + DeleteAuthorResponse, + DeleteAuthorResponseTypedDict, + ) + from .deletechannelop import ( + DeleteChannelRequest, + DeleteChannelRequestTypedDict, + DeleteChannelResponse, + DeleteChannelResponseTypedDict, + ) + from .deleteprojectop import ( + DeleteProjectRequest, + DeleteProjectRequestTypedDict, + DeleteProjectResponse, + DeleteProjectResponseTypedDict, + ) + from .deletewebhookop import ( + DeleteWebhookRequest, + DeleteWebhookRequestTypedDict, + DeleteWebhookResponse, + DeleteWebhookResponseTypedDict, + ) + from .duplicatechannelop import ( + DuplicateChannelRequest, + DuplicateChannelRequestBody, + DuplicateChannelRequestBodyTypedDict, + DuplicateChannelRequestTypedDict, + ) + from .duplicateprojectop import ( + DuplicateProjectRequest, + DuplicateProjectRequestBody, + DuplicateProjectRequestBodyTypedDict, + DuplicateProjectRequestTypedDict, + ) + from .errorissue import ErrorIssue, ErrorIssueTypedDict, Path, PathTypedDict + from .executeactionop import ( + ExecuteActionRequest, + ExecuteActionRequestTypedDict, + ExecuteActionResponse, + ExecuteActionResponseTypedDict, + ) + from .getaccountop import ( + CurrentProject, + CurrentProjectTypedDict, + GetAccountResponse, + GetAccountResponseTypedDict, + ) + from .getactionop import ( + GetActionPosition, + GetActionPossibleValue, + GetActionPossibleValueTypedDict, + GetActionQueueBehaviour, + GetActionRequest, + GetActionRequestTypedDict, + GetActionResponse, + GetActionResponseTypedDict, + GetActionType, + ) + from .getauthorop import GetAuthorRequest, GetAuthorRequestTypedDict + from .getchannelop import GetChannelRequest, GetChannelRequestTypedDict + from .getprojectop import GetProjectRequest, GetProjectRequestTypedDict + from .getqueueop import ( + CasebookAgreement, + CasebookAnswer, + CheckStatus, + Filter, + FilterLabel, + FilterLabelTypedDict, + FilterTypedDict, + GetQueueContentType, + GetQueueQueue, + GetQueueQueueTypedDict, + GetQueueRecommendationAction, + GetQueueRequest, + GetQueueRequestTypedDict, + GetQueueResponse, + GetQueueResponseTypedDict, + GetQueueType, + IsFlagged, + MediaType, + MetadataFilter, + MetadataFilterTypedDict, + WithinUnit, + ) + from .getqueuestatsop import ( + ActionStat, + ActionStatTypedDict, + DailyReviewCount, + DailyReviewCountTypedDict, + FlaggedContentTrend, + FlaggedContentTrendTypedDict, + GetQueueStatsRequest, + GetQueueStatsRequestTypedDict, + GetQueueStatsResponse, + GetQueueStatsResponseTypedDict, + ReviewStats, + ReviewStatsTypedDict, + TopAction, + TopActionTypedDict, + TopReviewer, + TopReviewerTypedDict, + Trends, + TrendsTypedDict, + ) + from .getwebhookop import ( + GetWebhookEventType, + GetWebhookPayloadVersion, + GetWebhookRequest, + GetWebhookRequestTypedDict, + GetWebhookResponse, + GetWebhookResponseTypedDict, + ) + from .getwebhooksecretop import ( + GetWebhookSecretResponse, + GetWebhookSecretResponseTypedDict, + ) + from .getwordlistembeddingstatusop import ( + GetWordlistEmbeddingStatusRequest, + GetWordlistEmbeddingStatusRequestTypedDict, + GetWordlistEmbeddingStatusResponse, + GetWordlistEmbeddingStatusResponseTypedDict, + ) + from .getwordlistop import ( + GetWordlistRequest, + GetWordlistRequestTypedDict, + GetWordlistResponse, + GetWordlistResponseTypedDict, + ) + from .internalservererror import InternalServerErrorCode + from .listactionsop import ( + ListActionsData, + ListActionsDataTypedDict, + ListActionsPosition, + ListActionsPossibleValue, + ListActionsPossibleValueTypedDict, + ListActionsQueueBehaviour, + ListActionsRequest, + ListActionsRequestTypedDict, + ListActionsResponse, + ListActionsResponseBody, + ListActionsResponseBodyTypedDict, + ListActionsResponseTypedDict, + ListActionsType, + ) + from .listauthorsop import ( + ListAuthorsRequest, + ListAuthorsRequestTypedDict, + ListAuthorsResponse, + ListAuthorsResponseBody, + ListAuthorsResponseBodyTypedDict, + ListAuthorsResponseTypedDict, + ListAuthorsSortDirection, + SortBy, + ) + from .listchannelsop import ( + ListChannelsRequest, + ListChannelsRequestTypedDict, + ListChannelsResponse, + ListChannelsResponseBody, + ListChannelsResponseBodyTypedDict, + ListChannelsResponseTypedDict, + ) + from .listprojectsop import ( + ListProjectsRequest, + ListProjectsRequestTypedDict, + ListProjectsResponse, + ListProjectsResponseBody, + ListProjectsResponseBodyTypedDict, + ListProjectsResponseTypedDict, + ) + from .listqueueitemsop import ( + ListQueueItemsAction, + ListQueueItemsActionTypedDict, + ListQueueItemsData, + ListQueueItemsDataTypedDict, + ListQueueItemsLabel, + ListQueueItemsLabelTypedDict, + ListQueueItemsRequest, + ListQueueItemsRequestTypedDict, + ListQueueItemsResponse, + ListQueueItemsResponseBody, + ListQueueItemsResponseBodyTypedDict, + ListQueueItemsResponseTypedDict, + ListQueueItemsSortDirection, + ListQueueItemsStatus, + SortField, + ) + from .listwebhooksop import ( + ListWebhooksData, + ListWebhooksDataTypedDict, + ListWebhooksEventType, + ListWebhooksPayloadVersion, + ListWebhooksRequest, + ListWebhooksRequestTypedDict, + ListWebhooksResponse, + ListWebhooksResponseBody, + ListWebhooksResponseBodyTypedDict, + ListWebhooksResponseTypedDict, + ) + from .listwordlistsop import ( + ListWordlistsData, + ListWordlistsDataTypedDict, + ListWordlistsRequest, + ListWordlistsRequestTypedDict, + ListWordlistsResponse, + ListWordlistsResponseBody, + ListWordlistsResponseBodyTypedDict, + ListWordlistsResponseTypedDict, + ) + from .notfounderror import NotFoundErrorCode + from .pagination import Pagination, PaginationTypedDict + from .permissiondeniederror import PermissionDeniedErrorCode + from .project import Project, ProjectGlobalFlaggingMode, ProjectTypedDict + from .projectwithkey import ( + ProjectWithKey, + ProjectWithKeyGlobalFlaggingMode, + ProjectWithKeyTypedDict, + ) + from .publicauthor import ( + Block, + BlockTypedDict, + Metadata, + MetadataTypedDict, + Metrics, + MetricsTypedDict, + PublicAuthor, + PublicAuthorTypedDict, + RiskEvaluation, + RiskEvaluationTypedDict, + Status, + TrustLevel, + TrustLevelTypedDict, + ) + from .ratelimiterror import RateLimitErrorCode + from .removewordlistwordsop import ( + RemoveWordlistWordsRequest, + RemoveWordlistWordsRequestTypedDict, + RemoveWordlistWordsResponse, + RemoveWordlistWordsResponseTypedDict, + ) + from .resolvequeueitemop import ( + ResolveQueueItemRequest, + ResolveQueueItemRequestBody, + ResolveQueueItemRequestBodyTypedDict, + ResolveQueueItemRequestTypedDict, + ResolveQueueItemResponse, + ResolveQueueItemResponseTypedDict, + ) + from .security import Security, SecurityTypedDict + from .submitcontentop import ( + ActionRequest, + Adult, + AdultTypedDict, + Alcohol, + AlcoholTypedDict, + AudioRequest, + AudioRequestTypedDict, + AudioResponse, + AudioResponseTypedDict, + Author, + AuthorStatus, + AuthorTypedDict, + Cannabis, + CannabisTypedDict, + Casebook, + CasebookTypedDict, + ChannelCircumvention, + ChannelCircumventionTypedDict, + ClassifierOutput, + ClassifierOutputTypedDict, + CodeAbuse, + CodeAbuseTypedDict, + Comparator, + CompassionInsight, + CompassionInsightTypedDict, + ContentLeafRequest, + ContentLeafRequestTypedDict, + ContentLeafResponse, + ContentLeafResponseTypedDict, + ContentRequest, + ContentRequestTypedDict, + ContentResponse, + ContentResponseTypedDict, + Crypto, + CryptoTypedDict, + CuriosityInsight, + CuriosityInsightTypedDict, + DataAudioRequest, + DataAudioRequestTypedDict, + DataImageRequest, + DataImageRequestTypedDict, + DataTextRequest, + DataTextRequestTypedDict, + DataVideoRequest, + DataVideoRequestTypedDict, + Drugs, + DrugsTypedDict, + Entities1, + Entities1TypedDict, + Entities2, + Entities2TypedDict, + EntityMatcherOutput, + EntityMatcherOutputTypedDict, + Error, + ErrorTypedDict, + Evaluation, + EvaluationTypedDict, + Extremism, + ExtremismTypedDict, + FaceDetection, + FaceDetectionTypedDict, + Gambling, + GamblingTypedDict, + Guideline, + GuidelineTypedDict, + Hate, + HateTypedDict, + IllegalTrade, + IllegalTradeTypedDict, + Illicit, + IllicitAlcohol, + IllicitAlcoholTypedDict, + IllicitDrugs, + IllicitDrugsTypedDict, + IllicitFirearms, + IllicitFirearmsTypedDict, + IllicitGambling, + IllicitGamblingTypedDict, + IllicitTobacco, + IllicitTobaccoTypedDict, + IllicitTypedDict, + ImageRequest, + ImageRequestTypedDict, + ImageResponse, + ImageResponseTypedDict, + Insight, + InsightTypedDict, + LanguageInsight, + LanguageInsightTypedDict, + LowQualityContent, + LowQualityContentTypedDict, + MatchedRule, + MatchedRuleTypedDict, + Meta, + MetaStatus, + MetaTypedDict, + Method, + Modified, + ModifiedTypedDict, + PIIMasking, + PIIMaskingTypedDict, + PersonalInformation, + PersonalInformationTypedDict, + PersonalStoryInsight, + PersonalStoryInsightTypedDict, + PolicyRequest, + PolicyRequestTypedDict, + PolicyResponse, + PolicyResponseTypedDict, + Political, + PoliticalTypedDict, + Profanity, + ProfanityTypedDict, + Promotion, + PromotionTypedDict, + ReasonCode, + ReasonCodeEnum, + ReasonCodeTypedDict, + Recommendation, + RecommendationTypedDict, + Religion, + ReligionTypedDict, + RespectInsight, + RespectInsightTypedDict, + Scores, + ScoresTypedDict, + SelfHarm, + SelfHarmTypedDict, + SelfPromotion, + SelfPromotionTypedDict, + SentimentInsight, + SentimentInsightTypedDict, + Sexual, + SexualMinors, + SexualMinorsTypedDict, + SexualSolicitation, + SexualSolicitationTypedDict, + SexualTypedDict, + SignalsRequest, + SignalsRequestTypedDict, + SignalsResponse, + SignalsResponseTypedDict, + Spam, + SpamTypedDict, + SubmitContentBehavior, + SubmitContentBlock, + SubmitContentBlockTypedDict, + SubmitContentBrandImpersonation, + SubmitContentBrandImpersonationTypedDict, + SubmitContentClientAction, + SubmitContentClientActionTypedDict, + SubmitContentLabel, + SubmitContentLabelTypedDict, + SubmitContentMatch, + SubmitContentMatchTypedDict, + SubmitContentMetaType, + SubmitContentObject, + SubmitContentObjectTypedDict, + SubmitContentRecommendationAction, + SubmitContentRequest, + SubmitContentRequestTypedDict, + SubmitContentResponse, + SubmitContentResponseTypedDict, + SubmitContentTrustLevel, + SubmitContentTrustLevelTypedDict, + TextRequest, + TextRequestTypedDict, + TextResponse, + TextResponseTypedDict, + Threat, + ThreatTypedDict, + Tobacco, + TobaccoTypedDict, + Topic, + TopicTypedDict, + Toxicity, + ToxicityTypedDict, + URLMasking, + URLMaskingTypedDict, + URLRisk, + URLRiskTypedDict, + UnicodeSpoofing, + UnicodeSpoofingTypedDict, + UnknownContentLeafResponse, + UnknownInsight, + UnknownPolicyResponse, + Value, + Verdict, + VideoRequest, + VideoRequestTypedDict, + VideoResponse, + VideoResponseTypedDict, + Violence, + ViolenceTypedDict, + Weapons, + WeaponsTypedDict, + ) + from .unresolvequeueitemop import ( + UnresolveQueueItemRequest, + UnresolveQueueItemRequestBody, + UnresolveQueueItemRequestBodyTypedDict, + UnresolveQueueItemRequestTypedDict, + UnresolveQueueItemResponse, + UnresolveQueueItemResponseTypedDict, + ) + from .updateactionop import ( + UpdateActionPositionRequest, + UpdateActionPositionResponse, + UpdateActionPossibleValueRequest, + UpdateActionPossibleValueRequestTypedDict, + UpdateActionPossibleValueResponse, + UpdateActionPossibleValueResponseTypedDict, + UpdateActionQueueBehaviourRequest, + UpdateActionQueueBehaviourResponse, + UpdateActionRequest, + UpdateActionRequestBody, + UpdateActionRequestBodyTypedDict, + UpdateActionRequestTypedDict, + UpdateActionResponse, + UpdateActionResponseTypedDict, + UpdateActionTypeRequest, + UpdateActionTypeResponse, + ) + from .updateauthorop import ( + UpdateAuthorMetadata, + UpdateAuthorMetadataTypedDict, + UpdateAuthorRequest, + UpdateAuthorRequestBody, + UpdateAuthorRequestBodyTypedDict, + UpdateAuthorRequestTypedDict, + ) + from .updatechannelop import ( + UpdateChannelContentType, + UpdateChannelFlaggingMode, + UpdateChannelRequest, + UpdateChannelRequestBody, + UpdateChannelRequestBodyTypedDict, + UpdateChannelRequestTypedDict, + UpdateChannelTranscriptionQuality, + ) + from .updateprojectop import ( + UpdateProjectGlobalFlaggingMode, + UpdateProjectRequest, + UpdateProjectRequestBody, + UpdateProjectRequestBodyTypedDict, + UpdateProjectRequestTypedDict, + ) + from .updatewebhookop import ( + UpdateWebhookEventTypeRequest, + UpdateWebhookEventTypeResponse, + UpdateWebhookPayloadVersion, + UpdateWebhookRequest, + UpdateWebhookRequestBody, + UpdateWebhookRequestBodyTypedDict, + UpdateWebhookRequestTypedDict, + UpdateWebhookResponse, + UpdateWebhookResponseTypedDict, + ) + from .updatewordlistop import ( + UpdateWordlistRequest, + UpdateWordlistRequestBody, + UpdateWordlistRequestBodyTypedDict, + UpdateWordlistRequestTypedDict, + UpdateWordlistResponse, + UpdateWordlistResponseTypedDict, + ) + +__all__ = [ + "ActionRequest", + "ActionStat", + "ActionStatTypedDict", + "AddWordlistWordsRequest", + "AddWordlistWordsRequestBody", + "AddWordlistWordsRequestBodyTypedDict", + "AddWordlistWordsRequestTypedDict", + "AddWordlistWordsResponse", + "AddWordlistWordsResponseTypedDict", + "Adult", + "AdultTypedDict", + "Alcohol", + "AlcoholTypedDict", + "AudioRequest", + "AudioRequestTypedDict", + "AudioResponse", + "AudioResponseTypedDict", + "AuthenticationErrorCode", + "Author", + "AuthorStatus", + "AuthorTypedDict", + "BadRequestErrorCode", + "Block", + "BlockTypedDict", + "Cannabis", + "CannabisTypedDict", + "Casebook", + "CasebookAgreement", + "CasebookAnswer", + "CasebookTypedDict", + "Channel", + "ChannelCircumvention", + "ChannelCircumventionTypedDict", + "ChannelTypedDict", + "CheckStatus", + "ClassifierOutput", + "ClassifierOutputTypedDict", + "CodeAbuse", + "CodeAbuseTypedDict", + "Comparator", + "CompassionInsight", + "CompassionInsightTypedDict", + "ConflictErrorCode", + "ContentLeafRequest", + "ContentLeafRequestTypedDict", + "ContentLeafResponse", + "ContentLeafResponseTypedDict", + "ContentRequest", + "ContentRequestTypedDict", + "ContentResponse", + "ContentResponseTypedDict", + "ContentType", + "CreateActionPositionRequest", + "CreateActionPositionResponse", + "CreateActionPossibleValueRequest", + "CreateActionPossibleValueRequestTypedDict", + "CreateActionPossibleValueResponse", + "CreateActionPossibleValueResponseTypedDict", + "CreateActionQueueBehaviourRequest", + "CreateActionQueueBehaviourResponse", + "CreateActionRequest", + "CreateActionRequestTypedDict", + "CreateActionResponse", + "CreateActionResponseTypedDict", + "CreateActionTypeRequest", + "CreateActionTypeResponse", + "CreateAuthorMetadata", + "CreateAuthorMetadataTypedDict", + "CreateAuthorRequest", + "CreateAuthorRequestTypedDict", + "CreateChannelContentType", + "CreateChannelRequest", + "CreateChannelRequestBody", + "CreateChannelRequestBodyTypedDict", + "CreateChannelRequestTypedDict", + "CreateProjectRequest", + "CreateProjectRequestTypedDict", + "CreateWebhookEventTypeRequest", + "CreateWebhookEventTypeResponse", + "CreateWebhookPayloadVersion", + "CreateWebhookRequest", + "CreateWebhookRequestTypedDict", + "CreateWebhookResponse", + "CreateWebhookResponseTypedDict", + "Crypto", + "CryptoTypedDict", + "CuriosityInsight", + "CuriosityInsightTypedDict", + "CurrentProject", + "CurrentProjectTypedDict", + "DailyReviewCount", + "DailyReviewCountTypedDict", + "DataAudioRequest", + "DataAudioRequestTypedDict", + "DataImageRequest", + "DataImageRequestTypedDict", + "DataTextRequest", + "DataTextRequestTypedDict", + "DataVideoRequest", + "DataVideoRequestTypedDict", + "DeleteActionRequest", + "DeleteActionRequestTypedDict", + "DeleteActionResponse", + "DeleteActionResponseTypedDict", + "DeleteAuthorRequest", + "DeleteAuthorRequestTypedDict", + "DeleteAuthorResponse", + "DeleteAuthorResponseTypedDict", + "DeleteChannelRequest", + "DeleteChannelRequestTypedDict", + "DeleteChannelResponse", + "DeleteChannelResponseTypedDict", + "DeleteProjectRequest", + "DeleteProjectRequestTypedDict", + "DeleteProjectResponse", + "DeleteProjectResponseTypedDict", + "DeleteWebhookRequest", + "DeleteWebhookRequestTypedDict", + "DeleteWebhookResponse", + "DeleteWebhookResponseTypedDict", + "Drugs", + "DrugsTypedDict", + "DuplicateChannelRequest", + "DuplicateChannelRequestBody", + "DuplicateChannelRequestBodyTypedDict", + "DuplicateChannelRequestTypedDict", + "DuplicateProjectRequest", + "DuplicateProjectRequestBody", + "DuplicateProjectRequestBodyTypedDict", + "DuplicateProjectRequestTypedDict", + "Entities1", + "Entities1TypedDict", + "Entities2", + "Entities2TypedDict", + "EntityMatcherOutput", + "EntityMatcherOutputTypedDict", + "Error", + "ErrorIssue", + "ErrorIssueTypedDict", + "ErrorTypedDict", + "Evaluation", + "EvaluationTypedDict", + "ExecuteActionRequest", + "ExecuteActionRequestTypedDict", + "ExecuteActionResponse", + "ExecuteActionResponseTypedDict", + "Extremism", + "ExtremismTypedDict", + "FaceDetection", + "FaceDetectionTypedDict", + "Filter", + "FilterLabel", + "FilterLabelTypedDict", + "FilterTypedDict", + "FlaggedContentTrend", + "FlaggedContentTrendTypedDict", + "FlaggingMode", + "Gambling", + "GamblingTypedDict", + "GetAccountResponse", + "GetAccountResponseTypedDict", + "GetActionPosition", + "GetActionPossibleValue", + "GetActionPossibleValueTypedDict", + "GetActionQueueBehaviour", + "GetActionRequest", + "GetActionRequestTypedDict", + "GetActionResponse", + "GetActionResponseTypedDict", + "GetActionType", + "GetAuthorRequest", + "GetAuthorRequestTypedDict", + "GetChannelRequest", + "GetChannelRequestTypedDict", + "GetProjectRequest", + "GetProjectRequestTypedDict", + "GetQueueContentType", + "GetQueueQueue", + "GetQueueQueueTypedDict", + "GetQueueRecommendationAction", + "GetQueueRequest", + "GetQueueRequestTypedDict", + "GetQueueResponse", + "GetQueueResponseTypedDict", + "GetQueueStatsRequest", + "GetQueueStatsRequestTypedDict", + "GetQueueStatsResponse", + "GetQueueStatsResponseTypedDict", + "GetQueueType", + "GetWebhookEventType", + "GetWebhookPayloadVersion", + "GetWebhookRequest", + "GetWebhookRequestTypedDict", + "GetWebhookResponse", + "GetWebhookResponseTypedDict", + "GetWebhookSecretResponse", + "GetWebhookSecretResponseTypedDict", + "GetWordlistEmbeddingStatusRequest", + "GetWordlistEmbeddingStatusRequestTypedDict", + "GetWordlistEmbeddingStatusResponse", + "GetWordlistEmbeddingStatusResponseTypedDict", + "GetWordlistRequest", + "GetWordlistRequestTypedDict", + "GetWordlistResponse", + "GetWordlistResponseTypedDict", + "Guideline", + "GuidelineTypedDict", + "Hate", + "HateTypedDict", + "IllegalTrade", + "IllegalTradeTypedDict", + "Illicit", + "IllicitAlcohol", + "IllicitAlcoholTypedDict", + "IllicitDrugs", + "IllicitDrugsTypedDict", + "IllicitFirearms", + "IllicitFirearmsTypedDict", + "IllicitGambling", + "IllicitGamblingTypedDict", + "IllicitTobacco", + "IllicitTobaccoTypedDict", + "IllicitTypedDict", + "ImageRequest", + "ImageRequestTypedDict", + "ImageResponse", + "ImageResponseTypedDict", + "Insight", + "InsightTypedDict", + "InternalServerErrorCode", + "IsFlagged", + "LanguageInsight", + "LanguageInsightTypedDict", + "ListActionsData", + "ListActionsDataTypedDict", + "ListActionsPosition", + "ListActionsPossibleValue", + "ListActionsPossibleValueTypedDict", + "ListActionsQueueBehaviour", + "ListActionsRequest", + "ListActionsRequestTypedDict", + "ListActionsResponse", + "ListActionsResponseBody", + "ListActionsResponseBodyTypedDict", + "ListActionsResponseTypedDict", + "ListActionsType", + "ListAuthorsRequest", + "ListAuthorsRequestTypedDict", + "ListAuthorsResponse", + "ListAuthorsResponseBody", + "ListAuthorsResponseBodyTypedDict", + "ListAuthorsResponseTypedDict", + "ListAuthorsSortDirection", + "ListChannelsRequest", + "ListChannelsRequestTypedDict", + "ListChannelsResponse", + "ListChannelsResponseBody", + "ListChannelsResponseBodyTypedDict", + "ListChannelsResponseTypedDict", + "ListProjectsRequest", + "ListProjectsRequestTypedDict", + "ListProjectsResponse", + "ListProjectsResponseBody", + "ListProjectsResponseBodyTypedDict", + "ListProjectsResponseTypedDict", + "ListQueueItemsAction", + "ListQueueItemsActionTypedDict", + "ListQueueItemsData", + "ListQueueItemsDataTypedDict", + "ListQueueItemsLabel", + "ListQueueItemsLabelTypedDict", + "ListQueueItemsRequest", + "ListQueueItemsRequestTypedDict", + "ListQueueItemsResponse", + "ListQueueItemsResponseBody", + "ListQueueItemsResponseBodyTypedDict", + "ListQueueItemsResponseTypedDict", + "ListQueueItemsSortDirection", + "ListQueueItemsStatus", + "ListWebhooksData", + "ListWebhooksDataTypedDict", + "ListWebhooksEventType", + "ListWebhooksPayloadVersion", + "ListWebhooksRequest", + "ListWebhooksRequestTypedDict", + "ListWebhooksResponse", + "ListWebhooksResponseBody", + "ListWebhooksResponseBodyTypedDict", + "ListWebhooksResponseTypedDict", + "ListWordlistsData", + "ListWordlistsDataTypedDict", + "ListWordlistsRequest", + "ListWordlistsRequestTypedDict", + "ListWordlistsResponse", + "ListWordlistsResponseBody", + "ListWordlistsResponseBodyTypedDict", + "ListWordlistsResponseTypedDict", + "LowQualityContent", + "LowQualityContentTypedDict", + "MatchedRule", + "MatchedRuleTypedDict", + "MediaType", + "Meta", + "MetaStatus", + "MetaTypedDict", + "Metadata", + "MetadataFilter", + "MetadataFilterTypedDict", + "MetadataTypedDict", + "Method", + "Metrics", + "MetricsTypedDict", + "Modified", + "ModifiedTypedDict", + "NotFoundErrorCode", + "PIIMasking", + "PIIMaskingTypedDict", + "Pagination", + "PaginationTypedDict", + "Path", + "PathTypedDict", + "PermissionDeniedErrorCode", + "PersonalInformation", + "PersonalInformationTypedDict", + "PersonalStoryInsight", + "PersonalStoryInsightTypedDict", + "PolicyRequest", + "PolicyRequestTypedDict", + "PolicyResponse", + "PolicyResponseTypedDict", + "Political", + "PoliticalTypedDict", + "Profanity", + "ProfanityTypedDict", + "Project", + "ProjectGlobalFlaggingMode", + "ProjectTypedDict", + "ProjectWithKey", + "ProjectWithKeyGlobalFlaggingMode", + "ProjectWithKeyTypedDict", + "Promotion", + "PromotionTypedDict", + "PublicAuthor", + "PublicAuthorTypedDict", + "RateLimitErrorCode", + "ReasonCode", + "ReasonCodeEnum", + "ReasonCodeTypedDict", + "Recommendation", + "RecommendationTypedDict", + "Religion", + "ReligionTypedDict", + "RemoveWordlistWordsRequest", + "RemoveWordlistWordsRequestTypedDict", + "RemoveWordlistWordsResponse", + "RemoveWordlistWordsResponseTypedDict", + "ResolveQueueItemRequest", + "ResolveQueueItemRequestBody", + "ResolveQueueItemRequestBodyTypedDict", + "ResolveQueueItemRequestTypedDict", + "ResolveQueueItemResponse", + "ResolveQueueItemResponseTypedDict", + "RespectInsight", + "RespectInsightTypedDict", + "ReviewStats", + "ReviewStatsTypedDict", + "RiskEvaluation", + "RiskEvaluationTypedDict", + "Scores", + "ScoresTypedDict", + "Security", + "SecurityTypedDict", + "SelfHarm", + "SelfHarmTypedDict", + "SelfPromotion", + "SelfPromotionTypedDict", + "SentimentInsight", + "SentimentInsightTypedDict", + "Sexual", + "SexualMinors", + "SexualMinorsTypedDict", + "SexualSolicitation", + "SexualSolicitationTypedDict", + "SexualTypedDict", + "SignalsRequest", + "SignalsRequestTypedDict", + "SignalsResponse", + "SignalsResponseTypedDict", + "SortBy", + "SortField", + "Spam", + "SpamTypedDict", + "Status", + "SubmitContentBehavior", + "SubmitContentBlock", + "SubmitContentBlockTypedDict", + "SubmitContentBrandImpersonation", + "SubmitContentBrandImpersonationTypedDict", + "SubmitContentClientAction", + "SubmitContentClientActionTypedDict", + "SubmitContentLabel", + "SubmitContentLabelTypedDict", + "SubmitContentMatch", + "SubmitContentMatchTypedDict", + "SubmitContentMetaType", + "SubmitContentObject", + "SubmitContentObjectTypedDict", + "SubmitContentRecommendationAction", + "SubmitContentRequest", + "SubmitContentRequestTypedDict", + "SubmitContentResponse", + "SubmitContentResponseTypedDict", + "SubmitContentTrustLevel", + "SubmitContentTrustLevelTypedDict", + "TextRequest", + "TextRequestTypedDict", + "TextResponse", + "TextResponseTypedDict", + "Threat", + "ThreatTypedDict", + "Tobacco", + "TobaccoTypedDict", + "TopAction", + "TopActionTypedDict", + "TopReviewer", + "TopReviewerTypedDict", + "Topic", + "TopicTypedDict", + "Toxicity", + "ToxicityTypedDict", + "TranscriptionQuality", + "Trends", + "TrendsTypedDict", + "TrustLevel", + "TrustLevelTypedDict", + "URLMasking", + "URLMaskingTypedDict", + "URLRisk", + "URLRiskTypedDict", + "UnicodeSpoofing", + "UnicodeSpoofingTypedDict", + "UnknownContentLeafResponse", + "UnknownInsight", + "UnknownPolicyResponse", + "UnresolveQueueItemRequest", + "UnresolveQueueItemRequestBody", + "UnresolveQueueItemRequestBodyTypedDict", + "UnresolveQueueItemRequestTypedDict", + "UnresolveQueueItemResponse", + "UnresolveQueueItemResponseTypedDict", + "UpdateActionPositionRequest", + "UpdateActionPositionResponse", + "UpdateActionPossibleValueRequest", + "UpdateActionPossibleValueRequestTypedDict", + "UpdateActionPossibleValueResponse", + "UpdateActionPossibleValueResponseTypedDict", + "UpdateActionQueueBehaviourRequest", + "UpdateActionQueueBehaviourResponse", + "UpdateActionRequest", + "UpdateActionRequestBody", + "UpdateActionRequestBodyTypedDict", + "UpdateActionRequestTypedDict", + "UpdateActionResponse", + "UpdateActionResponseTypedDict", + "UpdateActionTypeRequest", + "UpdateActionTypeResponse", + "UpdateAuthorMetadata", + "UpdateAuthorMetadataTypedDict", + "UpdateAuthorRequest", + "UpdateAuthorRequestBody", + "UpdateAuthorRequestBodyTypedDict", + "UpdateAuthorRequestTypedDict", + "UpdateChannelContentType", + "UpdateChannelFlaggingMode", + "UpdateChannelRequest", + "UpdateChannelRequestBody", + "UpdateChannelRequestBodyTypedDict", + "UpdateChannelRequestTypedDict", + "UpdateChannelTranscriptionQuality", + "UpdateProjectGlobalFlaggingMode", + "UpdateProjectRequest", + "UpdateProjectRequestBody", + "UpdateProjectRequestBodyTypedDict", + "UpdateProjectRequestTypedDict", + "UpdateWebhookEventTypeRequest", + "UpdateWebhookEventTypeResponse", + "UpdateWebhookPayloadVersion", + "UpdateWebhookRequest", + "UpdateWebhookRequestBody", + "UpdateWebhookRequestBodyTypedDict", + "UpdateWebhookRequestTypedDict", + "UpdateWebhookResponse", + "UpdateWebhookResponseTypedDict", + "UpdateWordlistRequest", + "UpdateWordlistRequestBody", + "UpdateWordlistRequestBodyTypedDict", + "UpdateWordlistRequestTypedDict", + "UpdateWordlistResponse", + "UpdateWordlistResponseTypedDict", + "Value", + "Verdict", + "VideoRequest", + "VideoRequestTypedDict", + "VideoResponse", + "VideoResponseTypedDict", + "Violence", + "ViolenceTypedDict", + "Weapons", + "WeaponsTypedDict", + "WithinUnit", +] + +_dynamic_imports: dict[str, str] = { + "AddWordlistWordsRequest": ".addwordlistwordsop", + "AddWordlistWordsRequestBody": ".addwordlistwordsop", + "AddWordlistWordsRequestBodyTypedDict": ".addwordlistwordsop", + "AddWordlistWordsRequestTypedDict": ".addwordlistwordsop", + "AddWordlistWordsResponse": ".addwordlistwordsop", + "AddWordlistWordsResponseTypedDict": ".addwordlistwordsop", + "AuthenticationErrorCode": ".authenticationerror", + "BadRequestErrorCode": ".badrequesterror", + "Channel": ".channel", + "ChannelTypedDict": ".channel", + "ContentType": ".channel", + "FlaggingMode": ".channel", + "TranscriptionQuality": ".channel", + "ConflictErrorCode": ".conflicterror", + "CreateActionPositionRequest": ".createactionop", + "CreateActionPositionResponse": ".createactionop", + "CreateActionPossibleValueRequest": ".createactionop", + "CreateActionPossibleValueRequestTypedDict": ".createactionop", + "CreateActionPossibleValueResponse": ".createactionop", + "CreateActionPossibleValueResponseTypedDict": ".createactionop", + "CreateActionQueueBehaviourRequest": ".createactionop", + "CreateActionQueueBehaviourResponse": ".createactionop", + "CreateActionRequest": ".createactionop", + "CreateActionRequestTypedDict": ".createactionop", + "CreateActionResponse": ".createactionop", + "CreateActionResponseTypedDict": ".createactionop", + "CreateActionTypeRequest": ".createactionop", + "CreateActionTypeResponse": ".createactionop", + "CreateAuthorMetadata": ".createauthorop", + "CreateAuthorMetadataTypedDict": ".createauthorop", + "CreateAuthorRequest": ".createauthorop", + "CreateAuthorRequestTypedDict": ".createauthorop", + "CreateChannelContentType": ".createchannelop", + "CreateChannelRequest": ".createchannelop", + "CreateChannelRequestBody": ".createchannelop", + "CreateChannelRequestBodyTypedDict": ".createchannelop", + "CreateChannelRequestTypedDict": ".createchannelop", + "CreateProjectRequest": ".createprojectop", + "CreateProjectRequestTypedDict": ".createprojectop", + "CreateWebhookEventTypeRequest": ".createwebhookop", + "CreateWebhookEventTypeResponse": ".createwebhookop", + "CreateWebhookPayloadVersion": ".createwebhookop", + "CreateWebhookRequest": ".createwebhookop", + "CreateWebhookRequestTypedDict": ".createwebhookop", + "CreateWebhookResponse": ".createwebhookop", + "CreateWebhookResponseTypedDict": ".createwebhookop", + "DeleteActionRequest": ".deleteactionop", + "DeleteActionRequestTypedDict": ".deleteactionop", + "DeleteActionResponse": ".deleteactionop", + "DeleteActionResponseTypedDict": ".deleteactionop", + "DeleteAuthorRequest": ".deleteauthorop", + "DeleteAuthorRequestTypedDict": ".deleteauthorop", + "DeleteAuthorResponse": ".deleteauthorop", + "DeleteAuthorResponseTypedDict": ".deleteauthorop", + "DeleteChannelRequest": ".deletechannelop", + "DeleteChannelRequestTypedDict": ".deletechannelop", + "DeleteChannelResponse": ".deletechannelop", + "DeleteChannelResponseTypedDict": ".deletechannelop", + "DeleteProjectRequest": ".deleteprojectop", + "DeleteProjectRequestTypedDict": ".deleteprojectop", + "DeleteProjectResponse": ".deleteprojectop", + "DeleteProjectResponseTypedDict": ".deleteprojectop", + "DeleteWebhookRequest": ".deletewebhookop", + "DeleteWebhookRequestTypedDict": ".deletewebhookop", + "DeleteWebhookResponse": ".deletewebhookop", + "DeleteWebhookResponseTypedDict": ".deletewebhookop", + "DuplicateChannelRequest": ".duplicatechannelop", + "DuplicateChannelRequestBody": ".duplicatechannelop", + "DuplicateChannelRequestBodyTypedDict": ".duplicatechannelop", + "DuplicateChannelRequestTypedDict": ".duplicatechannelop", + "DuplicateProjectRequest": ".duplicateprojectop", + "DuplicateProjectRequestBody": ".duplicateprojectop", + "DuplicateProjectRequestBodyTypedDict": ".duplicateprojectop", + "DuplicateProjectRequestTypedDict": ".duplicateprojectop", + "ErrorIssue": ".errorissue", + "ErrorIssueTypedDict": ".errorissue", + "Path": ".errorissue", + "PathTypedDict": ".errorissue", + "ExecuteActionRequest": ".executeactionop", + "ExecuteActionRequestTypedDict": ".executeactionop", + "ExecuteActionResponse": ".executeactionop", + "ExecuteActionResponseTypedDict": ".executeactionop", + "CurrentProject": ".getaccountop", + "CurrentProjectTypedDict": ".getaccountop", + "GetAccountResponse": ".getaccountop", + "GetAccountResponseTypedDict": ".getaccountop", + "GetActionPosition": ".getactionop", + "GetActionPossibleValue": ".getactionop", + "GetActionPossibleValueTypedDict": ".getactionop", + "GetActionQueueBehaviour": ".getactionop", + "GetActionRequest": ".getactionop", + "GetActionRequestTypedDict": ".getactionop", + "GetActionResponse": ".getactionop", + "GetActionResponseTypedDict": ".getactionop", + "GetActionType": ".getactionop", + "GetAuthorRequest": ".getauthorop", + "GetAuthorRequestTypedDict": ".getauthorop", + "GetChannelRequest": ".getchannelop", + "GetChannelRequestTypedDict": ".getchannelop", + "GetProjectRequest": ".getprojectop", + "GetProjectRequestTypedDict": ".getprojectop", + "CasebookAgreement": ".getqueueop", + "CasebookAnswer": ".getqueueop", + "CheckStatus": ".getqueueop", + "Filter": ".getqueueop", + "FilterLabel": ".getqueueop", + "FilterLabelTypedDict": ".getqueueop", + "FilterTypedDict": ".getqueueop", + "GetQueueContentType": ".getqueueop", + "GetQueueQueue": ".getqueueop", + "GetQueueQueueTypedDict": ".getqueueop", + "GetQueueRecommendationAction": ".getqueueop", + "GetQueueRequest": ".getqueueop", + "GetQueueRequestTypedDict": ".getqueueop", + "GetQueueResponse": ".getqueueop", + "GetQueueResponseTypedDict": ".getqueueop", + "GetQueueType": ".getqueueop", + "IsFlagged": ".getqueueop", + "MediaType": ".getqueueop", + "MetadataFilter": ".getqueueop", + "MetadataFilterTypedDict": ".getqueueop", + "WithinUnit": ".getqueueop", + "ActionStat": ".getqueuestatsop", + "ActionStatTypedDict": ".getqueuestatsop", + "DailyReviewCount": ".getqueuestatsop", + "DailyReviewCountTypedDict": ".getqueuestatsop", + "FlaggedContentTrend": ".getqueuestatsop", + "FlaggedContentTrendTypedDict": ".getqueuestatsop", + "GetQueueStatsRequest": ".getqueuestatsop", + "GetQueueStatsRequestTypedDict": ".getqueuestatsop", + "GetQueueStatsResponse": ".getqueuestatsop", + "GetQueueStatsResponseTypedDict": ".getqueuestatsop", + "ReviewStats": ".getqueuestatsop", + "ReviewStatsTypedDict": ".getqueuestatsop", + "TopAction": ".getqueuestatsop", + "TopActionTypedDict": ".getqueuestatsop", + "TopReviewer": ".getqueuestatsop", + "TopReviewerTypedDict": ".getqueuestatsop", + "Trends": ".getqueuestatsop", + "TrendsTypedDict": ".getqueuestatsop", + "GetWebhookEventType": ".getwebhookop", + "GetWebhookPayloadVersion": ".getwebhookop", + "GetWebhookRequest": ".getwebhookop", + "GetWebhookRequestTypedDict": ".getwebhookop", + "GetWebhookResponse": ".getwebhookop", + "GetWebhookResponseTypedDict": ".getwebhookop", + "GetWebhookSecretResponse": ".getwebhooksecretop", + "GetWebhookSecretResponseTypedDict": ".getwebhooksecretop", + "GetWordlistEmbeddingStatusRequest": ".getwordlistembeddingstatusop", + "GetWordlistEmbeddingStatusRequestTypedDict": ".getwordlistembeddingstatusop", + "GetWordlistEmbeddingStatusResponse": ".getwordlistembeddingstatusop", + "GetWordlistEmbeddingStatusResponseTypedDict": ".getwordlistembeddingstatusop", + "GetWordlistRequest": ".getwordlistop", + "GetWordlistRequestTypedDict": ".getwordlistop", + "GetWordlistResponse": ".getwordlistop", + "GetWordlistResponseTypedDict": ".getwordlistop", + "InternalServerErrorCode": ".internalservererror", + "ListActionsData": ".listactionsop", + "ListActionsDataTypedDict": ".listactionsop", + "ListActionsPosition": ".listactionsop", + "ListActionsPossibleValue": ".listactionsop", + "ListActionsPossibleValueTypedDict": ".listactionsop", + "ListActionsQueueBehaviour": ".listactionsop", + "ListActionsRequest": ".listactionsop", + "ListActionsRequestTypedDict": ".listactionsop", + "ListActionsResponse": ".listactionsop", + "ListActionsResponseBody": ".listactionsop", + "ListActionsResponseBodyTypedDict": ".listactionsop", + "ListActionsResponseTypedDict": ".listactionsop", + "ListActionsType": ".listactionsop", + "ListAuthorsRequest": ".listauthorsop", + "ListAuthorsRequestTypedDict": ".listauthorsop", + "ListAuthorsResponse": ".listauthorsop", + "ListAuthorsResponseBody": ".listauthorsop", + "ListAuthorsResponseBodyTypedDict": ".listauthorsop", + "ListAuthorsResponseTypedDict": ".listauthorsop", + "ListAuthorsSortDirection": ".listauthorsop", + "SortBy": ".listauthorsop", + "ListChannelsRequest": ".listchannelsop", + "ListChannelsRequestTypedDict": ".listchannelsop", + "ListChannelsResponse": ".listchannelsop", + "ListChannelsResponseBody": ".listchannelsop", + "ListChannelsResponseBodyTypedDict": ".listchannelsop", + "ListChannelsResponseTypedDict": ".listchannelsop", + "ListProjectsRequest": ".listprojectsop", + "ListProjectsRequestTypedDict": ".listprojectsop", + "ListProjectsResponse": ".listprojectsop", + "ListProjectsResponseBody": ".listprojectsop", + "ListProjectsResponseBodyTypedDict": ".listprojectsop", + "ListProjectsResponseTypedDict": ".listprojectsop", + "ListQueueItemsAction": ".listqueueitemsop", + "ListQueueItemsActionTypedDict": ".listqueueitemsop", + "ListQueueItemsData": ".listqueueitemsop", + "ListQueueItemsDataTypedDict": ".listqueueitemsop", + "ListQueueItemsLabel": ".listqueueitemsop", + "ListQueueItemsLabelTypedDict": ".listqueueitemsop", + "ListQueueItemsRequest": ".listqueueitemsop", + "ListQueueItemsRequestTypedDict": ".listqueueitemsop", + "ListQueueItemsResponse": ".listqueueitemsop", + "ListQueueItemsResponseBody": ".listqueueitemsop", + "ListQueueItemsResponseBodyTypedDict": ".listqueueitemsop", + "ListQueueItemsResponseTypedDict": ".listqueueitemsop", + "ListQueueItemsSortDirection": ".listqueueitemsop", + "ListQueueItemsStatus": ".listqueueitemsop", + "SortField": ".listqueueitemsop", + "ListWebhooksData": ".listwebhooksop", + "ListWebhooksDataTypedDict": ".listwebhooksop", + "ListWebhooksEventType": ".listwebhooksop", + "ListWebhooksPayloadVersion": ".listwebhooksop", + "ListWebhooksRequest": ".listwebhooksop", + "ListWebhooksRequestTypedDict": ".listwebhooksop", + "ListWebhooksResponse": ".listwebhooksop", + "ListWebhooksResponseBody": ".listwebhooksop", + "ListWebhooksResponseBodyTypedDict": ".listwebhooksop", + "ListWebhooksResponseTypedDict": ".listwebhooksop", + "ListWordlistsData": ".listwordlistsop", + "ListWordlistsDataTypedDict": ".listwordlistsop", + "ListWordlistsRequest": ".listwordlistsop", + "ListWordlistsRequestTypedDict": ".listwordlistsop", + "ListWordlistsResponse": ".listwordlistsop", + "ListWordlistsResponseBody": ".listwordlistsop", + "ListWordlistsResponseBodyTypedDict": ".listwordlistsop", + "ListWordlistsResponseTypedDict": ".listwordlistsop", + "NotFoundErrorCode": ".notfounderror", + "Pagination": ".pagination", + "PaginationTypedDict": ".pagination", + "PermissionDeniedErrorCode": ".permissiondeniederror", + "Project": ".project", + "ProjectGlobalFlaggingMode": ".project", + "ProjectTypedDict": ".project", + "ProjectWithKey": ".projectwithkey", + "ProjectWithKeyGlobalFlaggingMode": ".projectwithkey", + "ProjectWithKeyTypedDict": ".projectwithkey", + "Block": ".publicauthor", + "BlockTypedDict": ".publicauthor", + "Metadata": ".publicauthor", + "MetadataTypedDict": ".publicauthor", + "Metrics": ".publicauthor", + "MetricsTypedDict": ".publicauthor", + "PublicAuthor": ".publicauthor", + "PublicAuthorTypedDict": ".publicauthor", + "RiskEvaluation": ".publicauthor", + "RiskEvaluationTypedDict": ".publicauthor", + "Status": ".publicauthor", + "TrustLevel": ".publicauthor", + "TrustLevelTypedDict": ".publicauthor", + "RateLimitErrorCode": ".ratelimiterror", + "RemoveWordlistWordsRequest": ".removewordlistwordsop", + "RemoveWordlistWordsRequestTypedDict": ".removewordlistwordsop", + "RemoveWordlistWordsResponse": ".removewordlistwordsop", + "RemoveWordlistWordsResponseTypedDict": ".removewordlistwordsop", + "ResolveQueueItemRequest": ".resolvequeueitemop", + "ResolveQueueItemRequestBody": ".resolvequeueitemop", + "ResolveQueueItemRequestBodyTypedDict": ".resolvequeueitemop", + "ResolveQueueItemRequestTypedDict": ".resolvequeueitemop", + "ResolveQueueItemResponse": ".resolvequeueitemop", + "ResolveQueueItemResponseTypedDict": ".resolvequeueitemop", + "Security": ".security", + "SecurityTypedDict": ".security", + "ActionRequest": ".submitcontentop", + "Adult": ".submitcontentop", + "AdultTypedDict": ".submitcontentop", + "Alcohol": ".submitcontentop", + "AlcoholTypedDict": ".submitcontentop", + "AudioRequest": ".submitcontentop", + "AudioRequestTypedDict": ".submitcontentop", + "AudioResponse": ".submitcontentop", + "AudioResponseTypedDict": ".submitcontentop", + "Author": ".submitcontentop", + "AuthorStatus": ".submitcontentop", + "AuthorTypedDict": ".submitcontentop", + "Cannabis": ".submitcontentop", + "CannabisTypedDict": ".submitcontentop", + "Casebook": ".submitcontentop", + "CasebookTypedDict": ".submitcontentop", + "ChannelCircumvention": ".submitcontentop", + "ChannelCircumventionTypedDict": ".submitcontentop", + "ClassifierOutput": ".submitcontentop", + "ClassifierOutputTypedDict": ".submitcontentop", + "CodeAbuse": ".submitcontentop", + "CodeAbuseTypedDict": ".submitcontentop", + "Comparator": ".submitcontentop", + "CompassionInsight": ".submitcontentop", + "CompassionInsightTypedDict": ".submitcontentop", + "ContentLeafRequest": ".submitcontentop", + "ContentLeafRequestTypedDict": ".submitcontentop", + "ContentLeafResponse": ".submitcontentop", + "ContentLeafResponseTypedDict": ".submitcontentop", + "ContentRequest": ".submitcontentop", + "ContentRequestTypedDict": ".submitcontentop", + "ContentResponse": ".submitcontentop", + "ContentResponseTypedDict": ".submitcontentop", + "Crypto": ".submitcontentop", + "CryptoTypedDict": ".submitcontentop", + "CuriosityInsight": ".submitcontentop", + "CuriosityInsightTypedDict": ".submitcontentop", + "DataAudioRequest": ".submitcontentop", + "DataAudioRequestTypedDict": ".submitcontentop", + "DataImageRequest": ".submitcontentop", + "DataImageRequestTypedDict": ".submitcontentop", + "DataTextRequest": ".submitcontentop", + "DataTextRequestTypedDict": ".submitcontentop", + "DataVideoRequest": ".submitcontentop", + "DataVideoRequestTypedDict": ".submitcontentop", + "Drugs": ".submitcontentop", + "DrugsTypedDict": ".submitcontentop", + "Entities1": ".submitcontentop", + "Entities1TypedDict": ".submitcontentop", + "Entities2": ".submitcontentop", + "Entities2TypedDict": ".submitcontentop", + "EntityMatcherOutput": ".submitcontentop", + "EntityMatcherOutputTypedDict": ".submitcontentop", + "Error": ".submitcontentop", + "ErrorTypedDict": ".submitcontentop", + "Evaluation": ".submitcontentop", + "EvaluationTypedDict": ".submitcontentop", + "Extremism": ".submitcontentop", + "ExtremismTypedDict": ".submitcontentop", + "FaceDetection": ".submitcontentop", + "FaceDetectionTypedDict": ".submitcontentop", + "Gambling": ".submitcontentop", + "GamblingTypedDict": ".submitcontentop", + "Guideline": ".submitcontentop", + "GuidelineTypedDict": ".submitcontentop", + "Hate": ".submitcontentop", + "HateTypedDict": ".submitcontentop", + "IllegalTrade": ".submitcontentop", + "IllegalTradeTypedDict": ".submitcontentop", + "Illicit": ".submitcontentop", + "IllicitAlcohol": ".submitcontentop", + "IllicitAlcoholTypedDict": ".submitcontentop", + "IllicitDrugs": ".submitcontentop", + "IllicitDrugsTypedDict": ".submitcontentop", + "IllicitFirearms": ".submitcontentop", + "IllicitFirearmsTypedDict": ".submitcontentop", + "IllicitGambling": ".submitcontentop", + "IllicitGamblingTypedDict": ".submitcontentop", + "IllicitTobacco": ".submitcontentop", + "IllicitTobaccoTypedDict": ".submitcontentop", + "IllicitTypedDict": ".submitcontentop", + "ImageRequest": ".submitcontentop", + "ImageRequestTypedDict": ".submitcontentop", + "ImageResponse": ".submitcontentop", + "ImageResponseTypedDict": ".submitcontentop", + "Insight": ".submitcontentop", + "InsightTypedDict": ".submitcontentop", + "LanguageInsight": ".submitcontentop", + "LanguageInsightTypedDict": ".submitcontentop", + "LowQualityContent": ".submitcontentop", + "LowQualityContentTypedDict": ".submitcontentop", + "MatchedRule": ".submitcontentop", + "MatchedRuleTypedDict": ".submitcontentop", + "Meta": ".submitcontentop", + "MetaStatus": ".submitcontentop", + "MetaTypedDict": ".submitcontentop", + "Method": ".submitcontentop", + "Modified": ".submitcontentop", + "ModifiedTypedDict": ".submitcontentop", + "PIIMasking": ".submitcontentop", + "PIIMaskingTypedDict": ".submitcontentop", + "PersonalInformation": ".submitcontentop", + "PersonalInformationTypedDict": ".submitcontentop", + "PersonalStoryInsight": ".submitcontentop", + "PersonalStoryInsightTypedDict": ".submitcontentop", + "PolicyRequest": ".submitcontentop", + "PolicyRequestTypedDict": ".submitcontentop", + "PolicyResponse": ".submitcontentop", + "PolicyResponseTypedDict": ".submitcontentop", + "Political": ".submitcontentop", + "PoliticalTypedDict": ".submitcontentop", + "Profanity": ".submitcontentop", + "ProfanityTypedDict": ".submitcontentop", + "Promotion": ".submitcontentop", + "PromotionTypedDict": ".submitcontentop", + "ReasonCode": ".submitcontentop", + "ReasonCodeEnum": ".submitcontentop", + "ReasonCodeTypedDict": ".submitcontentop", + "Recommendation": ".submitcontentop", + "RecommendationTypedDict": ".submitcontentop", + "Religion": ".submitcontentop", + "ReligionTypedDict": ".submitcontentop", + "RespectInsight": ".submitcontentop", + "RespectInsightTypedDict": ".submitcontentop", + "Scores": ".submitcontentop", + "ScoresTypedDict": ".submitcontentop", + "SelfHarm": ".submitcontentop", + "SelfHarmTypedDict": ".submitcontentop", + "SelfPromotion": ".submitcontentop", + "SelfPromotionTypedDict": ".submitcontentop", + "SentimentInsight": ".submitcontentop", + "SentimentInsightTypedDict": ".submitcontentop", + "Sexual": ".submitcontentop", + "SexualMinors": ".submitcontentop", + "SexualMinorsTypedDict": ".submitcontentop", + "SexualSolicitation": ".submitcontentop", + "SexualSolicitationTypedDict": ".submitcontentop", + "SexualTypedDict": ".submitcontentop", + "SignalsRequest": ".submitcontentop", + "SignalsRequestTypedDict": ".submitcontentop", + "SignalsResponse": ".submitcontentop", + "SignalsResponseTypedDict": ".submitcontentop", + "Spam": ".submitcontentop", + "SpamTypedDict": ".submitcontentop", + "SubmitContentBehavior": ".submitcontentop", + "SubmitContentBlock": ".submitcontentop", + "SubmitContentBlockTypedDict": ".submitcontentop", + "SubmitContentBrandImpersonation": ".submitcontentop", + "SubmitContentBrandImpersonationTypedDict": ".submitcontentop", + "SubmitContentClientAction": ".submitcontentop", + "SubmitContentClientActionTypedDict": ".submitcontentop", + "SubmitContentLabel": ".submitcontentop", + "SubmitContentLabelTypedDict": ".submitcontentop", + "SubmitContentMatch": ".submitcontentop", + "SubmitContentMatchTypedDict": ".submitcontentop", + "SubmitContentMetaType": ".submitcontentop", + "SubmitContentObject": ".submitcontentop", + "SubmitContentObjectTypedDict": ".submitcontentop", + "SubmitContentRecommendationAction": ".submitcontentop", + "SubmitContentRequest": ".submitcontentop", + "SubmitContentRequestTypedDict": ".submitcontentop", + "SubmitContentResponse": ".submitcontentop", + "SubmitContentResponseTypedDict": ".submitcontentop", + "SubmitContentTrustLevel": ".submitcontentop", + "SubmitContentTrustLevelTypedDict": ".submitcontentop", + "TextRequest": ".submitcontentop", + "TextRequestTypedDict": ".submitcontentop", + "TextResponse": ".submitcontentop", + "TextResponseTypedDict": ".submitcontentop", + "Threat": ".submitcontentop", + "ThreatTypedDict": ".submitcontentop", + "Tobacco": ".submitcontentop", + "TobaccoTypedDict": ".submitcontentop", + "Topic": ".submitcontentop", + "TopicTypedDict": ".submitcontentop", + "Toxicity": ".submitcontentop", + "ToxicityTypedDict": ".submitcontentop", + "URLMasking": ".submitcontentop", + "URLMaskingTypedDict": ".submitcontentop", + "URLRisk": ".submitcontentop", + "URLRiskTypedDict": ".submitcontentop", + "UnicodeSpoofing": ".submitcontentop", + "UnicodeSpoofingTypedDict": ".submitcontentop", + "UnknownContentLeafResponse": ".submitcontentop", + "UnknownInsight": ".submitcontentop", + "UnknownPolicyResponse": ".submitcontentop", + "Value": ".submitcontentop", + "Verdict": ".submitcontentop", + "VideoRequest": ".submitcontentop", + "VideoRequestTypedDict": ".submitcontentop", + "VideoResponse": ".submitcontentop", + "VideoResponseTypedDict": ".submitcontentop", + "Violence": ".submitcontentop", + "ViolenceTypedDict": ".submitcontentop", + "Weapons": ".submitcontentop", + "WeaponsTypedDict": ".submitcontentop", + "UnresolveQueueItemRequest": ".unresolvequeueitemop", + "UnresolveQueueItemRequestBody": ".unresolvequeueitemop", + "UnresolveQueueItemRequestBodyTypedDict": ".unresolvequeueitemop", + "UnresolveQueueItemRequestTypedDict": ".unresolvequeueitemop", + "UnresolveQueueItemResponse": ".unresolvequeueitemop", + "UnresolveQueueItemResponseTypedDict": ".unresolvequeueitemop", + "UpdateActionPositionRequest": ".updateactionop", + "UpdateActionPositionResponse": ".updateactionop", + "UpdateActionPossibleValueRequest": ".updateactionop", + "UpdateActionPossibleValueRequestTypedDict": ".updateactionop", + "UpdateActionPossibleValueResponse": ".updateactionop", + "UpdateActionPossibleValueResponseTypedDict": ".updateactionop", + "UpdateActionQueueBehaviourRequest": ".updateactionop", + "UpdateActionQueueBehaviourResponse": ".updateactionop", + "UpdateActionRequest": ".updateactionop", + "UpdateActionRequestBody": ".updateactionop", + "UpdateActionRequestBodyTypedDict": ".updateactionop", + "UpdateActionRequestTypedDict": ".updateactionop", + "UpdateActionResponse": ".updateactionop", + "UpdateActionResponseTypedDict": ".updateactionop", + "UpdateActionTypeRequest": ".updateactionop", + "UpdateActionTypeResponse": ".updateactionop", + "UpdateAuthorMetadata": ".updateauthorop", + "UpdateAuthorMetadataTypedDict": ".updateauthorop", + "UpdateAuthorRequest": ".updateauthorop", + "UpdateAuthorRequestBody": ".updateauthorop", + "UpdateAuthorRequestBodyTypedDict": ".updateauthorop", + "UpdateAuthorRequestTypedDict": ".updateauthorop", + "UpdateChannelContentType": ".updatechannelop", + "UpdateChannelFlaggingMode": ".updatechannelop", + "UpdateChannelRequest": ".updatechannelop", + "UpdateChannelRequestBody": ".updatechannelop", + "UpdateChannelRequestBodyTypedDict": ".updatechannelop", + "UpdateChannelRequestTypedDict": ".updatechannelop", + "UpdateChannelTranscriptionQuality": ".updatechannelop", + "UpdateProjectGlobalFlaggingMode": ".updateprojectop", + "UpdateProjectRequest": ".updateprojectop", + "UpdateProjectRequestBody": ".updateprojectop", + "UpdateProjectRequestBodyTypedDict": ".updateprojectop", + "UpdateProjectRequestTypedDict": ".updateprojectop", + "UpdateWebhookEventTypeRequest": ".updatewebhookop", + "UpdateWebhookEventTypeResponse": ".updatewebhookop", + "UpdateWebhookPayloadVersion": ".updatewebhookop", + "UpdateWebhookRequest": ".updatewebhookop", + "UpdateWebhookRequestBody": ".updatewebhookop", + "UpdateWebhookRequestBodyTypedDict": ".updatewebhookop", + "UpdateWebhookRequestTypedDict": ".updatewebhookop", + "UpdateWebhookResponse": ".updatewebhookop", + "UpdateWebhookResponseTypedDict": ".updatewebhookop", + "UpdateWordlistRequest": ".updatewordlistop", + "UpdateWordlistRequestBody": ".updatewordlistop", + "UpdateWordlistRequestBodyTypedDict": ".updatewordlistop", + "UpdateWordlistRequestTypedDict": ".updatewordlistop", + "UpdateWordlistResponse": ".updatewordlistop", + "UpdateWordlistResponseTypedDict": ".updatewordlistop", +} + + +def __getattr__(attr_name: str) -> Any: + return lazy_getattr( + attr_name, package=__package__, dynamic_imports=_dynamic_imports + ) + + +def __dir__(): + return lazy_dir(dynamic_imports=_dynamic_imports) diff --git a/src/moderation_api/models/addwordlistwordsop.py b/src/moderation_api/models/addwordlistwordsop.py new file mode 100644 index 0000000..698e516 --- /dev/null +++ b/src/moderation_api/models/addwordlistwordsop.py @@ -0,0 +1,66 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel +from moderation_api.utils import FieldMetadata, PathParamMetadata, RequestMetadata +import pydantic +from typing import List +from typing_extensions import Annotated, TypedDict + + +class AddWordlistWordsRequestBodyTypedDict(TypedDict): + words: List[str] + r"""Array of words to add to the wordlist. Duplicate words will be ignored.""" + + +class AddWordlistWordsRequestBody(BaseModel): + words: List[str] + r"""Array of words to add to the wordlist. Duplicate words will be ignored.""" + + +class AddWordlistWordsRequestTypedDict(TypedDict): + id: str + r"""ID of the wordlist to add words to""" + body: AddWordlistWordsRequestBodyTypedDict + + +class AddWordlistWordsRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""ID of the wordlist to add words to""" + + body: Annotated[ + AddWordlistWordsRequestBody, + FieldMetadata(request=RequestMetadata(media_type="application/json")), + ] + + +class AddWordlistWordsResponseTypedDict(TypedDict): + r"""Words successfully added to wordlist""" + + added_count: float + r"""Number of words added""" + total_count: float + r"""Total number of words in wordlist""" + added_words: List[str] + r"""List of words that were added""" + + +class AddWordlistWordsResponse(BaseModel): + r"""Words successfully added to wordlist""" + + added_count: Annotated[float, pydantic.Field(alias="addedCount")] + r"""Number of words added""" + + total_count: Annotated[float, pydantic.Field(alias="totalCount")] + r"""Total number of words in wordlist""" + + added_words: Annotated[List[str], pydantic.Field(alias="addedWords")] + r"""List of words that were added""" + + +try: + AddWordlistWordsResponse.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/authenticationerror.py b/src/moderation_api/models/authenticationerror.py new file mode 100644 index 0000000..d5cdba4 --- /dev/null +++ b/src/moderation_api/models/authenticationerror.py @@ -0,0 +1,23 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import UnrecognizedStr +from typing import Literal, Union + + +AuthenticationErrorCode = Union[ + Literal[ + "unauthorized", + "missing_api_key", + "malformed_api_key", + "invalid_api_key", + ], + UnrecognizedStr, +] +r"""Exactly what went wrong. Stable; safe to branch on. New codes may be added. + +- `unauthorized`: Authentication failed. +- `missing_api_key`: No API key in the `Authorization` header. +- `malformed_api_key`: The `Authorization` header does not contain a valid key format. +- `invalid_api_key`: The API key does not exist or was revoked. +""" diff --git a/src/moderation_api/models/badrequesterror.py b/src/moderation_api/models/badrequesterror.py new file mode 100644 index 0000000..5149f03 --- /dev/null +++ b/src/moderation_api/models/badrequesterror.py @@ -0,0 +1,19 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import UnrecognizedStr +from typing import Literal, Union + + +BadRequestErrorCode = Union[ + Literal[ + "invalid_request", + "validation_failed", + ], + UnrecognizedStr, +] +r"""Exactly what went wrong. Stable; safe to branch on. New codes may be added. + +- `invalid_request`: A parameter is invalid. `message` says which one. +- `validation_failed`: The request body does not match the schema. `issues` lists each problem with its `path`. +""" diff --git a/src/moderation_api/models/channel.py b/src/moderation_api/models/channel.py new file mode 100644 index 0000000..1750a04 --- /dev/null +++ b/src/moderation_api/models/channel.py @@ -0,0 +1,190 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel, Nullable, UNSET_SENTINEL, UnrecognizedStr +import pydantic +from pydantic import model_serializer +from typing import Literal, Union +from typing_extensions import Annotated, TypedDict + + +ContentType = Union[ + Literal[ + "profile", + "message", + "post", + "comment", + "event", + "product", + "review", + "voice", + "other", + ], + UnrecognizedStr, +] +r"""The kind of content the channel moderates.""" + + +FlaggingMode = Union[ + Literal[ + "DRY_RUN", + "ACTIVE", + ], + UnrecognizedStr, +] +r"""`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it.""" + + +TranscriptionQuality = Union[ + Literal[ + "SPEED", + "BALANCED", + "ACCURACY", + ], + UnrecognizedStr, +] +r"""Trade-off between transcription speed and accuracy.""" + + +class ChannelTypedDict(TypedDict): + id: str + r"""The ID of the channel.""" + project_id: str + r"""The ID of the project the channel is in.""" + key: str + r"""The key you pass as `channel` when you moderate content. Unique in the project.""" + name: str + r"""The channel's name.""" + description: Nullable[str] + r"""The channel's description.""" + is_default: bool + r"""Whether requests that name no channel use this one. Each project has one default channel.""" + content_type: Nullable[ContentType] + flagging_mode: FlaggingMode + r"""`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it.""" + context_enabled: bool + r"""Whether earlier messages in the conversation are read when moderating a message.""" + enable_image_moderation: bool + r"""Whether images are moderated.""" + enable_video_moderation: bool + r"""Whether videos are moderated.""" + enable_audio_moderation: bool + r"""Whether audio is moderated.""" + enable_voice_moderation: bool + r"""Whether real-time voice streams are moderated.""" + include_transcript_in_response: bool + r"""Whether audio and video transcripts are returned in responses.""" + transcription_quality: TranscriptionQuality + r"""Trade-off between transcription speed and accuracy.""" + unicode_spoof_detection_enabled: bool + r"""Whether look-alike Unicode characters are normalized before moderation.""" + frame_capture_interval: float + r"""Seconds between frames captured from a video.""" + max_frames_per_video: int + r"""The most frames moderated per video.""" + spread_frames: bool + r"""Whether frames are spread evenly across the whole video.""" + created_at: str + r"""When the channel was created.""" + updated_at: str + r"""When the channel was last changed.""" + + +class Channel(BaseModel): + id: str + r"""The ID of the channel.""" + + project_id: Annotated[str, pydantic.Field(alias="projectId")] + r"""The ID of the project the channel is in.""" + + key: str + r"""The key you pass as `channel` when you moderate content. Unique in the project.""" + + name: str + r"""The channel's name.""" + + description: Nullable[str] + r"""The channel's description.""" + + is_default: Annotated[bool, pydantic.Field(alias="isDefault")] + r"""Whether requests that name no channel use this one. Each project has one default channel.""" + + content_type: Annotated[Nullable[ContentType], pydantic.Field(alias="contentType")] + + flagging_mode: Annotated[FlaggingMode, pydantic.Field(alias="flaggingMode")] + r"""`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it.""" + + context_enabled: Annotated[bool, pydantic.Field(alias="contextEnabled")] + r"""Whether earlier messages in the conversation are read when moderating a message.""" + + enable_image_moderation: Annotated[ + bool, pydantic.Field(alias="enableImageModeration") + ] + r"""Whether images are moderated.""" + + enable_video_moderation: Annotated[ + bool, pydantic.Field(alias="enableVideoModeration") + ] + r"""Whether videos are moderated.""" + + enable_audio_moderation: Annotated[ + bool, pydantic.Field(alias="enableAudioModeration") + ] + r"""Whether audio is moderated.""" + + enable_voice_moderation: Annotated[ + bool, pydantic.Field(alias="enableVoiceModeration") + ] + r"""Whether real-time voice streams are moderated.""" + + include_transcript_in_response: Annotated[ + bool, pydantic.Field(alias="includeTranscriptInResponse") + ] + r"""Whether audio and video transcripts are returned in responses.""" + + transcription_quality: Annotated[ + TranscriptionQuality, pydantic.Field(alias="transcriptionQuality") + ] + r"""Trade-off between transcription speed and accuracy.""" + + unicode_spoof_detection_enabled: Annotated[ + bool, pydantic.Field(alias="unicodeSpoofDetectionEnabled") + ] + r"""Whether look-alike Unicode characters are normalized before moderation.""" + + frame_capture_interval: Annotated[ + float, pydantic.Field(alias="frameCaptureInterval") + ] + r"""Seconds between frames captured from a video.""" + + max_frames_per_video: Annotated[int, pydantic.Field(alias="maxFramesPerVideo")] + r"""The most frames moderated per video.""" + + spread_frames: Annotated[bool, pydantic.Field(alias="spreadFrames")] + r"""Whether frames are spread evenly across the whole video.""" + + created_at: Annotated[str, pydantic.Field(alias="createdAt")] + r"""When the channel was created.""" + + updated_at: Annotated[str, pydantic.Field(alias="updatedAt")] + r"""When the channel was last changed.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + m[k] = val + + return m + + +try: + Channel.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/conflicterror.py b/src/moderation_api/models/conflicterror.py new file mode 100644 index 0000000..edc2e73 --- /dev/null +++ b/src/moderation_api/models/conflicterror.py @@ -0,0 +1,12 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import UnrecognizedStr +from typing import Literal, Union + + +ConflictErrorCode = Union[Literal["resource_conflict",], UnrecognizedStr] +r"""Exactly what went wrong. Stable; safe to branch on. New codes may be added. + +- `resource_conflict`: The request conflicts with an existing resource. +""" diff --git a/src/moderation_api/models/createactionop.py b/src/moderation_api/models/createactionop.py new file mode 100644 index 0000000..5a0ed27 --- /dev/null +++ b/src/moderation_api/models/createactionop.py @@ -0,0 +1,339 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import ( + BaseModel, + Nullable, + OptionalNullable, + UNSET, + UNSET_SENTINEL, + UnrecognizedStr, +) +import pydantic +from pydantic import model_serializer +from typing import List, Literal, Optional, Union +from typing_extensions import Annotated, NotRequired, TypedDict + + +CreateActionTypeRequest = Literal[ + "AUTHOR_BLOCK", + "AUTHOR_BLOCK_TEMP", + "AUTHOR_UNBLOCK", + "AUTHOR_DELETE", + "AUTHOR_REPORT", + "AUTHOR_WARN", + "AUTHOR_CUSTOM", + "ITEM_REJECT", + "ITEM_ALLOW", + "ITEM_CUSTOM", +] + + +CreateActionQueueBehaviourRequest = Literal[ + "REMOVE", + "ADD", + "NO_CHANGE", +] +r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + + +CreateActionPositionRequest = Literal[ + "ALL_QUEUES", + "SOME_QUEUES", + "HIDDEN", +] +r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + + +class CreateActionPossibleValueRequestTypedDict(TypedDict): + value: str + r"""The value of the action.""" + + +class CreateActionPossibleValueRequest(BaseModel): + value: str + r"""The value of the action.""" + + +class CreateActionRequestTypedDict(TypedDict): + name: str + r"""The name of the action.""" + key: NotRequired[Nullable[str]] + r"""User defined key of the action.""" + description: NotRequired[Nullable[str]] + r"""The description of the action.""" + type: NotRequired[Nullable[CreateActionTypeRequest]] + r"""The type of the action.""" + built_in: NotRequired[Nullable[bool]] + r"""Whether the action is a built-in action or a custom one.""" + queue_behaviour: NotRequired[CreateActionQueueBehaviourRequest] + r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + filter_in_queue_ids: NotRequired[List[str]] + r"""The IDs of the queues the action is available in.""" + position: NotRequired[CreateActionPositionRequest] + r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + possible_values: NotRequired[List[CreateActionPossibleValueRequestTypedDict]] + r"""The possible values of the action. The user will be prompted to select one of these values when executing the action.""" + value_required: NotRequired[bool] + r"""Whether the action requires a value to be executed.""" + free_text: NotRequired[bool] + r"""Whether the action allows any text to be entered as a value or if it must be one of the possible values.""" + + +class CreateActionRequest(BaseModel): + name: str + r"""The name of the action.""" + + key: OptionalNullable[str] = UNSET + r"""User defined key of the action.""" + + description: OptionalNullable[str] = UNSET + r"""The description of the action.""" + + type: OptionalNullable[CreateActionTypeRequest] = UNSET + r"""The type of the action.""" + + built_in: Annotated[OptionalNullable[bool], pydantic.Field(alias="builtIn")] = False + r"""Whether the action is a built-in action or a custom one.""" + + queue_behaviour: Annotated[ + Optional[CreateActionQueueBehaviourRequest], + pydantic.Field(alias="queueBehaviour"), + ] = "NO_CHANGE" + r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + + filter_in_queue_ids: Annotated[ + Optional[List[str]], pydantic.Field(alias="filterInQueueIds") + ] = None + r"""The IDs of the queues the action is available in.""" + + position: Optional[CreateActionPositionRequest] = "ALL_QUEUES" + r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + + possible_values: Annotated[ + Optional[List[CreateActionPossibleValueRequest]], + pydantic.Field(alias="possibleValues"), + ] = None + r"""The possible values of the action. The user will be prompted to select one of these values when executing the action.""" + + value_required: Annotated[Optional[bool], pydantic.Field(alias="valueRequired")] = ( + False + ) + r"""Whether the action requires a value to be executed.""" + + free_text: Annotated[Optional[bool], pydantic.Field(alias="freeText")] = False + r"""Whether the action allows any text to be entered as a value or if it must be one of the possible values.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "key", + "description", + "type", + "builtIn", + "queueBehaviour", + "filterInQueueIds", + "position", + "possibleValues", + "valueRequired", + "freeText", + ] + ) + nullable_fields = set(["key", "description", "type", "builtIn"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +CreateActionTypeResponse = Union[ + Literal[ + "AUTHOR_BLOCK", + "AUTHOR_BLOCK_TEMP", + "AUTHOR_UNBLOCK", + "AUTHOR_DELETE", + "AUTHOR_REPORT", + "AUTHOR_WARN", + "AUTHOR_CUSTOM", + "ITEM_REJECT", + "ITEM_ALLOW", + "ITEM_CUSTOM", + ], + UnrecognizedStr, +] + + +CreateActionQueueBehaviourResponse = Union[ + Literal[ + "REMOVE", + "ADD", + "NO_CHANGE", + ], + UnrecognizedStr, +] +r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + + +CreateActionPositionResponse = Union[ + Literal[ + "ALL_QUEUES", + "SOME_QUEUES", + "HIDDEN", + ], + UnrecognizedStr, +] +r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + + +class CreateActionPossibleValueResponseTypedDict(TypedDict): + value: str + r"""The value of the action.""" + + +class CreateActionPossibleValueResponse(BaseModel): + value: str + r"""The value of the action.""" + + +class CreateActionResponseTypedDict(TypedDict): + r"""Action created successfully""" + + id: str + r"""The ID of the action.""" + created_at: str + r"""The date the action was created.""" + name: str + r"""The name of the action.""" + filter_in_queue_ids: List[str] + r"""The IDs of the queues the action is available in.""" + possible_values: List[CreateActionPossibleValueResponseTypedDict] + r"""The possible values of the action. The user will be prompted to select one of these values when executing the action.""" + key: NotRequired[Nullable[str]] + r"""User defined key of the action.""" + description: NotRequired[Nullable[str]] + r"""The description of the action.""" + type: NotRequired[Nullable[CreateActionTypeResponse]] + r"""The type of the action.""" + built_in: NotRequired[Nullable[bool]] + r"""Whether the action is a built-in action or a custom one.""" + queue_behaviour: NotRequired[CreateActionQueueBehaviourResponse] + r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + position: NotRequired[CreateActionPositionResponse] + r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + value_required: NotRequired[bool] + r"""Whether the action requires a value to be executed.""" + free_text: NotRequired[bool] + r"""Whether the action allows any text to be entered as a value or if it must be one of the possible values.""" + + +class CreateActionResponse(BaseModel): + r"""Action created successfully""" + + id: str + r"""The ID of the action.""" + + created_at: Annotated[str, pydantic.Field(alias="createdAt")] + r"""The date the action was created.""" + + name: str + r"""The name of the action.""" + + filter_in_queue_ids: Annotated[List[str], pydantic.Field(alias="filterInQueueIds")] + r"""The IDs of the queues the action is available in.""" + + possible_values: Annotated[ + List[CreateActionPossibleValueResponse], pydantic.Field(alias="possibleValues") + ] + r"""The possible values of the action. The user will be prompted to select one of these values when executing the action.""" + + key: OptionalNullable[str] = UNSET + r"""User defined key of the action.""" + + description: OptionalNullable[str] = UNSET + r"""The description of the action.""" + + type: OptionalNullable[CreateActionTypeResponse] = UNSET + r"""The type of the action.""" + + built_in: Annotated[OptionalNullable[bool], pydantic.Field(alias="builtIn")] = False + r"""Whether the action is a built-in action or a custom one.""" + + queue_behaviour: Annotated[ + Optional[CreateActionQueueBehaviourResponse], + pydantic.Field(alias="queueBehaviour"), + ] = "NO_CHANGE" + r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + + position: Optional[CreateActionPositionResponse] = "ALL_QUEUES" + r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + + value_required: Annotated[Optional[bool], pydantic.Field(alias="valueRequired")] = ( + False + ) + r"""Whether the action requires a value to be executed.""" + + free_text: Annotated[Optional[bool], pydantic.Field(alias="freeText")] = False + r"""Whether the action allows any text to be entered as a value or if it must be one of the possible values.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "key", + "description", + "type", + "builtIn", + "queueBehaviour", + "position", + "valueRequired", + "freeText", + ] + ) + nullable_fields = set(["key", "description", "type", "builtIn"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +try: + CreateActionRequest.model_rebuild() +except NameError: + pass +try: + CreateActionResponse.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/createauthorop.py b/src/moderation_api/models/createauthorop.py new file mode 100644 index 0000000..df2f2e9 --- /dev/null +++ b/src/moderation_api/models/createauthorop.py @@ -0,0 +1,197 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import ( + BaseModel, + Nullable, + OptionalNullable, + UNSET, + UNSET_SENTINEL, +) +import pydantic +from pydantic import ConfigDict, model_serializer +from typing import Any, Dict, Optional +from typing_extensions import NotRequired, TypedDict + + +class CreateAuthorMetadataTypedDict(TypedDict): + r"""Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.""" + + email_verified: NotRequired[Nullable[bool]] + r"""Whether the author's email is verified""" + phone_verified: NotRequired[Nullable[bool]] + r"""Whether the author's phone number is verified""" + identity_verified: NotRequired[Nullable[bool]] + r"""Whether the author's identity is verified""" + is_paying_customer: NotRequired[Nullable[bool]] + r"""Whether the author is a paying customer""" + + +class CreateAuthorMetadata(BaseModel): + r"""Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.""" + + model_config = ConfigDict( + populate_by_name=True, arbitrary_types_allowed=True, extra="allow" + ) + __pydantic_extra__: Dict[str, Any] = pydantic.Field(init=False) + + email_verified: OptionalNullable[bool] = UNSET + r"""Whether the author's email is verified""" + + phone_verified: OptionalNullable[bool] = UNSET + r"""Whether the author's phone number is verified""" + + identity_verified: OptionalNullable[bool] = UNSET + r"""Whether the author's identity is verified""" + + is_paying_customer: OptionalNullable[bool] = UNSET + r"""Whether the author is a paying customer""" + + @property + def additional_properties(self): + return self.__pydantic_extra__ + + @additional_properties.setter + def additional_properties(self, value): + self.__pydantic_extra__ = value # pyright: ignore[reportIncompatibleVariableOverride] + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "email_verified", + "phone_verified", + "identity_verified", + "is_paying_customer", + ] + ) + nullable_fields = set( + [ + "email_verified", + "phone_verified", + "identity_verified", + "is_paying_customer", + ] + ) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + serialized.pop(k, serialized.pop(n, None)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + for k, v in serialized.items(): + m[k] = v + + return m + + +class CreateAuthorRequestTypedDict(TypedDict): + external_id: str + r"""External ID of the user, typically the ID of the author in your database.""" + profile_picture: NotRequired[Nullable[str]] + r"""URL of the author's profile picture""" + external_link: NotRequired[Nullable[str]] + r"""URL of the author's external profile""" + name: NotRequired[Nullable[str]] + r"""Author name or identifier""" + email: NotRequired[Nullable[str]] + r"""Author email address""" + company: NotRequired[Nullable[str]] + r"""The author's company or organization""" + metadata: NotRequired[CreateAuthorMetadataTypedDict] + r"""Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.""" + first_seen: NotRequired[float] + r"""Timestamp when author first appeared""" + last_seen: NotRequired[float] + r"""Timestamp of last activity""" + manual_trust_level: NotRequired[Nullable[float]] + + +class CreateAuthorRequest(BaseModel): + external_id: str + r"""External ID of the user, typically the ID of the author in your database.""" + + profile_picture: OptionalNullable[str] = UNSET + r"""URL of the author's profile picture""" + + external_link: OptionalNullable[str] = UNSET + r"""URL of the author's external profile""" + + name: OptionalNullable[str] = UNSET + r"""Author name or identifier""" + + email: OptionalNullable[str] = UNSET + r"""Author email address""" + + company: OptionalNullable[str] = UNSET + r"""The author's company or organization""" + + metadata: Optional[CreateAuthorMetadata] = None + r"""Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.""" + + first_seen: Optional[float] = None + r"""Timestamp when author first appeared""" + + last_seen: Optional[float] = None + r"""Timestamp of last activity""" + + manual_trust_level: OptionalNullable[float] = UNSET + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "profile_picture", + "external_link", + "name", + "email", + "company", + "metadata", + "first_seen", + "last_seen", + "manual_trust_level", + ] + ) + nullable_fields = set( + [ + "profile_picture", + "external_link", + "name", + "email", + "company", + "manual_trust_level", + ] + ) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m diff --git a/src/moderation_api/models/createchannelop.py b/src/moderation_api/models/createchannelop.py new file mode 100644 index 0000000..e00b322 --- /dev/null +++ b/src/moderation_api/models/createchannelop.py @@ -0,0 +1,92 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel, UNSET_SENTINEL +from moderation_api.utils import FieldMetadata, PathParamMetadata, RequestMetadata +import pydantic +from pydantic import model_serializer +from typing import Literal, Optional +from typing_extensions import Annotated, NotRequired, TypedDict + + +CreateChannelContentType = Literal[ + "profile", + "message", + "post", + "comment", + "event", + "product", + "review", + "voice", + "other", +] +r"""The kind of content the channel moderates.""" + + +class CreateChannelRequestBodyTypedDict(TypedDict): + name: str + r"""The channel's name.""" + key: NotRequired[str] + r"""The key you pass as `channel` when you moderate content. Defaults to the name in lowercase with dashes. Must be unique in the project.""" + description: NotRequired[str] + r"""The channel's description.""" + content_type: NotRequired[CreateChannelContentType] + r"""The kind of content the channel moderates.""" + + +class CreateChannelRequestBody(BaseModel): + name: str + r"""The channel's name.""" + + key: Optional[str] = None + r"""The key you pass as `channel` when you moderate content. Defaults to the name in lowercase with dashes. Must be unique in the project.""" + + description: Optional[str] = None + r"""The channel's description.""" + + content_type: Annotated[ + Optional[CreateChannelContentType], pydantic.Field(alias="contentType") + ] = None + r"""The kind of content the channel moderates.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["key", "description", "contentType"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class CreateChannelRequestTypedDict(TypedDict): + project_id: str + r"""The ID of the project to add the channel to.""" + body: CreateChannelRequestBodyTypedDict + + +class CreateChannelRequest(BaseModel): + project_id: Annotated[ + str, + pydantic.Field(alias="projectId"), + FieldMetadata(path=PathParamMetadata(style="simple", explode=False)), + ] + r"""The ID of the project to add the channel to.""" + + body: Annotated[ + CreateChannelRequestBody, + FieldMetadata(request=RequestMetadata(media_type="application/json")), + ] + + +try: + CreateChannelRequestBody.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/createprojectop.py b/src/moderation_api/models/createprojectop.py new file mode 100644 index 0000000..2024236 --- /dev/null +++ b/src/moderation_api/models/createprojectop.py @@ -0,0 +1,62 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel, UNSET_SENTINEL +import pydantic +from pydantic import model_serializer +from typing import Optional +from typing_extensions import Annotated, NotRequired, TypedDict + + +class CreateProjectRequestTypedDict(TypedDict): + name: str + r"""The project's name.""" + domain: NotRequired[str] + r"""The website or app the project moderates content for. Used to write the context when none is given.""" + context: NotRequired[str] + r"""What the platform is and who uses it.""" + brief: NotRequired[str] + r"""What you moderate, the problems you see and your guidelines.""" + used_by_minors: NotRequired[bool] + r"""Whether children use the platform.""" + + +class CreateProjectRequest(BaseModel): + name: str + r"""The project's name.""" + + domain: Optional[str] = None + r"""The website or app the project moderates content for. Used to write the context when none is given.""" + + context: Optional[str] = None + r"""What the platform is and who uses it.""" + + brief: Optional[str] = None + r"""What you moderate, the problems you see and your guidelines.""" + + used_by_minors: Annotated[Optional[bool], pydantic.Field(alias="usedByMinors")] = ( + False + ) + r"""Whether children use the platform.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["domain", "context", "brief", "usedByMinors"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +try: + CreateProjectRequest.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/createwebhookop.py b/src/moderation_api/models/createwebhookop.py new file mode 100644 index 0000000..63b95fd --- /dev/null +++ b/src/moderation_api/models/createwebhookop.py @@ -0,0 +1,193 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import ( + BaseModel, + Nullable, + OptionalNullable, + UNSET, + UNSET_SENTINEL, + UnrecognizedStr, +) +import pydantic +from pydantic import model_serializer +from typing import List, Literal, Union +from typing_extensions import Annotated, NotRequired, TypedDict + + +CreateWebhookEventTypeRequest = Literal[ + "QUEUE_ITEM_NEW", + "QUEUE_ITEM_COMPLETED", + "QUEUE_ITEM_ACTION", + "QUEUE_ITEM_REJECTED", + "QUEUE_ITEM_ALLOWED", + "AUTHOR_BLOCKED", + "AUTHOR_UNBLOCKED", + "AUTHOR_SUSPENDED", + "AUTHOR_UPDATED", + "AUTHOR_TRUST_LEVEL_CHANGED", + "AUTHOR_ACTION", +] + + +class CreateWebhookRequestTypedDict(TypedDict): + name: str + r"""The webhook's name, used to identify it in the dashboard""" + url: str + r"""The webhook's URL. We'll call this URL when an event occurs.""" + event_types: List[CreateWebhookEventTypeRequest] + r"""Event types this webhook subscribes to. One webhook URL receives all events you list here.""" + description: NotRequired[Nullable[str]] + r"""The webhook's description""" + + +class CreateWebhookRequest(BaseModel): + name: str + r"""The webhook's name, used to identify it in the dashboard""" + + url: str + r"""The webhook's URL. We'll call this URL when an event occurs.""" + + event_types: Annotated[ + List[CreateWebhookEventTypeRequest], pydantic.Field(alias="eventTypes") + ] + r"""Event types this webhook subscribes to. One webhook URL receives all events you list here.""" + + description: OptionalNullable[str] = UNSET + r"""The webhook's description""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["description"]) + nullable_fields = set(["description"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +CreateWebhookEventTypeResponse = Union[ + Literal[ + "QUEUE_ITEM_NEW", + "QUEUE_ITEM_COMPLETED", + "QUEUE_ITEM_ACTION", + "QUEUE_ITEM_REJECTED", + "QUEUE_ITEM_ALLOWED", + "AUTHOR_BLOCKED", + "AUTHOR_UNBLOCKED", + "AUTHOR_SUSPENDED", + "AUTHOR_UPDATED", + "AUTHOR_TRUST_LEVEL_CHANGED", + "AUTHOR_ACTION", + ], + UnrecognizedStr, +] + + +CreateWebhookPayloadVersion = Union[ + Literal[ + "V1", + "V2", + ], + UnrecognizedStr, +] +r"""Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API.""" + + +class CreateWebhookResponseTypedDict(TypedDict): + r"""Webhook created successfully""" + + id: str + r"""The ID of the webhook.""" + name: str + r"""The webhook's name.""" + url: str + r"""The URL we call when a subscribed event occurs.""" + event_types: List[CreateWebhookEventTypeResponse] + r"""Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead.""" + payload_version: CreateWebhookPayloadVersion + r"""Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API.""" + created_at: str + r"""The date the webhook was created.""" + description: NotRequired[Nullable[str]] + r"""The webhook's description.""" + + +class CreateWebhookResponse(BaseModel): + r"""Webhook created successfully""" + + id: str + r"""The ID of the webhook.""" + + name: str + r"""The webhook's name.""" + + url: str + r"""The URL we call when a subscribed event occurs.""" + + event_types: Annotated[ + List[CreateWebhookEventTypeResponse], pydantic.Field(alias="eventTypes") + ] + r"""Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead.""" + + payload_version: Annotated[ + CreateWebhookPayloadVersion, pydantic.Field(alias="payloadVersion") + ] + r"""Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API.""" + + created_at: Annotated[str, pydantic.Field(alias="createdAt")] + r"""The date the webhook was created.""" + + description: OptionalNullable[str] = UNSET + r"""The webhook's description.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["description"]) + nullable_fields = set(["description"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +try: + CreateWebhookRequest.model_rebuild() +except NameError: + pass +try: + CreateWebhookResponse.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/deleteactionop.py b/src/moderation_api/models/deleteactionop.py new file mode 100644 index 0000000..11154e2 --- /dev/null +++ b/src/moderation_api/models/deleteactionop.py @@ -0,0 +1,37 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel +from moderation_api.utils import FieldMetadata, PathParamMetadata +from typing_extensions import Annotated, TypedDict + + +class DeleteActionRequestTypedDict(TypedDict): + id: str + r"""The ID of the action to delete.""" + + +class DeleteActionRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The ID of the action to delete.""" + + +class DeleteActionResponseTypedDict(TypedDict): + r"""Action deleted successfully""" + + deleted: bool + r"""Whether the action was deleted.""" + id: str + r"""The ID of the action.""" + + +class DeleteActionResponse(BaseModel): + r"""Action deleted successfully""" + + deleted: bool + r"""Whether the action was deleted.""" + + id: str + r"""The ID of the action.""" diff --git a/src/moderation_api/models/deleteauthorop.py b/src/moderation_api/models/deleteauthorop.py new file mode 100644 index 0000000..f7b764d --- /dev/null +++ b/src/moderation_api/models/deleteauthorop.py @@ -0,0 +1,30 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel +from moderation_api.utils import FieldMetadata, PathParamMetadata +from typing_extensions import Annotated, TypedDict + + +class DeleteAuthorRequestTypedDict(TypedDict): + id: str + r"""Either external ID or the ID assigned by moderation API.""" + + +class DeleteAuthorRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""Either external ID or the ID assigned by moderation API.""" + + +class DeleteAuthorResponseTypedDict(TypedDict): + r"""Successful response""" + + success: bool + + +class DeleteAuthorResponse(BaseModel): + r"""Successful response""" + + success: bool diff --git a/src/moderation_api/models/deletechannelop.py b/src/moderation_api/models/deletechannelop.py new file mode 100644 index 0000000..c289778 --- /dev/null +++ b/src/moderation_api/models/deletechannelop.py @@ -0,0 +1,37 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel +from moderation_api.utils import FieldMetadata, PathParamMetadata +from typing_extensions import Annotated, TypedDict + + +class DeleteChannelRequestTypedDict(TypedDict): + id: str + r"""The ID of the channel.""" + + +class DeleteChannelRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The ID of the channel.""" + + +class DeleteChannelResponseTypedDict(TypedDict): + r"""Channel deleted successfully""" + + deleted: bool + r"""Whether the channel was deleted.""" + id: str + r"""The ID of the channel.""" + + +class DeleteChannelResponse(BaseModel): + r"""Channel deleted successfully""" + + deleted: bool + r"""Whether the channel was deleted.""" + + id: str + r"""The ID of the channel.""" diff --git a/src/moderation_api/models/deleteprojectop.py b/src/moderation_api/models/deleteprojectop.py new file mode 100644 index 0000000..b558b63 --- /dev/null +++ b/src/moderation_api/models/deleteprojectop.py @@ -0,0 +1,37 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel +from moderation_api.utils import FieldMetadata, PathParamMetadata +from typing_extensions import Annotated, TypedDict + + +class DeleteProjectRequestTypedDict(TypedDict): + id: str + r"""The ID of the project.""" + + +class DeleteProjectRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The ID of the project.""" + + +class DeleteProjectResponseTypedDict(TypedDict): + r"""Project deleted successfully""" + + deleted: bool + r"""Whether the project was deleted.""" + id: str + r"""The ID of the project.""" + + +class DeleteProjectResponse(BaseModel): + r"""Project deleted successfully""" + + deleted: bool + r"""Whether the project was deleted.""" + + id: str + r"""The ID of the project.""" diff --git a/src/moderation_api/models/deletewebhookop.py b/src/moderation_api/models/deletewebhookop.py new file mode 100644 index 0000000..4fe0be3 --- /dev/null +++ b/src/moderation_api/models/deletewebhookop.py @@ -0,0 +1,37 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel +from moderation_api.utils import FieldMetadata, PathParamMetadata +from typing_extensions import Annotated, TypedDict + + +class DeleteWebhookRequestTypedDict(TypedDict): + id: str + r"""The ID of the webhook to delete.""" + + +class DeleteWebhookRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The ID of the webhook to delete.""" + + +class DeleteWebhookResponseTypedDict(TypedDict): + r"""Webhook deleted successfully""" + + deleted: bool + r"""Whether the webhook was deleted.""" + id: str + r"""The ID of the webhook.""" + + +class DeleteWebhookResponse(BaseModel): + r"""Webhook deleted successfully""" + + deleted: bool + r"""Whether the webhook was deleted.""" + + id: str + r"""The ID of the webhook.""" diff --git a/src/moderation_api/models/duplicatechannelop.py b/src/moderation_api/models/duplicatechannelop.py new file mode 100644 index 0000000..7094a05 --- /dev/null +++ b/src/moderation_api/models/duplicatechannelop.py @@ -0,0 +1,57 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel, UNSET_SENTINEL +from moderation_api.utils import FieldMetadata, PathParamMetadata, RequestMetadata +from pydantic import model_serializer +from typing import Optional +from typing_extensions import Annotated, NotRequired, TypedDict + + +class DuplicateChannelRequestBodyTypedDict(TypedDict): + name: NotRequired[str] + r"""Name of the copy. Defaults to the original name with \"(copy)\".""" + key: NotRequired[str] + r"""Key of the copy. Defaults to the original key with a timestamp. Must be unique in the project.""" + + +class DuplicateChannelRequestBody(BaseModel): + name: Optional[str] = None + r"""Name of the copy. Defaults to the original name with \"(copy)\".""" + + key: Optional[str] = None + r"""Key of the copy. Defaults to the original key with a timestamp. Must be unique in the project.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["name", "key"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class DuplicateChannelRequestTypedDict(TypedDict): + id: str + r"""The ID of the channel to copy.""" + body: DuplicateChannelRequestBodyTypedDict + + +class DuplicateChannelRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The ID of the channel to copy.""" + + body: Annotated[ + DuplicateChannelRequestBody, + FieldMetadata(request=RequestMetadata(media_type="application/json")), + ] diff --git a/src/moderation_api/models/duplicateprojectop.py b/src/moderation_api/models/duplicateprojectop.py new file mode 100644 index 0000000..28f435b --- /dev/null +++ b/src/moderation_api/models/duplicateprojectop.py @@ -0,0 +1,52 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel, UNSET_SENTINEL +from moderation_api.utils import FieldMetadata, PathParamMetadata, RequestMetadata +from pydantic import model_serializer +from typing import Optional +from typing_extensions import Annotated, NotRequired, TypedDict + + +class DuplicateProjectRequestBodyTypedDict(TypedDict): + name: NotRequired[str] + r"""Name of the copy. Defaults to the original name with \"(copy)\".""" + + +class DuplicateProjectRequestBody(BaseModel): + name: Optional[str] = None + r"""Name of the copy. Defaults to the original name with \"(copy)\".""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["name"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class DuplicateProjectRequestTypedDict(TypedDict): + id: str + r"""The ID of the project to copy.""" + body: DuplicateProjectRequestBodyTypedDict + + +class DuplicateProjectRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The ID of the project to copy.""" + + body: Annotated[ + DuplicateProjectRequestBody, + FieldMetadata(request=RequestMetadata(media_type="application/json")), + ] diff --git a/src/moderation_api/models/errorissue.py b/src/moderation_api/models/errorissue.py new file mode 100644 index 0000000..60891c7 --- /dev/null +++ b/src/moderation_api/models/errorissue.py @@ -0,0 +1,49 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel, UNSET_SENTINEL +from pydantic import model_serializer +from typing import List, Optional, Union +from typing_extensions import NotRequired, TypeAliasType, TypedDict + + +PathTypedDict = TypeAliasType("PathTypedDict", Union[str, float]) + + +Path = TypeAliasType("Path", Union[str, float]) + + +class ErrorIssueTypedDict(TypedDict): + message: str + r"""What is wrong.""" + path: NotRequired[List[PathTypedDict]] + r"""Location of the invalid field in the request body.""" + code: NotRequired[str] + r"""Validation rule that failed.""" + + +class ErrorIssue(BaseModel): + message: str + r"""What is wrong.""" + + path: Optional[List[Path]] = None + r"""Location of the invalid field in the request body.""" + + code: Optional[str] = None + r"""Validation rule that failed.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["path", "code"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m diff --git a/src/moderation_api/models/executeactionop.py b/src/moderation_api/models/executeactionop.py new file mode 100644 index 0000000..9ebd953 --- /dev/null +++ b/src/moderation_api/models/executeactionop.py @@ -0,0 +1,87 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel, UNSET_SENTINEL +import pydantic +from pydantic import model_serializer +from typing import List, Optional +from typing_extensions import Annotated, NotRequired, TypedDict + + +class ExecuteActionRequestTypedDict(TypedDict): + r"""Input parameters""" + + action_key: str + r"""ID or key of the action to execute""" + content_ids: NotRequired[List[str]] + r"""IDs of the content items to apply the action to. Provide this or authorIds.""" + author_ids: NotRequired[List[str]] + r"""IDs of the authors to apply the action to. Provide this or contentIds.""" + value: NotRequired[str] + r"""Optional value to provide with the action""" + queue_id: NotRequired[str] + r"""Optional queue ID if the action is queue-specific""" + duration: NotRequired[float] + r"""Optional duration in milliseconds for actions with timeouts""" + + +class ExecuteActionRequest(BaseModel): + r"""Input parameters""" + + action_key: Annotated[str, pydantic.Field(alias="actionKey")] + r"""ID or key of the action to execute""" + + content_ids: Annotated[Optional[List[str]], pydantic.Field(alias="contentIds")] = ( + None + ) + r"""IDs of the content items to apply the action to. Provide this or authorIds.""" + + author_ids: Annotated[Optional[List[str]], pydantic.Field(alias="authorIds")] = None + r"""IDs of the authors to apply the action to. Provide this or contentIds.""" + + value: Optional[str] = None + r"""Optional value to provide with the action""" + + queue_id: Annotated[Optional[str], pydantic.Field(alias="queueId")] = None + r"""Optional queue ID if the action is queue-specific""" + + duration: Optional[float] = None + r"""Optional duration in milliseconds for actions with timeouts""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + ["contentIds", "authorIds", "value", "queueId", "duration"] + ) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ExecuteActionResponseTypedDict(TypedDict): + r"""Execution result""" + + success: bool + r"""Whether the action was executed successfully""" + + +class ExecuteActionResponse(BaseModel): + r"""Execution result""" + + success: bool + r"""Whether the action was executed successfully""" + + +try: + ExecuteActionRequest.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/getaccountop.py b/src/moderation_api/models/getaccountop.py new file mode 100644 index 0000000..61b841a --- /dev/null +++ b/src/moderation_api/models/getaccountop.py @@ -0,0 +1,70 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel, UNSET_SENTINEL +from pydantic import model_serializer +from typing import Optional +from typing_extensions import NotRequired, TypedDict + + +class CurrentProjectTypedDict(TypedDict): + id: str + r"""ID of the current project""" + name: str + r"""Name of the current project""" + + +class CurrentProject(BaseModel): + id: str + r"""ID of the current project""" + + name: str + r"""Name of the current project""" + + +class GetAccountResponseTypedDict(TypedDict): + r"""Account details retrieved successfully""" + + id: str + r"""ID of the account""" + paid_plan_name: str + r"""Name of the paid plan""" + text_api_quota: float + r"""Text API quota""" + remaining_quota: float + r"""Remaining quota""" + current_project: NotRequired[CurrentProjectTypedDict] + + +class GetAccountResponse(BaseModel): + r"""Account details retrieved successfully""" + + id: str + r"""ID of the account""" + + paid_plan_name: str + r"""Name of the paid plan""" + + text_api_quota: float + r"""Text API quota""" + + remaining_quota: float + r"""Remaining quota""" + + current_project: Optional[CurrentProject] = None + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["current_project"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m diff --git a/src/moderation_api/models/getactionop.py b/src/moderation_api/models/getactionop.py new file mode 100644 index 0000000..8c6804f --- /dev/null +++ b/src/moderation_api/models/getactionop.py @@ -0,0 +1,199 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import ( + BaseModel, + Nullable, + OptionalNullable, + UNSET, + UNSET_SENTINEL, + UnrecognizedStr, +) +from moderation_api.utils import FieldMetadata, PathParamMetadata +import pydantic +from pydantic import model_serializer +from typing import List, Literal, Optional, Union +from typing_extensions import Annotated, NotRequired, TypedDict + + +class GetActionRequestTypedDict(TypedDict): + id: str + r"""The ID of the action to get.""" + + +class GetActionRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The ID of the action to get.""" + + +GetActionType = Union[ + Literal[ + "AUTHOR_BLOCK", + "AUTHOR_BLOCK_TEMP", + "AUTHOR_UNBLOCK", + "AUTHOR_DELETE", + "AUTHOR_REPORT", + "AUTHOR_WARN", + "AUTHOR_CUSTOM", + "ITEM_REJECT", + "ITEM_ALLOW", + "ITEM_CUSTOM", + ], + UnrecognizedStr, +] + + +GetActionQueueBehaviour = Union[ + Literal[ + "REMOVE", + "ADD", + "NO_CHANGE", + ], + UnrecognizedStr, +] +r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + + +GetActionPosition = Union[ + Literal[ + "ALL_QUEUES", + "SOME_QUEUES", + "HIDDEN", + ], + UnrecognizedStr, +] +r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + + +class GetActionPossibleValueTypedDict(TypedDict): + value: str + r"""The value of the action.""" + + +class GetActionPossibleValue(BaseModel): + value: str + r"""The value of the action.""" + + +class GetActionResponseTypedDict(TypedDict): + r"""Action retrieved successfully""" + + id: str + r"""The ID of the action.""" + created_at: str + r"""The date the action was created.""" + name: str + r"""The name of the action.""" + filter_in_queue_ids: List[str] + r"""The IDs of the queues the action is available in.""" + possible_values: List[GetActionPossibleValueTypedDict] + r"""The possible values of the action. The user will be prompted to select one of these values when executing the action.""" + key: NotRequired[Nullable[str]] + r"""User defined key of the action.""" + description: NotRequired[Nullable[str]] + r"""The description of the action.""" + type: NotRequired[Nullable[GetActionType]] + r"""The type of the action.""" + built_in: NotRequired[Nullable[bool]] + r"""Whether the action is a built-in action or a custom one.""" + queue_behaviour: NotRequired[GetActionQueueBehaviour] + r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + position: NotRequired[GetActionPosition] + r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + value_required: NotRequired[bool] + r"""Whether the action requires a value to be executed.""" + free_text: NotRequired[bool] + r"""Whether the action allows any text to be entered as a value or if it must be one of the possible values.""" + + +class GetActionResponse(BaseModel): + r"""Action retrieved successfully""" + + id: str + r"""The ID of the action.""" + + created_at: Annotated[str, pydantic.Field(alias="createdAt")] + r"""The date the action was created.""" + + name: str + r"""The name of the action.""" + + filter_in_queue_ids: Annotated[List[str], pydantic.Field(alias="filterInQueueIds")] + r"""The IDs of the queues the action is available in.""" + + possible_values: Annotated[ + List[GetActionPossibleValue], pydantic.Field(alias="possibleValues") + ] + r"""The possible values of the action. The user will be prompted to select one of these values when executing the action.""" + + key: OptionalNullable[str] = UNSET + r"""User defined key of the action.""" + + description: OptionalNullable[str] = UNSET + r"""The description of the action.""" + + type: OptionalNullable[GetActionType] = UNSET + r"""The type of the action.""" + + built_in: Annotated[OptionalNullable[bool], pydantic.Field(alias="builtIn")] = False + r"""Whether the action is a built-in action or a custom one.""" + + queue_behaviour: Annotated[ + Optional[GetActionQueueBehaviour], pydantic.Field(alias="queueBehaviour") + ] = "NO_CHANGE" + r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + + position: Optional[GetActionPosition] = "ALL_QUEUES" + r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + + value_required: Annotated[Optional[bool], pydantic.Field(alias="valueRequired")] = ( + False + ) + r"""Whether the action requires a value to be executed.""" + + free_text: Annotated[Optional[bool], pydantic.Field(alias="freeText")] = False + r"""Whether the action allows any text to be entered as a value or if it must be one of the possible values.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "key", + "description", + "type", + "builtIn", + "queueBehaviour", + "position", + "valueRequired", + "freeText", + ] + ) + nullable_fields = set(["key", "description", "type", "builtIn"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +try: + GetActionResponse.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/getauthorop.py b/src/moderation_api/models/getauthorop.py new file mode 100644 index 0000000..f336bb7 --- /dev/null +++ b/src/moderation_api/models/getauthorop.py @@ -0,0 +1,18 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel +from moderation_api.utils import FieldMetadata, PathParamMetadata +from typing_extensions import Annotated, TypedDict + + +class GetAuthorRequestTypedDict(TypedDict): + id: str + r"""Either external ID or the ID assigned by moderation API.""" + + +class GetAuthorRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""Either external ID or the ID assigned by moderation API.""" diff --git a/src/moderation_api/models/getchannelop.py b/src/moderation_api/models/getchannelop.py new file mode 100644 index 0000000..6fd4dda --- /dev/null +++ b/src/moderation_api/models/getchannelop.py @@ -0,0 +1,18 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel +from moderation_api.utils import FieldMetadata, PathParamMetadata +from typing_extensions import Annotated, TypedDict + + +class GetChannelRequestTypedDict(TypedDict): + id: str + r"""The ID of the channel.""" + + +class GetChannelRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The ID of the channel.""" diff --git a/src/moderation_api/models/getprojectop.py b/src/moderation_api/models/getprojectop.py new file mode 100644 index 0000000..9f1f31b --- /dev/null +++ b/src/moderation_api/models/getprojectop.py @@ -0,0 +1,18 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel +from moderation_api.utils import FieldMetadata, PathParamMetadata +from typing_extensions import Annotated, TypedDict + + +class GetProjectRequestTypedDict(TypedDict): + id: str + r"""The ID of the project.""" + + +class GetProjectRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The ID of the project.""" diff --git a/src/moderation_api/models/getqueueop.py b/src/moderation_api/models/getqueueop.py new file mode 100644 index 0000000..bbc2a1d --- /dev/null +++ b/src/moderation_api/models/getqueueop.py @@ -0,0 +1,405 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import ( + BaseModel, + Nullable, + OptionalNullable, + UNSET, + UNSET_SENTINEL, + UnrecognizedStr, +) +from moderation_api.utils import FieldMetadata, PathParamMetadata +import pydantic +from pydantic import model_serializer +from typing import List, Literal, Optional, Union +from typing_extensions import Annotated, NotRequired, TypedDict + + +class GetQueueRequestTypedDict(TypedDict): + id: str + r"""The queue ID""" + + +class GetQueueRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The queue ID""" + + +IsFlagged = Union[ + Literal[ + "ALL", + "FLAGGED", + "NOT_FLAGGED", + "SHADOW_FLAGGED", + ], + UnrecognizedStr, +] + + +CasebookAnswer = Union[ + Literal[ + "ALL", + "ALLOWED", + "REJECTED", + "NO_ANSWER", + ], + UnrecognizedStr, +] + + +CasebookAgreement = Union[ + Literal[ + "ALL", + "OVERRULED", + "AGREED", + ], + UnrecognizedStr, +] + + +GetQueueType = Union[ + Literal[ + "FLAGGED", + "NOT_FLAGGED", + "THRESHOLDS", + "MATCHED", + ], + UnrecognizedStr, +] + + +class FilterLabelTypedDict(TypedDict): + label: str + type: GetQueueType + min_threshold: NotRequired[Nullable[float]] + max_threshold: NotRequired[Nullable[float]] + + +class FilterLabel(BaseModel): + label: str + + type: GetQueueType + + min_threshold: Annotated[ + OptionalNullable[float], pydantic.Field(alias="minThreshold") + ] = UNSET + + max_threshold: Annotated[ + OptionalNullable[float], pydantic.Field(alias="maxThreshold") + ] = UNSET + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["minThreshold", "maxThreshold"]) + nullable_fields = set(["minThreshold", "maxThreshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +class MetadataFilterTypedDict(TypedDict): + key: str + value: str + + +class MetadataFilter(BaseModel): + key: str + + value: str + + +GetQueueContentType = Union[ + Literal[ + "profile", + "message", + "post", + "comment", + "event", + "product", + "review", + "voice", + "other", + ], + UnrecognizedStr, +] + + +MediaType = Union[ + Literal[ + "text", + "image", + "video", + "object", + "audio", + ], + UnrecognizedStr, +] + + +GetQueueRecommendationAction = Union[ + Literal[ + "review", + "allow", + "reject", + ], + UnrecognizedStr, +] + + +WithinUnit = Union[ + Literal[ + "MINUTES", + "HOURS", + "DAYS", + "WEEKS", + "MONTHS", + "YEARS", + ], + UnrecognizedStr, +] + + +CheckStatus = Union[ + Literal[ + "all", + "checked", + "unchecked", + ], + UnrecognizedStr, +] + + +class FilterTypedDict(TypedDict): + is_flagged: NotRequired[IsFlagged] + casebook_answer: NotRequired[CasebookAnswer] + casebook_agreement: NotRequired[CasebookAgreement] + labels: NotRequired[List[str]] + filter_labels: NotRequired[List[FilterLabelTypedDict]] + metadata_filters: NotRequired[List[MetadataFilterTypedDict]] + filtered_channel_ids: NotRequired[List[str]] + filtered_action_ids: NotRequired[List[str]] + languages: NotRequired[List[str]] + content_types: NotRequired[List[GetQueueContentType]] + media_types: NotRequired[List[MediaType]] + author_trust_levels: NotRequired[List[int]] + recommendation_actions: NotRequired[List[GetQueueRecommendationAction]] + min_severity: NotRequired[int] + max_severity: NotRequired[int] + after_date: NotRequired[str] + before_date: NotRequired[str] + within: NotRequired[float] + within_unit: NotRequired[WithinUnit] + clear_date_window: NotRequired[bool] + check_status: NotRequired[Nullable[CheckStatus]] + author_id: NotRequired[str] + content_id: NotRequired[str] + conversation_ids: NotRequired[List[Nullable[str]]] + search: NotRequired[List[str]] + + +class Filter(BaseModel): + is_flagged: Annotated[Optional[IsFlagged], pydantic.Field(alias="isFlagged")] = None + + casebook_answer: Annotated[ + Optional[CasebookAnswer], pydantic.Field(alias="casebookAnswer") + ] = None + + casebook_agreement: Annotated[ + Optional[CasebookAgreement], pydantic.Field(alias="casebookAgreement") + ] = None + + labels: Optional[List[str]] = None + + filter_labels: Annotated[ + Optional[List[FilterLabel]], pydantic.Field(alias="filterLabels") + ] = None + + metadata_filters: Annotated[ + Optional[List[MetadataFilter]], pydantic.Field(alias="metadataFilters") + ] = None + + filtered_channel_ids: Annotated[ + Optional[List[str]], pydantic.Field(alias="filteredChannelIds") + ] = None + + filtered_action_ids: Annotated[ + Optional[List[str]], pydantic.Field(alias="filteredActionIds") + ] = None + + languages: Optional[List[str]] = None + + content_types: Annotated[ + Optional[List[GetQueueContentType]], pydantic.Field(alias="contentTypes") + ] = None + + media_types: Annotated[ + Optional[List[MediaType]], pydantic.Field(alias="mediaTypes") + ] = None + + author_trust_levels: Annotated[ + Optional[List[int]], pydantic.Field(alias="authorTrustLevels") + ] = None + + recommendation_actions: Annotated[ + Optional[List[GetQueueRecommendationAction]], + pydantic.Field(alias="recommendationActions"), + ] = None + + min_severity: Annotated[Optional[int], pydantic.Field(alias="minSeverity")] = None + + max_severity: Annotated[Optional[int], pydantic.Field(alias="maxSeverity")] = None + + after_date: Annotated[Optional[str], pydantic.Field(alias="afterDate")] = None + + before_date: Annotated[Optional[str], pydantic.Field(alias="beforeDate")] = None + + within: Optional[float] = None + + within_unit: Annotated[Optional[WithinUnit], pydantic.Field(alias="withinUnit")] = ( + None + ) + + clear_date_window: Annotated[ + Optional[bool], pydantic.Field(alias="clearDateWindow") + ] = None + + check_status: Annotated[ + OptionalNullable[CheckStatus], pydantic.Field(alias="checkStatus") + ] = UNSET + + author_id: Annotated[Optional[str], pydantic.Field(alias="authorID")] = None + + content_id: Annotated[Optional[str], pydantic.Field(alias="contentID")] = None + + conversation_ids: Annotated[ + Optional[List[Nullable[str]]], pydantic.Field(alias="conversationIds") + ] = None + + search: Optional[List[str]] = None + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "isFlagged", + "casebookAnswer", + "casebookAgreement", + "labels", + "filterLabels", + "metadataFilters", + "filteredChannelIds", + "filteredActionIds", + "languages", + "contentTypes", + "mediaTypes", + "authorTrustLevels", + "recommendationActions", + "minSeverity", + "maxSeverity", + "afterDate", + "beforeDate", + "within", + "withinUnit", + "clearDateWindow", + "checkStatus", + "authorID", + "contentID", + "conversationIds", + "search", + ] + ) + nullable_fields = set(["checkStatus"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +class GetQueueQueueTypedDict(TypedDict): + id: str + name: str + description: str + filter_: FilterTypedDict + unresolved_items_count: float + resolved_items_count: float + total_items_count: float + + +class GetQueueQueue(BaseModel): + id: str + + name: str + + description: str + + filter_: Annotated[Filter, pydantic.Field(alias="filter")] + + unresolved_items_count: Annotated[ + float, pydantic.Field(alias="unresolvedItemsCount") + ] + + resolved_items_count: Annotated[float, pydantic.Field(alias="resolvedItemsCount")] + + total_items_count: Annotated[float, pydantic.Field(alias="totalItemsCount")] + + +class GetQueueResponseTypedDict(TypedDict): + r"""Successful response""" + + queue: GetQueueQueueTypedDict + + +class GetQueueResponse(BaseModel): + r"""Successful response""" + + queue: GetQueueQueue + + +try: + FilterLabel.model_rebuild() +except NameError: + pass +try: + Filter.model_rebuild() +except NameError: + pass +try: + GetQueueQueue.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/getqueuestatsop.py b/src/moderation_api/models/getqueuestatsop.py new file mode 100644 index 0000000..d332ae1 --- /dev/null +++ b/src/moderation_api/models/getqueuestatsop.py @@ -0,0 +1,264 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel, UNSET_SENTINEL +from moderation_api.utils import FieldMetadata, PathParamMetadata, QueryParamMetadata +import pydantic +from pydantic import model_serializer +from typing import List, Optional +from typing_extensions import Annotated, NotRequired, TypedDict + + +class GetQueueStatsRequestTypedDict(TypedDict): + id: str + r"""The queue ID""" + within_days: NotRequired[int] + + +class GetQueueStatsRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The queue ID""" + + within_days: Annotated[ + Optional[int], + pydantic.Field(alias="withinDays"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = 30 + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["withinDays"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ReviewStatsTypedDict(TypedDict): + average_time_to_review: float + r"""Average time in milliseconds to review an item""" + total_reviewed: float + r"""Total number of items reviewed""" + total_pending: float + r"""Total number of items pending review""" + + +class ReviewStats(BaseModel): + average_time_to_review: Annotated[ + float, pydantic.Field(alias="averageTimeToReview") + ] + r"""Average time in milliseconds to review an item""" + + total_reviewed: Annotated[float, pydantic.Field(alias="totalReviewed")] + r"""Total number of items reviewed""" + + total_pending: Annotated[float, pydantic.Field(alias="totalPending")] + r"""Total number of items pending review""" + + +class ActionStatTypedDict(TypedDict): + action_id: str + r"""ID of the moderation action""" + action_name: str + r"""Name of the moderation action""" + count: float + r"""Number of times this action was taken""" + percentage_of_total: float + r"""Percentage this action represents of all actions""" + + +class ActionStat(BaseModel): + action_id: Annotated[str, pydantic.Field(alias="actionId")] + r"""ID of the moderation action""" + + action_name: Annotated[str, pydantic.Field(alias="actionName")] + r"""Name of the moderation action""" + + count: float + r"""Number of times this action was taken""" + + percentage_of_total: Annotated[float, pydantic.Field(alias="percentageOfTotal")] + r"""Percentage this action represents of all actions""" + + +class TopActionTypedDict(TypedDict): + action_id: str + r"""Most used action by this reviewer""" + action_name: str + r"""Name of the most used action""" + count: float + r"""Number of times this action was used""" + + +class TopAction(BaseModel): + action_id: Annotated[str, pydantic.Field(alias="actionId")] + r"""Most used action by this reviewer""" + + action_name: Annotated[str, pydantic.Field(alias="actionName")] + r"""Name of the most used action""" + + count: float + r"""Number of times this action was used""" + + +class TopReviewerTypedDict(TypedDict): + user_id: str + r"""ID of the reviewer""" + name: str + r"""Name of the reviewer""" + review_count: float + r"""Number of items reviewed""" + average_time_per_review: float + r"""Average review time in milliseconds""" + top_actions: List[TopActionTypedDict] + r"""Most common actions taken by this reviewer""" + accuracy_score: NotRequired[float] + r"""Optional accuracy score based on review quality metrics""" + + +class TopReviewer(BaseModel): + user_id: Annotated[str, pydantic.Field(alias="userId")] + r"""ID of the reviewer""" + + name: str + r"""Name of the reviewer""" + + review_count: Annotated[float, pydantic.Field(alias="reviewCount")] + r"""Number of items reviewed""" + + average_time_per_review: Annotated[ + float, pydantic.Field(alias="averageTimePerReview") + ] + r"""Average review time in milliseconds""" + + top_actions: Annotated[List[TopAction], pydantic.Field(alias="topActions")] + r"""Most common actions taken by this reviewer""" + + accuracy_score: Annotated[ + Optional[float], pydantic.Field(alias="accuracyScore") + ] = None + r"""Optional accuracy score based on review quality metrics""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["accuracyScore"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class DailyReviewCountTypedDict(TypedDict): + date_: str + r"""Date in YYYY-MM-DD format""" + count: float + r"""Number of reviews on this date""" + + +class DailyReviewCount(BaseModel): + date_: Annotated[str, pydantic.Field(alias="date")] + r"""Date in YYYY-MM-DD format""" + + count: float + r"""Number of reviews on this date""" + + +class FlaggedContentTrendTypedDict(TypedDict): + label: str + r"""Content flag/label""" + trend: float + r"""Trend indicator (-1 to 1) showing if this type of flagged content is increasing or decreasing""" + + +class FlaggedContentTrend(BaseModel): + label: str + r"""Content flag/label""" + + trend: float + r"""Trend indicator (-1 to 1) showing if this type of flagged content is increasing or decreasing""" + + +class TrendsTypedDict(TypedDict): + daily_review_counts: List[DailyReviewCountTypedDict] + flagged_content_trends: List[FlaggedContentTrendTypedDict] + + +class Trends(BaseModel): + daily_review_counts: Annotated[ + List[DailyReviewCount], pydantic.Field(alias="dailyReviewCounts") + ] + + flagged_content_trends: Annotated[ + List[FlaggedContentTrend], pydantic.Field(alias="flaggedContentTrends") + ] + + +class GetQueueStatsResponseTypedDict(TypedDict): + r"""Successful response""" + + review_stats: ReviewStatsTypedDict + action_stats: List[ActionStatTypedDict] + top_reviewers: List[TopReviewerTypedDict] + r"""List of top reviewers and their statistics""" + trends: TrendsTypedDict + + +class GetQueueStatsResponse(BaseModel): + r"""Successful response""" + + review_stats: Annotated[ReviewStats, pydantic.Field(alias="reviewStats")] + + action_stats: Annotated[List[ActionStat], pydantic.Field(alias="actionStats")] + + top_reviewers: Annotated[List[TopReviewer], pydantic.Field(alias="topReviewers")] + r"""List of top reviewers and their statistics""" + + trends: Trends + + +try: + ReviewStats.model_rebuild() +except NameError: + pass +try: + ActionStat.model_rebuild() +except NameError: + pass +try: + TopAction.model_rebuild() +except NameError: + pass +try: + TopReviewer.model_rebuild() +except NameError: + pass +try: + DailyReviewCount.model_rebuild() +except NameError: + pass +try: + Trends.model_rebuild() +except NameError: + pass +try: + GetQueueStatsResponse.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/getwebhookop.py b/src/moderation_api/models/getwebhookop.py new file mode 100644 index 0000000..1698366 --- /dev/null +++ b/src/moderation_api/models/getwebhookop.py @@ -0,0 +1,135 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import ( + BaseModel, + Nullable, + OptionalNullable, + UNSET, + UNSET_SENTINEL, + UnrecognizedStr, +) +from moderation_api.utils import FieldMetadata, PathParamMetadata +import pydantic +from pydantic import model_serializer +from typing import List, Literal, Union +from typing_extensions import Annotated, NotRequired, TypedDict + + +class GetWebhookRequestTypedDict(TypedDict): + id: str + r"""The ID of the webhook to get.""" + + +class GetWebhookRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The ID of the webhook to get.""" + + +GetWebhookEventType = Union[ + Literal[ + "QUEUE_ITEM_NEW", + "QUEUE_ITEM_COMPLETED", + "QUEUE_ITEM_ACTION", + "QUEUE_ITEM_REJECTED", + "QUEUE_ITEM_ALLOWED", + "AUTHOR_BLOCKED", + "AUTHOR_UNBLOCKED", + "AUTHOR_SUSPENDED", + "AUTHOR_UPDATED", + "AUTHOR_TRUST_LEVEL_CHANGED", + "AUTHOR_ACTION", + ], + UnrecognizedStr, +] + + +GetWebhookPayloadVersion = Union[ + Literal[ + "V1", + "V2", + ], + UnrecognizedStr, +] +r"""Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API.""" + + +class GetWebhookResponseTypedDict(TypedDict): + r"""Webhook retrieved successfully""" + + id: str + r"""The ID of the webhook.""" + name: str + r"""The webhook's name.""" + url: str + r"""The URL we call when a subscribed event occurs.""" + event_types: List[GetWebhookEventType] + r"""Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead.""" + payload_version: GetWebhookPayloadVersion + r"""Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API.""" + created_at: str + r"""The date the webhook was created.""" + description: NotRequired[Nullable[str]] + r"""The webhook's description.""" + + +class GetWebhookResponse(BaseModel): + r"""Webhook retrieved successfully""" + + id: str + r"""The ID of the webhook.""" + + name: str + r"""The webhook's name.""" + + url: str + r"""The URL we call when a subscribed event occurs.""" + + event_types: Annotated[ + List[GetWebhookEventType], pydantic.Field(alias="eventTypes") + ] + r"""Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead.""" + + payload_version: Annotated[ + GetWebhookPayloadVersion, pydantic.Field(alias="payloadVersion") + ] + r"""Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API.""" + + created_at: Annotated[str, pydantic.Field(alias="createdAt")] + r"""The date the webhook was created.""" + + description: OptionalNullable[str] = UNSET + r"""The webhook's description.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["description"]) + nullable_fields = set(["description"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +try: + GetWebhookResponse.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/getwebhooksecretop.py b/src/moderation_api/models/getwebhooksecretop.py new file mode 100644 index 0000000..f53f76f --- /dev/null +++ b/src/moderation_api/models/getwebhooksecretop.py @@ -0,0 +1,19 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel +from typing_extensions import TypedDict + + +class GetWebhookSecretResponseTypedDict(TypedDict): + r"""Signing secret retrieved successfully""" + + secret: str + r"""The signing secret for this project. Every webhook delivery is signed with HMAC-SHA256 over the raw JSON body, hex-encoded in the `modapi-signature` header.""" + + +class GetWebhookSecretResponse(BaseModel): + r"""Signing secret retrieved successfully""" + + secret: str + r"""The signing secret for this project. Every webhook delivery is signed with HMAC-SHA256 over the raw JSON body, hex-encoded in the `modapi-signature` header.""" diff --git a/src/moderation_api/models/getwordlistembeddingstatusop.py b/src/moderation_api/models/getwordlistembeddingstatusop.py new file mode 100644 index 0000000..ba5bc47 --- /dev/null +++ b/src/moderation_api/models/getwordlistembeddingstatusop.py @@ -0,0 +1,49 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel +from moderation_api.utils import FieldMetadata, PathParamMetadata +import pydantic +from typing_extensions import Annotated, TypedDict + + +class GetWordlistEmbeddingStatusRequestTypedDict(TypedDict): + id: str + r"""ID of the wordlist to check embedding status for""" + + +class GetWordlistEmbeddingStatusRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""ID of the wordlist to check embedding status for""" + + +class GetWordlistEmbeddingStatusResponseTypedDict(TypedDict): + r"""Embedding status details""" + + progress: float + r"""Percentage of words that have been embedded (0-100)""" + total_words: float + r"""Total number of words in the wordlist""" + remaining_words: float + r"""Number of words still waiting to be embedded""" + + +class GetWordlistEmbeddingStatusResponse(BaseModel): + r"""Embedding status details""" + + progress: float + r"""Percentage of words that have been embedded (0-100)""" + + total_words: Annotated[float, pydantic.Field(alias="totalWords")] + r"""Total number of words in the wordlist""" + + remaining_words: Annotated[float, pydantic.Field(alias="remainingWords")] + r"""Number of words still waiting to be embedded""" + + +try: + GetWordlistEmbeddingStatusResponse.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/getwordlistop.py b/src/moderation_api/models/getwordlistop.py new file mode 100644 index 0000000..6c2f5f6 --- /dev/null +++ b/src/moderation_api/models/getwordlistop.py @@ -0,0 +1,81 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel, Nullable, UNSET_SENTINEL +from moderation_api.utils import FieldMetadata, PathParamMetadata +import pydantic +from pydantic import model_serializer +from typing import List +from typing_extensions import Annotated, TypedDict + + +class GetWordlistRequestTypedDict(TypedDict): + id: str + r"""ID of the wordlist to get""" + + +class GetWordlistRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""ID of the wordlist to get""" + + +class GetWordlistResponseTypedDict(TypedDict): + r"""Successful response""" + + id: str + r"""ID of the wordlist""" + name: Nullable[str] + words: List[str] + r"""Words in the wordlist""" + created_at: str + r"""Creation date of the wordlist""" + organization_id: str + r"""ID of the organization""" + user_id: Nullable[str] + strict: bool + r"""Strict mode""" + + +class GetWordlistResponse(BaseModel): + r"""Successful response""" + + id: str + r"""ID of the wordlist""" + + name: Nullable[str] + + words: List[str] + r"""Words in the wordlist""" + + created_at: Annotated[str, pydantic.Field(alias="createdAt")] + r"""Creation date of the wordlist""" + + organization_id: Annotated[str, pydantic.Field(alias="organizationId")] + r"""ID of the organization""" + + user_id: Annotated[Nullable[str], pydantic.Field(alias="userId")] + + strict: bool + r"""Strict mode""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + m[k] = val + + return m + + +try: + GetWordlistResponse.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/internalservererror.py b/src/moderation_api/models/internalservererror.py new file mode 100644 index 0000000..babe02e --- /dev/null +++ b/src/moderation_api/models/internalservererror.py @@ -0,0 +1,12 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import UnrecognizedStr +from typing import Literal, Union + + +InternalServerErrorCode = Union[Literal["internal_error",], UnrecognizedStr] +r"""Exactly what went wrong. Stable; safe to branch on. New codes may be added. + +- `internal_error`: Something went wrong on our side. Retry with backoff. +""" diff --git a/src/moderation_api/models/listactionsop.py b/src/moderation_api/models/listactionsop.py new file mode 100644 index 0000000..8b72b39 --- /dev/null +++ b/src/moderation_api/models/listactionsop.py @@ -0,0 +1,257 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from .pagination import Pagination, PaginationTypedDict +from moderation_api.types import ( + BaseModel, + Nullable, + OptionalNullable, + UNSET, + UNSET_SENTINEL, + UnrecognizedStr, +) +from moderation_api.utils import FieldMetadata, QueryParamMetadata +import pydantic +from pydantic import model_serializer +from typing import Awaitable, Callable, List, Literal, Optional, Union +from typing_extensions import Annotated, NotRequired, TypedDict + + +class ListActionsRequestTypedDict(TypedDict): + queue_id: NotRequired[str] + page: NotRequired[int] + page_size: NotRequired[int] + + +class ListActionsRequest(BaseModel): + queue_id: Annotated[ + Optional[str], + pydantic.Field(alias="queueId"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = None + + page: Annotated[ + Optional[int], + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = 1 + + page_size: Annotated[ + Optional[int], + pydantic.Field(alias="pageSize"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = 20 + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["queueId", "page", "pageSize"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +ListActionsType = Union[ + Literal[ + "AUTHOR_BLOCK", + "AUTHOR_BLOCK_TEMP", + "AUTHOR_UNBLOCK", + "AUTHOR_DELETE", + "AUTHOR_REPORT", + "AUTHOR_WARN", + "AUTHOR_CUSTOM", + "ITEM_REJECT", + "ITEM_ALLOW", + "ITEM_CUSTOM", + ], + UnrecognizedStr, +] + + +ListActionsQueueBehaviour = Union[ + Literal[ + "REMOVE", + "ADD", + "NO_CHANGE", + ], + UnrecognizedStr, +] +r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + + +ListActionsPosition = Union[ + Literal[ + "ALL_QUEUES", + "SOME_QUEUES", + "HIDDEN", + ], + UnrecognizedStr, +] +r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + + +class ListActionsPossibleValueTypedDict(TypedDict): + value: str + r"""The value of the action.""" + + +class ListActionsPossibleValue(BaseModel): + value: str + r"""The value of the action.""" + + +class ListActionsDataTypedDict(TypedDict): + id: str + r"""The ID of the action.""" + created_at: str + r"""The date the action was created.""" + name: str + r"""The name of the action.""" + filter_in_queue_ids: List[str] + r"""The IDs of the queues the action is available in.""" + possible_values: List[ListActionsPossibleValueTypedDict] + r"""The possible values of the action. The user will be prompted to select one of these values when executing the action.""" + key: NotRequired[Nullable[str]] + r"""User defined key of the action.""" + description: NotRequired[Nullable[str]] + r"""The description of the action.""" + type: NotRequired[Nullable[ListActionsType]] + r"""The type of the action.""" + built_in: NotRequired[Nullable[bool]] + r"""Whether the action is a built-in action or a custom one.""" + queue_behaviour: NotRequired[ListActionsQueueBehaviour] + r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + position: NotRequired[ListActionsPosition] + r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + value_required: NotRequired[bool] + r"""Whether the action requires a value to be executed.""" + free_text: NotRequired[bool] + r"""Whether the action allows any text to be entered as a value or if it must be one of the possible values.""" + + +class ListActionsData(BaseModel): + id: str + r"""The ID of the action.""" + + created_at: Annotated[str, pydantic.Field(alias="createdAt")] + r"""The date the action was created.""" + + name: str + r"""The name of the action.""" + + filter_in_queue_ids: Annotated[List[str], pydantic.Field(alias="filterInQueueIds")] + r"""The IDs of the queues the action is available in.""" + + possible_values: Annotated[ + List[ListActionsPossibleValue], pydantic.Field(alias="possibleValues") + ] + r"""The possible values of the action. The user will be prompted to select one of these values when executing the action.""" + + key: OptionalNullable[str] = UNSET + r"""User defined key of the action.""" + + description: OptionalNullable[str] = UNSET + r"""The description of the action.""" + + type: OptionalNullable[ListActionsType] = UNSET + r"""The type of the action.""" + + built_in: Annotated[OptionalNullable[bool], pydantic.Field(alias="builtIn")] = False + r"""Whether the action is a built-in action or a custom one.""" + + queue_behaviour: Annotated[ + Optional[ListActionsQueueBehaviour], pydantic.Field(alias="queueBehaviour") + ] = "NO_CHANGE" + r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + + position: Optional[ListActionsPosition] = "ALL_QUEUES" + r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + + value_required: Annotated[Optional[bool], pydantic.Field(alias="valueRequired")] = ( + False + ) + r"""Whether the action requires a value to be executed.""" + + free_text: Annotated[Optional[bool], pydantic.Field(alias="freeText")] = False + r"""Whether the action allows any text to be entered as a value or if it must be one of the possible values.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "key", + "description", + "type", + "builtIn", + "queueBehaviour", + "position", + "valueRequired", + "freeText", + ] + ) + nullable_fields = set(["key", "description", "type", "builtIn"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +class ListActionsResponseBodyTypedDict(TypedDict): + r"""Successful response""" + + data: List[ListActionsDataTypedDict] + r"""The items on this page.""" + pagination: PaginationTypedDict + r"""Where this page is in the full list.""" + + +class ListActionsResponseBody(BaseModel): + r"""Successful response""" + + data: List[ListActionsData] + r"""The items on this page.""" + + pagination: Pagination + r"""Where this page is in the full list.""" + + +class ListActionsResponseTypedDict(TypedDict): + result: ListActionsResponseBodyTypedDict + + +class ListActionsResponse(BaseModel): + next: Union[ + Callable[[], Optional[ListActionsResponse]], + Callable[[], Awaitable[Optional[ListActionsResponse]]], + ] + + result: ListActionsResponseBody + + +try: + ListActionsData.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/listauthorsop.py b/src/moderation_api/models/listauthorsop.py new file mode 100644 index 0000000..39ce87a --- /dev/null +++ b/src/moderation_api/models/listauthorsop.py @@ -0,0 +1,143 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from .pagination import Pagination, PaginationTypedDict +from .publicauthor import PublicAuthor, PublicAuthorTypedDict +from moderation_api.types import BaseModel, UNSET_SENTINEL +from moderation_api.utils import FieldMetadata, QueryParamMetadata +import pydantic +from pydantic import model_serializer +from typing import Awaitable, Callable, List, Literal, Optional, Union +from typing_extensions import Annotated, NotRequired, TypedDict + + +SortBy = Literal[ + "trustLevel", + "violationCount", + "reportCount", + "memberSince", + "lastActive", + "contentCount", + "flaggedContentRatio", + "averageSentiment", +] + + +ListAuthorsSortDirection = Literal[ + "asc", + "desc", +] +r"""Sort direction""" + + +class ListAuthorsRequestTypedDict(TypedDict): + page: NotRequired[int] + page_size: NotRequired[int] + sort_by: NotRequired[SortBy] + sort_direction: NotRequired[ListAuthorsSortDirection] + r"""Sort direction""" + member_since_date: NotRequired[str] + last_active_date: NotRequired[str] + content_types: NotRequired[List[str]] + + +class ListAuthorsRequest(BaseModel): + page: Annotated[ + Optional[int], + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = 1 + + page_size: Annotated[ + Optional[int], + pydantic.Field(alias="pageSize"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = 20 + + sort_by: Annotated[ + Optional[SortBy], + pydantic.Field(alias="sortBy"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = None + + sort_direction: Annotated[ + Optional[ListAuthorsSortDirection], + pydantic.Field(alias="sortDirection"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = "desc" + r"""Sort direction""" + + member_since_date: Annotated[ + Optional[str], + pydantic.Field(alias="memberSinceDate"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = None + + last_active_date: Annotated[ + Optional[str], + pydantic.Field(alias="lastActiveDate"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = None + + content_types: Annotated[ + Optional[List[str]], + pydantic.Field(alias="contentTypes"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = None + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "page", + "pageSize", + "sortBy", + "sortDirection", + "memberSinceDate", + "lastActiveDate", + "contentTypes", + ] + ) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ListAuthorsResponseBodyTypedDict(TypedDict): + r"""Successful response""" + + data: List[PublicAuthorTypedDict] + r"""The items on this page.""" + pagination: PaginationTypedDict + r"""Where this page is in the full list.""" + + +class ListAuthorsResponseBody(BaseModel): + r"""Successful response""" + + data: List[PublicAuthor] + r"""The items on this page.""" + + pagination: Pagination + r"""Where this page is in the full list.""" + + +class ListAuthorsResponseTypedDict(TypedDict): + result: ListAuthorsResponseBodyTypedDict + + +class ListAuthorsResponse(BaseModel): + next: Union[ + Callable[[], Optional[ListAuthorsResponse]], + Callable[[], Awaitable[Optional[ListAuthorsResponse]]], + ] + + result: ListAuthorsResponseBody diff --git a/src/moderation_api/models/listchannelsop.py b/src/moderation_api/models/listchannelsop.py new file mode 100644 index 0000000..2f31087 --- /dev/null +++ b/src/moderation_api/models/listchannelsop.py @@ -0,0 +1,86 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from .channel import Channel, ChannelTypedDict +from .pagination import Pagination, PaginationTypedDict +from moderation_api.types import BaseModel, UNSET_SENTINEL +from moderation_api.utils import FieldMetadata, PathParamMetadata, QueryParamMetadata +import pydantic +from pydantic import model_serializer +from typing import Awaitable, Callable, List, Optional, Union +from typing_extensions import Annotated, NotRequired, TypedDict + + +class ListChannelsRequestTypedDict(TypedDict): + project_id: str + r"""The ID of the project.""" + page: NotRequired[int] + page_size: NotRequired[int] + + +class ListChannelsRequest(BaseModel): + project_id: Annotated[ + str, + pydantic.Field(alias="projectId"), + FieldMetadata(path=PathParamMetadata(style="simple", explode=False)), + ] + r"""The ID of the project.""" + + page: Annotated[ + Optional[int], + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = 1 + + page_size: Annotated[ + Optional[int], + pydantic.Field(alias="pageSize"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = 20 + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["page", "pageSize"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ListChannelsResponseBodyTypedDict(TypedDict): + r"""Channels retrieved successfully""" + + data: List[ChannelTypedDict] + r"""The items on this page.""" + pagination: PaginationTypedDict + r"""Where this page is in the full list.""" + + +class ListChannelsResponseBody(BaseModel): + r"""Channels retrieved successfully""" + + data: List[Channel] + r"""The items on this page.""" + + pagination: Pagination + r"""Where this page is in the full list.""" + + +class ListChannelsResponseTypedDict(TypedDict): + result: ListChannelsResponseBodyTypedDict + + +class ListChannelsResponse(BaseModel): + next: Union[ + Callable[[], Optional[ListChannelsResponse]], + Callable[[], Awaitable[Optional[ListChannelsResponse]]], + ] + + result: ListChannelsResponseBody diff --git a/src/moderation_api/models/listprojectsop.py b/src/moderation_api/models/listprojectsop.py new file mode 100644 index 0000000..796ff6d --- /dev/null +++ b/src/moderation_api/models/listprojectsop.py @@ -0,0 +1,77 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from .pagination import Pagination, PaginationTypedDict +from .project import Project, ProjectTypedDict +from moderation_api.types import BaseModel, UNSET_SENTINEL +from moderation_api.utils import FieldMetadata, QueryParamMetadata +import pydantic +from pydantic import model_serializer +from typing import Awaitable, Callable, List, Optional, Union +from typing_extensions import Annotated, NotRequired, TypedDict + + +class ListProjectsRequestTypedDict(TypedDict): + page: NotRequired[int] + page_size: NotRequired[int] + + +class ListProjectsRequest(BaseModel): + page: Annotated[ + Optional[int], + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = 1 + + page_size: Annotated[ + Optional[int], + pydantic.Field(alias="pageSize"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = 20 + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["page", "pageSize"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ListProjectsResponseBodyTypedDict(TypedDict): + r"""Projects retrieved successfully""" + + data: List[ProjectTypedDict] + r"""The items on this page.""" + pagination: PaginationTypedDict + r"""Where this page is in the full list.""" + + +class ListProjectsResponseBody(BaseModel): + r"""Projects retrieved successfully""" + + data: List[Project] + r"""The items on this page.""" + + pagination: Pagination + r"""Where this page is in the full list.""" + + +class ListProjectsResponseTypedDict(TypedDict): + result: ListProjectsResponseBodyTypedDict + + +class ListProjectsResponse(BaseModel): + next: Union[ + Callable[[], Optional[ListProjectsResponse]], + Callable[[], Awaitable[Optional[ListProjectsResponse]]], + ] + + result: ListProjectsResponseBody diff --git a/src/moderation_api/models/listqueueitemsop.py b/src/moderation_api/models/listqueueitemsop.py new file mode 100644 index 0000000..e069518 --- /dev/null +++ b/src/moderation_api/models/listqueueitemsop.py @@ -0,0 +1,335 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from .pagination import Pagination, PaginationTypedDict +from moderation_api.types import BaseModel, UNSET_SENTINEL, UnrecognizedStr +from moderation_api.utils import FieldMetadata, PathParamMetadata, QueryParamMetadata +import pydantic +from pydantic import model_serializer +from typing import Awaitable, Callable, List, Literal, Optional, Union +from typing_extensions import Annotated, NotRequired, TypedDict + + +SortField = Literal[ + "createdAt", + "severity", + "reviewedAt", +] + + +ListQueueItemsSortDirection = Literal[ + "asc", + "desc", +] +r"""Sort direction""" + + +class ListQueueItemsRequestTypedDict(TypedDict): + id: str + r"""The queue ID""" + page: NotRequired[int] + page_size: NotRequired[int] + sort_field: NotRequired[SortField] + sort_direction: NotRequired[ListQueueItemsSortDirection] + r"""Sort direction""" + conversation_ids: NotRequired[List[str]] + labels: NotRequired[List[str]] + after_date: NotRequired[str] + before_date: NotRequired[str] + include_resolved: NotRequired[bool] + author_id: NotRequired[str] + filtered_action_ids: NotRequired[List[str]] + + +class ListQueueItemsRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The queue ID""" + + page: Annotated[ + Optional[int], + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = 1 + + page_size: Annotated[ + Optional[int], + pydantic.Field(alias="pageSize"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = 20 + + sort_field: Annotated[ + Optional[SortField], + pydantic.Field(alias="sortField"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = None + + sort_direction: Annotated[ + Optional[ListQueueItemsSortDirection], + pydantic.Field(alias="sortDirection"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = "desc" + r"""Sort direction""" + + conversation_ids: Annotated[ + Optional[List[str]], + pydantic.Field(alias="conversationIds"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = None + + labels: Annotated[ + Optional[List[str]], + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = None + + after_date: Annotated[ + Optional[str], + pydantic.Field(alias="afterDate"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = None + + before_date: Annotated[ + Optional[str], + pydantic.Field(alias="beforeDate"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = None + + include_resolved: Annotated[ + Optional[bool], + pydantic.Field(alias="includeResolved"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = None + + author_id: Annotated[ + Optional[str], + pydantic.Field(alias="authorId"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = None + + filtered_action_ids: Annotated[ + Optional[List[str]], + pydantic.Field(alias="filteredActionIds"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = None + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "page", + "pageSize", + "sortField", + "sortDirection", + "conversationIds", + "labels", + "afterDate", + "beforeDate", + "includeResolved", + "authorId", + "filteredActionIds", + ] + ) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ListQueueItemsLabelTypedDict(TypedDict): + label: str + r"""Label name""" + score: float + r"""Confidence score of the label""" + flagged: bool + r"""Whether this label caused a flag""" + + +class ListQueueItemsLabel(BaseModel): + label: str + r"""Label name""" + + score: float + r"""Confidence score of the label""" + + flagged: bool + r"""Whether this label caused a flag""" + + +class ListQueueItemsActionTypedDict(TypedDict): + id: str + r"""Action ID""" + name: str + r"""Action name""" + timestamp: float + r"""Unix timestamp of when the action was taken""" + comment: NotRequired[str] + r"""Action comment""" + reviewer: NotRequired[str] + r"""Moderator userID""" + + +class ListQueueItemsAction(BaseModel): + id: str + r"""Action ID""" + + name: str + r"""Action name""" + + timestamp: float + r"""Unix timestamp of when the action was taken""" + + comment: Optional[str] = None + r"""Action comment""" + + reviewer: Optional[str] = None + r"""Moderator userID""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["comment", "reviewer"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +ListQueueItemsStatus = Union[ + Literal[ + "pending", + "resolved", + ], + UnrecognizedStr, +] +r"""Status of the item""" + + +class ListQueueItemsDataTypedDict(TypedDict): + id: str + r"""Content ID""" + content: str + r"""The content to be moderated""" + labels: List[ListQueueItemsLabelTypedDict] + flagged: bool + r"""Whether the item is flagged by any label""" + status: ListQueueItemsStatus + r"""Status of the item""" + timestamp: float + r"""Unix timestamp of when the item was created""" + language: NotRequired[str] + r"""Content language""" + conversation_id: NotRequired[str] + r"""Conversation ID""" + author_id: NotRequired[str] + r"""Author ID""" + actions: NotRequired[List[ListQueueItemsActionTypedDict]] + r"""Action IDs taken on this item""" + content_type: NotRequired[str] + r"""Type of the content object""" + + +class ListQueueItemsData(BaseModel): + id: str + r"""Content ID""" + + content: str + r"""The content to be moderated""" + + labels: List[ListQueueItemsLabel] + + flagged: bool + r"""Whether the item is flagged by any label""" + + status: ListQueueItemsStatus + r"""Status of the item""" + + timestamp: float + r"""Unix timestamp of when the item was created""" + + language: Optional[str] = None + r"""Content language""" + + conversation_id: Annotated[ + Optional[str], pydantic.Field(alias="conversationId") + ] = None + r"""Conversation ID""" + + author_id: Annotated[Optional[str], pydantic.Field(alias="authorId")] = None + r"""Author ID""" + + actions: Optional[List[ListQueueItemsAction]] = None + r"""Action IDs taken on this item""" + + content_type: Annotated[Optional[str], pydantic.Field(alias="contentType")] = None + r"""Type of the content object""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + ["language", "conversationId", "authorId", "actions", "contentType"] + ) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ListQueueItemsResponseBodyTypedDict(TypedDict): + r"""Successful response""" + + data: List[ListQueueItemsDataTypedDict] + r"""The items on this page.""" + pagination: PaginationTypedDict + r"""Where this page is in the full list.""" + + +class ListQueueItemsResponseBody(BaseModel): + r"""Successful response""" + + data: List[ListQueueItemsData] + r"""The items on this page.""" + + pagination: Pagination + r"""Where this page is in the full list.""" + + +class ListQueueItemsResponseTypedDict(TypedDict): + result: ListQueueItemsResponseBodyTypedDict + + +class ListQueueItemsResponse(BaseModel): + next: Union[ + Callable[[], Optional[ListQueueItemsResponse]], + Callable[[], Awaitable[Optional[ListQueueItemsResponse]]], + ] + + result: ListQueueItemsResponseBody + + +try: + ListQueueItemsData.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/listwebhooksop.py b/src/moderation_api/models/listwebhooksop.py new file mode 100644 index 0000000..23c3124 --- /dev/null +++ b/src/moderation_api/models/listwebhooksop.py @@ -0,0 +1,186 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from .pagination import Pagination, PaginationTypedDict +from moderation_api.types import ( + BaseModel, + Nullable, + OptionalNullable, + UNSET, + UNSET_SENTINEL, + UnrecognizedStr, +) +from moderation_api.utils import FieldMetadata, QueryParamMetadata +import pydantic +from pydantic import model_serializer +from typing import Awaitable, Callable, List, Literal, Optional, Union +from typing_extensions import Annotated, NotRequired, TypedDict + + +class ListWebhooksRequestTypedDict(TypedDict): + page: NotRequired[int] + page_size: NotRequired[int] + + +class ListWebhooksRequest(BaseModel): + page: Annotated[ + Optional[int], + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = 1 + + page_size: Annotated[ + Optional[int], + pydantic.Field(alias="pageSize"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = 20 + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["page", "pageSize"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +ListWebhooksEventType = Union[ + Literal[ + "QUEUE_ITEM_NEW", + "QUEUE_ITEM_COMPLETED", + "QUEUE_ITEM_ACTION", + "QUEUE_ITEM_REJECTED", + "QUEUE_ITEM_ALLOWED", + "AUTHOR_BLOCKED", + "AUTHOR_UNBLOCKED", + "AUTHOR_SUSPENDED", + "AUTHOR_UPDATED", + "AUTHOR_TRUST_LEVEL_CHANGED", + "AUTHOR_ACTION", + ], + UnrecognizedStr, +] + + +ListWebhooksPayloadVersion = Union[ + Literal[ + "V1", + "V2", + ], + UnrecognizedStr, +] +r"""Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API.""" + + +class ListWebhooksDataTypedDict(TypedDict): + id: str + r"""The ID of the webhook.""" + name: str + r"""The webhook's name.""" + url: str + r"""The URL we call when a subscribed event occurs.""" + event_types: List[ListWebhooksEventType] + r"""Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead.""" + payload_version: ListWebhooksPayloadVersion + r"""Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API.""" + created_at: str + r"""The date the webhook was created.""" + description: NotRequired[Nullable[str]] + r"""The webhook's description.""" + + +class ListWebhooksData(BaseModel): + id: str + r"""The ID of the webhook.""" + + name: str + r"""The webhook's name.""" + + url: str + r"""The URL we call when a subscribed event occurs.""" + + event_types: Annotated[ + List[ListWebhooksEventType], pydantic.Field(alias="eventTypes") + ] + r"""Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead.""" + + payload_version: Annotated[ + ListWebhooksPayloadVersion, pydantic.Field(alias="payloadVersion") + ] + r"""Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API.""" + + created_at: Annotated[str, pydantic.Field(alias="createdAt")] + r"""The date the webhook was created.""" + + description: OptionalNullable[str] = UNSET + r"""The webhook's description.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["description"]) + nullable_fields = set(["description"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +class ListWebhooksResponseBodyTypedDict(TypedDict): + r"""Webhooks retrieved successfully""" + + data: List[ListWebhooksDataTypedDict] + r"""The items on this page.""" + pagination: PaginationTypedDict + r"""Where this page is in the full list.""" + + +class ListWebhooksResponseBody(BaseModel): + r"""Webhooks retrieved successfully""" + + data: List[ListWebhooksData] + r"""The items on this page.""" + + pagination: Pagination + r"""Where this page is in the full list.""" + + +class ListWebhooksResponseTypedDict(TypedDict): + result: ListWebhooksResponseBodyTypedDict + + +class ListWebhooksResponse(BaseModel): + next: Union[ + Callable[[], Optional[ListWebhooksResponse]], + Callable[[], Awaitable[Optional[ListWebhooksResponse]]], + ] + + result: ListWebhooksResponseBody + + +try: + ListWebhooksData.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/listwordlistsop.py b/src/moderation_api/models/listwordlistsop.py new file mode 100644 index 0000000..60d2bea --- /dev/null +++ b/src/moderation_api/models/listwordlistsop.py @@ -0,0 +1,126 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from .pagination import Pagination, PaginationTypedDict +from moderation_api.types import BaseModel, Nullable, UNSET_SENTINEL +from moderation_api.utils import FieldMetadata, QueryParamMetadata +import pydantic +from pydantic import model_serializer +from typing import Awaitable, Callable, List, Optional, Union +from typing_extensions import Annotated, NotRequired, TypedDict + + +class ListWordlistsRequestTypedDict(TypedDict): + page: NotRequired[int] + page_size: NotRequired[int] + + +class ListWordlistsRequest(BaseModel): + page: Annotated[ + Optional[int], + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = 1 + + page_size: Annotated[ + Optional[int], + pydantic.Field(alias="pageSize"), + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = 20 + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["page", "pageSize"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ListWordlistsDataTypedDict(TypedDict): + id: str + r"""Unique identifier of the wordlist""" + name: Nullable[str] + r"""Name of the wordlist""" + description: Nullable[str] + r"""Description of the wordlist""" + created_at: Nullable[str] + r"""When the wordlist was created""" + user_id: Nullable[str] + r"""User who created the wordlist""" + + +class ListWordlistsData(BaseModel): + id: str + r"""Unique identifier of the wordlist""" + + name: Nullable[str] + r"""Name of the wordlist""" + + description: Nullable[str] + r"""Description of the wordlist""" + + created_at: Annotated[Nullable[str], pydantic.Field(alias="createdAt")] + r"""When the wordlist was created""" + + user_id: Annotated[Nullable[str], pydantic.Field(alias="userId")] + r"""User who created the wordlist""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + m[k] = val + + return m + + +class ListWordlistsResponseBodyTypedDict(TypedDict): + r"""Successful response""" + + data: List[ListWordlistsDataTypedDict] + r"""The items on this page.""" + pagination: PaginationTypedDict + r"""Where this page is in the full list.""" + + +class ListWordlistsResponseBody(BaseModel): + r"""Successful response""" + + data: List[ListWordlistsData] + r"""The items on this page.""" + + pagination: Pagination + r"""Where this page is in the full list.""" + + +class ListWordlistsResponseTypedDict(TypedDict): + result: ListWordlistsResponseBodyTypedDict + + +class ListWordlistsResponse(BaseModel): + next: Union[ + Callable[[], Optional[ListWordlistsResponse]], + Callable[[], Awaitable[Optional[ListWordlistsResponse]]], + ] + + result: ListWordlistsResponseBody + + +try: + ListWordlistsData.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/notfounderror.py b/src/moderation_api/models/notfounderror.py new file mode 100644 index 0000000..91aa2a3 --- /dev/null +++ b/src/moderation_api/models/notfounderror.py @@ -0,0 +1,31 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import UnrecognizedStr +from typing import Literal, Union + + +NotFoundErrorCode = Union[ + Literal[ + "resource_not_found", + "action_not_found", + "author_not_found", + "queue_not_found", + "wordlist_not_found", + "webhook_not_found", + "project_not_found", + "channel_not_found", + ], + UnrecognizedStr, +] +r"""Exactly what went wrong. Stable; safe to branch on. New codes may be added. + +- `resource_not_found`: The resource does not exist. +- `action_not_found`: The action does not exist. +- `author_not_found`: The author does not exist. +- `queue_not_found`: The review queue does not exist. +- `wordlist_not_found`: The wordlist does not exist. +- `webhook_not_found`: The webhook does not exist. +- `project_not_found`: The project does not exist. +- `channel_not_found`: The channel does not exist. +""" diff --git a/src/moderation_api/models/pagination.py b/src/moderation_api/models/pagination.py new file mode 100644 index 0000000..9cbd567 --- /dev/null +++ b/src/moderation_api/models/pagination.py @@ -0,0 +1,46 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel +import pydantic +from typing_extensions import Annotated, TypedDict + + +class PaginationTypedDict(TypedDict): + r"""Where this page is in the full list.""" + + page: int + r"""The current page, starting at 1.""" + page_size: int + r"""Number of items per page.""" + total: int + r"""Total number of items.""" + total_pages: int + r"""Total number of pages.""" + has_more: bool + r"""Whether there is a next page.""" + + +class Pagination(BaseModel): + r"""Where this page is in the full list.""" + + page: int + r"""The current page, starting at 1.""" + + page_size: Annotated[int, pydantic.Field(alias="pageSize")] + r"""Number of items per page.""" + + total: int + r"""Total number of items.""" + + total_pages: Annotated[int, pydantic.Field(alias="totalPages")] + r"""Total number of pages.""" + + has_more: Annotated[bool, pydantic.Field(alias="hasMore")] + r"""Whether there is a next page.""" + + +try: + Pagination.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/permissiondeniederror.py b/src/moderation_api/models/permissiondeniederror.py new file mode 100644 index 0000000..92f5fa3 --- /dev/null +++ b/src/moderation_api/models/permissiondeniederror.py @@ -0,0 +1,33 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import UnrecognizedStr +from typing import Literal, Union + + +PermissionDeniedErrorCode = Union[ + Literal[ + "permission_denied", + "account_disabled", + "quota_exceeded", + "plan_feature_unavailable", + "plan_limit_exceeded", + "project_key_required", + "organization_key_required", + "missing_key_permission", + "project_not_reachable", + ], + UnrecognizedStr, +] +r"""Exactly what went wrong. Stable; safe to branch on. New codes may be added. + +- `permission_denied`: The API key cannot perform this request. +- `account_disabled`: The organization account is disabled. +- `quota_exceeded`: The organization has used all API requests on its plan. +- `plan_feature_unavailable`: The current plan does not include this feature. +- `plan_limit_exceeded`: The request goes over a limit of the current plan. +- `project_key_required`: This endpoint needs a project API key, but an organization key was used. +- `organization_key_required`: This endpoint needs an organization API key, but a project key was used. +- `missing_key_permission`: The organization key does not have the permission this endpoint needs. +- `project_not_reachable`: The organization key is limited to other projects. +""" diff --git a/src/moderation_api/models/project.py b/src/moderation_api/models/project.py new file mode 100644 index 0000000..6b34a5f --- /dev/null +++ b/src/moderation_api/models/project.py @@ -0,0 +1,97 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel, Nullable, UNSET_SENTINEL, UnrecognizedStr +import pydantic +from pydantic import model_serializer +from typing import Literal, Union +from typing_extensions import Annotated, TypedDict + + +ProjectGlobalFlaggingMode = Union[ + Literal[ + "DRY_RUN", + "ACTIVE", + ], + UnrecognizedStr, +] +r"""`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it, for testing a setup.""" + + +class ProjectTypedDict(TypedDict): + id: str + r"""The ID of the project.""" + name: str + r"""The project's name.""" + description: Nullable[str] + r"""The project's description.""" + domain: Nullable[str] + r"""The website or app the project moderates content for.""" + context: Nullable[str] + r"""What the platform is and who uses it. AI moderation reads it to judge content in context.""" + global_flagging_mode: ProjectGlobalFlaggingMode + r"""`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it, for testing a setup.""" + enable_request_logging: bool + r"""Whether moderated content is stored and shown in the dashboard.""" + casebook_enabled: bool + r"""Whether decisions made in the review queue are recorded in the project's casebook.""" + created_at: str + r"""When the project was created.""" + updated_at: str + r"""When the project was last changed.""" + + +class Project(BaseModel): + id: str + r"""The ID of the project.""" + + name: str + r"""The project's name.""" + + description: Nullable[str] + r"""The project's description.""" + + domain: Nullable[str] + r"""The website or app the project moderates content for.""" + + context: Nullable[str] + r"""What the platform is and who uses it. AI moderation reads it to judge content in context.""" + + global_flagging_mode: Annotated[ + ProjectGlobalFlaggingMode, pydantic.Field(alias="globalFlaggingMode") + ] + r"""`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it, for testing a setup.""" + + enable_request_logging: Annotated[ + bool, pydantic.Field(alias="enableRequestLogging") + ] + r"""Whether moderated content is stored and shown in the dashboard.""" + + casebook_enabled: Annotated[bool, pydantic.Field(alias="casebookEnabled")] + r"""Whether decisions made in the review queue are recorded in the project's casebook.""" + + created_at: Annotated[str, pydantic.Field(alias="createdAt")] + r"""When the project was created.""" + + updated_at: Annotated[str, pydantic.Field(alias="updatedAt")] + r"""When the project was last changed.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + m[k] = val + + return m + + +try: + Project.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/projectwithkey.py b/src/moderation_api/models/projectwithkey.py new file mode 100644 index 0000000..4f9121e --- /dev/null +++ b/src/moderation_api/models/projectwithkey.py @@ -0,0 +1,102 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel, Nullable, UNSET_SENTINEL, UnrecognizedStr +import pydantic +from pydantic import model_serializer +from typing import Literal, Union +from typing_extensions import Annotated, TypedDict + + +ProjectWithKeyGlobalFlaggingMode = Union[ + Literal[ + "DRY_RUN", + "ACTIVE", + ], + UnrecognizedStr, +] +r"""`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it, for testing a setup.""" + + +class ProjectWithKeyTypedDict(TypedDict): + id: str + r"""The ID of the project.""" + name: str + r"""The project's name.""" + description: Nullable[str] + r"""The project's description.""" + domain: Nullable[str] + r"""The website or app the project moderates content for.""" + context: Nullable[str] + r"""What the platform is and who uses it. AI moderation reads it to judge content in context.""" + global_flagging_mode: ProjectWithKeyGlobalFlaggingMode + r"""`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it, for testing a setup.""" + enable_request_logging: bool + r"""Whether moderated content is stored and shown in the dashboard.""" + casebook_enabled: bool + r"""Whether decisions made in the review queue are recorded in the project's casebook.""" + created_at: str + r"""When the project was created.""" + updated_at: str + r"""When the project was last changed.""" + secret_key: str + r"""A secret API key for the new project. Use it to moderate content in this project. It is also shown in the project's settings.""" + + +class ProjectWithKey(BaseModel): + id: str + r"""The ID of the project.""" + + name: str + r"""The project's name.""" + + description: Nullable[str] + r"""The project's description.""" + + domain: Nullable[str] + r"""The website or app the project moderates content for.""" + + context: Nullable[str] + r"""What the platform is and who uses it. AI moderation reads it to judge content in context.""" + + global_flagging_mode: Annotated[ + ProjectWithKeyGlobalFlaggingMode, pydantic.Field(alias="globalFlaggingMode") + ] + r"""`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it, for testing a setup.""" + + enable_request_logging: Annotated[ + bool, pydantic.Field(alias="enableRequestLogging") + ] + r"""Whether moderated content is stored and shown in the dashboard.""" + + casebook_enabled: Annotated[bool, pydantic.Field(alias="casebookEnabled")] + r"""Whether decisions made in the review queue are recorded in the project's casebook.""" + + created_at: Annotated[str, pydantic.Field(alias="createdAt")] + r"""When the project was created.""" + + updated_at: Annotated[str, pydantic.Field(alias="updatedAt")] + r"""When the project was last changed.""" + + secret_key: Annotated[str, pydantic.Field(alias="secretKey")] + r"""A secret API key for the new project. Use it to moderate content in this project. It is also shown in the project's settings.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + m[k] = val + + return m + + +try: + ProjectWithKey.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/publicauthor.py b/src/moderation_api/models/publicauthor.py new file mode 100644 index 0000000..457e955 --- /dev/null +++ b/src/moderation_api/models/publicauthor.py @@ -0,0 +1,373 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import ( + BaseModel, + Nullable, + OptionalNullable, + UNSET, + UNSET_SENTINEL, + UnrecognizedStr, +) +import pydantic +from pydantic import ConfigDict, model_serializer +from typing import Any, Dict, Literal, Union +from typing_extensions import NotRequired, TypedDict + + +Status = Union[ + Literal[ + "enabled", + "suspended", + "blocked", + ], + UnrecognizedStr, +] +r"""Current author status""" + + +class TrustLevelTypedDict(TypedDict): + level: float + r"""Author trust level (-1, 0, 1, 2, 3, or 4)""" + manual: bool + r"""True if the trust level was set manually by a moderator""" + + +class TrustLevel(BaseModel): + level: float + r"""Author trust level (-1, 0, 1, 2, 3, or 4)""" + + manual: bool + r"""True if the trust level was set manually by a moderator""" + + +class BlockTypedDict(TypedDict): + until: NotRequired[Nullable[float]] + r"""The timestamp until which they are blocked if the author is suspended.""" + reason: NotRequired[Nullable[str]] + r"""The moderators reason why the author was blocked or suspended.""" + + +class Block(BaseModel): + until: OptionalNullable[float] = UNSET + r"""The timestamp until which they are blocked if the author is suspended.""" + + reason: OptionalNullable[str] = UNSET + r"""The moderators reason why the author was blocked or suspended.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["until", "reason"]) + nullable_fields = set(["until", "reason"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +class RiskEvaluationTypedDict(TypedDict): + risk_level: NotRequired[Nullable[float]] + r"""Calculated risk level based on more than 10 behavioral signals.""" + + +class RiskEvaluation(BaseModel): + risk_level: OptionalNullable[float] = UNSET + r"""Calculated risk level based on more than 10 behavioral signals.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["risk_level"]) + nullable_fields = set(["risk_level"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +class MetricsTypedDict(TypedDict): + total_content: float + r"""Total pieces of content""" + flagged_content: float + r"""Number of flagged content pieces""" + average_sentiment: NotRequired[Nullable[float]] + r"""Average sentiment score of content (-1 to 1). Requires a sentiment model in your project.""" + + +class Metrics(BaseModel): + total_content: float + r"""Total pieces of content""" + + flagged_content: float + r"""Number of flagged content pieces""" + + average_sentiment: OptionalNullable[float] = UNSET + r"""Average sentiment score of content (-1 to 1). Requires a sentiment model in your project.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["average_sentiment"]) + nullable_fields = set(["average_sentiment"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +class MetadataTypedDict(TypedDict): + r"""Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.""" + + email_verified: NotRequired[Nullable[bool]] + r"""Whether the author's email is verified""" + phone_verified: NotRequired[Nullable[bool]] + r"""Whether the author's phone number is verified""" + identity_verified: NotRequired[Nullable[bool]] + r"""Whether the author's identity is verified""" + is_paying_customer: NotRequired[Nullable[bool]] + r"""Whether the author is a paying customer""" + + +class Metadata(BaseModel): + r"""Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.""" + + model_config = ConfigDict( + populate_by_name=True, arbitrary_types_allowed=True, extra="allow" + ) + __pydantic_extra__: Dict[str, Any] = pydantic.Field(init=False) + + email_verified: OptionalNullable[bool] = UNSET + r"""Whether the author's email is verified""" + + phone_verified: OptionalNullable[bool] = UNSET + r"""Whether the author's phone number is verified""" + + identity_verified: OptionalNullable[bool] = UNSET + r"""Whether the author's identity is verified""" + + is_paying_customer: OptionalNullable[bool] = UNSET + r"""Whether the author is a paying customer""" + + @property + def additional_properties(self): + return self.__pydantic_extra__ + + @additional_properties.setter + def additional_properties(self, value): + self.__pydantic_extra__ = value # pyright: ignore[reportIncompatibleVariableOverride] + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "email_verified", + "phone_verified", + "identity_verified", + "is_paying_customer", + ] + ) + nullable_fields = set( + [ + "email_verified", + "phone_verified", + "identity_verified", + "is_paying_customer", + ] + ) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + serialized.pop(k, serialized.pop(n, None)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + for k, v in serialized.items(): + m[k] = v + + return m + + +class PublicAuthorTypedDict(TypedDict): + id: str + r"""Author ID in Moderation API""" + first_seen: float + r"""Timestamp when author first appeared""" + last_seen: float + r"""Timestamp of last activity""" + status: Status + r"""Current author status""" + trust_level: TrustLevelTypedDict + block: Nullable[BlockTypedDict] + r"""Block or suspension details, if applicable. Null if the author is enabled.""" + risk_evaluation: Nullable[RiskEvaluationTypedDict] + r"""Risk assessment details, if available.""" + metrics: MetricsTypedDict + metadata: MetadataTypedDict + r"""Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.""" + external_id: NotRequired[Nullable[str]] + r"""The author's ID from your system""" + profile_picture: NotRequired[Nullable[str]] + r"""URL of the author's profile picture""" + external_link: NotRequired[Nullable[str]] + r"""URL of the author's external profile""" + name: NotRequired[Nullable[str]] + r"""Author name or identifier""" + email: NotRequired[Nullable[str]] + r"""Author email address""" + company: NotRequired[Nullable[str]] + r"""The author's company or organization""" + last_incident: NotRequired[Nullable[float]] + r"""Timestamp of last incident""" + + +class PublicAuthor(BaseModel): + id: str + r"""Author ID in Moderation API""" + + first_seen: float + r"""Timestamp when author first appeared""" + + last_seen: float + r"""Timestamp of last activity""" + + status: Status + r"""Current author status""" + + trust_level: TrustLevel + + block: Nullable[Block] + r"""Block or suspension details, if applicable. Null if the author is enabled.""" + + risk_evaluation: Nullable[RiskEvaluation] + r"""Risk assessment details, if available.""" + + metrics: Metrics + + metadata: Metadata + r"""Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.""" + + external_id: OptionalNullable[str] = UNSET + r"""The author's ID from your system""" + + profile_picture: OptionalNullable[str] = UNSET + r"""URL of the author's profile picture""" + + external_link: OptionalNullable[str] = UNSET + r"""URL of the author's external profile""" + + name: OptionalNullable[str] = UNSET + r"""Author name or identifier""" + + email: OptionalNullable[str] = UNSET + r"""Author email address""" + + company: OptionalNullable[str] = UNSET + r"""The author's company or organization""" + + last_incident: OptionalNullable[float] = UNSET + r"""Timestamp of last incident""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "external_id", + "profile_picture", + "external_link", + "name", + "email", + "company", + "last_incident", + ] + ) + nullable_fields = set( + [ + "external_id", + "profile_picture", + "external_link", + "name", + "email", + "company", + "last_incident", + "block", + "risk_evaluation", + ] + ) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m diff --git a/src/moderation_api/models/ratelimiterror.py b/src/moderation_api/models/ratelimiterror.py new file mode 100644 index 0000000..2f8644c --- /dev/null +++ b/src/moderation_api/models/ratelimiterror.py @@ -0,0 +1,12 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import UnrecognizedStr +from typing import Literal, Union + + +RateLimitErrorCode = Union[Literal["rate_limit_exceeded",], UnrecognizedStr] +r"""Exactly what went wrong. Stable; safe to branch on. New codes may be added. + +- `rate_limit_exceeded`: Too many requests. Wait for the `Retry-After` seconds. +""" diff --git a/src/moderation_api/models/removewordlistwordsop.py b/src/moderation_api/models/removewordlistwordsop.py new file mode 100644 index 0000000..e984b27 --- /dev/null +++ b/src/moderation_api/models/removewordlistwordsop.py @@ -0,0 +1,57 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel +from moderation_api.utils import FieldMetadata, PathParamMetadata, QueryParamMetadata +import pydantic +from typing import List +from typing_extensions import Annotated, TypedDict + + +class RemoveWordlistWordsRequestTypedDict(TypedDict): + id: str + r"""ID of the wordlist to remove words from""" + words: List[str] + r"""Array of words to remove from the wordlist""" + + +class RemoveWordlistWordsRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""ID of the wordlist to remove words from""" + + words: Annotated[ + List[str], FieldMetadata(query=QueryParamMetadata(style="form", explode=True)) + ] + r"""Array of words to remove from the wordlist""" + + +class RemoveWordlistWordsResponseTypedDict(TypedDict): + r"""Words successfully removed from wordlist""" + + removed_count: float + r"""Number of words removed""" + total_count: float + r"""Total number of words in wordlist""" + removed_words: List[str] + r"""List of words removed""" + + +class RemoveWordlistWordsResponse(BaseModel): + r"""Words successfully removed from wordlist""" + + removed_count: Annotated[float, pydantic.Field(alias="removedCount")] + r"""Number of words removed""" + + total_count: Annotated[float, pydantic.Field(alias="totalCount")] + r"""Total number of words in wordlist""" + + removed_words: Annotated[List[str], pydantic.Field(alias="removedWords")] + r"""List of words removed""" + + +try: + RemoveWordlistWordsResponse.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/resolvequeueitemop.py b/src/moderation_api/models/resolvequeueitemop.py new file mode 100644 index 0000000..691d4f6 --- /dev/null +++ b/src/moderation_api/models/resolvequeueitemop.py @@ -0,0 +1,106 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel, UNSET_SENTINEL +from moderation_api.utils import FieldMetadata, PathParamMetadata, RequestMetadata +import pydantic +from pydantic import model_serializer +from typing import Optional +from typing_extensions import Annotated, NotRequired, TypedDict + + +class ResolveQueueItemRequestBodyTypedDict(TypedDict): + comment: NotRequired[str] + r"""Optional comment""" + + +class ResolveQueueItemRequestBody(BaseModel): + comment: Optional[str] = None + r"""Optional comment""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["comment"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ResolveQueueItemRequestTypedDict(TypedDict): + id: str + r"""The queue ID""" + item_id: str + r"""The item ID to resolve""" + body: ResolveQueueItemRequestBodyTypedDict + + +class ResolveQueueItemRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The queue ID""" + + item_id: Annotated[ + str, + pydantic.Field(alias="itemId"), + FieldMetadata(path=PathParamMetadata(style="simple", explode=False)), + ] + r"""The item ID to resolve""" + + body: Annotated[ + ResolveQueueItemRequestBody, + FieldMetadata(request=RequestMetadata(media_type="application/json")), + ] + + +class ResolveQueueItemResponseTypedDict(TypedDict): + r"""Successful response""" + + success: bool + resolved_at: str + r"""Timestamp when the item was resolved""" + comment: NotRequired[str] + r"""Optional comment""" + + +class ResolveQueueItemResponse(BaseModel): + r"""Successful response""" + + success: bool + + resolved_at: Annotated[str, pydantic.Field(alias="resolvedAt")] + r"""Timestamp when the item was resolved""" + + comment: Optional[str] = None + r"""Optional comment""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["comment"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +try: + ResolveQueueItemResponse.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/security.py b/src/moderation_api/models/security.py new file mode 100644 index 0000000..3f92f21 --- /dev/null +++ b/src/moderation_api/models/security.py @@ -0,0 +1,42 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel, UNSET_SENTINEL +from moderation_api.utils import FieldMetadata, SecurityMetadata +from pydantic import model_serializer +from typing import Optional +from typing_extensions import Annotated, NotRequired, TypedDict + + +class SecurityTypedDict(TypedDict): + secret_key: NotRequired[str] + + +class Security(BaseModel): + secret_key: Annotated[ + Optional[str], + FieldMetadata( + security=SecurityMetadata( + scheme=True, + scheme_type="http", + sub_type="bearer", + field_name="Authorization", + ) + ), + ] = None + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["secretKey"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m diff --git a/src/moderation_api/models/submitcontentop.py b/src/moderation_api/models/submitcontentop.py new file mode 100644 index 0000000..ee8a44f --- /dev/null +++ b/src/moderation_api/models/submitcontentop.py @@ -0,0 +1,3637 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from functools import partial +from moderation_api.types import ( + BaseModel, + Nullable, + OptionalNullable, + UNSET, + UNSET_SENTINEL, + UnrecognizedStr, +) +from moderation_api.utils import validate_const +from moderation_api.utils.unions import parse_open_union +import pydantic +from pydantic import ConfigDict, Field, model_serializer +from pydantic.functional_validators import AfterValidator, BeforeValidator +from typing import Any, Dict, List, Literal, Optional, Union +from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict + + +class DataAudioRequestTypedDict(TypedDict): + r"""Audio""" + + url: str + r"""The URL of the audio content""" + type: Literal["audio"] + + +class DataAudioRequest(BaseModel): + r"""Audio""" + + url: str + r"""The URL of the audio content""" + + type: Annotated[ + Annotated[Literal["audio"], AfterValidator(validate_const("audio"))], + pydantic.Field(alias="type"), + ] = "audio" + + +class DataVideoRequestTypedDict(TypedDict): + r"""Video""" + + url: str + r"""A public URL of the video content""" + type: Literal["video"] + + +class DataVideoRequest(BaseModel): + r"""Video""" + + url: str + r"""A public URL of the video content""" + + type: Annotated[ + Annotated[Literal["video"], AfterValidator(validate_const("video"))], + pydantic.Field(alias="type"), + ] = "video" + + +class DataImageRequestTypedDict(TypedDict): + r"""Image""" + + type: Literal["image"] + url: NotRequired[str] + r"""A public URL of the image content. Either url or data must be provided.""" + data: NotRequired[str] + r"""Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue.""" + + +class DataImageRequest(BaseModel): + r"""Image""" + + type: Annotated[ + Annotated[Literal["image"], AfterValidator(validate_const("image"))], + pydantic.Field(alias="type"), + ] = "image" + + url: Optional[str] = None + r"""A public URL of the image content. Either url or data must be provided.""" + + data: Optional[str] = None + r"""Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["url", "data"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class DataTextRequestTypedDict(TypedDict): + r"""Text""" + + text: str + r"""The content text""" + type: Literal["text"] + + +class DataTextRequest(BaseModel): + r"""Text""" + + text: str + r"""The content text""" + + type: Annotated[ + Annotated[Literal["text"], AfterValidator(validate_const("text"))], + pydantic.Field(alias="type"), + ] = "text" + + +ContentLeafRequestTypedDict = TypeAliasType( + "ContentLeafRequestTypedDict", + Union[ + DataTextRequestTypedDict, + DataVideoRequestTypedDict, + DataAudioRequestTypedDict, + DataImageRequestTypedDict, + ], +) + + +ContentLeafRequest = Annotated[ + Union[DataTextRequest, DataImageRequest, DataVideoRequest, DataAudioRequest], + Field(discriminator="type"), +] + + +class SubmitContentObjectTypedDict(TypedDict): + r"""Object""" + + data: Dict[str, ContentLeafRequestTypedDict] + r"""Values in the object. Can be mixed content types.""" + type: Literal["object"] + + +class SubmitContentObject(BaseModel): + r"""Object""" + + data: Dict[str, ContentLeafRequest] + r"""Values in the object. Can be mixed content types.""" + + type: Annotated[ + Annotated[Literal["object"], AfterValidator(validate_const("object"))], + pydantic.Field(alias="type"), + ] = "object" + + +class AudioRequestTypedDict(TypedDict): + r"""Audio""" + + url: str + r"""The URL of the audio content""" + type: Literal["audio"] + + +class AudioRequest(BaseModel): + r"""Audio""" + + url: str + r"""The URL of the audio content""" + + type: Annotated[ + Annotated[Literal["audio"], AfterValidator(validate_const("audio"))], + pydantic.Field(alias="type"), + ] = "audio" + + +class VideoRequestTypedDict(TypedDict): + r"""Video""" + + url: str + r"""A public URL of the video content""" + type: Literal["video"] + + +class VideoRequest(BaseModel): + r"""Video""" + + url: str + r"""A public URL of the video content""" + + type: Annotated[ + Annotated[Literal["video"], AfterValidator(validate_const("video"))], + pydantic.Field(alias="type"), + ] = "video" + + +class ImageRequestTypedDict(TypedDict): + r"""Image""" + + type: Literal["image"] + url: NotRequired[str] + r"""A public URL of the image content. Either url or data must be provided.""" + data: NotRequired[str] + r"""Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue.""" + + +class ImageRequest(BaseModel): + r"""Image""" + + type: Annotated[ + Annotated[Literal["image"], AfterValidator(validate_const("image"))], + pydantic.Field(alias="type"), + ] = "image" + + url: Optional[str] = None + r"""A public URL of the image content. Either url or data must be provided.""" + + data: Optional[str] = None + r"""Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["url", "data"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class TextRequestTypedDict(TypedDict): + r"""Text""" + + text: str + r"""The content text""" + type: Literal["text"] + + +class TextRequest(BaseModel): + r"""Text""" + + text: str + r"""The content text""" + + type: Annotated[ + Annotated[Literal["text"], AfterValidator(validate_const("text"))], + pydantic.Field(alias="type"), + ] = "text" + + +ContentRequestTypedDict = TypeAliasType( + "ContentRequestTypedDict", + Union[ + TextRequestTypedDict, + VideoRequestTypedDict, + AudioRequestTypedDict, + SubmitContentObjectTypedDict, + ImageRequestTypedDict, + ], +) +r"""The content sent for moderation""" + + +ContentRequest = Annotated[ + Union[TextRequest, ImageRequest, VideoRequest, AudioRequest, SubmitContentObject], + Field(discriminator="type"), +] +r"""The content sent for moderation""" + + +SubmitContentMetaType = Literal[ + "profile", + "message", + "post", + "comment", + "event", + "product", + "review", + "voice", + "other", +] +r"""The meta type of content being moderated""" + + +ActionRequest = Literal[ + "review", + "allow", + "reject", +] +r"""Your recommendation for the content: allow, review, or reject.""" + + +SubmitContentBehavior = Literal[ + "override", + "escalate", +] +r"""How your recommendation combines with ours. Defaults to 'escalate', which only applies it when stricter than ours; 'override' replaces ours outright.""" + + +class SubmitContentClientActionTypedDict(TypedDict): + r"""A recommendation from your own client-side flagging (e.g. a banned-IP list or a third-party tool). Feeds the rules engine and can escalate or override the recommended action. Does not change whether our analysis flagged the content.""" + + action: ActionRequest + r"""Your recommendation for the content: allow, review, or reject.""" + behavior: NotRequired[SubmitContentBehavior] + r"""How your recommendation combines with ours. Defaults to 'escalate', which only applies it when stricter than ours; 'override' replaces ours outright.""" + source: NotRequired[str] + r"""Where your recommendation came from, e.g. \"banned-ip\".""" + reason: NotRequired[str] + r"""A human-readable explanation for your recommendation.""" + + +class SubmitContentClientAction(BaseModel): + r"""A recommendation from your own client-side flagging (e.g. a banned-IP list or a third-party tool). Feeds the rules engine and can escalate or override the recommended action. Does not change whether our analysis flagged the content.""" + + action: ActionRequest + r"""Your recommendation for the content: allow, review, or reject.""" + + behavior: Optional[SubmitContentBehavior] = None + r"""How your recommendation combines with ours. Defaults to 'escalate', which only applies it when stricter than ours; 'override' replaces ours outright.""" + + source: Optional[str] = None + r"""Where your recommendation came from, e.g. \"banned-ip\".""" + + reason: Optional[str] = None + r"""A human-readable explanation for your recommendation.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["behavior", "source", "reason"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class GuidelineTypedDict(TypedDict): + flag: bool + instructions: str + guideline_key: str + threshold: NotRequired[float] + id: Literal["guideline"] + + +class Guideline(BaseModel): + flag: bool + + instructions: str + + guideline_key: Annotated[str, pydantic.Field(alias="guidelineKey")] + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["guideline"], AfterValidator(validate_const("guideline"))], + pydantic.Field(alias="id"), + ] = "guideline" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class URLRiskTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["url_risk"] + allowlist_wordlist_ids: NotRequired[List[str]] + r"""IDs of wordlists whose entries are treated as allowed URL domains. Matches short-circuit the risk model and are never flagged.""" + blocklist_wordlist_ids: NotRequired[List[str]] + r"""IDs of wordlists whose entries are treated as blocked URL domains. Matches short-circuit the risk model and are always flagged. Blocklists take precedence over allowlists.""" + flag_link_shorteners: NotRequired[bool] + r"""When true, any URL detected as a free link shortener (bit.ly, t.co, tinyurl, etc.) is always flagged regardless of risk score. Allowlist matches still win.""" + + +class URLRisk(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["url_risk"], AfterValidator(validate_const("url_risk"))], + pydantic.Field(alias="id"), + ] = "url_risk" + + allowlist_wordlist_ids: Annotated[ + Optional[List[str]], pydantic.Field(alias="allowlistWordlistIds") + ] = None + r"""IDs of wordlists whose entries are treated as allowed URL domains. Matches short-circuit the risk model and are never flagged.""" + + blocklist_wordlist_ids: Annotated[ + Optional[List[str]], pydantic.Field(alias="blocklistWordlistIds") + ] = None + r"""IDs of wordlists whose entries are treated as blocked URL domains. Matches short-circuit the risk model and are always flagged. Blocklists take precedence over allowlists.""" + + flag_link_shorteners: Annotated[ + Optional[bool], pydantic.Field(alias="flagLinkShorteners") + ] = None + r"""When true, any URL detected as a free link shortener (bit.ly, t.co, tinyurl, etc.) is always flagged regardless of risk score. Allowlist matches still win.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "threshold", + "allowlistWordlistIds", + "blocklistWordlistIds", + "flagLinkShorteners", + ] + ) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class Entities2TypedDict(TypedDict): + enable: NotRequired[bool] + flag: NotRequired[bool] + should_mask: NotRequired[bool] + mask: NotRequired[str] + + +class Entities2(BaseModel): + model_config = ConfigDict( + populate_by_name=True, arbitrary_types_allowed=True, extra="allow" + ) + __pydantic_extra__: Dict[str, Any] = pydantic.Field(init=False) + + enable: Optional[bool] = None + + flag: Optional[bool] = None + + should_mask: Annotated[Optional[bool], pydantic.Field(alias="shouldMask")] = None + + mask: Optional[str] = None + + @property + def additional_properties(self): + return self.__pydantic_extra__ + + @additional_properties.setter + def additional_properties(self, value): + self.__pydantic_extra__ = value # pyright: ignore[reportIncompatibleVariableOverride] + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["enable", "flag", "shouldMask", "mask"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + serialized.pop(k, serialized.pop(n, None)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + for k, v in serialized.items(): + m[k] = v + + return m + + +class URLMaskingTypedDict(TypedDict): + entities: Dict[str, Entities2TypedDict] + id: Literal["url"] + + +class URLMasking(BaseModel): + entities: Dict[str, Entities2] + + id: Annotated[ + Annotated[Literal["url"], AfterValidator(validate_const("url"))], + pydantic.Field(alias="id"), + ] = "url" + + +class Entities1TypedDict(TypedDict): + enable: NotRequired[bool] + flag: NotRequired[bool] + should_mask: NotRequired[bool] + mask: NotRequired[str] + + +class Entities1(BaseModel): + model_config = ConfigDict( + populate_by_name=True, arbitrary_types_allowed=True, extra="allow" + ) + __pydantic_extra__: Dict[str, Any] = pydantic.Field(init=False) + + enable: Optional[bool] = None + + flag: Optional[bool] = None + + should_mask: Annotated[Optional[bool], pydantic.Field(alias="shouldMask")] = None + + mask: Optional[str] = None + + @property + def additional_properties(self): + return self.__pydantic_extra__ + + @additional_properties.setter + def additional_properties(self, value): + self.__pydantic_extra__ = value # pyright: ignore[reportIncompatibleVariableOverride] + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["enable", "flag", "shouldMask", "mask"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + serialized.pop(k, serialized.pop(n, None)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + for k, v in serialized.items(): + m[k] = v + + return m + + +class PIIMaskingTypedDict(TypedDict): + entities: Dict[str, Entities1TypedDict] + id: Literal["pii"] + + +class PIIMasking(BaseModel): + entities: Dict[str, Entities1] + + id: Annotated[ + Annotated[Literal["pii"], AfterValidator(validate_const("pii"))], + pydantic.Field(alias="id"), + ] = "pii" + + +class SignalsRequestTypedDict(TypedDict): + flag: NotRequired[bool] + + +class SignalsRequest(BaseModel): + flag: Optional[bool] = None + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["flag"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class UnicodeSpoofingTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["unicode_spoofing"] + signals: NotRequired[Dict[str, SignalsRequestTypedDict]] + r"""Per-signal flag toggles. Omitted spoofing signals are enabled; encoding_damage defaults to off because decode damage (U+FFFD) marks a broken pipeline, not an attack. A disabled signal is still detected and reported as a label, but does not by itself flag the policy.""" + + +class UnicodeSpoofing(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[ + Literal["unicode_spoofing"], + AfterValidator(validate_const("unicode_spoofing")), + ], + pydantic.Field(alias="id"), + ] = "unicode_spoofing" + + signals: Optional[Dict[str, SignalsRequest]] = None + r"""Per-signal flag toggles. Omitted spoofing signals are enabled; encoding_damage defaults to off because decode damage (U+FFFD) marks a broken pipeline, not an attack. A disabled signal is still detected and reported as a label, but does not by itself flag the policy.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold", "signals"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class CodeAbuseTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["code_abuse"] + + +class CodeAbuse(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["code_abuse"], AfterValidator(validate_const("code_abuse"))], + pydantic.Field(alias="id"), + ] = "code_abuse" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ExtremismTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["extremism"] + + +class Extremism(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["extremism"], AfterValidator(validate_const("extremism"))], + pydantic.Field(alias="id"), + ] = "extremism" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class PromotionTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["promotion"] + + +class Promotion(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["promotion"], AfterValidator(validate_const("promotion"))], + pydantic.Field(alias="id"), + ] = "promotion" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class WeaponsTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["weapons"] + + +class Weapons(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["weapons"], AfterValidator(validate_const("weapons"))], + pydantic.Field(alias="id"), + ] = "weapons" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class TobaccoTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["tobacco"] + + +class Tobacco(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["tobacco"], AfterValidator(validate_const("tobacco"))], + pydantic.Field(alias="id"), + ] = "tobacco" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class GamblingTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["gambling"] + + +class Gambling(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["gambling"], AfterValidator(validate_const("gambling"))], + pydantic.Field(alias="id"), + ] = "gambling" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class DrugsTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["drugs"] + + +class Drugs(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["drugs"], AfterValidator(validate_const("drugs"))], + pydantic.Field(alias="id"), + ] = "drugs" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class AlcoholTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["alcohol"] + + +class Alcohol(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["alcohol"], AfterValidator(validate_const("alcohol"))], + pydantic.Field(alias="id"), + ] = "alcohol" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class IllegalTradeTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["illegal_trade"] + + +class IllegalTrade(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[ + Literal["illegal_trade"], AfterValidator(validate_const("illegal_trade")) + ], + pydantic.Field(alias="id"), + ] = "illegal_trade" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ChannelCircumventionTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["channel_circumvent"] + + +class ChannelCircumvention(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[ + Literal["channel_circumvent"], + AfterValidator(validate_const("channel_circumvent")), + ], + pydantic.Field(alias="id"), + ] = "channel_circumvent" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class SexualSolicitationTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["sexual_solicitation"] + + +class SexualSolicitation(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[ + Literal["sexual_solicitation"], + AfterValidator(validate_const("sexual_solicitation")), + ], + pydantic.Field(alias="id"), + ] = "sexual_solicitation" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ThreatTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["threat"] + + +class Threat(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["threat"], AfterValidator(validate_const("threat"))], + pydantic.Field(alias="id"), + ] = "threat" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ReligionTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["religion"] + + +class Religion(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["religion"], AfterValidator(validate_const("religion"))], + pydantic.Field(alias="id"), + ] = "religion" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class PoliticalTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["political"] + + +class Political(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["political"], AfterValidator(validate_const("political"))], + pydantic.Field(alias="id"), + ] = "political" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class SelfPromotionTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["self_promotion"] + + +class SelfPromotion(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[ + Literal["self_promotion"], AfterValidator(validate_const("self_promotion")) + ], + pydantic.Field(alias="id"), + ] = "self_promotion" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +Comparator = Literal[ + "at_least", + "fewer_than", +] +r"""Flag images that contain \"at least\" or \"fewer than\" the configured number of faces. Defaults to at_least.""" + + +class FaceDetectionTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["face_detection"] + comparator: NotRequired[Comparator] + r"""Flag images that contain \"at least\" or \"fewer than\" the configured number of faces. Defaults to at_least.""" + count: NotRequired[int] + r"""Number of faces the comparator applies to. Defaults to 1, so the default rule flags any image containing a face.""" + + +class FaceDetection(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[ + Literal["face_detection"], AfterValidator(validate_const("face_detection")) + ], + pydantic.Field(alias="id"), + ] = "face_detection" + + comparator: Optional[Comparator] = None + r"""Flag images that contain \"at least\" or \"fewer than\" the configured number of faces. Defaults to at_least.""" + + count: Optional[int] = None + r"""Number of faces the comparator applies to. Defaults to 1, so the default rule flags any image containing a face.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold", "comparator", "count"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class LowQualityContentTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["low_quality"] + min_words: NotRequired[int] + r"""Flag content with fewer than this many words as low-effort. Defaults to 3. Set to disable by omitting.""" + + +class LowQualityContent(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[ + Literal["low_quality"], AfterValidator(validate_const("low_quality")) + ], + pydantic.Field(alias="id"), + ] = "low_quality" + + min_words: Annotated[Optional[int], pydantic.Field(alias="minWords")] = None + r"""Flag content with fewer than this many words as low-effort. Defaults to 3. Set to disable by omitting.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold", "minWords"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class SpamTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["spam"] + + +class Spam(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["spam"], AfterValidator(validate_const("spam"))], + pydantic.Field(alias="id"), + ] = "spam" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class SelfHarmTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["self_harm"] + + +class SelfHarm(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["self_harm"], AfterValidator(validate_const("self_harm"))], + pydantic.Field(alias="id"), + ] = "self_harm" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ViolenceTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["violence"] + + +class Violence(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["violence"], AfterValidator(validate_const("violence"))], + pydantic.Field(alias="id"), + ] = "violence" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ProfanityTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["profanity"] + + +class Profanity(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["profanity"], AfterValidator(validate_const("profanity"))], + pydantic.Field(alias="id"), + ] = "profanity" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class SexualMinorsTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["sexual_minors"] + + +class SexualMinors(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[ + Literal["sexual_minors"], AfterValidator(validate_const("sexual_minors")) + ], + pydantic.Field(alias="id"), + ] = "sexual_minors" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class SexualTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["sexual"] + + +class Sexual(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["sexual"], AfterValidator(validate_const("sexual"))], + pydantic.Field(alias="id"), + ] = "sexual" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class CryptoTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["crypto"] + + +class Crypto(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["crypto"], AfterValidator(validate_const("crypto"))], + pydantic.Field(alias="id"), + ] = "crypto" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class AdultTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["adult"] + + +class Adult(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["adult"], AfterValidator(validate_const("adult"))], + pydantic.Field(alias="id"), + ] = "adult" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class CannabisTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["cannabis"] + + +class Cannabis(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["cannabis"], AfterValidator(validate_const("cannabis"))], + pydantic.Field(alias="id"), + ] = "cannabis" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class IllicitGamblingTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["illicit_gambling"] + + +class IllicitGambling(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[ + Literal["illicit_gambling"], + AfterValidator(validate_const("illicit_gambling")), + ], + pydantic.Field(alias="id"), + ] = "illicit_gambling" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class IllicitTobaccoTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["illicit_tobacco"] + + +class IllicitTobacco(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[ + Literal["illicit_tobacco"], + AfterValidator(validate_const("illicit_tobacco")), + ], + pydantic.Field(alias="id"), + ] = "illicit_tobacco" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class IllicitFirearmsTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["illicit_firearms"] + + +class IllicitFirearms(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[ + Literal["illicit_firearms"], + AfterValidator(validate_const("illicit_firearms")), + ], + pydantic.Field(alias="id"), + ] = "illicit_firearms" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class IllicitAlcoholTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["illicit_alcohol"] + + +class IllicitAlcohol(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[ + Literal["illicit_alcohol"], + AfterValidator(validate_const("illicit_alcohol")), + ], + pydantic.Field(alias="id"), + ] = "illicit_alcohol" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class IllicitDrugsTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["illicit_drugs"] + + +class IllicitDrugs(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[ + Literal["illicit_drugs"], AfterValidator(validate_const("illicit_drugs")) + ], + pydantic.Field(alias="id"), + ] = "illicit_drugs" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class IllicitTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["illicit"] + + +class Illicit(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["illicit"], AfterValidator(validate_const("illicit"))], + pydantic.Field(alias="id"), + ] = "illicit" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class HateTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["hate"] + + +class Hate(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["hate"], AfterValidator(validate_const("hate"))], + pydantic.Field(alias="id"), + ] = "hate" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class PersonalInformationTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["personal_information"] + + +class PersonalInformation(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[ + Literal["personal_information"], + AfterValidator(validate_const("personal_information")), + ], + pydantic.Field(alias="id"), + ] = "personal_information" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ToxicityTypedDict(TypedDict): + flag: bool + threshold: NotRequired[float] + id: Literal["toxicity"] + + +class Toxicity(BaseModel): + flag: bool + + threshold: Optional[float] = None + + id: Annotated[ + Annotated[Literal["toxicity"], AfterValidator(validate_const("toxicity"))], + pydantic.Field(alias="id"), + ] = "toxicity" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["threshold"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +PolicyRequestTypedDict = TypeAliasType( + "PolicyRequestTypedDict", + Union[ + PIIMaskingTypedDict, + URLMaskingTypedDict, + SelfPromotionTypedDict, + PoliticalTypedDict, + IllicitDrugsTypedDict, + IllicitAlcoholTypedDict, + IllicitFirearmsTypedDict, + IllicitTobaccoTypedDict, + IllicitGamblingTypedDict, + CannabisTypedDict, + AdultTypedDict, + ReligionTypedDict, + SexualTypedDict, + SexualMinorsTypedDict, + ProfanityTypedDict, + ViolenceTypedDict, + SelfHarmTypedDict, + SpamTypedDict, + PersonalInformationTypedDict, + HateTypedDict, + IllicitTypedDict, + ToxicityTypedDict, + CryptoTypedDict, + ThreatTypedDict, + SexualSolicitationTypedDict, + ChannelCircumventionTypedDict, + IllegalTradeTypedDict, + AlcoholTypedDict, + DrugsTypedDict, + GamblingTypedDict, + TobaccoTypedDict, + WeaponsTypedDict, + PromotionTypedDict, + ExtremismTypedDict, + CodeAbuseTypedDict, + UnicodeSpoofingTypedDict, + LowQualityContentTypedDict, + FaceDetectionTypedDict, + GuidelineTypedDict, + URLRiskTypedDict, + ], +) + + +PolicyRequest = Annotated[ + Union[ + Toxicity, + PersonalInformation, + Hate, + Illicit, + IllicitDrugs, + IllicitAlcohol, + IllicitFirearms, + IllicitTobacco, + IllicitGambling, + Cannabis, + Adult, + Crypto, + Sexual, + SexualMinors, + Profanity, + Violence, + SelfHarm, + Spam, + LowQualityContent, + FaceDetection, + SelfPromotion, + Political, + Religion, + Threat, + SexualSolicitation, + ChannelCircumvention, + IllegalTrade, + Alcohol, + Drugs, + Gambling, + Tobacco, + Weapons, + Promotion, + Extremism, + CodeAbuse, + UnicodeSpoofing, + PIIMasking, + URLMasking, + URLRisk, + Guideline, + ], + Field(discriminator="id"), +] + + +class SubmitContentRequestTypedDict(TypedDict): + content: ContentRequestTypedDict + r"""The content sent for moderation""" + timestamp: NotRequired[float] + r"""Unix timestamp (in milliseconds) of when the content was created. Use if content is not submitted in real-time.""" + channel: NotRequired[str] + r"""Provide a channel ID or key. Will use the project's default channel if not provided.""" + content_id: NotRequired[str] + r"""The unique ID of the content in your database.""" + meta_type: NotRequired[SubmitContentMetaType] + r"""The meta type of content being moderated""" + author_id: NotRequired[str] + r"""The author of the content.""" + conversation_id: NotRequired[str] + r"""For example the ID of a chat room or a post""" + metadata: NotRequired[Dict[str, Any]] + r"""Any metadata you want to store with the content""" + client_action: NotRequired[SubmitContentClientActionTypedDict] + r"""A recommendation from your own client-side flagging (e.g. a banned-IP list or a third-party tool). Feeds the rules engine and can escalate or override the recommended action. Does not change whether our analysis flagged the content.""" + do_not_store: NotRequired[bool] + r"""Do not store the content. The content won't enter the review queue""" + policies: NotRequired[List[PolicyRequestTypedDict]] + r"""(Enterprise) override the channel policies for this moderation request only.""" + + +class SubmitContentRequest(BaseModel): + content: ContentRequest + r"""The content sent for moderation""" + + timestamp: Optional[float] = None + r"""Unix timestamp (in milliseconds) of when the content was created. Use if content is not submitted in real-time.""" + + channel: Optional[str] = None + r"""Provide a channel ID or key. Will use the project's default channel if not provided.""" + + content_id: Annotated[Optional[str], pydantic.Field(alias="contentId")] = None + r"""The unique ID of the content in your database.""" + + meta_type: Annotated[ + Optional[SubmitContentMetaType], pydantic.Field(alias="metaType") + ] = None + r"""The meta type of content being moderated""" + + author_id: Annotated[Optional[str], pydantic.Field(alias="authorId")] = None + r"""The author of the content.""" + + conversation_id: Annotated[ + Optional[str], pydantic.Field(alias="conversationId") + ] = None + r"""For example the ID of a chat room or a post""" + + metadata: Optional[Dict[str, Any]] = None + r"""Any metadata you want to store with the content""" + + client_action: Annotated[ + Optional[SubmitContentClientAction], pydantic.Field(alias="clientAction") + ] = None + r"""A recommendation from your own client-side flagging (e.g. a banned-IP list or a third-party tool). Feeds the rules engine and can escalate or override the recommended action. Does not change whether our analysis flagged the content.""" + + do_not_store: Annotated[Optional[bool], pydantic.Field(alias="doNotStore")] = None + r"""Do not store the content. The content won't enter the review queue""" + + policies: Optional[List[PolicyRequest]] = None + r"""(Enterprise) override the channel policies for this moderation request only.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "timestamp", + "channel", + "contentId", + "metaType", + "authorId", + "conversationId", + "metadata", + "clientAction", + "doNotStore", + "policies", + ] + ) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class AudioResponseTypedDict(TypedDict): + r"""Audio""" + + url: str + r"""The URL of the audio content""" + type: Literal["audio"] + + +class AudioResponse(BaseModel): + r"""Audio""" + + url: str + r"""The URL of the audio content""" + + type: Annotated[ + Annotated[Literal["audio"], AfterValidator(validate_const("audio"))], + pydantic.Field(alias="type"), + ] = "audio" + + +class VideoResponseTypedDict(TypedDict): + r"""Video""" + + url: str + r"""A public URL of the video content""" + type: Literal["video"] + + +class VideoResponse(BaseModel): + r"""Video""" + + url: str + r"""A public URL of the video content""" + + type: Annotated[ + Annotated[Literal["video"], AfterValidator(validate_const("video"))], + pydantic.Field(alias="type"), + ] = "video" + + +class ImageResponseTypedDict(TypedDict): + r"""Image""" + + type: Literal["image"] + url: NotRequired[str] + r"""A public URL of the image content. Either url or data must be provided.""" + data: NotRequired[str] + r"""Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue.""" + + +class ImageResponse(BaseModel): + r"""Image""" + + type: Annotated[ + Annotated[Literal["image"], AfterValidator(validate_const("image"))], + pydantic.Field(alias="type"), + ] = "image" + + url: Optional[str] = None + r"""A public URL of the image content. Either url or data must be provided.""" + + data: Optional[str] = None + r"""Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["url", "data"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class TextResponseTypedDict(TypedDict): + r"""Text""" + + text: str + r"""The content text""" + type: Literal["text"] + + +class TextResponse(BaseModel): + r"""Text""" + + text: str + r"""The content text""" + + type: Annotated[ + Annotated[Literal["text"], AfterValidator(validate_const("text"))], + pydantic.Field(alias="type"), + ] = "text" + + +ContentLeafResponseTypedDict = TypeAliasType( + "ContentLeafResponseTypedDict", + Union[ + TextResponseTypedDict, + VideoResponseTypedDict, + AudioResponseTypedDict, + ImageResponseTypedDict, + ], +) + + +class UnknownContentLeafResponse(BaseModel): + r"""A ContentLeafResponse variant the SDK doesn't recognize. Preserves the raw payload.""" + + type: Literal["UNKNOWN"] = "UNKNOWN" + raw: Any + is_unknown: Literal[True] = True + + model_config = ConfigDict(frozen=True) + + +_CONTENT_LEAF_RESPONSE_VARIANTS: dict[str, Any] = { + "text": TextResponse, + "image": ImageResponse, + "video": VideoResponse, + "audio": AudioResponse, +} + + +ContentLeafResponse = Annotated[ + Union[ + TextResponse, + ImageResponse, + VideoResponse, + AudioResponse, + UnknownContentLeafResponse, + ], + BeforeValidator( + partial( + parse_open_union, + disc_key="type", + variants=_CONTENT_LEAF_RESPONSE_VARIANTS, + unknown_cls=UnknownContentLeafResponse, + union_name="ContentLeafResponse", + ) + ), +] + + +ModifiedTypedDict = TypeAliasType( + "ModifiedTypedDict", + Union[str, Dict[str, Any], Dict[str, ContentLeafResponseTypedDict]], +) + + +Modified = TypeAliasType( + "Modified", Union[str, Dict[str, Any], Dict[str, ContentLeafResponse]] +) + + +class ContentResponseTypedDict(TypedDict): + r"""Potentially modified content.""" + + id: str + r"""The unique identifier for the content. Either the contentId provided by you or an autogenerated ID.""" + masked: bool + r"""Whether any values have been masked.""" + unicode_cleaned: bool + r"""Whether Unicode spoofing normalization rewrote the content — confusables folded to their Latin lookalikes, invisible characters and combining-mark abuse stripped.""" + modified: Nullable[ModifiedTypedDict] + r"""The modified content, if any.""" + transcript: NotRequired[Nullable[str]] + r"""The transcribed text from audio content. Only present when audio moderation is used and transcript inclusion is enabled on the channel.""" + + +class ContentResponse(BaseModel): + r"""Potentially modified content.""" + + id: str + r"""The unique identifier for the content. Either the contentId provided by you or an autogenerated ID.""" + + masked: bool + r"""Whether any values have been masked.""" + + unicode_cleaned: bool + r"""Whether Unicode spoofing normalization rewrote the content — confusables folded to their Latin lookalikes, invisible characters and combining-mark abuse stripped.""" + + modified: Nullable[Modified] + r"""The modified content, if any.""" + + transcript: OptionalNullable[str] = UNSET + r"""The transcribed text from audio content. Only present when audio moderation is used and transcript inclusion is enabled on the channel.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["transcript"]) + nullable_fields = set(["modified", "transcript"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +class SubmitContentBlockTypedDict(TypedDict): + until: NotRequired[Nullable[float]] + r"""The timestamp until which they are blocked if the author is suspended.""" + reason: NotRequired[Nullable[str]] + r"""The moderators reason why the author was blocked or suspended.""" + + +class SubmitContentBlock(BaseModel): + until: OptionalNullable[float] = UNSET + r"""The timestamp until which they are blocked if the author is suspended.""" + + reason: OptionalNullable[str] = UNSET + r"""The moderators reason why the author was blocked or suspended.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["until", "reason"]) + nullable_fields = set(["until", "reason"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +AuthorStatus = Union[ + Literal[ + "enabled", + "suspended", + "blocked", + ], + UnrecognizedStr, +] +r"""Current author status""" + + +class SubmitContentTrustLevelTypedDict(TypedDict): + level: float + r"""Author trust level (-1, 0, 1, 2, 3, or 4)""" + manual: bool + r"""True if the trust level was set manually by a moderator""" + + +class SubmitContentTrustLevel(BaseModel): + level: float + r"""Author trust level (-1, 0, 1, 2, 3, or 4)""" + + manual: bool + r"""True if the trust level was set manually by a moderator""" + + +class AuthorTypedDict(TypedDict): + id: str + r"""Author ID in Moderation API""" + block: Nullable[SubmitContentBlockTypedDict] + r"""Block or suspension details, if applicable. Null if the author is enabled.""" + status: AuthorStatus + r"""Current author status""" + trust_level: SubmitContentTrustLevelTypedDict + external_id: NotRequired[Nullable[str]] + r"""The author's ID from your system""" + + +class Author(BaseModel): + id: str + r"""Author ID in Moderation API""" + + block: Nullable[SubmitContentBlock] + r"""Block or suspension details, if applicable. Null if the author is enabled.""" + + status: AuthorStatus + r"""Current author status""" + + trust_level: SubmitContentTrustLevel + + external_id: OptionalNullable[str] = UNSET + r"""The author's ID from your system""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["external_id"]) + nullable_fields = set(["external_id", "block"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +class EvaluationTypedDict(TypedDict): + r"""The evaluation of the content after running the channel policies.""" + + flagged: bool + r"""Whether the content was flagged by any policy.""" + flag_probability: float + r"""The probability that the content should be flagged.""" + severity_score: float + r"""The severity score of the content. A higher score indicates more severe content.""" + unicode_spoofed: NotRequired[bool] + r"""Whether the content was flagged for Unicode spoofing.""" + shadow_flagged: NotRequired[bool] + r"""Whether the content would have been flagged by analyze-only policies (flag: false).""" + + +class Evaluation(BaseModel): + r"""The evaluation of the content after running the channel policies.""" + + flagged: bool + r"""Whether the content was flagged by any policy.""" + + flag_probability: float + r"""The probability that the content should be flagged.""" + + severity_score: float + r"""The severity score of the content. A higher score indicates more severe content.""" + + unicode_spoofed: Optional[bool] = None + r"""Whether the content was flagged for Unicode spoofing.""" + + shadow_flagged: Optional[bool] = None + r"""Whether the content would have been flagged by analyze-only policies (flag: false).""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["unicode_spoofed", "shadow_flagged"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +SubmitContentRecommendationAction = Union[ + Literal[ + "review", + "allow", + "reject", + ], + UnrecognizedStr, +] +r"""The action to take based on the recommendation""" + + +ReasonCodeEnum = Union[ + Literal[ + "severity_reject", + "severity_review", + "author_block", + "dry_run", + "trusted_allow", + "untrusted_severity", + "rule_match", + "rule_default", + "rule_fallback", + "client_override", + "casebook_match", + ], + UnrecognizedStr, +] + + +ReasonCodeTypedDict = TypeAliasType("ReasonCodeTypedDict", Union[ReasonCodeEnum, str]) + + +ReasonCode = TypeAliasType("ReasonCode", Union[ReasonCodeEnum, str]) + + +class MatchedRuleTypedDict(TypedDict): + key: str + name: str + + +class MatchedRule(BaseModel): + key: str + + name: str + + +class RecommendationTypedDict(TypedDict): + r"""The recommendation for the content based on the evaluation.""" + + action: SubmitContentRecommendationAction + r"""The action to take based on the recommendation""" + reason_codes: List[ReasonCodeTypedDict] + r"""Reason codes for the recommendation. Standard codes plus a `rule:` code identifying the rule that produced the action. Can be used to display a reason to the user.""" + matched_rules: NotRequired[List[MatchedRuleTypedDict]] + r"""Rules that matched during evaluation, if rules engine is active.""" + + +class Recommendation(BaseModel): + r"""The recommendation for the content based on the evaluation.""" + + action: SubmitContentRecommendationAction + r"""The action to take based on the recommendation""" + + reason_codes: List[ReasonCode] + r"""Reason codes for the recommendation. Standard codes plus a `rule:` code identifying the rule that produced the action. Can be used to display a reason to the user.""" + + matched_rules: Optional[List[MatchedRule]] = None + r"""Rules that matched during evaluation, if rules engine is active.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["matched_rules"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +Verdict = Union[ + Literal[ + "allow", + "reject", + ], + UnrecognizedStr, +] +r"""The ruling your past decisions point to for this content.""" + + +class TopicTypedDict(TypedDict): + id: str + label: str + + +class Topic(BaseModel): + id: str + + label: str + + +class CasebookTypedDict(TypedDict): + verdict: Verdict + r"""The ruling your past decisions point to for this content.""" + similarity: float + r"""How close the nearest matching case is, from 0 to 1. 1 means the content is identical to something you have already decided.""" + agreement: float + r"""How unanimous the matching cases are, from 0 to 1: the share of them that decided this way, ignoring how many there are. Always at least 0.8 when a ruling is returned — below that the casebook reports a disagreement instead of picking a side — so it tells you how clean the consensus was, and is not a threshold to re-apply yourself.""" + confidence: float + r"""How strongly the casebook holds this ruling, from 0 to 1: the agreement scaled by how much evidence backs it, so a handful of close, recent cases outweighs one distant one. Older cases count for less, halving in weight roughly every 180 days. This is the number to use in rules when you want a strength condition.""" + case_count: float + r"""How many of your past cases backed this ruling.""" + topic: Nullable[TopicTypedDict] + r"""The topic the closest matching case is filed under, or null when it has not been grouped into one yet.""" + + +class Casebook(BaseModel): + verdict: Verdict + r"""The ruling your past decisions point to for this content.""" + + similarity: float + r"""How close the nearest matching case is, from 0 to 1. 1 means the content is identical to something you have already decided.""" + + agreement: float + r"""How unanimous the matching cases are, from 0 to 1: the share of them that decided this way, ignoring how many there are. Always at least 0.8 when a ruling is returned — below that the casebook reports a disagreement instead of picking a side — so it tells you how clean the consensus was, and is not a threshold to re-apply yourself.""" + + confidence: float + r"""How strongly the casebook holds this ruling, from 0 to 1: the agreement scaled by how much evidence backs it, so a handful of close, recent cases outweighs one distant one. Older cases count for less, halving in weight roughly every 180 days. This is the number to use in rules when you want a strength condition.""" + + case_count: float + r"""How many of your past cases backed this ruling.""" + + topic: Nullable[Topic] + r"""The topic the closest matching case is filed under, or null when it has not been grouped into one yet.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + m[k] = val + + return m + + +Method = Union[ + Literal[ + "registered_domain_token", + "subdomain_token", + ], + UnrecognizedStr, +] + + +class SubmitContentBrandImpersonationTypedDict(TypedDict): + brand: str + method: Method + + +class SubmitContentBrandImpersonation(BaseModel): + brand: str + + method: Method + + +class SignalsResponseTypedDict(TypedDict): + r"""Observable properties of a URL (URL Risk only). Absent for allow/block list matches.""" + + brand_impersonation: Nullable[SubmitContentBrandImpersonationTypedDict] + has_suspicious_characters: bool + is_link_shortener: bool + domain_age_days: Nullable[int] + has_email_setup: Nullable[bool] + redirect_count: Nullable[int] + final_url: Nullable[str] + bot_protection: Nullable[bool] + is_reported: bool + + +class SignalsResponse(BaseModel): + r"""Observable properties of a URL (URL Risk only). Absent for allow/block list matches.""" + + brand_impersonation: Nullable[SubmitContentBrandImpersonation] + + has_suspicious_characters: bool + + is_link_shortener: bool + + domain_age_days: Nullable[int] + + has_email_setup: Nullable[bool] + + redirect_count: Nullable[int] + + final_url: Nullable[str] + + bot_protection: Nullable[bool] + + is_reported: bool + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + m[k] = val + + return m + + +class SubmitContentMatchTypedDict(TypedDict): + probability: float + match: str + span: List[int] + reasons: NotRequired[List[str]] + r"""Stable codes explaining why a URL was flagged (URL Risk only).""" + signals: NotRequired[SignalsResponseTypedDict] + r"""Observable properties of a URL (URL Risk only). Absent for allow/block list matches.""" + entity_type: NotRequired[str] + r"""Sub-type of the entity match — e.g. the NER key (email, phone, name, …) for PII matches. Absent for URL Risk and wordlist matches where the type is already encoded in the parent label.""" + + +class SubmitContentMatch(BaseModel): + probability: float + + match: str + + span: List[int] + + reasons: Optional[List[str]] = None + r"""Stable codes explaining why a URL was flagged (URL Risk only).""" + + signals: Optional[SignalsResponse] = None + r"""Observable properties of a URL (URL Risk only). Absent for allow/block list matches.""" + + entity_type: Optional[str] = None + r"""Sub-type of the entity match — e.g. the NER key (email, phone, name, …) for PII matches. Absent for URL Risk and wordlist matches where the type is already encoded in the parent label.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["reasons", "signals", "entity_type"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class EntityMatcherOutputTypedDict(TypedDict): + r"""Entity matcher policy.""" + + id: str + probability: float + flagged: bool + matches: List[SubmitContentMatchTypedDict] + type: Literal["entity_matcher"] + flagged_fields: NotRequired[List[str]] + + +class EntityMatcherOutput(BaseModel): + r"""Entity matcher policy.""" + + id: str + + probability: float + + flagged: bool + + matches: List[SubmitContentMatch] + + type: Annotated[ + Annotated[ + Literal["entity_matcher"], AfterValidator(validate_const("entity_matcher")) + ], + pydantic.Field(alias="type"), + ] = "entity_matcher" + + flagged_fields: Optional[List[str]] = None + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["flagged_fields"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class SubmitContentLabelTypedDict(TypedDict): + id: str + probability: float + flagged: bool + shadow_flagged: NotRequired[bool] + + +class SubmitContentLabel(BaseModel): + id: str + + probability: float + + flagged: bool + + shadow_flagged: Annotated[Optional[bool], pydantic.Field(alias="shadowFlagged")] = ( + None + ) + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["shadowFlagged"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ClassifierOutputTypedDict(TypedDict): + r"""Classifier policy.""" + + id: str + r"""The unique identifier for the classifier output.""" + probability: float + flagged: bool + type: Literal["classifier"] + flagged_fields: NotRequired[List[str]] + r"""The keys of the flagged fields if submitting an object.""" + labels: NotRequired[List[SubmitContentLabelTypedDict]] + data: NotRequired[Dict[str, Any]] + r"""Optional structured data produced by the policy. For face detection: { count, faces: [{ confidence, gender, age }] }.""" + + +class ClassifierOutput(BaseModel): + r"""Classifier policy.""" + + id: str + r"""The unique identifier for the classifier output.""" + + probability: float + + flagged: bool + + type: Annotated[ + Annotated[Literal["classifier"], AfterValidator(validate_const("classifier"))], + pydantic.Field(alias="type"), + ] = "classifier" + + flagged_fields: Optional[List[str]] = None + r"""The keys of the flagged fields if submitting an object.""" + + labels: Optional[List[SubmitContentLabel]] = None + + data: Optional[Dict[str, Any]] = None + r"""Optional structured data produced by the policy. For face detection: { count, faces: [{ confidence, gender, age }] }.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["flagged_fields", "labels", "data"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +PolicyResponseTypedDict = TypeAliasType( + "PolicyResponseTypedDict", + Union[EntityMatcherOutputTypedDict, ClassifierOutputTypedDict], +) +r"""Policy output schema.""" + + +class UnknownPolicyResponse(BaseModel): + r"""A PolicyResponse variant the SDK doesn't recognize. Preserves the raw payload.""" + + type: Literal["UNKNOWN"] = "UNKNOWN" + raw: Any + is_unknown: Literal[True] = True + + model_config = ConfigDict(frozen=True) + + +_POLICY_RESPONSE_VARIANTS: dict[str, Any] = { + "classifier": ClassifierOutput, + "entity_matcher": EntityMatcherOutput, +} + + +PolicyResponse = Annotated[ + Union[ClassifierOutput, EntityMatcherOutput, UnknownPolicyResponse], + BeforeValidator( + partial( + parse_open_union, + disc_key="type", + variants=_POLICY_RESPONSE_VARIANTS, + unknown_cls=UnknownPolicyResponse, + union_name="PolicyResponse", + ) + ), +] +r"""Policy output schema.""" + + +class CompassionInsightTypedDict(TypedDict): + r"""Compassion insight. Identifies with or shows concern, empathy, or support for the feelings/emotions of others.""" + + probability: float + value: Nullable[bool] + r"""Whether the content shows compassion.""" + id: Literal["compassion"] + type: Literal["insight"] + + +class CompassionInsight(BaseModel): + r"""Compassion insight. Identifies with or shows concern, empathy, or support for the feelings/emotions of others.""" + + probability: float + + value: Nullable[bool] + r"""Whether the content shows compassion.""" + + id: Annotated[ + Annotated[Literal["compassion"], AfterValidator(validate_const("compassion"))], + pydantic.Field(alias="id"), + ] = "compassion" + + type: Annotated[ + Annotated[Literal["insight"], AfterValidator(validate_const("insight"))], + pydantic.Field(alias="type"), + ] = "insight" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + m[k] = val + + return m + + +class PersonalStoryInsightTypedDict(TypedDict): + r"""Personal story insight. Includes a personal experience or story as a source of support for the statements made in the comment.""" + + probability: float + value: Nullable[bool] + r"""Whether the content shows personal story.""" + id: Literal["personal_story"] + type: Literal["insight"] + + +class PersonalStoryInsight(BaseModel): + r"""Personal story insight. Includes a personal experience or story as a source of support for the statements made in the comment.""" + + probability: float + + value: Nullable[bool] + r"""Whether the content shows personal story.""" + + id: Annotated[ + Annotated[ + Literal["personal_story"], AfterValidator(validate_const("personal_story")) + ], + pydantic.Field(alias="id"), + ] = "personal_story" + + type: Annotated[ + Annotated[Literal["insight"], AfterValidator(validate_const("insight"))], + pydantic.Field(alias="type"), + ] = "insight" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + m[k] = val + + return m + + +class RespectInsightTypedDict(TypedDict): + r"""Respect insight. Shows deference or appreciation to others, or acknowledges the validity of another person.""" + + probability: float + value: Nullable[bool] + r"""Whether the content shows respect.""" + id: Literal["respect"] + type: Literal["insight"] + + +class RespectInsight(BaseModel): + r"""Respect insight. Shows deference or appreciation to others, or acknowledges the validity of another person.""" + + probability: float + + value: Nullable[bool] + r"""Whether the content shows respect.""" + + id: Annotated[ + Annotated[Literal["respect"], AfterValidator(validate_const("respect"))], + pydantic.Field(alias="id"), + ] = "respect" + + type: Annotated[ + Annotated[Literal["insight"], AfterValidator(validate_const("insight"))], + pydantic.Field(alias="type"), + ] = "insight" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + m[k] = val + + return m + + +class CuriosityInsightTypedDict(TypedDict): + r"""Curiosity insight. Attempts to clarify or ask follow-up questions to better understand another person or idea.""" + + probability: float + value: Nullable[bool] + r"""Whether the content shows curiosity.""" + id: Literal["curiosity"] + type: Literal["insight"] + + +class CuriosityInsight(BaseModel): + r"""Curiosity insight. Attempts to clarify or ask follow-up questions to better understand another person or idea.""" + + probability: float + + value: Nullable[bool] + r"""Whether the content shows curiosity.""" + + id: Annotated[ + Annotated[Literal["curiosity"], AfterValidator(validate_const("curiosity"))], + pydantic.Field(alias="id"), + ] = "curiosity" + + type: Annotated[ + Annotated[Literal["insight"], AfterValidator(validate_const("insight"))], + pydantic.Field(alias="type"), + ] = "insight" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + m[k] = val + + return m + + +class LanguageInsightTypedDict(TypedDict): + r"""Language insight""" + + probability: float + value: Nullable[str] + id: Literal["language"] + type: Literal["insight"] + + +class LanguageInsight(BaseModel): + r"""Language insight""" + + probability: float + + value: Nullable[str] + + id: Annotated[ + Annotated[Literal["language"], AfterValidator(validate_const("language"))], + pydantic.Field(alias="id"), + ] = "language" + + type: Annotated[ + Annotated[Literal["insight"], AfterValidator(validate_const("insight"))], + pydantic.Field(alias="type"), + ] = "insight" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + m[k] = val + + return m + + +Value = Union[ + Literal[ + "positive", + "neutral", + "negative", + ], + UnrecognizedStr, +] + + +class ScoresTypedDict(TypedDict): + r"""Positive and negative scored independently: mixed content scores high on both. Present whenever `value` is set.""" + + positive: float + r"""How positive the content is, 0 to 1.""" + negative: float + r"""How negative the content is, 0 to 1.""" + + +class Scores(BaseModel): + r"""Positive and negative scored independently: mixed content scores high on both. Present whenever `value` is set.""" + + positive: float + r"""How positive the content is, 0 to 1.""" + + negative: float + r"""How negative the content is, 0 to 1.""" + + +class SentimentInsightTypedDict(TypedDict): + r"""Sentiment insight""" + + probability: float + value: Nullable[Value] + id: Literal["sentiment"] + type: Literal["insight"] + scores: NotRequired[ScoresTypedDict] + r"""Positive and negative scored independently: mixed content scores high on both. Present whenever `value` is set.""" + + +class SentimentInsight(BaseModel): + r"""Sentiment insight""" + + probability: float + + value: Nullable[Value] + + id: Annotated[ + Annotated[Literal["sentiment"], AfterValidator(validate_const("sentiment"))], + pydantic.Field(alias="id"), + ] = "sentiment" + + type: Annotated[ + Annotated[Literal["insight"], AfterValidator(validate_const("insight"))], + pydantic.Field(alias="type"), + ] = "insight" + + scores: Optional[Scores] = None + r"""Positive and negative scored independently: mixed content scores high on both. Present whenever `value` is set.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["scores"]) + nullable_fields = set(["value"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +InsightTypedDict = TypeAliasType( + "InsightTypedDict", + Union[ + LanguageInsightTypedDict, + CuriosityInsightTypedDict, + RespectInsightTypedDict, + PersonalStoryInsightTypedDict, + CompassionInsightTypedDict, + SentimentInsightTypedDict, + ], +) + + +class UnknownInsight(BaseModel): + r"""A Insight variant the SDK doesn't recognize. Preserves the raw payload.""" + + id: Literal["UNKNOWN"] = "UNKNOWN" + raw: Any + is_unknown: Literal[True] = True + + model_config = ConfigDict(frozen=True) + + +_INSIGHT_VARIANTS: dict[str, Any] = { + "sentiment": SentimentInsight, + "language": LanguageInsight, + "curiosity": CuriosityInsight, + "respect": RespectInsight, + "personal_story": PersonalStoryInsight, + "compassion": CompassionInsight, +} + + +Insight = Annotated[ + Union[ + SentimentInsight, + LanguageInsight, + CuriosityInsight, + RespectInsight, + PersonalStoryInsight, + CompassionInsight, + UnknownInsight, + ], + BeforeValidator( + partial( + parse_open_union, + disc_key="id", + variants=_INSIGHT_VARIANTS, + unknown_cls=UnknownInsight, + union_name="Insight", + ) + ), +] + + +MetaStatus = Union[ + Literal[ + "success", + "partial_success", + ], + UnrecognizedStr, +] + + +class MetaTypedDict(TypedDict): + r"""Metadata about the moderation request""" + + status: MetaStatus + timestamp: float + channel_key: str + r"""The unique key of the channel where the content was handled. Either the channel provided by you or automatically routed.""" + usage: float + processing_time: NotRequired[str] + + +class Meta(BaseModel): + r"""Metadata about the moderation request""" + + status: MetaStatus + + timestamp: float + + channel_key: str + r"""The unique key of the channel where the content was handled. Either the channel provided by you or automatically routed.""" + + usage: float + + processing_time: Optional[str] = None + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["processing_time"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class ErrorTypedDict(TypedDict): + id: str + message: str + + +class Error(BaseModel): + id: str + + message: str + + +class SubmitContentResponseTypedDict(TypedDict): + r"""Successful response""" + + content: ContentResponseTypedDict + r"""Potentially modified content.""" + author: Nullable[AuthorTypedDict] + r"""The author of the content if your account has authors enabled. Requires you to send authorId when submitting content.""" + evaluation: EvaluationTypedDict + r"""The evaluation of the content after running the channel policies.""" + recommendation: RecommendationTypedDict + r"""The recommendation for the content based on the evaluation.""" + casebook: Nullable[CasebookTypedDict] + r"""What your casebook — the record of your past moderation decisions — found for this content, or null when it had nothing close enough to say, when the matching cases disagreed, or when casebook lookups are not switched on for this channel. Reports what the casebook found; whether it decided the outcome is shown in `recommendation`, where a higher-priority rule may have settled the item first.""" + policies: List[PolicyResponseTypedDict] + r"""Results of all policies in the channel. Sorted by highest probability.""" + insights: List[InsightTypedDict] + r"""Results of all insights enabled in the channel.""" + meta: MetaTypedDict + r"""Metadata about the moderation request""" + errors: NotRequired[List[ErrorTypedDict]] + r"""Policies that had errors""" + + +class SubmitContentResponse(BaseModel): + r"""Successful response""" + + content: ContentResponse + r"""Potentially modified content.""" + + author: Nullable[Author] + r"""The author of the content if your account has authors enabled. Requires you to send authorId when submitting content.""" + + evaluation: Evaluation + r"""The evaluation of the content after running the channel policies.""" + + recommendation: Recommendation + r"""The recommendation for the content based on the evaluation.""" + + casebook: Nullable[Casebook] + r"""What your casebook — the record of your past moderation decisions — found for this content, or null when it had nothing close enough to say, when the matching cases disagreed, or when casebook lookups are not switched on for this channel. Reports what the casebook found; whether it decided the outcome is shown in `recommendation`, where a higher-priority rule may have settled the item first.""" + + policies: List[PolicyResponse] + r"""Results of all policies in the channel. Sorted by highest probability.""" + + insights: List[Insight] + r"""Results of all insights enabled in the channel.""" + + meta: Meta + r"""Metadata about the moderation request""" + + errors: Optional[List[Error]] = None + r"""Policies that had errors""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["errors"]) + nullable_fields = set(["author", "casebook"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +try: + DataAudioRequest.model_rebuild() +except NameError: + pass +try: + DataVideoRequest.model_rebuild() +except NameError: + pass +try: + DataImageRequest.model_rebuild() +except NameError: + pass +try: + DataTextRequest.model_rebuild() +except NameError: + pass +try: + SubmitContentObject.model_rebuild() +except NameError: + pass +try: + AudioRequest.model_rebuild() +except NameError: + pass +try: + VideoRequest.model_rebuild() +except NameError: + pass +try: + ImageRequest.model_rebuild() +except NameError: + pass +try: + TextRequest.model_rebuild() +except NameError: + pass +try: + Guideline.model_rebuild() +except NameError: + pass +try: + URLRisk.model_rebuild() +except NameError: + pass +try: + Entities2.model_rebuild() +except NameError: + pass +try: + URLMasking.model_rebuild() +except NameError: + pass +try: + Entities1.model_rebuild() +except NameError: + pass +try: + PIIMasking.model_rebuild() +except NameError: + pass +try: + UnicodeSpoofing.model_rebuild() +except NameError: + pass +try: + CodeAbuse.model_rebuild() +except NameError: + pass +try: + Extremism.model_rebuild() +except NameError: + pass +try: + Promotion.model_rebuild() +except NameError: + pass +try: + Weapons.model_rebuild() +except NameError: + pass +try: + Tobacco.model_rebuild() +except NameError: + pass +try: + Gambling.model_rebuild() +except NameError: + pass +try: + Drugs.model_rebuild() +except NameError: + pass +try: + Alcohol.model_rebuild() +except NameError: + pass +try: + IllegalTrade.model_rebuild() +except NameError: + pass +try: + ChannelCircumvention.model_rebuild() +except NameError: + pass +try: + SexualSolicitation.model_rebuild() +except NameError: + pass +try: + Threat.model_rebuild() +except NameError: + pass +try: + Religion.model_rebuild() +except NameError: + pass +try: + Political.model_rebuild() +except NameError: + pass +try: + SelfPromotion.model_rebuild() +except NameError: + pass +try: + FaceDetection.model_rebuild() +except NameError: + pass +try: + LowQualityContent.model_rebuild() +except NameError: + pass +try: + Spam.model_rebuild() +except NameError: + pass +try: + SelfHarm.model_rebuild() +except NameError: + pass +try: + Violence.model_rebuild() +except NameError: + pass +try: + Profanity.model_rebuild() +except NameError: + pass +try: + SexualMinors.model_rebuild() +except NameError: + pass +try: + Sexual.model_rebuild() +except NameError: + pass +try: + Crypto.model_rebuild() +except NameError: + pass +try: + Adult.model_rebuild() +except NameError: + pass +try: + Cannabis.model_rebuild() +except NameError: + pass +try: + IllicitGambling.model_rebuild() +except NameError: + pass +try: + IllicitTobacco.model_rebuild() +except NameError: + pass +try: + IllicitFirearms.model_rebuild() +except NameError: + pass +try: + IllicitAlcohol.model_rebuild() +except NameError: + pass +try: + IllicitDrugs.model_rebuild() +except NameError: + pass +try: + Illicit.model_rebuild() +except NameError: + pass +try: + Hate.model_rebuild() +except NameError: + pass +try: + PersonalInformation.model_rebuild() +except NameError: + pass +try: + Toxicity.model_rebuild() +except NameError: + pass +try: + SubmitContentRequest.model_rebuild() +except NameError: + pass +try: + AudioResponse.model_rebuild() +except NameError: + pass +try: + VideoResponse.model_rebuild() +except NameError: + pass +try: + ImageResponse.model_rebuild() +except NameError: + pass +try: + TextResponse.model_rebuild() +except NameError: + pass +try: + EntityMatcherOutput.model_rebuild() +except NameError: + pass +try: + SubmitContentLabel.model_rebuild() +except NameError: + pass +try: + ClassifierOutput.model_rebuild() +except NameError: + pass +try: + CompassionInsight.model_rebuild() +except NameError: + pass +try: + PersonalStoryInsight.model_rebuild() +except NameError: + pass +try: + RespectInsight.model_rebuild() +except NameError: + pass +try: + CuriosityInsight.model_rebuild() +except NameError: + pass +try: + LanguageInsight.model_rebuild() +except NameError: + pass +try: + SentimentInsight.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/unresolvequeueitemop.py b/src/moderation_api/models/unresolvequeueitemop.py new file mode 100644 index 0000000..820625d --- /dev/null +++ b/src/moderation_api/models/unresolvequeueitemop.py @@ -0,0 +1,90 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel, UNSET_SENTINEL +from moderation_api.utils import FieldMetadata, PathParamMetadata, RequestMetadata +import pydantic +from pydantic import model_serializer +from typing import Optional +from typing_extensions import Annotated, NotRequired, TypedDict + + +class UnresolveQueueItemRequestBodyTypedDict(TypedDict): + comment: NotRequired[str] + r"""Optional reason for unresolving the item""" + + +class UnresolveQueueItemRequestBody(BaseModel): + comment: Optional[str] = None + r"""Optional reason for unresolving the item""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["comment"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class UnresolveQueueItemRequestTypedDict(TypedDict): + id: str + r"""The queue ID""" + item_id: str + r"""The item ID to unresolve""" + body: UnresolveQueueItemRequestBodyTypedDict + + +class UnresolveQueueItemRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The queue ID""" + + item_id: Annotated[ + str, + pydantic.Field(alias="itemId"), + FieldMetadata(path=PathParamMetadata(style="simple", explode=False)), + ] + r"""The item ID to unresolve""" + + body: Annotated[ + UnresolveQueueItemRequestBody, + FieldMetadata(request=RequestMetadata(media_type="application/json")), + ] + + +class UnresolveQueueItemResponseTypedDict(TypedDict): + r"""Successful response""" + + success: bool + status: str + r"""New status of the item""" + unresolved_at: str + r"""Timestamp when the item was unresolved""" + + +class UnresolveQueueItemResponse(BaseModel): + r"""Successful response""" + + success: bool + + status: str + r"""New status of the item""" + + unresolved_at: Annotated[str, pydantic.Field(alias="unresolvedAt")] + r"""Timestamp when the item was unresolved""" + + +try: + UnresolveQueueItemResponse.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/updateactionop.py b/src/moderation_api/models/updateactionop.py new file mode 100644 index 0000000..624099a --- /dev/null +++ b/src/moderation_api/models/updateactionop.py @@ -0,0 +1,359 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import ( + BaseModel, + Nullable, + OptionalNullable, + UNSET, + UNSET_SENTINEL, + UnrecognizedStr, +) +from moderation_api.utils import FieldMetadata, PathParamMetadata, RequestMetadata +import pydantic +from pydantic import model_serializer +from typing import List, Literal, Optional, Union +from typing_extensions import Annotated, NotRequired, TypedDict + + +UpdateActionTypeRequest = Literal[ + "AUTHOR_BLOCK", + "AUTHOR_BLOCK_TEMP", + "AUTHOR_UNBLOCK", + "AUTHOR_DELETE", + "AUTHOR_REPORT", + "AUTHOR_WARN", + "AUTHOR_CUSTOM", + "ITEM_REJECT", + "ITEM_ALLOW", + "ITEM_CUSTOM", +] + + +UpdateActionQueueBehaviourRequest = Literal[ + "REMOVE", + "ADD", + "NO_CHANGE", +] +r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + + +UpdateActionPositionRequest = Literal[ + "ALL_QUEUES", + "SOME_QUEUES", + "HIDDEN", +] +r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + + +class UpdateActionPossibleValueRequestTypedDict(TypedDict): + value: str + r"""The value of the action.""" + + +class UpdateActionPossibleValueRequest(BaseModel): + value: str + r"""The value of the action.""" + + +class UpdateActionRequestBodyTypedDict(TypedDict): + key: NotRequired[Nullable[str]] + r"""User defined key of the action.""" + name: NotRequired[str] + r"""The name of the action.""" + description: NotRequired[Nullable[str]] + r"""The description of the action.""" + type: NotRequired[Nullable[UpdateActionTypeRequest]] + r"""The type of the action.""" + built_in: NotRequired[Nullable[bool]] + r"""Whether the action is a built-in action or a custom one.""" + queue_behaviour: NotRequired[UpdateActionQueueBehaviourRequest] + r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + filter_in_queue_ids: NotRequired[List[str]] + r"""The IDs of the queues the action is available in.""" + position: NotRequired[UpdateActionPositionRequest] + r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + possible_values: NotRequired[List[UpdateActionPossibleValueRequestTypedDict]] + r"""The possible values of the action. The user will be prompted to select one of these values when executing the action.""" + value_required: NotRequired[bool] + r"""Whether the action requires a value to be executed.""" + free_text: NotRequired[bool] + r"""Whether the action allows any text to be entered as a value or if it must be one of the possible values.""" + + +class UpdateActionRequestBody(BaseModel): + key: OptionalNullable[str] = UNSET + r"""User defined key of the action.""" + + name: Optional[str] = None + r"""The name of the action.""" + + description: OptionalNullable[str] = UNSET + r"""The description of the action.""" + + type: OptionalNullable[UpdateActionTypeRequest] = UNSET + r"""The type of the action.""" + + built_in: Annotated[OptionalNullable[bool], pydantic.Field(alias="builtIn")] = False + r"""Whether the action is a built-in action or a custom one.""" + + queue_behaviour: Annotated[ + Optional[UpdateActionQueueBehaviourRequest], + pydantic.Field(alias="queueBehaviour"), + ] = "NO_CHANGE" + r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + + filter_in_queue_ids: Annotated[ + Optional[List[str]], pydantic.Field(alias="filterInQueueIds") + ] = None + r"""The IDs of the queues the action is available in.""" + + position: Optional[UpdateActionPositionRequest] = "ALL_QUEUES" + r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + + possible_values: Annotated[ + Optional[List[UpdateActionPossibleValueRequest]], + pydantic.Field(alias="possibleValues"), + ] = None + r"""The possible values of the action. The user will be prompted to select one of these values when executing the action.""" + + value_required: Annotated[Optional[bool], pydantic.Field(alias="valueRequired")] = ( + False + ) + r"""Whether the action requires a value to be executed.""" + + free_text: Annotated[Optional[bool], pydantic.Field(alias="freeText")] = False + r"""Whether the action allows any text to be entered as a value or if it must be one of the possible values.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "key", + "name", + "description", + "type", + "builtIn", + "queueBehaviour", + "filterInQueueIds", + "position", + "possibleValues", + "valueRequired", + "freeText", + ] + ) + nullable_fields = set(["key", "description", "type", "builtIn"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +class UpdateActionRequestTypedDict(TypedDict): + id: str + r"""The ID of the action to update.""" + body: UpdateActionRequestBodyTypedDict + + +class UpdateActionRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The ID of the action to update.""" + + body: Annotated[ + UpdateActionRequestBody, + FieldMetadata(request=RequestMetadata(media_type="application/json")), + ] + + +UpdateActionTypeResponse = Union[ + Literal[ + "AUTHOR_BLOCK", + "AUTHOR_BLOCK_TEMP", + "AUTHOR_UNBLOCK", + "AUTHOR_DELETE", + "AUTHOR_REPORT", + "AUTHOR_WARN", + "AUTHOR_CUSTOM", + "ITEM_REJECT", + "ITEM_ALLOW", + "ITEM_CUSTOM", + ], + UnrecognizedStr, +] + + +UpdateActionQueueBehaviourResponse = Union[ + Literal[ + "REMOVE", + "ADD", + "NO_CHANGE", + ], + UnrecognizedStr, +] +r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + + +UpdateActionPositionResponse = Union[ + Literal[ + "ALL_QUEUES", + "SOME_QUEUES", + "HIDDEN", + ], + UnrecognizedStr, +] +r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + + +class UpdateActionPossibleValueResponseTypedDict(TypedDict): + value: str + r"""The value of the action.""" + + +class UpdateActionPossibleValueResponse(BaseModel): + value: str + r"""The value of the action.""" + + +class UpdateActionResponseTypedDict(TypedDict): + r"""Action updated successfully""" + + id: str + r"""The ID of the action.""" + created_at: str + r"""The date the action was created.""" + name: str + r"""The name of the action.""" + filter_in_queue_ids: List[str] + r"""The IDs of the queues the action is available in.""" + possible_values: List[UpdateActionPossibleValueResponseTypedDict] + r"""The possible values of the action. The user will be prompted to select one of these values when executing the action.""" + key: NotRequired[Nullable[str]] + r"""User defined key of the action.""" + description: NotRequired[Nullable[str]] + r"""The description of the action.""" + type: NotRequired[Nullable[UpdateActionTypeResponse]] + r"""The type of the action.""" + built_in: NotRequired[Nullable[bool]] + r"""Whether the action is a built-in action or a custom one.""" + queue_behaviour: NotRequired[UpdateActionQueueBehaviourResponse] + r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + position: NotRequired[UpdateActionPositionResponse] + r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + value_required: NotRequired[bool] + r"""Whether the action requires a value to be executed.""" + free_text: NotRequired[bool] + r"""Whether the action allows any text to be entered as a value or if it must be one of the possible values.""" + + +class UpdateActionResponse(BaseModel): + r"""Action updated successfully""" + + id: str + r"""The ID of the action.""" + + created_at: Annotated[str, pydantic.Field(alias="createdAt")] + r"""The date the action was created.""" + + name: str + r"""The name of the action.""" + + filter_in_queue_ids: Annotated[List[str], pydantic.Field(alias="filterInQueueIds")] + r"""The IDs of the queues the action is available in.""" + + possible_values: Annotated[ + List[UpdateActionPossibleValueResponse], pydantic.Field(alias="possibleValues") + ] + r"""The possible values of the action. The user will be prompted to select one of these values when executing the action.""" + + key: OptionalNullable[str] = UNSET + r"""User defined key of the action.""" + + description: OptionalNullable[str] = UNSET + r"""The description of the action.""" + + type: OptionalNullable[UpdateActionTypeResponse] = UNSET + r"""The type of the action.""" + + built_in: Annotated[OptionalNullable[bool], pydantic.Field(alias="builtIn")] = False + r"""Whether the action is a built-in action or a custom one.""" + + queue_behaviour: Annotated[ + Optional[UpdateActionQueueBehaviourResponse], + pydantic.Field(alias="queueBehaviour"), + ] = "NO_CHANGE" + r"""Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.""" + + position: Optional[UpdateActionPositionResponse] = "ALL_QUEUES" + r"""Show the action in all queues, selected queues or no queues (to use via API only).""" + + value_required: Annotated[Optional[bool], pydantic.Field(alias="valueRequired")] = ( + False + ) + r"""Whether the action requires a value to be executed.""" + + free_text: Annotated[Optional[bool], pydantic.Field(alias="freeText")] = False + r"""Whether the action allows any text to be entered as a value or if it must be one of the possible values.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "key", + "description", + "type", + "builtIn", + "queueBehaviour", + "position", + "valueRequired", + "freeText", + ] + ) + nullable_fields = set(["key", "description", "type", "builtIn"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +try: + UpdateActionRequestBody.model_rebuild() +except NameError: + pass +try: + UpdateActionResponse.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/updateauthorop.py b/src/moderation_api/models/updateauthorop.py new file mode 100644 index 0000000..ceb8fb4 --- /dev/null +++ b/src/moderation_api/models/updateauthorop.py @@ -0,0 +1,211 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import ( + BaseModel, + Nullable, + OptionalNullable, + UNSET, + UNSET_SENTINEL, +) +from moderation_api.utils import FieldMetadata, PathParamMetadata, RequestMetadata +import pydantic +from pydantic import ConfigDict, model_serializer +from typing import Any, Dict, Optional +from typing_extensions import Annotated, NotRequired, TypedDict + + +class UpdateAuthorMetadataTypedDict(TypedDict): + r"""Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.""" + + email_verified: NotRequired[Nullable[bool]] + r"""Whether the author's email is verified""" + phone_verified: NotRequired[Nullable[bool]] + r"""Whether the author's phone number is verified""" + identity_verified: NotRequired[Nullable[bool]] + r"""Whether the author's identity is verified""" + is_paying_customer: NotRequired[Nullable[bool]] + r"""Whether the author is a paying customer""" + + +class UpdateAuthorMetadata(BaseModel): + r"""Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.""" + + model_config = ConfigDict( + populate_by_name=True, arbitrary_types_allowed=True, extra="allow" + ) + __pydantic_extra__: Dict[str, Any] = pydantic.Field(init=False) + + email_verified: OptionalNullable[bool] = UNSET + r"""Whether the author's email is verified""" + + phone_verified: OptionalNullable[bool] = UNSET + r"""Whether the author's phone number is verified""" + + identity_verified: OptionalNullable[bool] = UNSET + r"""Whether the author's identity is verified""" + + is_paying_customer: OptionalNullable[bool] = UNSET + r"""Whether the author is a paying customer""" + + @property + def additional_properties(self): + return self.__pydantic_extra__ + + @additional_properties.setter + def additional_properties(self, value): + self.__pydantic_extra__ = value # pyright: ignore[reportIncompatibleVariableOverride] + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "email_verified", + "phone_verified", + "identity_verified", + "is_paying_customer", + ] + ) + nullable_fields = set( + [ + "email_verified", + "phone_verified", + "identity_verified", + "is_paying_customer", + ] + ) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + serialized.pop(k, serialized.pop(n, None)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + for k, v in serialized.items(): + m[k] = v + + return m + + +class UpdateAuthorRequestBodyTypedDict(TypedDict): + profile_picture: NotRequired[Nullable[str]] + r"""URL of the author's profile picture""" + external_link: NotRequired[Nullable[str]] + r"""URL of the author's external profile""" + name: NotRequired[Nullable[str]] + r"""Author name or identifier""" + email: NotRequired[Nullable[str]] + r"""Author email address""" + company: NotRequired[Nullable[str]] + r"""The author's company or organization""" + metadata: NotRequired[UpdateAuthorMetadataTypedDict] + r"""Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.""" + first_seen: NotRequired[float] + r"""Timestamp when author first appeared""" + last_seen: NotRequired[float] + r"""Timestamp of last activity""" + manual_trust_level: NotRequired[Nullable[float]] + + +class UpdateAuthorRequestBody(BaseModel): + profile_picture: OptionalNullable[str] = UNSET + r"""URL of the author's profile picture""" + + external_link: OptionalNullable[str] = UNSET + r"""URL of the author's external profile""" + + name: OptionalNullable[str] = UNSET + r"""Author name or identifier""" + + email: OptionalNullable[str] = UNSET + r"""Author email address""" + + company: OptionalNullable[str] = UNSET + r"""The author's company or organization""" + + metadata: Optional[UpdateAuthorMetadata] = None + r"""Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.""" + + first_seen: Optional[float] = None + r"""Timestamp when author first appeared""" + + last_seen: Optional[float] = None + r"""Timestamp of last activity""" + + manual_trust_level: OptionalNullable[float] = UNSET + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "profile_picture", + "external_link", + "name", + "email", + "company", + "metadata", + "first_seen", + "last_seen", + "manual_trust_level", + ] + ) + nullable_fields = set( + [ + "profile_picture", + "external_link", + "name", + "email", + "company", + "manual_trust_level", + ] + ) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +class UpdateAuthorRequestTypedDict(TypedDict): + id: str + r"""Either external ID or the ID assigned by moderation API.""" + body: UpdateAuthorRequestBodyTypedDict + + +class UpdateAuthorRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""Either external ID or the ID assigned by moderation API.""" + + body: Annotated[ + UpdateAuthorRequestBody, + FieldMetadata(request=RequestMetadata(media_type="application/json")), + ] diff --git a/src/moderation_api/models/updatechannelop.py b/src/moderation_api/models/updatechannelop.py new file mode 100644 index 0000000..5f3a332 --- /dev/null +++ b/src/moderation_api/models/updatechannelop.py @@ -0,0 +1,224 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import ( + BaseModel, + Nullable, + OptionalNullable, + UNSET, + UNSET_SENTINEL, +) +from moderation_api.utils import FieldMetadata, PathParamMetadata, RequestMetadata +import pydantic +from pydantic import model_serializer +from typing import Literal, Optional +from typing_extensions import Annotated, NotRequired, TypedDict + + +UpdateChannelContentType = Literal[ + "profile", + "message", + "post", + "comment", + "event", + "product", + "review", + "voice", + "other", +] +r"""The kind of content the channel moderates.""" + + +UpdateChannelFlaggingMode = Literal[ + "DRY_RUN", + "ACTIVE", +] +r"""`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it.""" + + +UpdateChannelTranscriptionQuality = Literal[ + "SPEED", + "BALANCED", + "ACCURACY", +] +r"""Trade-off between transcription speed and accuracy.""" + + +class UpdateChannelRequestBodyTypedDict(TypedDict): + name: NotRequired[str] + r"""The channel's name.""" + key: NotRequired[str] + r"""The key you pass as `channel` when you moderate content. Changing it breaks integrations that send the old key.""" + description: NotRequired[Nullable[str]] + r"""The channel's description.""" + content_type: NotRequired[UpdateChannelContentType] + r"""The kind of content the channel moderates.""" + flagging_mode: NotRequired[UpdateChannelFlaggingMode] + r"""`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it.""" + context_enabled: NotRequired[bool] + r"""Whether earlier messages in the conversation are read when moderating a message.""" + enable_image_moderation: NotRequired[bool] + r"""Whether images are moderated.""" + enable_video_moderation: NotRequired[bool] + r"""Whether videos are moderated.""" + enable_audio_moderation: NotRequired[bool] + r"""Whether audio is moderated.""" + enable_voice_moderation: NotRequired[bool] + r"""Whether real-time voice streams are moderated.""" + include_transcript_in_response: NotRequired[bool] + r"""Whether audio and video transcripts are returned in responses.""" + transcription_quality: NotRequired[UpdateChannelTranscriptionQuality] + r"""Trade-off between transcription speed and accuracy.""" + unicode_spoof_detection_enabled: NotRequired[bool] + r"""Whether look-alike Unicode characters are normalized before moderation.""" + frame_capture_interval: NotRequired[float] + r"""Seconds between frames captured from a video.""" + max_frames_per_video: NotRequired[int] + r"""The most frames moderated per video.""" + spread_frames: NotRequired[bool] + r"""Whether frames are spread evenly across the whole video.""" + + +class UpdateChannelRequestBody(BaseModel): + name: Optional[str] = None + r"""The channel's name.""" + + key: Optional[str] = None + r"""The key you pass as `channel` when you moderate content. Changing it breaks integrations that send the old key.""" + + description: OptionalNullable[str] = UNSET + r"""The channel's description.""" + + content_type: Annotated[ + Optional[UpdateChannelContentType], pydantic.Field(alias="contentType") + ] = None + r"""The kind of content the channel moderates.""" + + flagging_mode: Annotated[ + Optional[UpdateChannelFlaggingMode], pydantic.Field(alias="flaggingMode") + ] = None + r"""`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it.""" + + context_enabled: Annotated[ + Optional[bool], pydantic.Field(alias="contextEnabled") + ] = None + r"""Whether earlier messages in the conversation are read when moderating a message.""" + + enable_image_moderation: Annotated[ + Optional[bool], pydantic.Field(alias="enableImageModeration") + ] = None + r"""Whether images are moderated.""" + + enable_video_moderation: Annotated[ + Optional[bool], pydantic.Field(alias="enableVideoModeration") + ] = None + r"""Whether videos are moderated.""" + + enable_audio_moderation: Annotated[ + Optional[bool], pydantic.Field(alias="enableAudioModeration") + ] = None + r"""Whether audio is moderated.""" + + enable_voice_moderation: Annotated[ + Optional[bool], pydantic.Field(alias="enableVoiceModeration") + ] = None + r"""Whether real-time voice streams are moderated.""" + + include_transcript_in_response: Annotated[ + Optional[bool], pydantic.Field(alias="includeTranscriptInResponse") + ] = None + r"""Whether audio and video transcripts are returned in responses.""" + + transcription_quality: Annotated[ + Optional[UpdateChannelTranscriptionQuality], + pydantic.Field(alias="transcriptionQuality"), + ] = None + r"""Trade-off between transcription speed and accuracy.""" + + unicode_spoof_detection_enabled: Annotated[ + Optional[bool], pydantic.Field(alias="unicodeSpoofDetectionEnabled") + ] = None + r"""Whether look-alike Unicode characters are normalized before moderation.""" + + frame_capture_interval: Annotated[ + Optional[float], pydantic.Field(alias="frameCaptureInterval") + ] = None + r"""Seconds between frames captured from a video.""" + + max_frames_per_video: Annotated[ + Optional[int], pydantic.Field(alias="maxFramesPerVideo") + ] = None + r"""The most frames moderated per video.""" + + spread_frames: Annotated[Optional[bool], pydantic.Field(alias="spreadFrames")] = ( + None + ) + r"""Whether frames are spread evenly across the whole video.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "name", + "key", + "description", + "contentType", + "flaggingMode", + "contextEnabled", + "enableImageModeration", + "enableVideoModeration", + "enableAudioModeration", + "enableVoiceModeration", + "includeTranscriptInResponse", + "transcriptionQuality", + "unicodeSpoofDetectionEnabled", + "frameCaptureInterval", + "maxFramesPerVideo", + "spreadFrames", + ] + ) + nullable_fields = set(["description"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +class UpdateChannelRequestTypedDict(TypedDict): + id: str + r"""The ID of the channel to update.""" + body: UpdateChannelRequestBodyTypedDict + + +class UpdateChannelRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The ID of the channel to update.""" + + body: Annotated[ + UpdateChannelRequestBody, + FieldMetadata(request=RequestMetadata(media_type="application/json")), + ] + + +try: + UpdateChannelRequestBody.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/updateprojectop.py b/src/moderation_api/models/updateprojectop.py new file mode 100644 index 0000000..f76173d --- /dev/null +++ b/src/moderation_api/models/updateprojectop.py @@ -0,0 +1,128 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import ( + BaseModel, + Nullable, + OptionalNullable, + UNSET, + UNSET_SENTINEL, +) +from moderation_api.utils import FieldMetadata, PathParamMetadata, RequestMetadata +import pydantic +from pydantic import model_serializer +from typing import Literal, Optional +from typing_extensions import Annotated, NotRequired, TypedDict + + +UpdateProjectGlobalFlaggingMode = Literal[ + "DRY_RUN", + "ACTIVE", +] +r"""`ACTIVE` flags content. `DRY_RUN` evaluates without flagging.""" + + +class UpdateProjectRequestBodyTypedDict(TypedDict): + name: NotRequired[str] + r"""The project's name.""" + description: NotRequired[Nullable[str]] + r"""The project's description.""" + domain: NotRequired[Nullable[str]] + r"""The website or app the project moderates content for. Setting it on a project without context writes one.""" + context: NotRequired[Nullable[str]] + r"""What the platform is and who uses it.""" + global_flagging_mode: NotRequired[UpdateProjectGlobalFlaggingMode] + r"""`ACTIVE` flags content. `DRY_RUN` evaluates without flagging.""" + enable_request_logging: NotRequired[bool] + r"""Whether moderated content is stored and shown in the dashboard.""" + casebook_enabled: NotRequired[bool] + r"""Whether review queue decisions are recorded in the casebook.""" + + +class UpdateProjectRequestBody(BaseModel): + name: Optional[str] = None + r"""The project's name.""" + + description: OptionalNullable[str] = UNSET + r"""The project's description.""" + + domain: OptionalNullable[str] = UNSET + r"""The website or app the project moderates content for. Setting it on a project without context writes one.""" + + context: OptionalNullable[str] = UNSET + r"""What the platform is and who uses it.""" + + global_flagging_mode: Annotated[ + Optional[UpdateProjectGlobalFlaggingMode], + pydantic.Field(alias="globalFlaggingMode"), + ] = None + r"""`ACTIVE` flags content. `DRY_RUN` evaluates without flagging.""" + + enable_request_logging: Annotated[ + Optional[bool], pydantic.Field(alias="enableRequestLogging") + ] = None + r"""Whether moderated content is stored and shown in the dashboard.""" + + casebook_enabled: Annotated[ + Optional[bool], pydantic.Field(alias="casebookEnabled") + ] = None + r"""Whether review queue decisions are recorded in the casebook.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "name", + "description", + "domain", + "context", + "globalFlaggingMode", + "enableRequestLogging", + "casebookEnabled", + ] + ) + nullable_fields = set(["description", "domain", "context"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +class UpdateProjectRequestTypedDict(TypedDict): + id: str + r"""The ID of the project to update.""" + body: UpdateProjectRequestBodyTypedDict + + +class UpdateProjectRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The ID of the project to update.""" + + body: Annotated[ + UpdateProjectRequestBody, + FieldMetadata(request=RequestMetadata(media_type="application/json")), + ] + + +try: + UpdateProjectRequestBody.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/updatewebhookop.py b/src/moderation_api/models/updatewebhookop.py new file mode 100644 index 0000000..c62d94d --- /dev/null +++ b/src/moderation_api/models/updatewebhookop.py @@ -0,0 +1,213 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import ( + BaseModel, + Nullable, + OptionalNullable, + UNSET, + UNSET_SENTINEL, + UnrecognizedStr, +) +from moderation_api.utils import FieldMetadata, PathParamMetadata, RequestMetadata +import pydantic +from pydantic import model_serializer +from typing import List, Literal, Optional, Union +from typing_extensions import Annotated, NotRequired, TypedDict + + +UpdateWebhookEventTypeRequest = Literal[ + "QUEUE_ITEM_NEW", + "QUEUE_ITEM_COMPLETED", + "QUEUE_ITEM_ACTION", + "QUEUE_ITEM_REJECTED", + "QUEUE_ITEM_ALLOWED", + "AUTHOR_BLOCKED", + "AUTHOR_UNBLOCKED", + "AUTHOR_SUSPENDED", + "AUTHOR_UPDATED", + "AUTHOR_TRUST_LEVEL_CHANGED", + "AUTHOR_ACTION", +] + + +class UpdateWebhookRequestBodyTypedDict(TypedDict): + name: NotRequired[str] + r"""The webhook's name, used to identify it in the dashboard""" + description: NotRequired[Nullable[str]] + r"""The webhook's description""" + url: NotRequired[str] + r"""The webhook's URL. We'll call this URL when an event occurs.""" + event_types: NotRequired[List[UpdateWebhookEventTypeRequest]] + r"""Event types this webhook subscribes to. One webhook URL receives all events you list here.""" + + +class UpdateWebhookRequestBody(BaseModel): + name: Optional[str] = None + r"""The webhook's name, used to identify it in the dashboard""" + + description: OptionalNullable[str] = UNSET + r"""The webhook's description""" + + url: Optional[str] = None + r"""The webhook's URL. We'll call this URL when an event occurs.""" + + event_types: Annotated[ + Optional[List[UpdateWebhookEventTypeRequest]], + pydantic.Field(alias="eventTypes"), + ] = None + r"""Event types this webhook subscribes to. One webhook URL receives all events you list here.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["name", "description", "url", "eventTypes"]) + nullable_fields = set(["description"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +class UpdateWebhookRequestTypedDict(TypedDict): + id: str + r"""The ID of the webhook to update.""" + body: UpdateWebhookRequestBodyTypedDict + + +class UpdateWebhookRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""The ID of the webhook to update.""" + + body: Annotated[ + UpdateWebhookRequestBody, + FieldMetadata(request=RequestMetadata(media_type="application/json")), + ] + + +UpdateWebhookEventTypeResponse = Union[ + Literal[ + "QUEUE_ITEM_NEW", + "QUEUE_ITEM_COMPLETED", + "QUEUE_ITEM_ACTION", + "QUEUE_ITEM_REJECTED", + "QUEUE_ITEM_ALLOWED", + "AUTHOR_BLOCKED", + "AUTHOR_UNBLOCKED", + "AUTHOR_SUSPENDED", + "AUTHOR_UPDATED", + "AUTHOR_TRUST_LEVEL_CHANGED", + "AUTHOR_ACTION", + ], + UnrecognizedStr, +] + + +UpdateWebhookPayloadVersion = Union[ + Literal[ + "V1", + "V2", + ], + UnrecognizedStr, +] +r"""Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API.""" + + +class UpdateWebhookResponseTypedDict(TypedDict): + r"""Webhook updated successfully""" + + id: str + r"""The ID of the webhook.""" + name: str + r"""The webhook's name.""" + url: str + r"""The URL we call when a subscribed event occurs.""" + event_types: List[UpdateWebhookEventTypeResponse] + r"""Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead.""" + payload_version: UpdateWebhookPayloadVersion + r"""Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API.""" + created_at: str + r"""The date the webhook was created.""" + description: NotRequired[Nullable[str]] + r"""The webhook's description.""" + + +class UpdateWebhookResponse(BaseModel): + r"""Webhook updated successfully""" + + id: str + r"""The ID of the webhook.""" + + name: str + r"""The webhook's name.""" + + url: str + r"""The URL we call when a subscribed event occurs.""" + + event_types: Annotated[ + List[UpdateWebhookEventTypeResponse], pydantic.Field(alias="eventTypes") + ] + r"""Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead.""" + + payload_version: Annotated[ + UpdateWebhookPayloadVersion, pydantic.Field(alias="payloadVersion") + ] + r"""Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API.""" + + created_at: Annotated[str, pydantic.Field(alias="createdAt")] + r"""The date the webhook was created.""" + + description: OptionalNullable[str] = UNSET + r"""The webhook's description.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["description"]) + nullable_fields = set(["description"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) + + if val != UNSET_SENTINEL: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): + m[k] = val + + return m + + +try: + UpdateWebhookRequestBody.model_rebuild() +except NameError: + pass +try: + UpdateWebhookResponse.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/models/updatewordlistop.py b/src/moderation_api/models/updatewordlistop.py new file mode 100644 index 0000000..d562295 --- /dev/null +++ b/src/moderation_api/models/updatewordlistop.py @@ -0,0 +1,133 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from moderation_api.types import BaseModel, Nullable, UNSET_SENTINEL +from moderation_api.utils import FieldMetadata, PathParamMetadata, RequestMetadata +import pydantic +from pydantic import model_serializer +from typing import List, Optional +from typing_extensions import Annotated, NotRequired, TypedDict + + +class UpdateWordlistRequestBodyTypedDict(TypedDict): + name: NotRequired[str] + r"""New name for the wordlist""" + key: NotRequired[str] + r"""New key for the wordlist""" + description: NotRequired[str] + r"""New description for the wordlist""" + words: NotRequired[List[str]] + r"""New words for the wordlist. Replace the existing words with these new ones. Duplicate words will be ignored.""" + strict: NotRequired[bool] + r"""Deprecated. Now using threshold in project settings.""" + + +class UpdateWordlistRequestBody(BaseModel): + name: Optional[str] = None + r"""New name for the wordlist""" + + key: Optional[str] = None + r"""New key for the wordlist""" + + description: Optional[str] = None + r"""New description for the wordlist""" + + words: Optional[List[str]] = None + r"""New words for the wordlist. Replace the existing words with these new ones. Duplicate words will be ignored.""" + + strict: Optional[bool] = None + r"""Deprecated. Now using threshold in project settings.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["name", "key", "description", "words", "strict"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +class UpdateWordlistRequestTypedDict(TypedDict): + id: str + r"""ID of the wordlist to update""" + body: UpdateWordlistRequestBodyTypedDict + + +class UpdateWordlistRequest(BaseModel): + id: Annotated[ + str, FieldMetadata(path=PathParamMetadata(style="simple", explode=False)) + ] + r"""ID of the wordlist to update""" + + body: Annotated[ + UpdateWordlistRequestBody, + FieldMetadata(request=RequestMetadata(media_type="application/json")), + ] + + +class UpdateWordlistResponseTypedDict(TypedDict): + r"""Wordlist updated successfully""" + + id: str + r"""ID of the wordlist""" + name: Nullable[str] + words: List[str] + r"""Words in the wordlist""" + created_at: str + r"""Creation date of the wordlist""" + organization_id: str + r"""ID of the organization""" + user_id: Nullable[str] + strict: bool + r"""Strict mode""" + + +class UpdateWordlistResponse(BaseModel): + r"""Wordlist updated successfully""" + + id: str + r"""ID of the wordlist""" + + name: Nullable[str] + + words: List[str] + r"""Words in the wordlist""" + + created_at: Annotated[str, pydantic.Field(alias="createdAt")] + r"""Creation date of the wordlist""" + + organization_id: Annotated[str, pydantic.Field(alias="organizationId")] + r"""ID of the organization""" + + user_id: Annotated[Nullable[str], pydantic.Field(alias="userId")] + + strict: bool + r"""Strict mode""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k, serialized.get(n)) + + if val != UNSET_SENTINEL: + m[k] = val + + return m + + +try: + UpdateWordlistResponse.model_rebuild() +except NameError: + pass diff --git a/src/moderation_api/projects.py b/src/moderation_api/projects.py new file mode 100644 index 0000000..f962195 --- /dev/null +++ b/src/moderation_api/projects.py @@ -0,0 +1,1487 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .basesdk import BaseSDK +from jsonpath import JSONPath +from moderation_api import errors, models, utils +from moderation_api._hooks import HookContext +from moderation_api.types import OptionalNullable, UNSET +from moderation_api.utils import get_security_from_env +from moderation_api.utils.unmarshal_json_response import unmarshal_json_response +from typing import Any, Awaitable, Dict, List, Mapping, Optional, Union + + +class Projects(BaseSDK): + def list( + self, + *, + page: Optional[int] = 1, + page_size: Optional[int] = 20, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> Optional[models.ListProjectsResponse]: + r"""List projects + + List the projects in the organization. The list is paginated. Needs the `projects:read` permission. + + :param page: + :param page_size: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.ListProjectsRequest( + page=page, + page_size=page_size, + ) + + req = self._build_request( + method="GET", + path="/projects", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="listProjects", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Projects"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + def next_func() -> Optional[models.ListProjectsResponse]: + body = utils.unmarshal_json(http_res.text, Union[Dict[Any, Any], List[Any]]) + + page = request.page if isinstance(request.page, int) else 1 + next_page = page + 1 + + num_pages = JSONPath("$.pagination.totalPages").parse(body) + if len(num_pages) == 0 or num_pages[0] <= page: + return None + + if not http_res.text: + return None + results = JSONPath("$.data").parse(body) + if len(results) == 0 or len(results[0]) == 0: + return None + limit_ = request.page_size if isinstance(request.page_size, int) else 20 + if len(results[0]) < limit_: + return None + + return self.list( + page=next_page, + page_size=page_size, + retries=retries, + server_url=server_url, + timeout_ms=timeout_ms, + http_headers=http_headers, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return models.ListProjectsResponse( + result=unmarshal_json_response( + models.ListProjectsResponseBody, http_res + ), + next=next_func, + ) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def list_async( + self, + *, + page: Optional[int] = 1, + page_size: Optional[int] = 20, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> Optional[models.ListProjectsResponse]: + r"""List projects + + List the projects in the organization. The list is paginated. Needs the `projects:read` permission. + + :param page: + :param page_size: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.ListProjectsRequest( + page=page, + page_size=page_size, + ) + + req = self._build_request_async( + method="GET", + path="/projects", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="listProjects", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Projects"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + def next_func() -> Awaitable[Optional[models.ListProjectsResponse]]: + body = utils.unmarshal_json(http_res.text, Union[Dict[Any, Any], List[Any]]) + + async def empty_result(): + return None + + page = request.page if isinstance(request.page, int) else 1 + next_page = page + 1 + + num_pages = JSONPath("$.pagination.totalPages").parse(body) + if len(num_pages) == 0 or num_pages[0] <= page: + return empty_result() + + if not http_res.text: + return empty_result() + results = JSONPath("$.data").parse(body) + if len(results) == 0 or len(results[0]) == 0: + return empty_result() + limit_ = request.page_size if isinstance(request.page_size, int) else 20 + if len(results[0]) < limit_: + return empty_result() + + return self.list_async( + page=next_page, + page_size=page_size, + retries=retries, + server_url=server_url, + timeout_ms=timeout_ms, + http_headers=http_headers, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return models.ListProjectsResponse( + result=unmarshal_json_response( + models.ListProjectsResponseBody, http_res + ), + next=next_func, + ) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def create( + self, + *, + name: str, + domain: Optional[str] = None, + context: Optional[str] = None, + brief: Optional[str] = None, + used_by_minors: Optional[bool] = False, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.ProjectWithKey: + r"""Create a project + + Create a project with one channel, a review queue inbox and a secret API key. Give a domain or context and the project suggests policies for the platform. Needs the `projects:write` permission and a key that reaches all projects. + + :param name: The project's name. + :param domain: The website or app the project moderates content for. Used to write the context when none is given. + :param context: What the platform is and who uses it. + :param brief: What you moderate, the problems you see and your guidelines. + :param used_by_minors: Whether children use the platform. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.CreateProjectRequest( + name=name, + domain=domain, + context=context, + brief=brief, + used_by_minors=used_by_minors, + ) + + req = self._build_request( + method="POST", + path="/projects", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request, False, False, "json", models.CreateProjectRequest + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="createProject", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Projects"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.ProjectWithKey, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def create_async( + self, + *, + name: str, + domain: Optional[str] = None, + context: Optional[str] = None, + brief: Optional[str] = None, + used_by_minors: Optional[bool] = False, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.ProjectWithKey: + r"""Create a project + + Create a project with one channel, a review queue inbox and a secret API key. Give a domain or context and the project suggests policies for the platform. Needs the `projects:write` permission and a key that reaches all projects. + + :param name: The project's name. + :param domain: The website or app the project moderates content for. Used to write the context when none is given. + :param context: What the platform is and who uses it. + :param brief: What you moderate, the problems you see and your guidelines. + :param used_by_minors: Whether children use the platform. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.CreateProjectRequest( + name=name, + domain=domain, + context=context, + brief=brief, + used_by_minors=used_by_minors, + ) + + req = self._build_request_async( + method="POST", + path="/projects", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request, False, False, "json", models.CreateProjectRequest + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="createProject", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Projects"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.ProjectWithKey, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def retrieve( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.Project: + r"""Get a project + + Get a project by ID. Needs the `projects:read` permission. + + :param id: The ID of the project. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetProjectRequest( + id=id, + ) + + req = self._build_request( + method="GET", + path="/projects/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getProject", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Projects"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.Project, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def retrieve_async( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.Project: + r"""Get a project + + Get a project by ID. Needs the `projects:read` permission. + + :param id: The ID of the project. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetProjectRequest( + id=id, + ) + + req = self._build_request_async( + method="GET", + path="/projects/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getProject", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Projects"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.Project, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def update( + self, + *, + id: str, + name: Optional[str] = None, + description: OptionalNullable[str] = UNSET, + domain: OptionalNullable[str] = UNSET, + context: OptionalNullable[str] = UNSET, + global_flagging_mode: Optional[models.UpdateProjectGlobalFlaggingMode] = None, + enable_request_logging: Optional[bool] = None, + casebook_enabled: Optional[bool] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.Project: + r"""Update a project + + Update a project. Fields you leave out keep their value. Needs the `projects:write` permission. + + :param id: The ID of the project to update. + :param name: The project's name. + :param description: The project's description. + :param domain: The website or app the project moderates content for. Setting it on a project without context writes one. + :param context: What the platform is and who uses it. + :param global_flagging_mode: `ACTIVE` flags content. `DRY_RUN` evaluates without flagging. + :param enable_request_logging: Whether moderated content is stored and shown in the dashboard. + :param casebook_enabled: Whether review queue decisions are recorded in the casebook. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.UpdateProjectRequest( + id=id, + body=models.UpdateProjectRequestBody( + name=name, + description=description, + domain=domain, + context=context, + global_flagging_mode=global_flagging_mode, + enable_request_logging=enable_request_logging, + casebook_enabled=casebook_enabled, + ), + ) + + req = self._build_request( + method="PUT", + path="/projects/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.UpdateProjectRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="updateProject", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Projects"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.Project, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def update_async( + self, + *, + id: str, + name: Optional[str] = None, + description: OptionalNullable[str] = UNSET, + domain: OptionalNullable[str] = UNSET, + context: OptionalNullable[str] = UNSET, + global_flagging_mode: Optional[models.UpdateProjectGlobalFlaggingMode] = None, + enable_request_logging: Optional[bool] = None, + casebook_enabled: Optional[bool] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.Project: + r"""Update a project + + Update a project. Fields you leave out keep their value. Needs the `projects:write` permission. + + :param id: The ID of the project to update. + :param name: The project's name. + :param description: The project's description. + :param domain: The website or app the project moderates content for. Setting it on a project without context writes one. + :param context: What the platform is and who uses it. + :param global_flagging_mode: `ACTIVE` flags content. `DRY_RUN` evaluates without flagging. + :param enable_request_logging: Whether moderated content is stored and shown in the dashboard. + :param casebook_enabled: Whether review queue decisions are recorded in the casebook. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.UpdateProjectRequest( + id=id, + body=models.UpdateProjectRequestBody( + name=name, + description=description, + domain=domain, + context=context, + global_flagging_mode=global_flagging_mode, + enable_request_logging=enable_request_logging, + casebook_enabled=casebook_enabled, + ), + ) + + req = self._build_request_async( + method="PUT", + path="/projects/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.UpdateProjectRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="updateProject", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Projects"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.Project, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def delete( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.DeleteProjectResponse: + r"""Delete a project + + Delete a project with everything in it: channels, queues, API keys and stored content. This can't be undone. The project's API keys stop working at once. Needs the `projects:write` permission. + + :param id: The ID of the project. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.DeleteProjectRequest( + id=id, + ) + + req = self._build_request( + method="DELETE", + path="/projects/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="deleteProject", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Projects"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.DeleteProjectResponse, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def delete_async( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.DeleteProjectResponse: + r"""Delete a project + + Delete a project with everything in it: channels, queues, API keys and stored content. This can't be undone. The project's API keys stop working at once. Needs the `projects:write` permission. + + :param id: The ID of the project. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.DeleteProjectRequest( + id=id, + ) + + req = self._build_request_async( + method="DELETE", + path="/projects/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="deleteProject", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Projects"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.DeleteProjectResponse, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def duplicate( + self, + *, + id: str, + name: Optional[str] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.ProjectWithKey: + r"""Duplicate a project + + Copy a project's configuration into a new project: its settings, every channel with its policies, rules and wordlists, its moderation actions and its review queues. The copy gets its own secret API key. Webhooks, integrations, API keys and content are not copied. Needs the `projects:write` permission and a key that reaches all projects. + + :param id: The ID of the project to copy. + :param name: Name of the copy. Defaults to the original name with \"(copy)\". + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.DuplicateProjectRequest( + id=id, + body=models.DuplicateProjectRequestBody( + name=name, + ), + ) + + req = self._build_request( + method="POST", + path="/projects/{id}/duplicate", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.DuplicateProjectRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="duplicateProject", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Projects"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.ProjectWithKey, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def duplicate_async( + self, + *, + id: str, + name: Optional[str] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.ProjectWithKey: + r"""Duplicate a project + + Copy a project's configuration into a new project: its settings, every channel with its policies, rules and wordlists, its moderation actions and its review queues. The copy gets its own secret API key. Webhooks, integrations, API keys and content are not copied. Needs the `projects:write` permission and a key that reaches all projects. + + :param id: The ID of the project to copy. + :param name: Name of the copy. Defaults to the original name with \"(copy)\". + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.DuplicateProjectRequest( + id=id, + body=models.DuplicateProjectRequestBody( + name=name, + ), + ) + + req = self._build_request_async( + method="POST", + path="/projects/{id}/duplicate", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.DuplicateProjectRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="duplicateProject", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Projects"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.ProjectWithKey, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) diff --git a/src/moderation_api/py.typed b/src/moderation_api/py.typed index e69de29..3e38f1a 100644 --- a/src/moderation_api/py.typed +++ b/src/moderation_api/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561. The package enables type hints. diff --git a/src/moderation_api/queue_sdk.py b/src/moderation_api/queue_sdk.py new file mode 100644 index 0000000..fe67b0b --- /dev/null +++ b/src/moderation_api/queue_sdk.py @@ -0,0 +1,499 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .basesdk import BaseSDK +from .sdkconfiguration import SDKConfiguration +from moderation_api import errors, models, utils +from moderation_api._hooks import HookContext +from moderation_api.items import Items +from moderation_api.types import OptionalNullable, UNSET +from moderation_api.utils import get_security_from_env +from moderation_api.utils.unmarshal_json_response import unmarshal_json_response +from typing import Any, Mapping, Optional + + +class QueueSDK(BaseSDK): + items: Items + + def __init__( + self, sdk_config: SDKConfiguration, parent_ref: Optional[object] = None + ) -> None: + BaseSDK.__init__(self, sdk_config, parent_ref=parent_ref) + self.sdk_configuration = sdk_config + self._init_sdks() + + def _init_sdks(self): + self.items = Items(self.sdk_configuration, parent_ref=self.parent_ref) + + def retrieve( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.GetQueueResponse: + r"""Get a queue + + Get a queue + + :param id: The queue ID + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetQueueRequest( + id=id, + ) + + req = self._build_request( + method="GET", + path="/queue/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getQueue", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Review queues"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.GetQueueResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def retrieve_async( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.GetQueueResponse: + r"""Get a queue + + Get a queue + + :param id: The queue ID + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetQueueRequest( + id=id, + ) + + req = self._build_request_async( + method="GET", + path="/queue/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getQueue", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Review queues"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.GetQueueResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def get_stats( + self, + *, + id: str, + within_days: Optional[int] = 30, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.GetQueueStatsResponse: + r"""Get queue statistics + + Get detailed statistics about a moderation queue including review times, action counts, and trends + + :param id: The queue ID + :param within_days: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetQueueStatsRequest( + id=id, + within_days=within_days, + ) + + req = self._build_request( + method="GET", + path="/queue/{id}/stats", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getQueueStats", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Review queues"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.GetQueueStatsResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def get_stats_async( + self, + *, + id: str, + within_days: Optional[int] = 30, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.GetQueueStatsResponse: + r"""Get queue statistics + + Get detailed statistics about a moderation queue including review times, action counts, and trends + + :param id: The queue ID + :param within_days: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetQueueStatsRequest( + id=id, + within_days=within_days, + ) + + req = self._build_request_async( + method="GET", + path="/queue/{id}/stats", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getQueueStats", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Review queues"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.GetQueueStatsResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) diff --git a/src/moderation_api/resources/__init__.py b/src/moderation_api/resources/__init__.py deleted file mode 100644 index 2a7ead1..0000000 --- a/src/moderation_api/resources/__init__.py +++ /dev/null @@ -1,131 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .auth import ( - AuthResource, - AsyncAuthResource, - AuthResourceWithRawResponse, - AsyncAuthResourceWithRawResponse, - AuthResourceWithStreamingResponse, - AsyncAuthResourceWithStreamingResponse, -) -from .queue import ( - QueueResource, - AsyncQueueResource, - QueueResourceWithRawResponse, - AsyncQueueResourceWithRawResponse, - QueueResourceWithStreamingResponse, - AsyncQueueResourceWithStreamingResponse, -) -from .account import ( - AccountResource, - AsyncAccountResource, - AccountResourceWithRawResponse, - AsyncAccountResourceWithRawResponse, - AccountResourceWithStreamingResponse, - AsyncAccountResourceWithStreamingResponse, -) -from .actions import ( - ActionsResource, - AsyncActionsResource, - ActionsResourceWithRawResponse, - AsyncActionsResourceWithRawResponse, - ActionsResourceWithStreamingResponse, - AsyncActionsResourceWithStreamingResponse, -) -from .authors import ( - AuthorsResource, - AsyncAuthorsResource, - AuthorsResourceWithRawResponse, - AsyncAuthorsResourceWithRawResponse, - AuthorsResourceWithStreamingResponse, - AsyncAuthorsResourceWithStreamingResponse, -) -from .content import ( - ContentResource, - AsyncContentResource, - ContentResourceWithRawResponse, - AsyncContentResourceWithRawResponse, - ContentResourceWithStreamingResponse, - AsyncContentResourceWithStreamingResponse, -) -from .webhooks import ( - WebhooksResource, - AsyncWebhooksResource, - WebhooksResourceWithRawResponse, - AsyncWebhooksResourceWithRawResponse, - WebhooksResourceWithStreamingResponse, - AsyncWebhooksResourceWithStreamingResponse, -) -from .wordlist import ( - WordlistResource, - AsyncWordlistResource, - WordlistResourceWithRawResponse, - AsyncWordlistResourceWithRawResponse, - WordlistResourceWithStreamingResponse, - AsyncWordlistResourceWithStreamingResponse, -) -from .webhook_secret import ( - WebhookSecretResource, - AsyncWebhookSecretResource, - WebhookSecretResourceWithRawResponse, - AsyncWebhookSecretResourceWithRawResponse, - WebhookSecretResourceWithStreamingResponse, - AsyncWebhookSecretResourceWithStreamingResponse, -) - -__all__ = [ - "AuthorsResource", - "AsyncAuthorsResource", - "AuthorsResourceWithRawResponse", - "AsyncAuthorsResourceWithRawResponse", - "AuthorsResourceWithStreamingResponse", - "AsyncAuthorsResourceWithStreamingResponse", - "QueueResource", - "AsyncQueueResource", - "QueueResourceWithRawResponse", - "AsyncQueueResourceWithRawResponse", - "QueueResourceWithStreamingResponse", - "AsyncQueueResourceWithStreamingResponse", - "ActionsResource", - "AsyncActionsResource", - "ActionsResourceWithRawResponse", - "AsyncActionsResourceWithRawResponse", - "ActionsResourceWithStreamingResponse", - "AsyncActionsResourceWithStreamingResponse", - "ContentResource", - "AsyncContentResource", - "ContentResourceWithRawResponse", - "AsyncContentResourceWithRawResponse", - "ContentResourceWithStreamingResponse", - "AsyncContentResourceWithStreamingResponse", - "AccountResource", - "AsyncAccountResource", - "AccountResourceWithRawResponse", - "AsyncAccountResourceWithRawResponse", - "AccountResourceWithStreamingResponse", - "AsyncAccountResourceWithStreamingResponse", - "AuthResource", - "AsyncAuthResource", - "AuthResourceWithRawResponse", - "AsyncAuthResourceWithRawResponse", - "AuthResourceWithStreamingResponse", - "AsyncAuthResourceWithStreamingResponse", - "WordlistResource", - "AsyncWordlistResource", - "WordlistResourceWithRawResponse", - "AsyncWordlistResourceWithRawResponse", - "WordlistResourceWithStreamingResponse", - "AsyncWordlistResourceWithStreamingResponse", - "WebhooksResource", - "AsyncWebhooksResource", - "WebhooksResourceWithRawResponse", - "AsyncWebhooksResourceWithRawResponse", - "WebhooksResourceWithStreamingResponse", - "AsyncWebhooksResourceWithStreamingResponse", - "WebhookSecretResource", - "AsyncWebhookSecretResource", - "WebhookSecretResourceWithRawResponse", - "AsyncWebhookSecretResourceWithRawResponse", - "WebhookSecretResourceWithStreamingResponse", - "AsyncWebhookSecretResourceWithStreamingResponse", -] diff --git a/src/moderation_api/resources/account.py b/src/moderation_api/resources/account.py deleted file mode 100644 index 17c9901..0000000 --- a/src/moderation_api/resources/account.py +++ /dev/null @@ -1,135 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import httpx - -from .._types import Body, Query, Headers, NotGiven, not_given -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from .._base_client import make_request_options -from ..types.account_list_response import AccountListResponse - -__all__ = ["AccountResource", "AsyncAccountResource"] - - -class AccountResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> AccountResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return AccountResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AccountResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return AccountResourceWithStreamingResponse(self) - - def list( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AccountListResponse: - """Get account details""" - return self._get( - "/account", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AccountListResponse, - ) - - -class AsyncAccountResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncAccountResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncAccountResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncAccountResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return AsyncAccountResourceWithStreamingResponse(self) - - async def list( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AccountListResponse: - """Get account details""" - return await self._get( - "/account", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AccountListResponse, - ) - - -class AccountResourceWithRawResponse: - def __init__(self, account: AccountResource) -> None: - self._account = account - - self.list = to_raw_response_wrapper( - account.list, - ) - - -class AsyncAccountResourceWithRawResponse: - def __init__(self, account: AsyncAccountResource) -> None: - self._account = account - - self.list = async_to_raw_response_wrapper( - account.list, - ) - - -class AccountResourceWithStreamingResponse: - def __init__(self, account: AccountResource) -> None: - self._account = account - - self.list = to_streamed_response_wrapper( - account.list, - ) - - -class AsyncAccountResourceWithStreamingResponse: - def __init__(self, account: AsyncAccountResource) -> None: - self._account = account - - self.list = async_to_streamed_response_wrapper( - account.list, - ) diff --git a/src/moderation_api/resources/actions/__init__.py b/src/moderation_api/resources/actions/__init__.py deleted file mode 100644 index 228b7b5..0000000 --- a/src/moderation_api/resources/actions/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .actions import ( - ActionsResource, - AsyncActionsResource, - ActionsResourceWithRawResponse, - AsyncActionsResourceWithRawResponse, - ActionsResourceWithStreamingResponse, - AsyncActionsResourceWithStreamingResponse, -) -from .execute import ( - ExecuteResource, - AsyncExecuteResource, - ExecuteResourceWithRawResponse, - AsyncExecuteResourceWithRawResponse, - ExecuteResourceWithStreamingResponse, - AsyncExecuteResourceWithStreamingResponse, -) - -__all__ = [ - "ExecuteResource", - "AsyncExecuteResource", - "ExecuteResourceWithRawResponse", - "AsyncExecuteResourceWithRawResponse", - "ExecuteResourceWithStreamingResponse", - "AsyncExecuteResourceWithStreamingResponse", - "ActionsResource", - "AsyncActionsResource", - "ActionsResourceWithRawResponse", - "AsyncActionsResourceWithRawResponse", - "ActionsResourceWithStreamingResponse", - "AsyncActionsResourceWithStreamingResponse", -] diff --git a/src/moderation_api/resources/actions/actions.py b/src/moderation_api/resources/actions/actions.py deleted file mode 100644 index 3d1cde1..0000000 --- a/src/moderation_api/resources/actions/actions.py +++ /dev/null @@ -1,794 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Iterable, Optional -from typing_extensions import Literal - -import httpx - -from ...types import action_list_params, action_create_params, action_update_params -from .execute import ( - ExecuteResource, - AsyncExecuteResource, - ExecuteResourceWithRawResponse, - AsyncExecuteResourceWithRawResponse, - ExecuteResourceWithStreamingResponse, - AsyncExecuteResourceWithStreamingResponse, -) -from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given -from ..._utils import path_template, maybe_transform, async_maybe_transform -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from ..._base_client import make_request_options -from ...types.action_list_response import ActionListResponse -from ...types.action_create_response import ActionCreateResponse -from ...types.action_delete_response import ActionDeleteResponse -from ...types.action_update_response import ActionUpdateResponse -from ...types.action_retrieve_response import ActionRetrieveResponse - -__all__ = ["ActionsResource", "AsyncActionsResource"] - - -class ActionsResource(SyncAPIResource): - @cached_property - def execute(self) -> ExecuteResource: - return ExecuteResource(self._client) - - @cached_property - def with_raw_response(self) -> ActionsResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return ActionsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> ActionsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return ActionsResourceWithStreamingResponse(self) - - def create( - self, - *, - name: str, - built_in: Optional[bool] | Omit = omit, - description: Optional[str] | Omit = omit, - filter_in_queue_ids: SequenceNotStr[str] | Omit = omit, - free_text: bool | Omit = omit, - key: Optional[str] | Omit = omit, - position: Literal["ALL_QUEUES", "SOME_QUEUES", "HIDDEN"] | Omit = omit, - possible_values: Iterable[action_create_params.PossibleValue] | Omit = omit, - queue_behaviour: Literal["REMOVE", "ADD", "NO_CHANGE"] | Omit = omit, - type: Optional[ - Literal[ - "AUTHOR_BLOCK", - "AUTHOR_BLOCK_TEMP", - "AUTHOR_UNBLOCK", - "AUTHOR_DELETE", - "AUTHOR_REPORT", - "AUTHOR_WARN", - "AUTHOR_CUSTOM", - "ITEM_REJECT", - "ITEM_ALLOW", - "ITEM_CUSTOM", - ] - ] - | Omit = omit, - value_required: bool | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ActionCreateResponse: - """ - Create an action. - - Args: - name: The name of the action. - - built_in: Whether the action is a built-in action or a custom one. - - description: The description of the action. - - filter_in_queue_ids: The IDs of the queues the action is available in. - - free_text: Whether the action allows any text to be entered as a value or if it must be one - of the possible values. - - key: User defined key of the action. - - position: Show the action in all queues, selected queues or no queues (to use via API - only). - - possible_values: The possible values of the action. The user will be prompted to select one of - these values when executing the action. - - queue_behaviour: Whether the action resolves and removes the item, unresolves and re-add it to - the queue, or does not change the resolve status. - - type: The type of the action. - - value_required: Whether the action requires a value to be executed. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._post( - "/actions", - body=maybe_transform( - { - "name": name, - "built_in": built_in, - "description": description, - "filter_in_queue_ids": filter_in_queue_ids, - "free_text": free_text, - "key": key, - "position": position, - "possible_values": possible_values, - "queue_behaviour": queue_behaviour, - "type": type, - "value_required": value_required, - }, - action_create_params.ActionCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ActionCreateResponse, - ) - - def retrieve( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ActionRetrieveResponse: - """ - Get an action by ID. - - Args: - id: The ID of the action to get. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._get( - path_template("/actions/{id}", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ActionRetrieveResponse, - ) - - def update( - self, - id: str, - *, - built_in: Optional[bool] | Omit = omit, - description: Optional[str] | Omit = omit, - filter_in_queue_ids: SequenceNotStr[str] | Omit = omit, - free_text: bool | Omit = omit, - key: Optional[str] | Omit = omit, - name: str | Omit = omit, - position: Literal["ALL_QUEUES", "SOME_QUEUES", "HIDDEN"] | Omit = omit, - possible_values: Iterable[action_update_params.PossibleValue] | Omit = omit, - queue_behaviour: Literal["REMOVE", "ADD", "NO_CHANGE"] | Omit = omit, - type: Optional[ - Literal[ - "AUTHOR_BLOCK", - "AUTHOR_BLOCK_TEMP", - "AUTHOR_UNBLOCK", - "AUTHOR_DELETE", - "AUTHOR_REPORT", - "AUTHOR_WARN", - "AUTHOR_CUSTOM", - "ITEM_REJECT", - "ITEM_ALLOW", - "ITEM_CUSTOM", - ] - ] - | Omit = omit, - value_required: bool | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ActionUpdateResponse: - """ - Update an action. - - Args: - id: The ID of the action to update. - - built_in: Whether the action is a built-in action or a custom one. - - description: The description of the action. - - filter_in_queue_ids: The IDs of the queues the action is available in. - - free_text: Whether the action allows any text to be entered as a value or if it must be one - of the possible values. - - key: User defined key of the action. - - name: The name of the action. - - position: Show the action in all queues, selected queues or no queues (to use via API - only). - - possible_values: The possible values of the action. The user will be prompted to select one of - these values when executing the action. - - queue_behaviour: Whether the action resolves and removes the item, unresolves and re-add it to - the queue, or does not change the resolve status. - - type: The type of the action. - - value_required: Whether the action requires a value to be executed. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._put( - path_template("/actions/{id}", id=id), - body=maybe_transform( - { - "built_in": built_in, - "description": description, - "filter_in_queue_ids": filter_in_queue_ids, - "free_text": free_text, - "key": key, - "name": name, - "position": position, - "possible_values": possible_values, - "queue_behaviour": queue_behaviour, - "type": type, - "value_required": value_required, - }, - action_update_params.ActionUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ActionUpdateResponse, - ) - - def list( - self, - *, - queue_id: str | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ActionListResponse: - """ - List all available moderation actions for the authenticated organization. - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._get( - "/actions", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform({"queue_id": queue_id}, action_list_params.ActionListParams), - ), - cast_to=ActionListResponse, - ) - - def delete( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ActionDeleteResponse: - """ - Delete an action and all of its webhooks. - - Args: - id: The ID of the action to delete. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._delete( - path_template("/actions/{id}", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ActionDeleteResponse, - ) - - -class AsyncActionsResource(AsyncAPIResource): - @cached_property - def execute(self) -> AsyncExecuteResource: - return AsyncExecuteResource(self._client) - - @cached_property - def with_raw_response(self) -> AsyncActionsResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncActionsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncActionsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return AsyncActionsResourceWithStreamingResponse(self) - - async def create( - self, - *, - name: str, - built_in: Optional[bool] | Omit = omit, - description: Optional[str] | Omit = omit, - filter_in_queue_ids: SequenceNotStr[str] | Omit = omit, - free_text: bool | Omit = omit, - key: Optional[str] | Omit = omit, - position: Literal["ALL_QUEUES", "SOME_QUEUES", "HIDDEN"] | Omit = omit, - possible_values: Iterable[action_create_params.PossibleValue] | Omit = omit, - queue_behaviour: Literal["REMOVE", "ADD", "NO_CHANGE"] | Omit = omit, - type: Optional[ - Literal[ - "AUTHOR_BLOCK", - "AUTHOR_BLOCK_TEMP", - "AUTHOR_UNBLOCK", - "AUTHOR_DELETE", - "AUTHOR_REPORT", - "AUTHOR_WARN", - "AUTHOR_CUSTOM", - "ITEM_REJECT", - "ITEM_ALLOW", - "ITEM_CUSTOM", - ] - ] - | Omit = omit, - value_required: bool | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ActionCreateResponse: - """ - Create an action. - - Args: - name: The name of the action. - - built_in: Whether the action is a built-in action or a custom one. - - description: The description of the action. - - filter_in_queue_ids: The IDs of the queues the action is available in. - - free_text: Whether the action allows any text to be entered as a value or if it must be one - of the possible values. - - key: User defined key of the action. - - position: Show the action in all queues, selected queues or no queues (to use via API - only). - - possible_values: The possible values of the action. The user will be prompted to select one of - these values when executing the action. - - queue_behaviour: Whether the action resolves and removes the item, unresolves and re-add it to - the queue, or does not change the resolve status. - - type: The type of the action. - - value_required: Whether the action requires a value to be executed. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._post( - "/actions", - body=await async_maybe_transform( - { - "name": name, - "built_in": built_in, - "description": description, - "filter_in_queue_ids": filter_in_queue_ids, - "free_text": free_text, - "key": key, - "position": position, - "possible_values": possible_values, - "queue_behaviour": queue_behaviour, - "type": type, - "value_required": value_required, - }, - action_create_params.ActionCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ActionCreateResponse, - ) - - async def retrieve( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ActionRetrieveResponse: - """ - Get an action by ID. - - Args: - id: The ID of the action to get. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._get( - path_template("/actions/{id}", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ActionRetrieveResponse, - ) - - async def update( - self, - id: str, - *, - built_in: Optional[bool] | Omit = omit, - description: Optional[str] | Omit = omit, - filter_in_queue_ids: SequenceNotStr[str] | Omit = omit, - free_text: bool | Omit = omit, - key: Optional[str] | Omit = omit, - name: str | Omit = omit, - position: Literal["ALL_QUEUES", "SOME_QUEUES", "HIDDEN"] | Omit = omit, - possible_values: Iterable[action_update_params.PossibleValue] | Omit = omit, - queue_behaviour: Literal["REMOVE", "ADD", "NO_CHANGE"] | Omit = omit, - type: Optional[ - Literal[ - "AUTHOR_BLOCK", - "AUTHOR_BLOCK_TEMP", - "AUTHOR_UNBLOCK", - "AUTHOR_DELETE", - "AUTHOR_REPORT", - "AUTHOR_WARN", - "AUTHOR_CUSTOM", - "ITEM_REJECT", - "ITEM_ALLOW", - "ITEM_CUSTOM", - ] - ] - | Omit = omit, - value_required: bool | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ActionUpdateResponse: - """ - Update an action. - - Args: - id: The ID of the action to update. - - built_in: Whether the action is a built-in action or a custom one. - - description: The description of the action. - - filter_in_queue_ids: The IDs of the queues the action is available in. - - free_text: Whether the action allows any text to be entered as a value or if it must be one - of the possible values. - - key: User defined key of the action. - - name: The name of the action. - - position: Show the action in all queues, selected queues or no queues (to use via API - only). - - possible_values: The possible values of the action. The user will be prompted to select one of - these values when executing the action. - - queue_behaviour: Whether the action resolves and removes the item, unresolves and re-add it to - the queue, or does not change the resolve status. - - type: The type of the action. - - value_required: Whether the action requires a value to be executed. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._put( - path_template("/actions/{id}", id=id), - body=await async_maybe_transform( - { - "built_in": built_in, - "description": description, - "filter_in_queue_ids": filter_in_queue_ids, - "free_text": free_text, - "key": key, - "name": name, - "position": position, - "possible_values": possible_values, - "queue_behaviour": queue_behaviour, - "type": type, - "value_required": value_required, - }, - action_update_params.ActionUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ActionUpdateResponse, - ) - - async def list( - self, - *, - queue_id: str | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ActionListResponse: - """ - List all available moderation actions for the authenticated organization. - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._get( - "/actions", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform({"queue_id": queue_id}, action_list_params.ActionListParams), - ), - cast_to=ActionListResponse, - ) - - async def delete( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ActionDeleteResponse: - """ - Delete an action and all of its webhooks. - - Args: - id: The ID of the action to delete. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._delete( - path_template("/actions/{id}", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ActionDeleteResponse, - ) - - -class ActionsResourceWithRawResponse: - def __init__(self, actions: ActionsResource) -> None: - self._actions = actions - - self.create = to_raw_response_wrapper( - actions.create, - ) - self.retrieve = to_raw_response_wrapper( - actions.retrieve, - ) - self.update = to_raw_response_wrapper( - actions.update, - ) - self.list = to_raw_response_wrapper( - actions.list, - ) - self.delete = to_raw_response_wrapper( - actions.delete, - ) - - @cached_property - def execute(self) -> ExecuteResourceWithRawResponse: - return ExecuteResourceWithRawResponse(self._actions.execute) - - -class AsyncActionsResourceWithRawResponse: - def __init__(self, actions: AsyncActionsResource) -> None: - self._actions = actions - - self.create = async_to_raw_response_wrapper( - actions.create, - ) - self.retrieve = async_to_raw_response_wrapper( - actions.retrieve, - ) - self.update = async_to_raw_response_wrapper( - actions.update, - ) - self.list = async_to_raw_response_wrapper( - actions.list, - ) - self.delete = async_to_raw_response_wrapper( - actions.delete, - ) - - @cached_property - def execute(self) -> AsyncExecuteResourceWithRawResponse: - return AsyncExecuteResourceWithRawResponse(self._actions.execute) - - -class ActionsResourceWithStreamingResponse: - def __init__(self, actions: ActionsResource) -> None: - self._actions = actions - - self.create = to_streamed_response_wrapper( - actions.create, - ) - self.retrieve = to_streamed_response_wrapper( - actions.retrieve, - ) - self.update = to_streamed_response_wrapper( - actions.update, - ) - self.list = to_streamed_response_wrapper( - actions.list, - ) - self.delete = to_streamed_response_wrapper( - actions.delete, - ) - - @cached_property - def execute(self) -> ExecuteResourceWithStreamingResponse: - return ExecuteResourceWithStreamingResponse(self._actions.execute) - - -class AsyncActionsResourceWithStreamingResponse: - def __init__(self, actions: AsyncActionsResource) -> None: - self._actions = actions - - self.create = async_to_streamed_response_wrapper( - actions.create, - ) - self.retrieve = async_to_streamed_response_wrapper( - actions.retrieve, - ) - self.update = async_to_streamed_response_wrapper( - actions.update, - ) - self.list = async_to_streamed_response_wrapper( - actions.list, - ) - self.delete = async_to_streamed_response_wrapper( - actions.delete, - ) - - @cached_property - def execute(self) -> AsyncExecuteResourceWithStreamingResponse: - return AsyncExecuteResourceWithStreamingResponse(self._actions.execute) diff --git a/src/moderation_api/resources/actions/execute.py b/src/moderation_api/resources/actions/execute.py deleted file mode 100644 index 2f9110e..0000000 --- a/src/moderation_api/resources/actions/execute.py +++ /dev/null @@ -1,354 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import typing_extensions - -import httpx - -from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given -from ..._utils import path_template, maybe_transform, async_maybe_transform -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from ..._base_client import make_request_options -from ...types.actions import execute_execute_params, execute_execute_by_id_params -from ...types.actions.execute_execute_response import ExecuteExecuteResponse -from ...types.actions.execute_execute_by_id_response import ExecuteExecuteByIDResponse - -__all__ = ["ExecuteResource", "AsyncExecuteResource"] - - -class ExecuteResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> ExecuteResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return ExecuteResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> ExecuteResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return ExecuteResourceWithStreamingResponse(self) - - def execute( - self, - *, - action_key: str, - author_ids: SequenceNotStr[str] | Omit = omit, - content_ids: SequenceNotStr[str] | Omit = omit, - duration: float | Omit = omit, - queue_id: str | Omit = omit, - value: str | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ExecuteExecuteResponse: - """ - Execute a moderation action on one or more content items. - - Args: - action_key: ID or key of the action to execute - - author_ids: IDs of the authors to apply the action to. Provide this or contentIds. - - content_ids: IDs of the content items to apply the action to. Provide this or authorIds. - - duration: Optional duration in milliseconds for actions with timeouts - - queue_id: Optional queue ID if the action is queue-specific - - value: Optional value to provide with the action - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._post( - "/actions/execute", - body=maybe_transform( - { - "action_key": action_key, - "author_ids": author_ids, - "content_ids": content_ids, - "duration": duration, - "queue_id": queue_id, - "value": value, - }, - execute_execute_params.ExecuteExecuteParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ExecuteExecuteResponse, - ) - - @typing_extensions.deprecated("deprecated") - def execute_by_id( - self, - action_id: str, - *, - author_ids: SequenceNotStr[str] | Omit = omit, - content_ids: SequenceNotStr[str] | Omit = omit, - queue_id: str | Omit = omit, - value: str | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ExecuteExecuteByIDResponse: - """ - Execute an action on a set of content items in a queue. - - Args: - action_id: The ID or key of the action to execute. - - author_ids: IDs of the authors to apply the action to - - content_ids: The IDs of the content items to perform the action on. - - queue_id: The ID of the queue the action was performed from if any. - - value: The value of the action. Useful to set a reason for the action etc. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not action_id: - raise ValueError(f"Expected a non-empty value for `action_id` but received {action_id!r}") - return self._post( - path_template("/actions/{action_id}/execute", action_id=action_id), - body=maybe_transform( - { - "author_ids": author_ids, - "content_ids": content_ids, - "queue_id": queue_id, - "value": value, - }, - execute_execute_by_id_params.ExecuteExecuteByIDParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ExecuteExecuteByIDResponse, - ) - - -class AsyncExecuteResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncExecuteResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncExecuteResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncExecuteResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return AsyncExecuteResourceWithStreamingResponse(self) - - async def execute( - self, - *, - action_key: str, - author_ids: SequenceNotStr[str] | Omit = omit, - content_ids: SequenceNotStr[str] | Omit = omit, - duration: float | Omit = omit, - queue_id: str | Omit = omit, - value: str | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ExecuteExecuteResponse: - """ - Execute a moderation action on one or more content items. - - Args: - action_key: ID or key of the action to execute - - author_ids: IDs of the authors to apply the action to. Provide this or contentIds. - - content_ids: IDs of the content items to apply the action to. Provide this or authorIds. - - duration: Optional duration in milliseconds for actions with timeouts - - queue_id: Optional queue ID if the action is queue-specific - - value: Optional value to provide with the action - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._post( - "/actions/execute", - body=await async_maybe_transform( - { - "action_key": action_key, - "author_ids": author_ids, - "content_ids": content_ids, - "duration": duration, - "queue_id": queue_id, - "value": value, - }, - execute_execute_params.ExecuteExecuteParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ExecuteExecuteResponse, - ) - - @typing_extensions.deprecated("deprecated") - async def execute_by_id( - self, - action_id: str, - *, - author_ids: SequenceNotStr[str] | Omit = omit, - content_ids: SequenceNotStr[str] | Omit = omit, - queue_id: str | Omit = omit, - value: str | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ExecuteExecuteByIDResponse: - """ - Execute an action on a set of content items in a queue. - - Args: - action_id: The ID or key of the action to execute. - - author_ids: IDs of the authors to apply the action to - - content_ids: The IDs of the content items to perform the action on. - - queue_id: The ID of the queue the action was performed from if any. - - value: The value of the action. Useful to set a reason for the action etc. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not action_id: - raise ValueError(f"Expected a non-empty value for `action_id` but received {action_id!r}") - return await self._post( - path_template("/actions/{action_id}/execute", action_id=action_id), - body=await async_maybe_transform( - { - "author_ids": author_ids, - "content_ids": content_ids, - "queue_id": queue_id, - "value": value, - }, - execute_execute_by_id_params.ExecuteExecuteByIDParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ExecuteExecuteByIDResponse, - ) - - -class ExecuteResourceWithRawResponse: - def __init__(self, execute: ExecuteResource) -> None: - self._execute = execute - - self.execute = to_raw_response_wrapper( - execute.execute, - ) - self.execute_by_id = ( # pyright: ignore[reportDeprecated] - to_raw_response_wrapper( - execute.execute_by_id, # pyright: ignore[reportDeprecated], - ) - ) - - -class AsyncExecuteResourceWithRawResponse: - def __init__(self, execute: AsyncExecuteResource) -> None: - self._execute = execute - - self.execute = async_to_raw_response_wrapper( - execute.execute, - ) - self.execute_by_id = ( # pyright: ignore[reportDeprecated] - async_to_raw_response_wrapper( - execute.execute_by_id, # pyright: ignore[reportDeprecated], - ) - ) - - -class ExecuteResourceWithStreamingResponse: - def __init__(self, execute: ExecuteResource) -> None: - self._execute = execute - - self.execute = to_streamed_response_wrapper( - execute.execute, - ) - self.execute_by_id = ( # pyright: ignore[reportDeprecated] - to_streamed_response_wrapper( - execute.execute_by_id, # pyright: ignore[reportDeprecated], - ) - ) - - -class AsyncExecuteResourceWithStreamingResponse: - def __init__(self, execute: AsyncExecuteResource) -> None: - self._execute = execute - - self.execute = async_to_streamed_response_wrapper( - execute.execute, - ) - self.execute_by_id = ( # pyright: ignore[reportDeprecated] - async_to_streamed_response_wrapper( - execute.execute_by_id, # pyright: ignore[reportDeprecated], - ) - ) diff --git a/src/moderation_api/resources/auth.py b/src/moderation_api/resources/auth.py deleted file mode 100644 index 81c37ce..0000000 --- a/src/moderation_api/resources/auth.py +++ /dev/null @@ -1,204 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import typing_extensions - -import httpx - -from .._types import Body, Query, Headers, NotGiven, not_given -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from .._base_client import make_request_options -from ..types.auth_create_response import AuthCreateResponse -from ..types.auth_retrieve_response import AuthRetrieveResponse - -__all__ = ["AuthResource", "AsyncAuthResource"] - - -class AuthResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> AuthResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return AuthResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AuthResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return AuthResourceWithStreamingResponse(self) - - @typing_extensions.deprecated("deprecated") - def create( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AuthCreateResponse: - return self._post( - "/auth", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthCreateResponse, - ) - - @typing_extensions.deprecated("deprecated") - def retrieve( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AuthRetrieveResponse: - return self._get( - "/auth", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthRetrieveResponse, - ) - - -class AsyncAuthResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncAuthResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncAuthResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncAuthResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return AsyncAuthResourceWithStreamingResponse(self) - - @typing_extensions.deprecated("deprecated") - async def create( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AuthCreateResponse: - return await self._post( - "/auth", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthCreateResponse, - ) - - @typing_extensions.deprecated("deprecated") - async def retrieve( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AuthRetrieveResponse: - return await self._get( - "/auth", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthRetrieveResponse, - ) - - -class AuthResourceWithRawResponse: - def __init__(self, auth: AuthResource) -> None: - self._auth = auth - - self.create = ( # pyright: ignore[reportDeprecated] - to_raw_response_wrapper( - auth.create, # pyright: ignore[reportDeprecated], - ) - ) - self.retrieve = ( # pyright: ignore[reportDeprecated] - to_raw_response_wrapper( - auth.retrieve, # pyright: ignore[reportDeprecated], - ) - ) - - -class AsyncAuthResourceWithRawResponse: - def __init__(self, auth: AsyncAuthResource) -> None: - self._auth = auth - - self.create = ( # pyright: ignore[reportDeprecated] - async_to_raw_response_wrapper( - auth.create, # pyright: ignore[reportDeprecated], - ) - ) - self.retrieve = ( # pyright: ignore[reportDeprecated] - async_to_raw_response_wrapper( - auth.retrieve, # pyright: ignore[reportDeprecated], - ) - ) - - -class AuthResourceWithStreamingResponse: - def __init__(self, auth: AuthResource) -> None: - self._auth = auth - - self.create = ( # pyright: ignore[reportDeprecated] - to_streamed_response_wrapper( - auth.create, # pyright: ignore[reportDeprecated], - ) - ) - self.retrieve = ( # pyright: ignore[reportDeprecated] - to_streamed_response_wrapper( - auth.retrieve, # pyright: ignore[reportDeprecated], - ) - ) - - -class AsyncAuthResourceWithStreamingResponse: - def __init__(self, auth: AsyncAuthResource) -> None: - self._auth = auth - - self.create = ( # pyright: ignore[reportDeprecated] - async_to_streamed_response_wrapper( - auth.create, # pyright: ignore[reportDeprecated], - ) - ) - self.retrieve = ( # pyright: ignore[reportDeprecated] - async_to_streamed_response_wrapper( - auth.retrieve, # pyright: ignore[reportDeprecated], - ) - ) diff --git a/src/moderation_api/resources/authors.py b/src/moderation_api/resources/authors.py deleted file mode 100644 index 06ac656..0000000 --- a/src/moderation_api/resources/authors.py +++ /dev/null @@ -1,740 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Optional -from typing_extensions import Literal - -import httpx - -from ..types import author_list_params, author_create_params, author_update_params -from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import path_template, maybe_transform, async_maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from .._base_client import make_request_options -from ..types.author_list_response import AuthorListResponse -from ..types.author_create_response import AuthorCreateResponse -from ..types.author_delete_response import AuthorDeleteResponse -from ..types.author_update_response import AuthorUpdateResponse -from ..types.author_retrieve_response import AuthorRetrieveResponse - -__all__ = ["AuthorsResource", "AsyncAuthorsResource"] - - -class AuthorsResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> AuthorsResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return AuthorsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AuthorsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return AuthorsResourceWithStreamingResponse(self) - - def create( - self, - *, - external_id: str, - company: Optional[str] | Omit = omit, - email: Optional[str] | Omit = omit, - external_link: Optional[str] | Omit = omit, - first_seen: float | Omit = omit, - last_seen: float | Omit = omit, - manual_trust_level: Optional[float] | Omit = omit, - metadata: author_create_params.Metadata | Omit = omit, - name: Optional[str] | Omit = omit, - profile_picture: Optional[str] | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AuthorCreateResponse: - """Create a new author. - - Typically not needed as authors are created automatically - when content is moderated. - - Args: - external_id: External ID of the user, typically the ID of the author in your database. - - company: The author's company or organization - - email: Author email address - - external_link: URL of the author's external profile - - first_seen: Timestamp when author first appeared - - last_seen: Timestamp of last activity - - metadata: Additional metadata provided by your system. We recommend including any relevant - information that may assist in the moderation process. - - name: Author name or identifier - - profile_picture: URL of the author's profile picture - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._post( - "/authors", - body=maybe_transform( - { - "external_id": external_id, - "company": company, - "email": email, - "external_link": external_link, - "first_seen": first_seen, - "last_seen": last_seen, - "manual_trust_level": manual_trust_level, - "metadata": metadata, - "name": name, - "profile_picture": profile_picture, - }, - author_create_params.AuthorCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthorCreateResponse, - ) - - def retrieve( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AuthorRetrieveResponse: - """ - Get detailed information about a specific author including historical data and - analysis - - Args: - id: Either external ID or the ID assigned by moderation API. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._get( - path_template("/authors/{id}", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthorRetrieveResponse, - ) - - def update( - self, - id: str, - *, - company: Optional[str] | Omit = omit, - email: Optional[str] | Omit = omit, - external_link: Optional[str] | Omit = omit, - first_seen: float | Omit = omit, - last_seen: float | Omit = omit, - manual_trust_level: Optional[float] | Omit = omit, - metadata: author_update_params.Metadata | Omit = omit, - name: Optional[str] | Omit = omit, - profile_picture: Optional[str] | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AuthorUpdateResponse: - """ - Update the details of a specific author - - Args: - id: Either external ID or the ID assigned by moderation API. - - company: The author's company or organization - - email: Author email address - - external_link: URL of the author's external profile - - first_seen: Timestamp when author first appeared - - last_seen: Timestamp of last activity - - metadata: Additional metadata provided by your system. We recommend including any relevant - information that may assist in the moderation process. - - name: Author name or identifier - - profile_picture: URL of the author's profile picture - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._put( - path_template("/authors/{id}", id=id), - body=maybe_transform( - { - "company": company, - "email": email, - "external_link": external_link, - "first_seen": first_seen, - "last_seen": last_seen, - "manual_trust_level": manual_trust_level, - "metadata": metadata, - "name": name, - "profile_picture": profile_picture, - }, - author_update_params.AuthorUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthorUpdateResponse, - ) - - def list( - self, - *, - content_types: str | Omit = omit, - last_active_date: str | Omit = omit, - member_since_date: str | Omit = omit, - page_number: float | Omit = omit, - page_size: float | Omit = omit, - sort_by: Literal[ - "trustLevel", - "violationCount", - "reportCount", - "memberSince", - "lastActive", - "contentCount", - "flaggedContentRatio", - "averageSentiment", - ] - | Omit = omit, - sort_direction: Literal["asc", "desc"] | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AuthorListResponse: - """ - Get a paginated list of authors with their activity metrics and reputation - - Args: - page_number: Page number to fetch - - page_size: Number of authors per page - - sort_direction: Sort direction - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._get( - "/authors", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "content_types": content_types, - "last_active_date": last_active_date, - "member_since_date": member_since_date, - "page_number": page_number, - "page_size": page_size, - "sort_by": sort_by, - "sort_direction": sort_direction, - }, - author_list_params.AuthorListParams, - ), - ), - cast_to=AuthorListResponse, - ) - - def delete( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AuthorDeleteResponse: - """Delete a specific author. - - This resets the author: status, blocks, trust level, - metrics and action history are removed. The author is created again with a clean - record the next time content is moderated for the same ID. - - Args: - id: Either external ID or the ID assigned by moderation API. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._delete( - path_template("/authors/{id}", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthorDeleteResponse, - ) - - -class AsyncAuthorsResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncAuthorsResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncAuthorsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncAuthorsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return AsyncAuthorsResourceWithStreamingResponse(self) - - async def create( - self, - *, - external_id: str, - company: Optional[str] | Omit = omit, - email: Optional[str] | Omit = omit, - external_link: Optional[str] | Omit = omit, - first_seen: float | Omit = omit, - last_seen: float | Omit = omit, - manual_trust_level: Optional[float] | Omit = omit, - metadata: author_create_params.Metadata | Omit = omit, - name: Optional[str] | Omit = omit, - profile_picture: Optional[str] | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AuthorCreateResponse: - """Create a new author. - - Typically not needed as authors are created automatically - when content is moderated. - - Args: - external_id: External ID of the user, typically the ID of the author in your database. - - company: The author's company or organization - - email: Author email address - - external_link: URL of the author's external profile - - first_seen: Timestamp when author first appeared - - last_seen: Timestamp of last activity - - metadata: Additional metadata provided by your system. We recommend including any relevant - information that may assist in the moderation process. - - name: Author name or identifier - - profile_picture: URL of the author's profile picture - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._post( - "/authors", - body=await async_maybe_transform( - { - "external_id": external_id, - "company": company, - "email": email, - "external_link": external_link, - "first_seen": first_seen, - "last_seen": last_seen, - "manual_trust_level": manual_trust_level, - "metadata": metadata, - "name": name, - "profile_picture": profile_picture, - }, - author_create_params.AuthorCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthorCreateResponse, - ) - - async def retrieve( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AuthorRetrieveResponse: - """ - Get detailed information about a specific author including historical data and - analysis - - Args: - id: Either external ID or the ID assigned by moderation API. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._get( - path_template("/authors/{id}", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthorRetrieveResponse, - ) - - async def update( - self, - id: str, - *, - company: Optional[str] | Omit = omit, - email: Optional[str] | Omit = omit, - external_link: Optional[str] | Omit = omit, - first_seen: float | Omit = omit, - last_seen: float | Omit = omit, - manual_trust_level: Optional[float] | Omit = omit, - metadata: author_update_params.Metadata | Omit = omit, - name: Optional[str] | Omit = omit, - profile_picture: Optional[str] | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AuthorUpdateResponse: - """ - Update the details of a specific author - - Args: - id: Either external ID or the ID assigned by moderation API. - - company: The author's company or organization - - email: Author email address - - external_link: URL of the author's external profile - - first_seen: Timestamp when author first appeared - - last_seen: Timestamp of last activity - - metadata: Additional metadata provided by your system. We recommend including any relevant - information that may assist in the moderation process. - - name: Author name or identifier - - profile_picture: URL of the author's profile picture - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._put( - path_template("/authors/{id}", id=id), - body=await async_maybe_transform( - { - "company": company, - "email": email, - "external_link": external_link, - "first_seen": first_seen, - "last_seen": last_seen, - "manual_trust_level": manual_trust_level, - "metadata": metadata, - "name": name, - "profile_picture": profile_picture, - }, - author_update_params.AuthorUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthorUpdateResponse, - ) - - async def list( - self, - *, - content_types: str | Omit = omit, - last_active_date: str | Omit = omit, - member_since_date: str | Omit = omit, - page_number: float | Omit = omit, - page_size: float | Omit = omit, - sort_by: Literal[ - "trustLevel", - "violationCount", - "reportCount", - "memberSince", - "lastActive", - "contentCount", - "flaggedContentRatio", - "averageSentiment", - ] - | Omit = omit, - sort_direction: Literal["asc", "desc"] | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AuthorListResponse: - """ - Get a paginated list of authors with their activity metrics and reputation - - Args: - page_number: Page number to fetch - - page_size: Number of authors per page - - sort_direction: Sort direction - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._get( - "/authors", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - { - "content_types": content_types, - "last_active_date": last_active_date, - "member_since_date": member_since_date, - "page_number": page_number, - "page_size": page_size, - "sort_by": sort_by, - "sort_direction": sort_direction, - }, - author_list_params.AuthorListParams, - ), - ), - cast_to=AuthorListResponse, - ) - - async def delete( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AuthorDeleteResponse: - """Delete a specific author. - - This resets the author: status, blocks, trust level, - metrics and action history are removed. The author is created again with a clean - record the next time content is moderated for the same ID. - - Args: - id: Either external ID or the ID assigned by moderation API. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._delete( - path_template("/authors/{id}", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthorDeleteResponse, - ) - - -class AuthorsResourceWithRawResponse: - def __init__(self, authors: AuthorsResource) -> None: - self._authors = authors - - self.create = to_raw_response_wrapper( - authors.create, - ) - self.retrieve = to_raw_response_wrapper( - authors.retrieve, - ) - self.update = to_raw_response_wrapper( - authors.update, - ) - self.list = to_raw_response_wrapper( - authors.list, - ) - self.delete = to_raw_response_wrapper( - authors.delete, - ) - - -class AsyncAuthorsResourceWithRawResponse: - def __init__(self, authors: AsyncAuthorsResource) -> None: - self._authors = authors - - self.create = async_to_raw_response_wrapper( - authors.create, - ) - self.retrieve = async_to_raw_response_wrapper( - authors.retrieve, - ) - self.update = async_to_raw_response_wrapper( - authors.update, - ) - self.list = async_to_raw_response_wrapper( - authors.list, - ) - self.delete = async_to_raw_response_wrapper( - authors.delete, - ) - - -class AuthorsResourceWithStreamingResponse: - def __init__(self, authors: AuthorsResource) -> None: - self._authors = authors - - self.create = to_streamed_response_wrapper( - authors.create, - ) - self.retrieve = to_streamed_response_wrapper( - authors.retrieve, - ) - self.update = to_streamed_response_wrapper( - authors.update, - ) - self.list = to_streamed_response_wrapper( - authors.list, - ) - self.delete = to_streamed_response_wrapper( - authors.delete, - ) - - -class AsyncAuthorsResourceWithStreamingResponse: - def __init__(self, authors: AsyncAuthorsResource) -> None: - self._authors = authors - - self.create = async_to_streamed_response_wrapper( - authors.create, - ) - self.retrieve = async_to_streamed_response_wrapper( - authors.retrieve, - ) - self.update = async_to_streamed_response_wrapper( - authors.update, - ) - self.list = async_to_streamed_response_wrapper( - authors.list, - ) - self.delete = async_to_streamed_response_wrapper( - authors.delete, - ) diff --git a/src/moderation_api/resources/content.py b/src/moderation_api/resources/content.py deleted file mode 100644 index 9d68f93..0000000 --- a/src/moderation_api/resources/content.py +++ /dev/null @@ -1,384 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Dict, Iterable -from typing_extensions import Literal - -import httpx - -from ..types import content_submit_params -from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from .._base_client import make_request_options -from ..types.content_submit_response import ContentSubmitResponse - -__all__ = ["ContentResource", "AsyncContentResource"] - - -class ContentResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> ContentResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return ContentResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> ContentResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return ContentResourceWithStreamingResponse(self) - - def stream( - self, - *, - sec_web_socket_protocol: Literal["moderationapi.v1"], - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> None: - """Open a WebSocket to moderate live voice/call audio in real time. - - Speech is - transcribed and each finalized utterance is moderated by your enabled text - policies; you receive a verdict per utterance as it's spoken. - - **This is a WebSocket upgrade, not a regular HTTP call.** The request body below - documents the frames you _send_ over the socket; the `101` response documents - the events you _receive_. - - - **Auth:** `Authorization: Bearer ` on the upgrade. A missing/invalid - key closes `4401`; voice not enabled on the plan/channel closes `4403`. - - **Subprotocol:** request `moderationapi.v1`. - - **Flow:** send one `start` frame, then `media` frames as audio arrives, then - `stop` (or disconnect). You receive `session.started`, `utterance.final` per - utterance, optional `utterance.partial`/`warning`, and `session.ended`. - - **Close codes:** `1000` normal · `1011` server error · `4400` bad request · - `4401` auth failed · `4403` voice not enabled · `4429` concurrency limit. - - See the - [Real-time voice guide](https://docs.moderationapi.com/content-moderation/real-time-voice) - for the full walkthrough and code examples. - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - extra_headers.update({"Sec-WebSocket-Protocol": str(sec_web_socket_protocol)}) - return self._get( - "/stream" if self._client._base_url_overridden else "wss://voice.moderationapi.com/v1/stream", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=NoneType, - ) - - def submit( - self, - *, - content: content_submit_params.Content, - author_id: str | Omit = omit, - channel: str | Omit = omit, - client_action: content_submit_params.ClientAction | Omit = omit, - content_id: str | Omit = omit, - conversation_id: str | Omit = omit, - do_not_store: bool | Omit = omit, - metadata: Dict[str, object] | Omit = omit, - meta_type: Literal["profile", "message", "post", "comment", "event", "product", "review", "voice", "other"] - | Omit = omit, - policies: Iterable[content_submit_params.Policy] | Omit = omit, - timestamp: float | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ContentSubmitResponse: - """ - Args: - content: The content sent for moderation - - author_id: The author of the content. - - channel: Provide a channel ID or key. Will use the project's default channel if not - provided. - - client_action: A recommendation from your own client-side flagging (e.g. a banned-IP list or a - third-party tool). Feeds the rules engine and can escalate or override the - recommended action. Does not change whether our analysis flagged the content. - - content_id: The unique ID of the content in your database. - - conversation_id: For example the ID of a chat room or a post - - do_not_store: Do not store the content. The content won't enter the review queue - - metadata: Any metadata you want to store with the content - - meta_type: The meta type of content being moderated - - policies: (Enterprise) override the channel policies for this moderation request only. - - timestamp: Unix timestamp (in milliseconds) of when the content was created. Use if content - is not submitted in real-time. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._post( - "/moderate", - body=maybe_transform( - { - "content": content, - "author_id": author_id, - "channel": channel, - "client_action": client_action, - "content_id": content_id, - "conversation_id": conversation_id, - "do_not_store": do_not_store, - "metadata": metadata, - "meta_type": meta_type, - "policies": policies, - "timestamp": timestamp, - }, - content_submit_params.ContentSubmitParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ContentSubmitResponse, - ) - - -class AsyncContentResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncContentResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncContentResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncContentResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return AsyncContentResourceWithStreamingResponse(self) - - async def stream( - self, - *, - sec_web_socket_protocol: Literal["moderationapi.v1"], - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> None: - """Open a WebSocket to moderate live voice/call audio in real time. - - Speech is - transcribed and each finalized utterance is moderated by your enabled text - policies; you receive a verdict per utterance as it's spoken. - - **This is a WebSocket upgrade, not a regular HTTP call.** The request body below - documents the frames you _send_ over the socket; the `101` response documents - the events you _receive_. - - - **Auth:** `Authorization: Bearer ` on the upgrade. A missing/invalid - key closes `4401`; voice not enabled on the plan/channel closes `4403`. - - **Subprotocol:** request `moderationapi.v1`. - - **Flow:** send one `start` frame, then `media` frames as audio arrives, then - `stop` (or disconnect). You receive `session.started`, `utterance.final` per - utterance, optional `utterance.partial`/`warning`, and `session.ended`. - - **Close codes:** `1000` normal · `1011` server error · `4400` bad request · - `4401` auth failed · `4403` voice not enabled · `4429` concurrency limit. - - See the - [Real-time voice guide](https://docs.moderationapi.com/content-moderation/real-time-voice) - for the full walkthrough and code examples. - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - extra_headers.update({"Sec-WebSocket-Protocol": str(sec_web_socket_protocol)}) - return await self._get( - "/stream" if self._client._base_url_overridden else "wss://voice.moderationapi.com/v1/stream", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=NoneType, - ) - - async def submit( - self, - *, - content: content_submit_params.Content, - author_id: str | Omit = omit, - channel: str | Omit = omit, - client_action: content_submit_params.ClientAction | Omit = omit, - content_id: str | Omit = omit, - conversation_id: str | Omit = omit, - do_not_store: bool | Omit = omit, - metadata: Dict[str, object] | Omit = omit, - meta_type: Literal["profile", "message", "post", "comment", "event", "product", "review", "voice", "other"] - | Omit = omit, - policies: Iterable[content_submit_params.Policy] | Omit = omit, - timestamp: float | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ContentSubmitResponse: - """ - Args: - content: The content sent for moderation - - author_id: The author of the content. - - channel: Provide a channel ID or key. Will use the project's default channel if not - provided. - - client_action: A recommendation from your own client-side flagging (e.g. a banned-IP list or a - third-party tool). Feeds the rules engine and can escalate or override the - recommended action. Does not change whether our analysis flagged the content. - - content_id: The unique ID of the content in your database. - - conversation_id: For example the ID of a chat room or a post - - do_not_store: Do not store the content. The content won't enter the review queue - - metadata: Any metadata you want to store with the content - - meta_type: The meta type of content being moderated - - policies: (Enterprise) override the channel policies for this moderation request only. - - timestamp: Unix timestamp (in milliseconds) of when the content was created. Use if content - is not submitted in real-time. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._post( - "/moderate", - body=await async_maybe_transform( - { - "content": content, - "author_id": author_id, - "channel": channel, - "client_action": client_action, - "content_id": content_id, - "conversation_id": conversation_id, - "do_not_store": do_not_store, - "metadata": metadata, - "meta_type": meta_type, - "policies": policies, - "timestamp": timestamp, - }, - content_submit_params.ContentSubmitParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ContentSubmitResponse, - ) - - -class ContentResourceWithRawResponse: - def __init__(self, content: ContentResource) -> None: - self._content = content - - self.stream = to_raw_response_wrapper( - content.stream, - ) - self.submit = to_raw_response_wrapper( - content.submit, - ) - - -class AsyncContentResourceWithRawResponse: - def __init__(self, content: AsyncContentResource) -> None: - self._content = content - - self.stream = async_to_raw_response_wrapper( - content.stream, - ) - self.submit = async_to_raw_response_wrapper( - content.submit, - ) - - -class ContentResourceWithStreamingResponse: - def __init__(self, content: ContentResource) -> None: - self._content = content - - self.stream = to_streamed_response_wrapper( - content.stream, - ) - self.submit = to_streamed_response_wrapper( - content.submit, - ) - - -class AsyncContentResourceWithStreamingResponse: - def __init__(self, content: AsyncContentResource) -> None: - self._content = content - - self.stream = async_to_streamed_response_wrapper( - content.stream, - ) - self.submit = async_to_streamed_response_wrapper( - content.submit, - ) diff --git a/src/moderation_api/resources/queue/__init__.py b/src/moderation_api/resources/queue/__init__.py deleted file mode 100644 index 55ef8b8..0000000 --- a/src/moderation_api/resources/queue/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .items import ( - ItemsResource, - AsyncItemsResource, - ItemsResourceWithRawResponse, - AsyncItemsResourceWithRawResponse, - ItemsResourceWithStreamingResponse, - AsyncItemsResourceWithStreamingResponse, -) -from .queue import ( - QueueResource, - AsyncQueueResource, - QueueResourceWithRawResponse, - AsyncQueueResourceWithRawResponse, - QueueResourceWithStreamingResponse, - AsyncQueueResourceWithStreamingResponse, -) - -__all__ = [ - "ItemsResource", - "AsyncItemsResource", - "ItemsResourceWithRawResponse", - "AsyncItemsResourceWithRawResponse", - "ItemsResourceWithStreamingResponse", - "AsyncItemsResourceWithStreamingResponse", - "QueueResource", - "AsyncQueueResource", - "QueueResourceWithRawResponse", - "AsyncQueueResourceWithRawResponse", - "QueueResourceWithStreamingResponse", - "AsyncQueueResourceWithStreamingResponse", -] diff --git a/src/moderation_api/resources/queue/items.py b/src/moderation_api/resources/queue/items.py deleted file mode 100644 index 9653876..0000000 --- a/src/moderation_api/resources/queue/items.py +++ /dev/null @@ -1,445 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal - -import httpx - -from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import path_template, maybe_transform, async_maybe_transform -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from ...types.queue import item_list_params, item_resolve_params, item_unresolve_params -from ..._base_client import make_request_options -from ...types.queue.item_list_response import ItemListResponse -from ...types.queue.item_resolve_response import ItemResolveResponse -from ...types.queue.item_unresolve_response import ItemUnresolveResponse - -__all__ = ["ItemsResource", "AsyncItemsResource"] - - -class ItemsResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> ItemsResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return ItemsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> ItemsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return ItemsResourceWithStreamingResponse(self) - - def list( - self, - id: str, - *, - after_date: str | Omit = omit, - author_id: str | Omit = omit, - before_date: str | Omit = omit, - conversation_ids: str | Omit = omit, - filtered_action_ids: str | Omit = omit, - include_resolved: str | Omit = omit, - labels: str | Omit = omit, - page_number: float | Omit = omit, - page_size: float | Omit = omit, - sort_direction: Literal["asc", "desc"] | Omit = omit, - sort_field: Literal["createdAt", "severity", "reviewedAt"] | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ItemListResponse: - """ - Get paginated list of items in a moderation queue with filtering options - - Args: - id: The queue ID - - page_number: Page number to fetch - - page_size: Number of items per page - - sort_direction: Sort direction - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._get( - path_template("/queue/{id}/items", id=id), - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "after_date": after_date, - "author_id": author_id, - "before_date": before_date, - "conversation_ids": conversation_ids, - "filtered_action_ids": filtered_action_ids, - "include_resolved": include_resolved, - "labels": labels, - "page_number": page_number, - "page_size": page_size, - "sort_direction": sort_direction, - "sort_field": sort_field, - }, - item_list_params.ItemListParams, - ), - ), - cast_to=ItemListResponse, - ) - - def resolve( - self, - item_id: str, - *, - id: str, - comment: str | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ItemResolveResponse: - """ - Mark a queue item as resolved with a specific moderation action - - Args: - id: The queue ID - - item_id: The item ID to resolve - - comment: Optional comment - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - if not item_id: - raise ValueError(f"Expected a non-empty value for `item_id` but received {item_id!r}") - return self._post( - path_template("/queue/{id}/items/{item_id}/resolve", id=id, item_id=item_id), - body=maybe_transform({"comment": comment}, item_resolve_params.ItemResolveParams), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ItemResolveResponse, - ) - - def unresolve( - self, - item_id: str, - *, - id: str, - comment: str | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ItemUnresolveResponse: - """ - Mark a previously resolved queue item as unresolved/pending - - Args: - id: The queue ID - - item_id: The item ID to unresolve - - comment: Optional reason for unresolving the item - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - if not item_id: - raise ValueError(f"Expected a non-empty value for `item_id` but received {item_id!r}") - return self._post( - path_template("/queue/{id}/items/{item_id}/unresolve", id=id, item_id=item_id), - body=maybe_transform({"comment": comment}, item_unresolve_params.ItemUnresolveParams), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ItemUnresolveResponse, - ) - - -class AsyncItemsResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncItemsResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncItemsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncItemsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return AsyncItemsResourceWithStreamingResponse(self) - - async def list( - self, - id: str, - *, - after_date: str | Omit = omit, - author_id: str | Omit = omit, - before_date: str | Omit = omit, - conversation_ids: str | Omit = omit, - filtered_action_ids: str | Omit = omit, - include_resolved: str | Omit = omit, - labels: str | Omit = omit, - page_number: float | Omit = omit, - page_size: float | Omit = omit, - sort_direction: Literal["asc", "desc"] | Omit = omit, - sort_field: Literal["createdAt", "severity", "reviewedAt"] | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ItemListResponse: - """ - Get paginated list of items in a moderation queue with filtering options - - Args: - id: The queue ID - - page_number: Page number to fetch - - page_size: Number of items per page - - sort_direction: Sort direction - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._get( - path_template("/queue/{id}/items", id=id), - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - { - "after_date": after_date, - "author_id": author_id, - "before_date": before_date, - "conversation_ids": conversation_ids, - "filtered_action_ids": filtered_action_ids, - "include_resolved": include_resolved, - "labels": labels, - "page_number": page_number, - "page_size": page_size, - "sort_direction": sort_direction, - "sort_field": sort_field, - }, - item_list_params.ItemListParams, - ), - ), - cast_to=ItemListResponse, - ) - - async def resolve( - self, - item_id: str, - *, - id: str, - comment: str | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ItemResolveResponse: - """ - Mark a queue item as resolved with a specific moderation action - - Args: - id: The queue ID - - item_id: The item ID to resolve - - comment: Optional comment - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - if not item_id: - raise ValueError(f"Expected a non-empty value for `item_id` but received {item_id!r}") - return await self._post( - path_template("/queue/{id}/items/{item_id}/resolve", id=id, item_id=item_id), - body=await async_maybe_transform({"comment": comment}, item_resolve_params.ItemResolveParams), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ItemResolveResponse, - ) - - async def unresolve( - self, - item_id: str, - *, - id: str, - comment: str | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ItemUnresolveResponse: - """ - Mark a previously resolved queue item as unresolved/pending - - Args: - id: The queue ID - - item_id: The item ID to unresolve - - comment: Optional reason for unresolving the item - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - if not item_id: - raise ValueError(f"Expected a non-empty value for `item_id` but received {item_id!r}") - return await self._post( - path_template("/queue/{id}/items/{item_id}/unresolve", id=id, item_id=item_id), - body=await async_maybe_transform({"comment": comment}, item_unresolve_params.ItemUnresolveParams), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ItemUnresolveResponse, - ) - - -class ItemsResourceWithRawResponse: - def __init__(self, items: ItemsResource) -> None: - self._items = items - - self.list = to_raw_response_wrapper( - items.list, - ) - self.resolve = to_raw_response_wrapper( - items.resolve, - ) - self.unresolve = to_raw_response_wrapper( - items.unresolve, - ) - - -class AsyncItemsResourceWithRawResponse: - def __init__(self, items: AsyncItemsResource) -> None: - self._items = items - - self.list = async_to_raw_response_wrapper( - items.list, - ) - self.resolve = async_to_raw_response_wrapper( - items.resolve, - ) - self.unresolve = async_to_raw_response_wrapper( - items.unresolve, - ) - - -class ItemsResourceWithStreamingResponse: - def __init__(self, items: ItemsResource) -> None: - self._items = items - - self.list = to_streamed_response_wrapper( - items.list, - ) - self.resolve = to_streamed_response_wrapper( - items.resolve, - ) - self.unresolve = to_streamed_response_wrapper( - items.unresolve, - ) - - -class AsyncItemsResourceWithStreamingResponse: - def __init__(self, items: AsyncItemsResource) -> None: - self._items = items - - self.list = async_to_streamed_response_wrapper( - items.list, - ) - self.resolve = async_to_streamed_response_wrapper( - items.resolve, - ) - self.unresolve = async_to_streamed_response_wrapper( - items.unresolve, - ) diff --git a/src/moderation_api/resources/queue/queue.py b/src/moderation_api/resources/queue/queue.py deleted file mode 100644 index f1e6975..0000000 --- a/src/moderation_api/resources/queue/queue.py +++ /dev/null @@ -1,302 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import httpx - -from .items import ( - ItemsResource, - AsyncItemsResource, - ItemsResourceWithRawResponse, - AsyncItemsResourceWithRawResponse, - ItemsResourceWithStreamingResponse, - AsyncItemsResourceWithStreamingResponse, -) -from ...types import queue_get_stats_params -from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import path_template, maybe_transform, async_maybe_transform -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from ..._base_client import make_request_options -from ...types.queue_retrieve_response import QueueRetrieveResponse -from ...types.queue_get_stats_response import QueueGetStatsResponse - -__all__ = ["QueueResource", "AsyncQueueResource"] - - -class QueueResource(SyncAPIResource): - @cached_property - def items(self) -> ItemsResource: - return ItemsResource(self._client) - - @cached_property - def with_raw_response(self) -> QueueResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return QueueResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> QueueResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return QueueResourceWithStreamingResponse(self) - - def retrieve( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> QueueRetrieveResponse: - """ - Get a queue - - Args: - id: The queue ID - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._get( - path_template("/queue/{id}", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=QueueRetrieveResponse, - ) - - def get_stats( - self, - id: str, - *, - within_days: str | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> QueueGetStatsResponse: - """ - Get detailed statistics about a moderation queue including review times, action - counts, and trends - - Args: - id: The queue ID - - within_days: Number of days to analyze statistics for - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._get( - path_template("/queue/{id}/stats", id=id), - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform({"within_days": within_days}, queue_get_stats_params.QueueGetStatsParams), - ), - cast_to=QueueGetStatsResponse, - ) - - -class AsyncQueueResource(AsyncAPIResource): - @cached_property - def items(self) -> AsyncItemsResource: - return AsyncItemsResource(self._client) - - @cached_property - def with_raw_response(self) -> AsyncQueueResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncQueueResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncQueueResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return AsyncQueueResourceWithStreamingResponse(self) - - async def retrieve( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> QueueRetrieveResponse: - """ - Get a queue - - Args: - id: The queue ID - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._get( - path_template("/queue/{id}", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=QueueRetrieveResponse, - ) - - async def get_stats( - self, - id: str, - *, - within_days: str | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> QueueGetStatsResponse: - """ - Get detailed statistics about a moderation queue including review times, action - counts, and trends - - Args: - id: The queue ID - - within_days: Number of days to analyze statistics for - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._get( - path_template("/queue/{id}/stats", id=id), - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - {"within_days": within_days}, queue_get_stats_params.QueueGetStatsParams - ), - ), - cast_to=QueueGetStatsResponse, - ) - - -class QueueResourceWithRawResponse: - def __init__(self, queue: QueueResource) -> None: - self._queue = queue - - self.retrieve = to_raw_response_wrapper( - queue.retrieve, - ) - self.get_stats = to_raw_response_wrapper( - queue.get_stats, - ) - - @cached_property - def items(self) -> ItemsResourceWithRawResponse: - return ItemsResourceWithRawResponse(self._queue.items) - - -class AsyncQueueResourceWithRawResponse: - def __init__(self, queue: AsyncQueueResource) -> None: - self._queue = queue - - self.retrieve = async_to_raw_response_wrapper( - queue.retrieve, - ) - self.get_stats = async_to_raw_response_wrapper( - queue.get_stats, - ) - - @cached_property - def items(self) -> AsyncItemsResourceWithRawResponse: - return AsyncItemsResourceWithRawResponse(self._queue.items) - - -class QueueResourceWithStreamingResponse: - def __init__(self, queue: QueueResource) -> None: - self._queue = queue - - self.retrieve = to_streamed_response_wrapper( - queue.retrieve, - ) - self.get_stats = to_streamed_response_wrapper( - queue.get_stats, - ) - - @cached_property - def items(self) -> ItemsResourceWithStreamingResponse: - return ItemsResourceWithStreamingResponse(self._queue.items) - - -class AsyncQueueResourceWithStreamingResponse: - def __init__(self, queue: AsyncQueueResource) -> None: - self._queue = queue - - self.retrieve = async_to_streamed_response_wrapper( - queue.retrieve, - ) - self.get_stats = async_to_streamed_response_wrapper( - queue.get_stats, - ) - - @cached_property - def items(self) -> AsyncItemsResourceWithStreamingResponse: - return AsyncItemsResourceWithStreamingResponse(self._queue.items) diff --git a/src/moderation_api/resources/webhook_secret.py b/src/moderation_api/resources/webhook_secret.py deleted file mode 100644 index 0bbd008..0000000 --- a/src/moderation_api/resources/webhook_secret.py +++ /dev/null @@ -1,143 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import httpx - -from .._types import Body, Query, Headers, NotGiven, not_given -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from .._base_client import make_request_options -from ..types.webhook_secret_retrieve_response import WebhookSecretRetrieveResponse - -__all__ = ["WebhookSecretResource", "AsyncWebhookSecretResource"] - - -class WebhookSecretResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> WebhookSecretResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return WebhookSecretResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> WebhookSecretResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return WebhookSecretResourceWithStreamingResponse(self) - - def retrieve( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WebhookSecretRetrieveResponse: - """ - Get the signing secret used to sign webhook deliveries for this project, - creating one if none exists yet. Verify deliveries by comparing the - `modapi-signature` header to HMAC-SHA256(raw request body, secret) hex-encoded. - """ - return self._get( - "/webhook-secret", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WebhookSecretRetrieveResponse, - ) - - -class AsyncWebhookSecretResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncWebhookSecretResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncWebhookSecretResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncWebhookSecretResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return AsyncWebhookSecretResourceWithStreamingResponse(self) - - async def retrieve( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WebhookSecretRetrieveResponse: - """ - Get the signing secret used to sign webhook deliveries for this project, - creating one if none exists yet. Verify deliveries by comparing the - `modapi-signature` header to HMAC-SHA256(raw request body, secret) hex-encoded. - """ - return await self._get( - "/webhook-secret", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WebhookSecretRetrieveResponse, - ) - - -class WebhookSecretResourceWithRawResponse: - def __init__(self, webhook_secret: WebhookSecretResource) -> None: - self._webhook_secret = webhook_secret - - self.retrieve = to_raw_response_wrapper( - webhook_secret.retrieve, - ) - - -class AsyncWebhookSecretResourceWithRawResponse: - def __init__(self, webhook_secret: AsyncWebhookSecretResource) -> None: - self._webhook_secret = webhook_secret - - self.retrieve = async_to_raw_response_wrapper( - webhook_secret.retrieve, - ) - - -class WebhookSecretResourceWithStreamingResponse: - def __init__(self, webhook_secret: WebhookSecretResource) -> None: - self._webhook_secret = webhook_secret - - self.retrieve = to_streamed_response_wrapper( - webhook_secret.retrieve, - ) - - -class AsyncWebhookSecretResourceWithStreamingResponse: - def __init__(self, webhook_secret: AsyncWebhookSecretResource) -> None: - self._webhook_secret = webhook_secret - - self.retrieve = async_to_streamed_response_wrapper( - webhook_secret.retrieve, - ) diff --git a/src/moderation_api/resources/webhooks.py b/src/moderation_api/resources/webhooks.py deleted file mode 100644 index 1366369..0000000 --- a/src/moderation_api/resources/webhooks.py +++ /dev/null @@ -1,618 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import List, Optional -from typing_extensions import Literal - -import httpx - -from ..types import webhook_create_params, webhook_update_params -from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import path_template, maybe_transform, async_maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from .._base_client import make_request_options -from ..types.webhook_list_response import WebhookListResponse -from ..types.webhook_create_response import WebhookCreateResponse -from ..types.webhook_delete_response import WebhookDeleteResponse -from ..types.webhook_update_response import WebhookUpdateResponse -from ..types.webhook_retrieve_response import WebhookRetrieveResponse - -__all__ = ["WebhooksResource", "AsyncWebhooksResource"] - - -class WebhooksResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> WebhooksResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return WebhooksResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> WebhooksResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return WebhooksResourceWithStreamingResponse(self) - - def create( - self, - *, - event_types: List[ - Literal[ - "QUEUE_ITEM_NEW", - "QUEUE_ITEM_COMPLETED", - "QUEUE_ITEM_ACTION", - "QUEUE_ITEM_REJECTED", - "QUEUE_ITEM_ALLOWED", - "AUTHOR_BLOCKED", - "AUTHOR_UNBLOCKED", - "AUTHOR_SUSPENDED", - "AUTHOR_UPDATED", - "AUTHOR_TRUST_LEVEL_CHANGED", - "AUTHOR_ACTION", - ] - ], - name: str, - url: str, - description: Optional[str] | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WebhookCreateResponse: - """Create a webhook subscribed to one or more event types. - - Deliveries use the v2 - envelope and are signed with the project signing secret (see the signing secret - endpoint). - - Args: - event_types: Event types this webhook subscribes to. One webhook URL receives all events you - list here. - - name: The webhook's name, used to identify it in the dashboard - - url: The webhook's URL. We'll call this URL when an event occurs. - - description: The webhook's description - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._post( - "/webhooks", - body=maybe_transform( - { - "event_types": event_types, - "name": name, - "url": url, - "description": description, - }, - webhook_create_params.WebhookCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WebhookCreateResponse, - ) - - def retrieve( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WebhookRetrieveResponse: - """ - Get a webhook by ID. - - Args: - id: The ID of the webhook to get. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._get( - path_template("/webhooks/{id}", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WebhookRetrieveResponse, - ) - - def update( - self, - id: str, - *, - description: Optional[str] | Omit = omit, - event_types: List[ - Literal[ - "QUEUE_ITEM_NEW", - "QUEUE_ITEM_COMPLETED", - "QUEUE_ITEM_ACTION", - "QUEUE_ITEM_REJECTED", - "QUEUE_ITEM_ALLOWED", - "AUTHOR_BLOCKED", - "AUTHOR_UNBLOCKED", - "AUTHOR_SUSPENDED", - "AUTHOR_UPDATED", - "AUTHOR_TRUST_LEVEL_CHANGED", - "AUTHOR_ACTION", - ] - ] - | Omit = omit, - name: str | Omit = omit, - url: str | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WebhookUpdateResponse: - """Update a webhook. - - Legacy v1 webhooks are read-only: delete them and create a new - webhook instead. - - Args: - id: The ID of the webhook to update. - - description: The webhook's description - - event_types: Event types this webhook subscribes to. One webhook URL receives all events you - list here. - - name: The webhook's name, used to identify it in the dashboard - - url: The webhook's URL. We'll call this URL when an event occurs. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._put( - path_template("/webhooks/{id}", id=id), - body=maybe_transform( - { - "description": description, - "event_types": event_types, - "name": name, - "url": url, - }, - webhook_update_params.WebhookUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WebhookUpdateResponse, - ) - - def list( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WebhookListResponse: - """List all webhooks for the authenticated project.""" - return self._get( - "/webhooks", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WebhookListResponse, - ) - - def delete( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WebhookDeleteResponse: - """ - Delete a webhook. - - Args: - id: The ID of the webhook to delete. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._delete( - path_template("/webhooks/{id}", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WebhookDeleteResponse, - ) - - -class AsyncWebhooksResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncWebhooksResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncWebhooksResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncWebhooksResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return AsyncWebhooksResourceWithStreamingResponse(self) - - async def create( - self, - *, - event_types: List[ - Literal[ - "QUEUE_ITEM_NEW", - "QUEUE_ITEM_COMPLETED", - "QUEUE_ITEM_ACTION", - "QUEUE_ITEM_REJECTED", - "QUEUE_ITEM_ALLOWED", - "AUTHOR_BLOCKED", - "AUTHOR_UNBLOCKED", - "AUTHOR_SUSPENDED", - "AUTHOR_UPDATED", - "AUTHOR_TRUST_LEVEL_CHANGED", - "AUTHOR_ACTION", - ] - ], - name: str, - url: str, - description: Optional[str] | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WebhookCreateResponse: - """Create a webhook subscribed to one or more event types. - - Deliveries use the v2 - envelope and are signed with the project signing secret (see the signing secret - endpoint). - - Args: - event_types: Event types this webhook subscribes to. One webhook URL receives all events you - list here. - - name: The webhook's name, used to identify it in the dashboard - - url: The webhook's URL. We'll call this URL when an event occurs. - - description: The webhook's description - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._post( - "/webhooks", - body=await async_maybe_transform( - { - "event_types": event_types, - "name": name, - "url": url, - "description": description, - }, - webhook_create_params.WebhookCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WebhookCreateResponse, - ) - - async def retrieve( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WebhookRetrieveResponse: - """ - Get a webhook by ID. - - Args: - id: The ID of the webhook to get. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._get( - path_template("/webhooks/{id}", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WebhookRetrieveResponse, - ) - - async def update( - self, - id: str, - *, - description: Optional[str] | Omit = omit, - event_types: List[ - Literal[ - "QUEUE_ITEM_NEW", - "QUEUE_ITEM_COMPLETED", - "QUEUE_ITEM_ACTION", - "QUEUE_ITEM_REJECTED", - "QUEUE_ITEM_ALLOWED", - "AUTHOR_BLOCKED", - "AUTHOR_UNBLOCKED", - "AUTHOR_SUSPENDED", - "AUTHOR_UPDATED", - "AUTHOR_TRUST_LEVEL_CHANGED", - "AUTHOR_ACTION", - ] - ] - | Omit = omit, - name: str | Omit = omit, - url: str | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WebhookUpdateResponse: - """Update a webhook. - - Legacy v1 webhooks are read-only: delete them and create a new - webhook instead. - - Args: - id: The ID of the webhook to update. - - description: The webhook's description - - event_types: Event types this webhook subscribes to. One webhook URL receives all events you - list here. - - name: The webhook's name, used to identify it in the dashboard - - url: The webhook's URL. We'll call this URL when an event occurs. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._put( - path_template("/webhooks/{id}", id=id), - body=await async_maybe_transform( - { - "description": description, - "event_types": event_types, - "name": name, - "url": url, - }, - webhook_update_params.WebhookUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WebhookUpdateResponse, - ) - - async def list( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WebhookListResponse: - """List all webhooks for the authenticated project.""" - return await self._get( - "/webhooks", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WebhookListResponse, - ) - - async def delete( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WebhookDeleteResponse: - """ - Delete a webhook. - - Args: - id: The ID of the webhook to delete. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._delete( - path_template("/webhooks/{id}", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WebhookDeleteResponse, - ) - - -class WebhooksResourceWithRawResponse: - def __init__(self, webhooks: WebhooksResource) -> None: - self._webhooks = webhooks - - self.create = to_raw_response_wrapper( - webhooks.create, - ) - self.retrieve = to_raw_response_wrapper( - webhooks.retrieve, - ) - self.update = to_raw_response_wrapper( - webhooks.update, - ) - self.list = to_raw_response_wrapper( - webhooks.list, - ) - self.delete = to_raw_response_wrapper( - webhooks.delete, - ) - - -class AsyncWebhooksResourceWithRawResponse: - def __init__(self, webhooks: AsyncWebhooksResource) -> None: - self._webhooks = webhooks - - self.create = async_to_raw_response_wrapper( - webhooks.create, - ) - self.retrieve = async_to_raw_response_wrapper( - webhooks.retrieve, - ) - self.update = async_to_raw_response_wrapper( - webhooks.update, - ) - self.list = async_to_raw_response_wrapper( - webhooks.list, - ) - self.delete = async_to_raw_response_wrapper( - webhooks.delete, - ) - - -class WebhooksResourceWithStreamingResponse: - def __init__(self, webhooks: WebhooksResource) -> None: - self._webhooks = webhooks - - self.create = to_streamed_response_wrapper( - webhooks.create, - ) - self.retrieve = to_streamed_response_wrapper( - webhooks.retrieve, - ) - self.update = to_streamed_response_wrapper( - webhooks.update, - ) - self.list = to_streamed_response_wrapper( - webhooks.list, - ) - self.delete = to_streamed_response_wrapper( - webhooks.delete, - ) - - -class AsyncWebhooksResourceWithStreamingResponse: - def __init__(self, webhooks: AsyncWebhooksResource) -> None: - self._webhooks = webhooks - - self.create = async_to_streamed_response_wrapper( - webhooks.create, - ) - self.retrieve = async_to_streamed_response_wrapper( - webhooks.retrieve, - ) - self.update = async_to_streamed_response_wrapper( - webhooks.update, - ) - self.list = async_to_streamed_response_wrapper( - webhooks.list, - ) - self.delete = async_to_streamed_response_wrapper( - webhooks.delete, - ) diff --git a/src/moderation_api/resources/wordlist/__init__.py b/src/moderation_api/resources/wordlist/__init__.py deleted file mode 100644 index 06a3b3b..0000000 --- a/src/moderation_api/resources/wordlist/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .words import ( - WordsResource, - AsyncWordsResource, - WordsResourceWithRawResponse, - AsyncWordsResourceWithRawResponse, - WordsResourceWithStreamingResponse, - AsyncWordsResourceWithStreamingResponse, -) -from .wordlist import ( - WordlistResource, - AsyncWordlistResource, - WordlistResourceWithRawResponse, - AsyncWordlistResourceWithRawResponse, - WordlistResourceWithStreamingResponse, - AsyncWordlistResourceWithStreamingResponse, -) - -__all__ = [ - "WordsResource", - "AsyncWordsResource", - "WordsResourceWithRawResponse", - "AsyncWordsResourceWithRawResponse", - "WordsResourceWithStreamingResponse", - "AsyncWordsResourceWithStreamingResponse", - "WordlistResource", - "AsyncWordlistResource", - "WordlistResourceWithRawResponse", - "AsyncWordlistResourceWithRawResponse", - "WordlistResourceWithStreamingResponse", - "AsyncWordlistResourceWithStreamingResponse", -] diff --git a/src/moderation_api/resources/wordlist/wordlist.py b/src/moderation_api/resources/wordlist/wordlist.py deleted file mode 100644 index 9ad95a2..0000000 --- a/src/moderation_api/resources/wordlist/wordlist.py +++ /dev/null @@ -1,470 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import httpx - -from .words import ( - WordsResource, - AsyncWordsResource, - WordsResourceWithRawResponse, - AsyncWordsResourceWithRawResponse, - WordsResourceWithStreamingResponse, - AsyncWordsResourceWithStreamingResponse, -) -from ...types import wordlist_update_params -from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given -from ..._utils import path_template, maybe_transform, async_maybe_transform -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from ..._base_client import make_request_options -from ...types.wordlist_list_response import WordlistListResponse -from ...types.wordlist_update_response import WordlistUpdateResponse -from ...types.wordlist_retrieve_response import WordlistRetrieveResponse -from ...types.wordlist_get_embedding_status_response import WordlistGetEmbeddingStatusResponse - -__all__ = ["WordlistResource", "AsyncWordlistResource"] - - -class WordlistResource(SyncAPIResource): - @cached_property - def words(self) -> WordsResource: - return WordsResource(self._client) - - @cached_property - def with_raw_response(self) -> WordlistResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return WordlistResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> WordlistResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return WordlistResourceWithStreamingResponse(self) - - def retrieve( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WordlistRetrieveResponse: - """ - Get a specific wordlist by ID - - Args: - id: ID of the wordlist to get - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._get( - path_template("/wordlist/{id}", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WordlistRetrieveResponse, - ) - - def update( - self, - id: str, - *, - description: str | Omit = omit, - key: str | Omit = omit, - name: str | Omit = omit, - strict: bool | Omit = omit, - words: SequenceNotStr[str] | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WordlistUpdateResponse: - """ - Update a wordlist - - Args: - id: ID of the wordlist to update - - description: New description for the wordlist - - key: New key for the wordlist - - name: New name for the wordlist - - strict: Deprecated. Now using threshold in project settings. - - words: New words for the wordlist. Replace the existing words with these new ones. - Duplicate words will be ignored. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._put( - path_template("/wordlist/{id}", id=id), - body=maybe_transform( - { - "description": description, - "key": key, - "name": name, - "strict": strict, - "words": words, - }, - wordlist_update_params.WordlistUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WordlistUpdateResponse, - ) - - def list( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WordlistListResponse: - """List all wordlists for the authenticated organization""" - return self._get( - "/wordlist", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WordlistListResponse, - ) - - def get_embedding_status( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WordlistGetEmbeddingStatusResponse: - """ - Get the current embedding progress status for a wordlist - - Args: - id: ID of the wordlist to check embedding status for - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._get( - path_template("/wordlist/{id}/embedding-status", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WordlistGetEmbeddingStatusResponse, - ) - - -class AsyncWordlistResource(AsyncAPIResource): - @cached_property - def words(self) -> AsyncWordsResource: - return AsyncWordsResource(self._client) - - @cached_property - def with_raw_response(self) -> AsyncWordlistResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncWordlistResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncWordlistResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return AsyncWordlistResourceWithStreamingResponse(self) - - async def retrieve( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WordlistRetrieveResponse: - """ - Get a specific wordlist by ID - - Args: - id: ID of the wordlist to get - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._get( - path_template("/wordlist/{id}", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WordlistRetrieveResponse, - ) - - async def update( - self, - id: str, - *, - description: str | Omit = omit, - key: str | Omit = omit, - name: str | Omit = omit, - strict: bool | Omit = omit, - words: SequenceNotStr[str] | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WordlistUpdateResponse: - """ - Update a wordlist - - Args: - id: ID of the wordlist to update - - description: New description for the wordlist - - key: New key for the wordlist - - name: New name for the wordlist - - strict: Deprecated. Now using threshold in project settings. - - words: New words for the wordlist. Replace the existing words with these new ones. - Duplicate words will be ignored. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._put( - path_template("/wordlist/{id}", id=id), - body=await async_maybe_transform( - { - "description": description, - "key": key, - "name": name, - "strict": strict, - "words": words, - }, - wordlist_update_params.WordlistUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WordlistUpdateResponse, - ) - - async def list( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WordlistListResponse: - """List all wordlists for the authenticated organization""" - return await self._get( - "/wordlist", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WordlistListResponse, - ) - - async def get_embedding_status( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WordlistGetEmbeddingStatusResponse: - """ - Get the current embedding progress status for a wordlist - - Args: - id: ID of the wordlist to check embedding status for - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._get( - path_template("/wordlist/{id}/embedding-status", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WordlistGetEmbeddingStatusResponse, - ) - - -class WordlistResourceWithRawResponse: - def __init__(self, wordlist: WordlistResource) -> None: - self._wordlist = wordlist - - self.retrieve = to_raw_response_wrapper( - wordlist.retrieve, - ) - self.update = to_raw_response_wrapper( - wordlist.update, - ) - self.list = to_raw_response_wrapper( - wordlist.list, - ) - self.get_embedding_status = to_raw_response_wrapper( - wordlist.get_embedding_status, - ) - - @cached_property - def words(self) -> WordsResourceWithRawResponse: - return WordsResourceWithRawResponse(self._wordlist.words) - - -class AsyncWordlistResourceWithRawResponse: - def __init__(self, wordlist: AsyncWordlistResource) -> None: - self._wordlist = wordlist - - self.retrieve = async_to_raw_response_wrapper( - wordlist.retrieve, - ) - self.update = async_to_raw_response_wrapper( - wordlist.update, - ) - self.list = async_to_raw_response_wrapper( - wordlist.list, - ) - self.get_embedding_status = async_to_raw_response_wrapper( - wordlist.get_embedding_status, - ) - - @cached_property - def words(self) -> AsyncWordsResourceWithRawResponse: - return AsyncWordsResourceWithRawResponse(self._wordlist.words) - - -class WordlistResourceWithStreamingResponse: - def __init__(self, wordlist: WordlistResource) -> None: - self._wordlist = wordlist - - self.retrieve = to_streamed_response_wrapper( - wordlist.retrieve, - ) - self.update = to_streamed_response_wrapper( - wordlist.update, - ) - self.list = to_streamed_response_wrapper( - wordlist.list, - ) - self.get_embedding_status = to_streamed_response_wrapper( - wordlist.get_embedding_status, - ) - - @cached_property - def words(self) -> WordsResourceWithStreamingResponse: - return WordsResourceWithStreamingResponse(self._wordlist.words) - - -class AsyncWordlistResourceWithStreamingResponse: - def __init__(self, wordlist: AsyncWordlistResource) -> None: - self._wordlist = wordlist - - self.retrieve = async_to_streamed_response_wrapper( - wordlist.retrieve, - ) - self.update = async_to_streamed_response_wrapper( - wordlist.update, - ) - self.list = async_to_streamed_response_wrapper( - wordlist.list, - ) - self.get_embedding_status = async_to_streamed_response_wrapper( - wordlist.get_embedding_status, - ) - - @cached_property - def words(self) -> AsyncWordsResourceWithStreamingResponse: - return AsyncWordsResourceWithStreamingResponse(self._wordlist.words) diff --git a/src/moderation_api/resources/wordlist/words.py b/src/moderation_api/resources/wordlist/words.py deleted file mode 100644 index 00a3813..0000000 --- a/src/moderation_api/resources/wordlist/words.py +++ /dev/null @@ -1,274 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import httpx - -from ..._types import Body, Query, Headers, NotGiven, SequenceNotStr, not_given -from ..._utils import path_template, maybe_transform, async_maybe_transform -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from ..._base_client import make_request_options -from ...types.wordlist import word_add_params, word_remove_params -from ...types.wordlist.word_add_response import WordAddResponse -from ...types.wordlist.word_remove_response import WordRemoveResponse - -__all__ = ["WordsResource", "AsyncWordsResource"] - - -class WordsResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> WordsResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return WordsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> WordsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return WordsResourceWithStreamingResponse(self) - - def add( - self, - id: str, - *, - words: SequenceNotStr[str], - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WordAddResponse: - """ - Add words to an existing wordlist - - Args: - id: ID of the wordlist to add words to - - words: Array of words to add to the wordlist. Duplicate words will be ignored. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._post( - path_template("/wordlist/{id}/words", id=id), - body=maybe_transform({"words": words}, word_add_params.WordAddParams), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WordAddResponse, - ) - - def remove( - self, - id: str, - *, - words: SequenceNotStr[str], - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WordRemoveResponse: - """ - Remove words from an existing wordlist - - Args: - id: ID of the wordlist to remove words from - - words: Array of words to remove from the wordlist - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._delete( - path_template("/wordlist/{id}/words", id=id), - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform({"words": words}, word_remove_params.WordRemoveParams), - ), - cast_to=WordRemoveResponse, - ) - - -class AsyncWordsResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncWordsResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncWordsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncWordsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response - """ - return AsyncWordsResourceWithStreamingResponse(self) - - async def add( - self, - id: str, - *, - words: SequenceNotStr[str], - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WordAddResponse: - """ - Add words to an existing wordlist - - Args: - id: ID of the wordlist to add words to - - words: Array of words to add to the wordlist. Duplicate words will be ignored. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._post( - path_template("/wordlist/{id}/words", id=id), - body=await async_maybe_transform({"words": words}, word_add_params.WordAddParams), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WordAddResponse, - ) - - async def remove( - self, - id: str, - *, - words: SequenceNotStr[str], - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WordRemoveResponse: - """ - Remove words from an existing wordlist - - Args: - id: ID of the wordlist to remove words from - - words: Array of words to remove from the wordlist - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._delete( - path_template("/wordlist/{id}/words", id=id), - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform({"words": words}, word_remove_params.WordRemoveParams), - ), - cast_to=WordRemoveResponse, - ) - - -class WordsResourceWithRawResponse: - def __init__(self, words: WordsResource) -> None: - self._words = words - - self.add = to_raw_response_wrapper( - words.add, - ) - self.remove = to_raw_response_wrapper( - words.remove, - ) - - -class AsyncWordsResourceWithRawResponse: - def __init__(self, words: AsyncWordsResource) -> None: - self._words = words - - self.add = async_to_raw_response_wrapper( - words.add, - ) - self.remove = async_to_raw_response_wrapper( - words.remove, - ) - - -class WordsResourceWithStreamingResponse: - def __init__(self, words: WordsResource) -> None: - self._words = words - - self.add = to_streamed_response_wrapper( - words.add, - ) - self.remove = to_streamed_response_wrapper( - words.remove, - ) - - -class AsyncWordsResourceWithStreamingResponse: - def __init__(self, words: AsyncWordsResource) -> None: - self._words = words - - self.add = async_to_streamed_response_wrapper( - words.add, - ) - self.remove = async_to_streamed_response_wrapper( - words.remove, - ) diff --git a/src/moderation_api/sdk.py b/src/moderation_api/sdk.py new file mode 100644 index 0000000..14ccb74 --- /dev/null +++ b/src/moderation_api/sdk.py @@ -0,0 +1,207 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .basesdk import BaseSDK +from .httpclient import AsyncHttpClient, ClientOwner, HttpClient, close_clients +from .sdkconfiguration import SDKConfiguration +from .utils.logger import Logger, get_default_logger +from .utils.retries import RetryConfig +import httpx +import importlib +from moderation_api import models, utils +from moderation_api._hooks import SDKHooks +from moderation_api.types import OptionalNullable, UNSET +import sys +from typing import Any, Callable, Dict, Optional, TYPE_CHECKING, Union, cast +import weakref + +if TYPE_CHECKING: + from moderation_api.account import Account + from moderation_api.actions import Actions + from moderation_api.authors import Authors + from moderation_api.channels import Channels + from moderation_api.content_sdk import ContentSDK + from moderation_api.projects import Projects + from moderation_api.queue_sdk import QueueSDK + from moderation_api.webhooks import Webhooks + from moderation_api.webhooksecret import WebhookSecret + from moderation_api.wordlist import Wordlist + + +class ModerationAPI(BaseSDK): + r"""Moderation API: Moderate text, images, video and audio in real time, manage authors, review queues and actions, and receive webhooks. + https://docs.moderationapi.com + """ + + authors: "Authors" + queue: "QueueSDK" + actions: "Actions" + webhooks: "Webhooks" + webhook_secret: "WebhookSecret" + account: "Account" + wordlist: "Wordlist" + content: "ContentSDK" + projects: "Projects" + channels: "Channels" + _sub_sdk_map = { + "authors": ("moderation_api.authors", "Authors"), + "queue": ("moderation_api.queue_sdk", "QueueSDK"), + "actions": ("moderation_api.actions", "Actions"), + "webhooks": ("moderation_api.webhooks", "Webhooks"), + "webhook_secret": ("moderation_api.webhooksecret", "WebhookSecret"), + "account": ("moderation_api.account", "Account"), + "wordlist": ("moderation_api.wordlist", "Wordlist"), + "content": ("moderation_api.content_sdk", "ContentSDK"), + "projects": ("moderation_api.projects", "Projects"), + "channels": ("moderation_api.channels", "Channels"), + } + + def __init__( + self, + secret_key: Optional[Union[Optional[str], Callable[[], Optional[str]]]] = None, + server_idx: Optional[int] = None, + url_params: Optional[Dict[str, str]] = None, + server_url: Optional[str] = None, + client: Optional[HttpClient] = None, + async_client: Optional[AsyncHttpClient] = None, + retry_config: OptionalNullable[RetryConfig] = UNSET, + timeout_ms: Optional[int] = None, + debug_logger: Optional[Logger] = None, + ) -> None: + r"""Instantiates the SDK configuring it with the provided parameters. + + :param secret_key: The secret_key required for authentication + :param server_idx: The index of the server to use for all methods + :param server_url: The server URL to use for all methods + :param url_params: Parameters to optionally template the server URL with + :param client: The HTTP client to use for all synchronous methods + :param async_client: The Async HTTP client to use for all asynchronous methods + :param retry_config: The retry configuration to use for all supported methods + :param timeout_ms: Optional request timeout applied to each operation in milliseconds + """ + client_supplied = True + if client is None: + client = httpx.Client(follow_redirects=True) + client_supplied = False + + assert issubclass( + type(client), HttpClient + ), "The provided client must implement the HttpClient protocol." + + async_client_supplied = True + if async_client is None: + async_client = httpx.AsyncClient(follow_redirects=True) + async_client_supplied = False + + if debug_logger is None: + debug_logger = get_default_logger() + + assert issubclass( + type(async_client), AsyncHttpClient + ), "The provided async_client must implement the AsyncHttpClient protocol." + + security: Any = None + if secret_key is None: + security = None + elif callable(secret_key): + # pylint: disable=unnecessary-lambda-assignment + security = lambda: models.Security(secret_key=secret_key()) + else: + security = models.Security(secret_key=secret_key) + + if server_url is not None: + if url_params is not None: + server_url = utils.template_url(server_url, url_params) + + BaseSDK.__init__( + self, + SDKConfiguration( + client=client, + client_supplied=client_supplied, + async_client=async_client, + async_client_supplied=async_client_supplied, + security=security, + server_url=server_url, + server_idx=server_idx, + retry_config=retry_config, + timeout_ms=timeout_ms, + debug_logger=debug_logger, + ), + parent_ref=self, + ) + + hooks = SDKHooks() + + # pylint: disable=protected-access + self.sdk_configuration.__dict__["_hooks"] = hooks + + self.sdk_configuration = hooks.sdk_init(self.sdk_configuration) + + weakref.finalize( + self, + close_clients, + cast(ClientOwner, self.sdk_configuration), + self.sdk_configuration.client, + self.sdk_configuration.client_supplied, + self.sdk_configuration.async_client, + self.sdk_configuration.async_client_supplied, + ) + + def dynamic_import(self, modname, retries=3): + for attempt in range(retries): + try: + return importlib.import_module(modname) + except KeyError: + # Clear any half-initialized module and retry + sys.modules.pop(modname, None) + if attempt == retries - 1: + break + raise KeyError(f"Failed to import module '{modname}' after {retries} attempts") + + def __getattr__(self, name: str): + if name in self._sub_sdk_map: + module_path, class_name = self._sub_sdk_map[name] + try: + module = self.dynamic_import(module_path) + klass = getattr(module, class_name) + instance = klass(self.sdk_configuration, parent_ref=self) + setattr(self, name, instance) + return instance + except ImportError as e: + raise AttributeError( + f"Failed to import module {module_path} for attribute {name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to find class {class_name} in module {module_path} for attribute {name}: {e}" + ) from e + + raise AttributeError( + f"'{type(self).__name__}' object has no attribute '{name}'" + ) + + def __dir__(self): + default_attrs = list(super().__dir__()) + lazy_attrs = list(self._sub_sdk_map.keys()) + return sorted(list(set(default_attrs + lazy_attrs))) + + def __enter__(self): + return self + + async def __aenter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if ( + self.sdk_configuration.client is not None + and not self.sdk_configuration.client_supplied + ): + self.sdk_configuration.client.close() + self.sdk_configuration.client = None + + async def __aexit__(self, exc_type, exc_val, exc_tb): + if ( + self.sdk_configuration.async_client is not None + and not self.sdk_configuration.async_client_supplied + ): + await self.sdk_configuration.async_client.aclose() + self.sdk_configuration.async_client = None diff --git a/src/moderation_api/sdkconfiguration.py b/src/moderation_api/sdkconfiguration.py new file mode 100644 index 0000000..8d8a9f9 --- /dev/null +++ b/src/moderation_api/sdkconfiguration.py @@ -0,0 +1,48 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ._version import ( + __gen_version__, + __openapi_doc_version__, + __user_agent__, + __version__, +) +from .httpclient import AsyncHttpClient, HttpClient +from .utils import Logger, RetryConfig, remove_suffix +from dataclasses import dataclass +from moderation_api import models +from moderation_api.types import OptionalNullable, UNSET +from pydantic import Field +from typing import Callable, Dict, Optional, Tuple, Union + + +SERVERS = [ + "https://moderationapi.com/api/v1", +] +"""Contains the list of servers available to the SDK""" + + +@dataclass +class SDKConfiguration: + client: Union[HttpClient, None] + client_supplied: bool + async_client: Union[AsyncHttpClient, None] + async_client_supplied: bool + debug_logger: Logger + security: Optional[Union[models.Security, Callable[[], models.Security]]] = None + server_url: Optional[str] = "" + server_idx: Optional[int] = 0 + language: str = "python" + openapi_doc_version: str = __openapi_doc_version__ + sdk_version: str = __version__ + gen_version: str = __gen_version__ + user_agent: str = __user_agent__ + retry_config: OptionalNullable[RetryConfig] = Field(default_factory=lambda: UNSET) + timeout_ms: Optional[int] = None + + def get_server_details(self) -> Tuple[str, Dict[str, str]]: + if self.server_url is not None and self.server_url: + return remove_suffix(self.server_url, "/"), {} + if self.server_idx is None: + self.server_idx = 0 + + return SERVERS[self.server_idx], {} diff --git a/src/moderation_api/types/__init__.py b/src/moderation_api/types/__init__.py index 217c8c2..faa2681 100644 --- a/src/moderation_api/types/__init__.py +++ b/src/moderation_api/types/__init__.py @@ -1,43 +1,24 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" -from __future__ import annotations - -from .action_list_params import ActionListParams as ActionListParams -from .author_list_params import AuthorListParams as AuthorListParams -from .action_create_params import ActionCreateParams as ActionCreateParams -from .action_list_response import ActionListResponse as ActionListResponse -from .action_update_params import ActionUpdateParams as ActionUpdateParams -from .auth_create_response import AuthCreateResponse as AuthCreateResponse -from .author_create_params import AuthorCreateParams as AuthorCreateParams -from .author_list_response import AuthorListResponse as AuthorListResponse -from .author_update_params import AuthorUpdateParams as AuthorUpdateParams -from .account_list_response import AccountListResponse as AccountListResponse -from .content_submit_params import ContentSubmitParams as ContentSubmitParams -from .webhook_create_params import WebhookCreateParams as WebhookCreateParams -from .webhook_list_response import WebhookListResponse as WebhookListResponse -from .webhook_update_params import WebhookUpdateParams as WebhookUpdateParams -from .action_create_response import ActionCreateResponse as ActionCreateResponse -from .action_delete_response import ActionDeleteResponse as ActionDeleteResponse -from .action_update_response import ActionUpdateResponse as ActionUpdateResponse -from .auth_retrieve_response import AuthRetrieveResponse as AuthRetrieveResponse -from .author_create_response import AuthorCreateResponse as AuthorCreateResponse -from .author_delete_response import AuthorDeleteResponse as AuthorDeleteResponse -from .author_update_response import AuthorUpdateResponse as AuthorUpdateResponse -from .queue_get_stats_params import QueueGetStatsParams as QueueGetStatsParams -from .wordlist_list_response import WordlistListResponse as WordlistListResponse -from .wordlist_update_params import WordlistUpdateParams as WordlistUpdateParams -from .content_submit_response import ContentSubmitResponse as ContentSubmitResponse -from .queue_retrieve_response import QueueRetrieveResponse as QueueRetrieveResponse -from .webhook_create_response import WebhookCreateResponse as WebhookCreateResponse -from .webhook_delete_response import WebhookDeleteResponse as WebhookDeleteResponse -from .webhook_update_response import WebhookUpdateResponse as WebhookUpdateResponse -from .action_retrieve_response import ActionRetrieveResponse as ActionRetrieveResponse -from .author_retrieve_response import AuthorRetrieveResponse as AuthorRetrieveResponse -from .queue_get_stats_response import QueueGetStatsResponse as QueueGetStatsResponse -from .wordlist_update_response import WordlistUpdateResponse as WordlistUpdateResponse -from .webhook_retrieve_response import WebhookRetrieveResponse as WebhookRetrieveResponse -from .wordlist_retrieve_response import WordlistRetrieveResponse as WordlistRetrieveResponse -from .webhook_secret_retrieve_response import WebhookSecretRetrieveResponse as WebhookSecretRetrieveResponse -from .wordlist_get_embedding_status_response import ( - WordlistGetEmbeddingStatusResponse as WordlistGetEmbeddingStatusResponse, +from .base64fileinput import Base64EncodedString, Base64FileInput +from .basemodel import ( + BaseModel, + Nullable, + OptionalNullable, + UnrecognizedInt, + UnrecognizedStr, + UNSET, + UNSET_SENTINEL, ) + +__all__ = [ + "Base64EncodedString", + "Base64FileInput", + "BaseModel", + "Nullable", + "OptionalNullable", + "UnrecognizedInt", + "UnrecognizedStr", + "UNSET", + "UNSET_SENTINEL", +] diff --git a/src/moderation_api/types/account_list_response.py b/src/moderation_api/types/account_list_response.py deleted file mode 100644 index e286fce..0000000 --- a/src/moderation_api/types/account_list_response.py +++ /dev/null @@ -1,31 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional - -from .._models import BaseModel - -__all__ = ["AccountListResponse", "CurrentProject"] - - -class CurrentProject(BaseModel): - id: str - """ID of the current project""" - - name: str - """Name of the current project""" - - -class AccountListResponse(BaseModel): - id: str - """ID of the account""" - - paid_plan_name: str - """Name of the paid plan""" - - remaining_quota: float - """Remaining quota""" - - text_api_quota: float - """Text API quota""" - - current_project: Optional[CurrentProject] = None diff --git a/src/moderation_api/types/action_create_params.py b/src/moderation_api/types/action_create_params.py deleted file mode 100644 index afcf7c8..0000000 --- a/src/moderation_api/types/action_create_params.py +++ /dev/null @@ -1,77 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Iterable, Optional -from typing_extensions import Literal, Required, Annotated, TypedDict - -from .._types import SequenceNotStr -from .._utils import PropertyInfo - -__all__ = ["ActionCreateParams", "PossibleValue"] - - -class ActionCreateParams(TypedDict, total=False): - name: Required[str] - """The name of the action.""" - - built_in: Annotated[Optional[bool], PropertyInfo(alias="builtIn")] - """Whether the action is a built-in action or a custom one.""" - - description: Optional[str] - """The description of the action.""" - - filter_in_queue_ids: Annotated[SequenceNotStr[str], PropertyInfo(alias="filterInQueueIds")] - """The IDs of the queues the action is available in.""" - - free_text: Annotated[bool, PropertyInfo(alias="freeText")] - """ - Whether the action allows any text to be entered as a value or if it must be one - of the possible values. - """ - - key: Optional[str] - """User defined key of the action.""" - - position: Literal["ALL_QUEUES", "SOME_QUEUES", "HIDDEN"] - """ - Show the action in all queues, selected queues or no queues (to use via API - only). - """ - - possible_values: Annotated[Iterable[PossibleValue], PropertyInfo(alias="possibleValues")] - """The possible values of the action. - - The user will be prompted to select one of these values when executing the - action. - """ - - queue_behaviour: Annotated[Literal["REMOVE", "ADD", "NO_CHANGE"], PropertyInfo(alias="queueBehaviour")] - """ - Whether the action resolves and removes the item, unresolves and re-add it to - the queue, or does not change the resolve status. - """ - - type: Optional[ - Literal[ - "AUTHOR_BLOCK", - "AUTHOR_BLOCK_TEMP", - "AUTHOR_UNBLOCK", - "AUTHOR_DELETE", - "AUTHOR_REPORT", - "AUTHOR_WARN", - "AUTHOR_CUSTOM", - "ITEM_REJECT", - "ITEM_ALLOW", - "ITEM_CUSTOM", - ] - ] - """The type of the action.""" - - value_required: Annotated[bool, PropertyInfo(alias="valueRequired")] - """Whether the action requires a value to be executed.""" - - -class PossibleValue(TypedDict, total=False): - value: Required[str] - """The value of the action.""" diff --git a/src/moderation_api/types/action_create_response.py b/src/moderation_api/types/action_create_response.py deleted file mode 100644 index 821e5ba..0000000 --- a/src/moderation_api/types/action_create_response.py +++ /dev/null @@ -1,82 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["ActionCreateResponse", "PossibleValue"] - - -class PossibleValue(BaseModel): - value: str - """The value of the action.""" - - -class ActionCreateResponse(BaseModel): - id: str - """The ID of the action.""" - - built_in: Optional[bool] = FieldInfo(alias="builtIn", default=None) - """Whether the action is a built-in action or a custom one.""" - - created_at: str = FieldInfo(alias="createdAt") - """The date the action was created.""" - - filter_in_queue_ids: List[str] = FieldInfo(alias="filterInQueueIds") - """The IDs of the queues the action is available in.""" - - free_text: bool = FieldInfo(alias="freeText") - """ - Whether the action allows any text to be entered as a value or if it must be one - of the possible values. - """ - - name: str - """The name of the action.""" - - position: Literal["ALL_QUEUES", "SOME_QUEUES", "HIDDEN"] - """ - Show the action in all queues, selected queues or no queues (to use via API - only). - """ - - possible_values: List[PossibleValue] = FieldInfo(alias="possibleValues") - """The possible values of the action. - - The user will be prompted to select one of these values when executing the - action. - """ - - queue_behaviour: Literal["REMOVE", "ADD", "NO_CHANGE"] = FieldInfo(alias="queueBehaviour") - """ - Whether the action resolves and removes the item, unresolves and re-add it to - the queue, or does not change the resolve status. - """ - - value_required: bool = FieldInfo(alias="valueRequired") - """Whether the action requires a value to be executed.""" - - description: Optional[str] = None - """The description of the action.""" - - key: Optional[str] = None - """User defined key of the action.""" - - type: Optional[ - Literal[ - "AUTHOR_BLOCK", - "AUTHOR_BLOCK_TEMP", - "AUTHOR_UNBLOCK", - "AUTHOR_DELETE", - "AUTHOR_REPORT", - "AUTHOR_WARN", - "AUTHOR_CUSTOM", - "ITEM_REJECT", - "ITEM_ALLOW", - "ITEM_CUSTOM", - ] - ] = None - """The type of the action.""" diff --git a/src/moderation_api/types/action_delete_response.py b/src/moderation_api/types/action_delete_response.py deleted file mode 100644 index b21105d..0000000 --- a/src/moderation_api/types/action_delete_response.py +++ /dev/null @@ -1,13 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .._models import BaseModel - -__all__ = ["ActionDeleteResponse"] - - -class ActionDeleteResponse(BaseModel): - id: str - """The ID of the action.""" - - deleted: bool - """Whether the action was deleted.""" diff --git a/src/moderation_api/types/action_list_params.py b/src/moderation_api/types/action_list_params.py deleted file mode 100644 index ef86f3d..0000000 --- a/src/moderation_api/types/action_list_params.py +++ /dev/null @@ -1,13 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["ActionListParams"] - - -class ActionListParams(TypedDict, total=False): - queue_id: Annotated[str, PropertyInfo(alias="queueId")] diff --git a/src/moderation_api/types/action_list_response.py b/src/moderation_api/types/action_list_response.py deleted file mode 100644 index 27a774b..0000000 --- a/src/moderation_api/types/action_list_response.py +++ /dev/null @@ -1,85 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from typing_extensions import Literal, TypeAlias - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["ActionListResponse", "ActionListResponseItem", "ActionListResponseItemPossibleValue"] - - -class ActionListResponseItemPossibleValue(BaseModel): - value: str - """The value of the action.""" - - -class ActionListResponseItem(BaseModel): - id: str - """The ID of the action.""" - - built_in: Optional[bool] = FieldInfo(alias="builtIn", default=None) - """Whether the action is a built-in action or a custom one.""" - - created_at: str = FieldInfo(alias="createdAt") - """The date the action was created.""" - - filter_in_queue_ids: List[str] = FieldInfo(alias="filterInQueueIds") - """The IDs of the queues the action is available in.""" - - free_text: bool = FieldInfo(alias="freeText") - """ - Whether the action allows any text to be entered as a value or if it must be one - of the possible values. - """ - - name: str - """The name of the action.""" - - position: Literal["ALL_QUEUES", "SOME_QUEUES", "HIDDEN"] - """ - Show the action in all queues, selected queues or no queues (to use via API - only). - """ - - possible_values: List[ActionListResponseItemPossibleValue] = FieldInfo(alias="possibleValues") - """The possible values of the action. - - The user will be prompted to select one of these values when executing the - action. - """ - - queue_behaviour: Literal["REMOVE", "ADD", "NO_CHANGE"] = FieldInfo(alias="queueBehaviour") - """ - Whether the action resolves and removes the item, unresolves and re-add it to - the queue, or does not change the resolve status. - """ - - value_required: bool = FieldInfo(alias="valueRequired") - """Whether the action requires a value to be executed.""" - - description: Optional[str] = None - """The description of the action.""" - - key: Optional[str] = None - """User defined key of the action.""" - - type: Optional[ - Literal[ - "AUTHOR_BLOCK", - "AUTHOR_BLOCK_TEMP", - "AUTHOR_UNBLOCK", - "AUTHOR_DELETE", - "AUTHOR_REPORT", - "AUTHOR_WARN", - "AUTHOR_CUSTOM", - "ITEM_REJECT", - "ITEM_ALLOW", - "ITEM_CUSTOM", - ] - ] = None - """The type of the action.""" - - -ActionListResponse: TypeAlias = List[ActionListResponseItem] diff --git a/src/moderation_api/types/action_retrieve_response.py b/src/moderation_api/types/action_retrieve_response.py deleted file mode 100644 index 5061231..0000000 --- a/src/moderation_api/types/action_retrieve_response.py +++ /dev/null @@ -1,82 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["ActionRetrieveResponse", "PossibleValue"] - - -class PossibleValue(BaseModel): - value: str - """The value of the action.""" - - -class ActionRetrieveResponse(BaseModel): - id: str - """The ID of the action.""" - - built_in: Optional[bool] = FieldInfo(alias="builtIn", default=None) - """Whether the action is a built-in action or a custom one.""" - - created_at: str = FieldInfo(alias="createdAt") - """The date the action was created.""" - - filter_in_queue_ids: List[str] = FieldInfo(alias="filterInQueueIds") - """The IDs of the queues the action is available in.""" - - free_text: bool = FieldInfo(alias="freeText") - """ - Whether the action allows any text to be entered as a value or if it must be one - of the possible values. - """ - - name: str - """The name of the action.""" - - position: Literal["ALL_QUEUES", "SOME_QUEUES", "HIDDEN"] - """ - Show the action in all queues, selected queues or no queues (to use via API - only). - """ - - possible_values: List[PossibleValue] = FieldInfo(alias="possibleValues") - """The possible values of the action. - - The user will be prompted to select one of these values when executing the - action. - """ - - queue_behaviour: Literal["REMOVE", "ADD", "NO_CHANGE"] = FieldInfo(alias="queueBehaviour") - """ - Whether the action resolves and removes the item, unresolves and re-add it to - the queue, or does not change the resolve status. - """ - - value_required: bool = FieldInfo(alias="valueRequired") - """Whether the action requires a value to be executed.""" - - description: Optional[str] = None - """The description of the action.""" - - key: Optional[str] = None - """User defined key of the action.""" - - type: Optional[ - Literal[ - "AUTHOR_BLOCK", - "AUTHOR_BLOCK_TEMP", - "AUTHOR_UNBLOCK", - "AUTHOR_DELETE", - "AUTHOR_REPORT", - "AUTHOR_WARN", - "AUTHOR_CUSTOM", - "ITEM_REJECT", - "ITEM_ALLOW", - "ITEM_CUSTOM", - ] - ] = None - """The type of the action.""" diff --git a/src/moderation_api/types/action_update_params.py b/src/moderation_api/types/action_update_params.py deleted file mode 100644 index ac56572..0000000 --- a/src/moderation_api/types/action_update_params.py +++ /dev/null @@ -1,77 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Iterable, Optional -from typing_extensions import Literal, Required, Annotated, TypedDict - -from .._types import SequenceNotStr -from .._utils import PropertyInfo - -__all__ = ["ActionUpdateParams", "PossibleValue"] - - -class ActionUpdateParams(TypedDict, total=False): - built_in: Annotated[Optional[bool], PropertyInfo(alias="builtIn")] - """Whether the action is a built-in action or a custom one.""" - - description: Optional[str] - """The description of the action.""" - - filter_in_queue_ids: Annotated[SequenceNotStr[str], PropertyInfo(alias="filterInQueueIds")] - """The IDs of the queues the action is available in.""" - - free_text: Annotated[bool, PropertyInfo(alias="freeText")] - """ - Whether the action allows any text to be entered as a value or if it must be one - of the possible values. - """ - - key: Optional[str] - """User defined key of the action.""" - - name: str - """The name of the action.""" - - position: Literal["ALL_QUEUES", "SOME_QUEUES", "HIDDEN"] - """ - Show the action in all queues, selected queues or no queues (to use via API - only). - """ - - possible_values: Annotated[Iterable[PossibleValue], PropertyInfo(alias="possibleValues")] - """The possible values of the action. - - The user will be prompted to select one of these values when executing the - action. - """ - - queue_behaviour: Annotated[Literal["REMOVE", "ADD", "NO_CHANGE"], PropertyInfo(alias="queueBehaviour")] - """ - Whether the action resolves and removes the item, unresolves and re-add it to - the queue, or does not change the resolve status. - """ - - type: Optional[ - Literal[ - "AUTHOR_BLOCK", - "AUTHOR_BLOCK_TEMP", - "AUTHOR_UNBLOCK", - "AUTHOR_DELETE", - "AUTHOR_REPORT", - "AUTHOR_WARN", - "AUTHOR_CUSTOM", - "ITEM_REJECT", - "ITEM_ALLOW", - "ITEM_CUSTOM", - ] - ] - """The type of the action.""" - - value_required: Annotated[bool, PropertyInfo(alias="valueRequired")] - """Whether the action requires a value to be executed.""" - - -class PossibleValue(TypedDict, total=False): - value: Required[str] - """The value of the action.""" diff --git a/src/moderation_api/types/action_update_response.py b/src/moderation_api/types/action_update_response.py deleted file mode 100644 index 458b6a2..0000000 --- a/src/moderation_api/types/action_update_response.py +++ /dev/null @@ -1,82 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["ActionUpdateResponse", "PossibleValue"] - - -class PossibleValue(BaseModel): - value: str - """The value of the action.""" - - -class ActionUpdateResponse(BaseModel): - id: str - """The ID of the action.""" - - built_in: Optional[bool] = FieldInfo(alias="builtIn", default=None) - """Whether the action is a built-in action or a custom one.""" - - created_at: str = FieldInfo(alias="createdAt") - """The date the action was created.""" - - filter_in_queue_ids: List[str] = FieldInfo(alias="filterInQueueIds") - """The IDs of the queues the action is available in.""" - - free_text: bool = FieldInfo(alias="freeText") - """ - Whether the action allows any text to be entered as a value or if it must be one - of the possible values. - """ - - name: str - """The name of the action.""" - - position: Literal["ALL_QUEUES", "SOME_QUEUES", "HIDDEN"] - """ - Show the action in all queues, selected queues or no queues (to use via API - only). - """ - - possible_values: List[PossibleValue] = FieldInfo(alias="possibleValues") - """The possible values of the action. - - The user will be prompted to select one of these values when executing the - action. - """ - - queue_behaviour: Literal["REMOVE", "ADD", "NO_CHANGE"] = FieldInfo(alias="queueBehaviour") - """ - Whether the action resolves and removes the item, unresolves and re-add it to - the queue, or does not change the resolve status. - """ - - value_required: bool = FieldInfo(alias="valueRequired") - """Whether the action requires a value to be executed.""" - - description: Optional[str] = None - """The description of the action.""" - - key: Optional[str] = None - """User defined key of the action.""" - - type: Optional[ - Literal[ - "AUTHOR_BLOCK", - "AUTHOR_BLOCK_TEMP", - "AUTHOR_UNBLOCK", - "AUTHOR_DELETE", - "AUTHOR_REPORT", - "AUTHOR_WARN", - "AUTHOR_CUSTOM", - "ITEM_REJECT", - "ITEM_ALLOW", - "ITEM_CUSTOM", - ] - ] = None - """The type of the action.""" diff --git a/src/moderation_api/types/actions/__init__.py b/src/moderation_api/types/actions/__init__.py deleted file mode 100644 index af74546..0000000 --- a/src/moderation_api/types/actions/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from .execute_execute_params import ExecuteExecuteParams as ExecuteExecuteParams -from .execute_execute_response import ExecuteExecuteResponse as ExecuteExecuteResponse -from .execute_execute_by_id_params import ExecuteExecuteByIDParams as ExecuteExecuteByIDParams -from .execute_execute_by_id_response import ExecuteExecuteByIDResponse as ExecuteExecuteByIDResponse diff --git a/src/moderation_api/types/actions/execute_execute_by_id_params.py b/src/moderation_api/types/actions/execute_execute_by_id_params.py deleted file mode 100644 index 8dbb493..0000000 --- a/src/moderation_api/types/actions/execute_execute_by_id_params.py +++ /dev/null @@ -1,24 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Annotated, TypedDict - -from ..._types import SequenceNotStr -from ..._utils import PropertyInfo - -__all__ = ["ExecuteExecuteByIDParams"] - - -class ExecuteExecuteByIDParams(TypedDict, total=False): - author_ids: Annotated[SequenceNotStr[str], PropertyInfo(alias="authorIds")] - """IDs of the authors to apply the action to""" - - content_ids: Annotated[SequenceNotStr[str], PropertyInfo(alias="contentIds")] - """The IDs of the content items to perform the action on.""" - - queue_id: Annotated[str, PropertyInfo(alias="queueId")] - """The ID of the queue the action was performed from if any.""" - - value: str - """The value of the action. Useful to set a reason for the action etc.""" diff --git a/src/moderation_api/types/actions/execute_execute_by_id_response.py b/src/moderation_api/types/actions/execute_execute_by_id_response.py deleted file mode 100644 index 54f178b..0000000 --- a/src/moderation_api/types/actions/execute_execute_by_id_response.py +++ /dev/null @@ -1,20 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List - -from pydantic import Field as FieldInfo - -from ..._models import BaseModel - -__all__ = ["ExecuteExecuteByIDResponse"] - - -class ExecuteExecuteByIDResponse(BaseModel): - action_id: str = FieldInfo(alias="actionId") - """The ID of the action.""" - - ids: List[str] - """The IDs of the content items.""" - - success: bool - """Action executed successfully.""" diff --git a/src/moderation_api/types/actions/execute_execute_params.py b/src/moderation_api/types/actions/execute_execute_params.py deleted file mode 100644 index 366957a..0000000 --- a/src/moderation_api/types/actions/execute_execute_params.py +++ /dev/null @@ -1,30 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Required, Annotated, TypedDict - -from ..._types import SequenceNotStr -from ..._utils import PropertyInfo - -__all__ = ["ExecuteExecuteParams"] - - -class ExecuteExecuteParams(TypedDict, total=False): - action_key: Required[Annotated[str, PropertyInfo(alias="actionKey")]] - """ID or key of the action to execute""" - - author_ids: Annotated[SequenceNotStr[str], PropertyInfo(alias="authorIds")] - """IDs of the authors to apply the action to. Provide this or contentIds.""" - - content_ids: Annotated[SequenceNotStr[str], PropertyInfo(alias="contentIds")] - """IDs of the content items to apply the action to. Provide this or authorIds.""" - - duration: float - """Optional duration in milliseconds for actions with timeouts""" - - queue_id: Annotated[str, PropertyInfo(alias="queueId")] - """Optional queue ID if the action is queue-specific""" - - value: str - """Optional value to provide with the action""" diff --git a/src/moderation_api/types/actions/execute_execute_response.py b/src/moderation_api/types/actions/execute_execute_response.py deleted file mode 100644 index 22dbab9..0000000 --- a/src/moderation_api/types/actions/execute_execute_response.py +++ /dev/null @@ -1,12 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from ..._models import BaseModel - -__all__ = ["ExecuteExecuteResponse"] - - -class ExecuteExecuteResponse(BaseModel): - """Execution result""" - - success: bool - """Whether the action was executed successfully""" diff --git a/src/moderation_api/types/auth_create_response.py b/src/moderation_api/types/auth_create_response.py deleted file mode 100644 index 5adbb45..0000000 --- a/src/moderation_api/types/auth_create_response.py +++ /dev/null @@ -1,16 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .._models import BaseModel - -__all__ = ["AuthCreateResponse"] - - -class AuthCreateResponse(BaseModel): - message: str - """Message of the authentication""" - - project: str - """Name of the authenticated project""" - - status: str - """Status of the authentication""" diff --git a/src/moderation_api/types/auth_retrieve_response.py b/src/moderation_api/types/auth_retrieve_response.py deleted file mode 100644 index 3243ec3..0000000 --- a/src/moderation_api/types/auth_retrieve_response.py +++ /dev/null @@ -1,13 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .._models import BaseModel - -__all__ = ["AuthRetrieveResponse"] - - -class AuthRetrieveResponse(BaseModel): - message: str - """Message of the authentication""" - - status: str - """Status of the authentication""" diff --git a/src/moderation_api/types/author_create_params.py b/src/moderation_api/types/author_create_params.py deleted file mode 100644 index 7c4692b..0000000 --- a/src/moderation_api/types/author_create_params.py +++ /dev/null @@ -1,66 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Optional -from typing_extensions import Required, TypedDict - -__all__ = ["AuthorCreateParams", "Metadata"] - - -class AuthorCreateParams(TypedDict, total=False): - external_id: Required[str] - """External ID of the user, typically the ID of the author in your database.""" - - company: Optional[str] - """The author's company or organization""" - - email: Optional[str] - """Author email address""" - - external_link: Optional[str] - """URL of the author's external profile""" - - first_seen: float - """Timestamp when author first appeared""" - - last_seen: float - """Timestamp of last activity""" - - manual_trust_level: Optional[float] - - metadata: Metadata - """Additional metadata provided by your system. - - We recommend including any relevant information that may assist in the - moderation process. - """ - - name: Optional[str] - """Author name or identifier""" - - profile_picture: Optional[str] - """URL of the author's profile picture""" - - -class Metadata( # type: ignore[call-arg] - TypedDict, - total=False, - extra_items=object, # pyright: ignore[reportGeneralTypeIssues] -): - """Additional metadata provided by your system. - - We recommend including any relevant information that may assist in the moderation process. - """ - - email_verified: Optional[bool] - """Whether the author's email is verified""" - - identity_verified: Optional[bool] - """Whether the author's identity is verified""" - - is_paying_customer: Optional[bool] - """Whether the author is a paying customer""" - - phone_verified: Optional[bool] - """Whether the author's phone number is verified""" diff --git a/src/moderation_api/types/author_create_response.py b/src/moderation_api/types/author_create_response.py deleted file mode 100644 index 6a4f8f5..0000000 --- a/src/moderation_api/types/author_create_response.py +++ /dev/null @@ -1,132 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import TYPE_CHECKING, Dict, Optional -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["AuthorCreateResponse", "Block", "Metadata", "Metrics", "RiskEvaluation", "TrustLevel"] - - -class Block(BaseModel): - """Block or suspension details, if applicable. Null if the author is enabled.""" - - reason: Optional[str] = None - """The moderators reason why the author was blocked or suspended.""" - - until: Optional[float] = None - """The timestamp until which they are blocked if the author is suspended.""" - - -class Metadata(BaseModel): - """Additional metadata provided by your system. - - We recommend including any relevant information that may assist in the moderation process. - """ - - email_verified: Optional[bool] = None - """Whether the author's email is verified""" - - identity_verified: Optional[bool] = None - """Whether the author's identity is verified""" - - is_paying_customer: Optional[bool] = None - """Whether the author is a paying customer""" - - phone_verified: Optional[bool] = None - """Whether the author's phone number is verified""" - - if TYPE_CHECKING: - # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a - # value to this field, so for compatibility we avoid doing it at runtime. - __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] - - # Stub to indicate that arbitrary properties are accepted. - # To access properties that are not valid identifiers you can use `getattr`, e.g. - # `getattr(obj, '$type')` - def __getattr__(self, attr: str) -> object: ... - else: - __pydantic_extra__: Dict[str, object] - - -class Metrics(BaseModel): - flagged_content: float - """Number of flagged content pieces""" - - total_content: float - """Total pieces of content""" - - average_sentiment: Optional[float] = None - """Average sentiment score of content (-1 to 1). - - Requires a sentiment model in your project. - """ - - -class RiskEvaluation(BaseModel): - """Risk assessment details, if available.""" - - risk_level: Optional[float] = None - """Calculated risk level based on more than 10 behavioral signals.""" - - -class TrustLevel(BaseModel): - level: float - """Author trust level (-1, 0, 1, 2, 3, or 4)""" - - manual: bool - """True if the trust level was set manually by a moderator""" - - -class AuthorCreateResponse(BaseModel): - id: str - """Author ID in Moderation API""" - - block: Optional[Block] = None - """Block or suspension details, if applicable. Null if the author is enabled.""" - - first_seen: float - """Timestamp when author first appeared""" - - last_seen: float - """Timestamp of last activity""" - - metadata: Metadata - """Additional metadata provided by your system. - - We recommend including any relevant information that may assist in the - moderation process. - """ - - metrics: Metrics - - risk_evaluation: Optional[RiskEvaluation] = None - """Risk assessment details, if available.""" - - status: Literal["enabled", "suspended", "blocked"] - """Current author status""" - - trust_level: TrustLevel - - company: Optional[str] = None - """The author's company or organization""" - - email: Optional[str] = None - """Author email address""" - - external_id: Optional[str] = None - """The author's ID from your system""" - - external_link: Optional[str] = None - """URL of the author's external profile""" - - last_incident: Optional[float] = None - """Timestamp of last incident""" - - name: Optional[str] = None - """Author name or identifier""" - - profile_picture: Optional[str] = None - """URL of the author's profile picture""" diff --git a/src/moderation_api/types/author_delete_response.py b/src/moderation_api/types/author_delete_response.py deleted file mode 100644 index ff950f5..0000000 --- a/src/moderation_api/types/author_delete_response.py +++ /dev/null @@ -1,9 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .._models import BaseModel - -__all__ = ["AuthorDeleteResponse"] - - -class AuthorDeleteResponse(BaseModel): - success: bool diff --git a/src/moderation_api/types/author_list_params.py b/src/moderation_api/types/author_list_params.py deleted file mode 100644 index 2184bcf..0000000 --- a/src/moderation_api/types/author_list_params.py +++ /dev/null @@ -1,40 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["AuthorListParams"] - - -class AuthorListParams(TypedDict, total=False): - content_types: Annotated[str, PropertyInfo(alias="contentTypes")] - - last_active_date: Annotated[str, PropertyInfo(alias="lastActiveDate")] - - member_since_date: Annotated[str, PropertyInfo(alias="memberSinceDate")] - - page_number: Annotated[float, PropertyInfo(alias="pageNumber")] - """Page number to fetch""" - - page_size: Annotated[float, PropertyInfo(alias="pageSize")] - """Number of authors per page""" - - sort_by: Annotated[ - Literal[ - "trustLevel", - "violationCount", - "reportCount", - "memberSince", - "lastActive", - "contentCount", - "flaggedContentRatio", - "averageSentiment", - ], - PropertyInfo(alias="sortBy"), - ] - - sort_direction: Annotated[Literal["asc", "desc"], PropertyInfo(alias="sortDirection")] - """Sort direction""" diff --git a/src/moderation_api/types/author_list_response.py b/src/moderation_api/types/author_list_response.py deleted file mode 100644 index aafcb37..0000000 --- a/src/moderation_api/types/author_list_response.py +++ /dev/null @@ -1,159 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import TYPE_CHECKING, Dict, List, Optional -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = [ - "AuthorListResponse", - "Author", - "AuthorBlock", - "AuthorMetadata", - "AuthorMetrics", - "AuthorRiskEvaluation", - "AuthorTrustLevel", - "Pagination", -] - - -class AuthorBlock(BaseModel): - """Block or suspension details, if applicable. Null if the author is enabled.""" - - reason: Optional[str] = None - """The moderators reason why the author was blocked or suspended.""" - - until: Optional[float] = None - """The timestamp until which they are blocked if the author is suspended.""" - - -class AuthorMetadata(BaseModel): - """Additional metadata provided by your system. - - We recommend including any relevant information that may assist in the moderation process. - """ - - email_verified: Optional[bool] = None - """Whether the author's email is verified""" - - identity_verified: Optional[bool] = None - """Whether the author's identity is verified""" - - is_paying_customer: Optional[bool] = None - """Whether the author is a paying customer""" - - phone_verified: Optional[bool] = None - """Whether the author's phone number is verified""" - - if TYPE_CHECKING: - # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a - # value to this field, so for compatibility we avoid doing it at runtime. - __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] - - # Stub to indicate that arbitrary properties are accepted. - # To access properties that are not valid identifiers you can use `getattr`, e.g. - # `getattr(obj, '$type')` - def __getattr__(self, attr: str) -> object: ... - else: - __pydantic_extra__: Dict[str, object] - - -class AuthorMetrics(BaseModel): - flagged_content: float - """Number of flagged content pieces""" - - total_content: float - """Total pieces of content""" - - average_sentiment: Optional[float] = None - """Average sentiment score of content (-1 to 1). - - Requires a sentiment model in your project. - """ - - -class AuthorRiskEvaluation(BaseModel): - """Risk assessment details, if available.""" - - risk_level: Optional[float] = None - """Calculated risk level based on more than 10 behavioral signals.""" - - -class AuthorTrustLevel(BaseModel): - level: float - """Author trust level (-1, 0, 1, 2, 3, or 4)""" - - manual: bool - """True if the trust level was set manually by a moderator""" - - -class Author(BaseModel): - id: str - """Author ID in Moderation API""" - - block: Optional[AuthorBlock] = None - """Block or suspension details, if applicable. Null if the author is enabled.""" - - first_seen: float - """Timestamp when author first appeared""" - - last_seen: float - """Timestamp of last activity""" - - metadata: AuthorMetadata - """Additional metadata provided by your system. - - We recommend including any relevant information that may assist in the - moderation process. - """ - - metrics: AuthorMetrics - - risk_evaluation: Optional[AuthorRiskEvaluation] = None - """Risk assessment details, if available.""" - - status: Literal["enabled", "suspended", "blocked"] - """Current author status""" - - trust_level: AuthorTrustLevel - - company: Optional[str] = None - """The author's company or organization""" - - email: Optional[str] = None - """Author email address""" - - external_id: Optional[str] = None - """The author's ID from your system""" - - external_link: Optional[str] = None - """URL of the author's external profile""" - - last_incident: Optional[float] = None - """Timestamp of last incident""" - - name: Optional[str] = None - """Author name or identifier""" - - profile_picture: Optional[str] = None - """URL of the author's profile picture""" - - -class Pagination(BaseModel): - has_next_page: bool = FieldInfo(alias="hasNextPage") - - has_previous_page: bool = FieldInfo(alias="hasPreviousPage") - - page_number: float = FieldInfo(alias="pageNumber") - - page_size: float = FieldInfo(alias="pageSize") - - total: float - - -class AuthorListResponse(BaseModel): - authors: List[Author] - - pagination: Pagination diff --git a/src/moderation_api/types/author_retrieve_response.py b/src/moderation_api/types/author_retrieve_response.py deleted file mode 100644 index 20fe406..0000000 --- a/src/moderation_api/types/author_retrieve_response.py +++ /dev/null @@ -1,132 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import TYPE_CHECKING, Dict, Optional -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["AuthorRetrieveResponse", "Block", "Metadata", "Metrics", "RiskEvaluation", "TrustLevel"] - - -class Block(BaseModel): - """Block or suspension details, if applicable. Null if the author is enabled.""" - - reason: Optional[str] = None - """The moderators reason why the author was blocked or suspended.""" - - until: Optional[float] = None - """The timestamp until which they are blocked if the author is suspended.""" - - -class Metadata(BaseModel): - """Additional metadata provided by your system. - - We recommend including any relevant information that may assist in the moderation process. - """ - - email_verified: Optional[bool] = None - """Whether the author's email is verified""" - - identity_verified: Optional[bool] = None - """Whether the author's identity is verified""" - - is_paying_customer: Optional[bool] = None - """Whether the author is a paying customer""" - - phone_verified: Optional[bool] = None - """Whether the author's phone number is verified""" - - if TYPE_CHECKING: - # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a - # value to this field, so for compatibility we avoid doing it at runtime. - __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] - - # Stub to indicate that arbitrary properties are accepted. - # To access properties that are not valid identifiers you can use `getattr`, e.g. - # `getattr(obj, '$type')` - def __getattr__(self, attr: str) -> object: ... - else: - __pydantic_extra__: Dict[str, object] - - -class Metrics(BaseModel): - flagged_content: float - """Number of flagged content pieces""" - - total_content: float - """Total pieces of content""" - - average_sentiment: Optional[float] = None - """Average sentiment score of content (-1 to 1). - - Requires a sentiment model in your project. - """ - - -class RiskEvaluation(BaseModel): - """Risk assessment details, if available.""" - - risk_level: Optional[float] = None - """Calculated risk level based on more than 10 behavioral signals.""" - - -class TrustLevel(BaseModel): - level: float - """Author trust level (-1, 0, 1, 2, 3, or 4)""" - - manual: bool - """True if the trust level was set manually by a moderator""" - - -class AuthorRetrieveResponse(BaseModel): - id: str - """Author ID in Moderation API""" - - block: Optional[Block] = None - """Block or suspension details, if applicable. Null if the author is enabled.""" - - first_seen: float - """Timestamp when author first appeared""" - - last_seen: float - """Timestamp of last activity""" - - metadata: Metadata - """Additional metadata provided by your system. - - We recommend including any relevant information that may assist in the - moderation process. - """ - - metrics: Metrics - - risk_evaluation: Optional[RiskEvaluation] = None - """Risk assessment details, if available.""" - - status: Literal["enabled", "suspended", "blocked"] - """Current author status""" - - trust_level: TrustLevel - - company: Optional[str] = None - """The author's company or organization""" - - email: Optional[str] = None - """Author email address""" - - external_id: Optional[str] = None - """The author's ID from your system""" - - external_link: Optional[str] = None - """URL of the author's external profile""" - - last_incident: Optional[float] = None - """Timestamp of last incident""" - - name: Optional[str] = None - """Author name or identifier""" - - profile_picture: Optional[str] = None - """URL of the author's profile picture""" diff --git a/src/moderation_api/types/author_update_params.py b/src/moderation_api/types/author_update_params.py deleted file mode 100644 index 4bc3cca..0000000 --- a/src/moderation_api/types/author_update_params.py +++ /dev/null @@ -1,63 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Optional -from typing_extensions import TypedDict - -__all__ = ["AuthorUpdateParams", "Metadata"] - - -class AuthorUpdateParams(TypedDict, total=False): - company: Optional[str] - """The author's company or organization""" - - email: Optional[str] - """Author email address""" - - external_link: Optional[str] - """URL of the author's external profile""" - - first_seen: float - """Timestamp when author first appeared""" - - last_seen: float - """Timestamp of last activity""" - - manual_trust_level: Optional[float] - - metadata: Metadata - """Additional metadata provided by your system. - - We recommend including any relevant information that may assist in the - moderation process. - """ - - name: Optional[str] - """Author name or identifier""" - - profile_picture: Optional[str] - """URL of the author's profile picture""" - - -class Metadata( # type: ignore[call-arg] - TypedDict, - total=False, - extra_items=object, # pyright: ignore[reportGeneralTypeIssues] -): - """Additional metadata provided by your system. - - We recommend including any relevant information that may assist in the moderation process. - """ - - email_verified: Optional[bool] - """Whether the author's email is verified""" - - identity_verified: Optional[bool] - """Whether the author's identity is verified""" - - is_paying_customer: Optional[bool] - """Whether the author is a paying customer""" - - phone_verified: Optional[bool] - """Whether the author's phone number is verified""" diff --git a/src/moderation_api/types/author_update_response.py b/src/moderation_api/types/author_update_response.py deleted file mode 100644 index 6bec395..0000000 --- a/src/moderation_api/types/author_update_response.py +++ /dev/null @@ -1,132 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import TYPE_CHECKING, Dict, Optional -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["AuthorUpdateResponse", "Block", "Metadata", "Metrics", "RiskEvaluation", "TrustLevel"] - - -class Block(BaseModel): - """Block or suspension details, if applicable. Null if the author is enabled.""" - - reason: Optional[str] = None - """The moderators reason why the author was blocked or suspended.""" - - until: Optional[float] = None - """The timestamp until which they are blocked if the author is suspended.""" - - -class Metadata(BaseModel): - """Additional metadata provided by your system. - - We recommend including any relevant information that may assist in the moderation process. - """ - - email_verified: Optional[bool] = None - """Whether the author's email is verified""" - - identity_verified: Optional[bool] = None - """Whether the author's identity is verified""" - - is_paying_customer: Optional[bool] = None - """Whether the author is a paying customer""" - - phone_verified: Optional[bool] = None - """Whether the author's phone number is verified""" - - if TYPE_CHECKING: - # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a - # value to this field, so for compatibility we avoid doing it at runtime. - __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] - - # Stub to indicate that arbitrary properties are accepted. - # To access properties that are not valid identifiers you can use `getattr`, e.g. - # `getattr(obj, '$type')` - def __getattr__(self, attr: str) -> object: ... - else: - __pydantic_extra__: Dict[str, object] - - -class Metrics(BaseModel): - flagged_content: float - """Number of flagged content pieces""" - - total_content: float - """Total pieces of content""" - - average_sentiment: Optional[float] = None - """Average sentiment score of content (-1 to 1). - - Requires a sentiment model in your project. - """ - - -class RiskEvaluation(BaseModel): - """Risk assessment details, if available.""" - - risk_level: Optional[float] = None - """Calculated risk level based on more than 10 behavioral signals.""" - - -class TrustLevel(BaseModel): - level: float - """Author trust level (-1, 0, 1, 2, 3, or 4)""" - - manual: bool - """True if the trust level was set manually by a moderator""" - - -class AuthorUpdateResponse(BaseModel): - id: str - """Author ID in Moderation API""" - - block: Optional[Block] = None - """Block or suspension details, if applicable. Null if the author is enabled.""" - - first_seen: float - """Timestamp when author first appeared""" - - last_seen: float - """Timestamp of last activity""" - - metadata: Metadata - """Additional metadata provided by your system. - - We recommend including any relevant information that may assist in the - moderation process. - """ - - metrics: Metrics - - risk_evaluation: Optional[RiskEvaluation] = None - """Risk assessment details, if available.""" - - status: Literal["enabled", "suspended", "blocked"] - """Current author status""" - - trust_level: TrustLevel - - company: Optional[str] = None - """The author's company or organization""" - - email: Optional[str] = None - """Author email address""" - - external_id: Optional[str] = None - """The author's ID from your system""" - - external_link: Optional[str] = None - """URL of the author's external profile""" - - last_incident: Optional[float] = None - """Timestamp of last incident""" - - name: Optional[str] = None - """Author name or identifier""" - - profile_picture: Optional[str] = None - """URL of the author's profile picture""" diff --git a/src/moderation_api/types/base64fileinput.py b/src/moderation_api/types/base64fileinput.py new file mode 100644 index 0000000..862566f --- /dev/null +++ b/src/moderation_api/types/base64fileinput.py @@ -0,0 +1,43 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations + +import base64 +import io +from os import PathLike +from typing import IO, Any, Union + +from pydantic.functional_validators import BeforeValidator +from typing_extensions import Annotated + + +Base64FileInput = Union[IO[bytes], PathLike[str]] + + +def encode_base64_file_input(value: Any) -> Any: + """Convert PathLike or IO[bytes] inputs to a base64 string. All standard binary streams + that inherit from io.IOBase are handled. Other values pass through. + """ + if isinstance(value, (PathLike, io.IOBase)): + if isinstance(value, PathLike): + with open(value, "rb") as fh: + binary = fh.read() + else: + # Restore position after reading: pydantic may validate the same stream more than once. + position = value.tell() if value.seekable() else None + binary = value.read() + if position is not None: + value.seek(position) + if isinstance(binary, str): + binary = binary.encode() + if not isinstance(binary, (bytes, bytearray)): + raise TypeError( + f"Base64FileInput expected binary IO returning bytes; got {type(binary).__name__}" + ) + return base64.b64encode(binary).decode("ascii") + return value + + +# Non-str inputs are converted to base64 by the BeforeValidator at construction time. +# Callers can also pass a pre-encoded base64 str. +Base64EncodedString = Annotated[str, BeforeValidator(encode_base64_file_input)] diff --git a/src/moderation_api/types/basemodel.py b/src/moderation_api/types/basemodel.py new file mode 100644 index 0000000..a9a640a --- /dev/null +++ b/src/moderation_api/types/basemodel.py @@ -0,0 +1,77 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from pydantic import ConfigDict, model_serializer +from pydantic import BaseModel as PydanticBaseModel +from pydantic_core import core_schema +from typing import TYPE_CHECKING, Any, Literal, Optional, TypeVar, Union +from typing_extensions import TypeAliasType, TypeAlias + + +class BaseModel(PydanticBaseModel): + model_config = ConfigDict( + populate_by_name=True, arbitrary_types_allowed=True, protected_namespaces=() + ) + + +class Unset(BaseModel): + @model_serializer(mode="plain") + def serialize_model(self): + return UNSET_SENTINEL + + def __bool__(self) -> Literal[False]: + return False + + +UNSET = Unset() +UNSET_SENTINEL = "~?~unset~?~sentinel~?~" + + +T = TypeVar("T") +if TYPE_CHECKING: + Nullable: TypeAlias = Union[T, None] + OptionalNullable: TypeAlias = Union[Optional[Nullable[T]], Unset] +else: + Nullable = TypeAliasType("Nullable", Union[T, None], type_params=(T,)) + OptionalNullable = TypeAliasType( + "OptionalNullable", Union[Optional[Nullable[T]], Unset], type_params=(T,) + ) + + +class UnrecognizedStr(str): + @classmethod + def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> core_schema.CoreSchema: + # Make UnrecognizedStr only work in lax mode, not strict mode + # This makes it a "fallback" option when more specific types (like Literals) don't match + def validate_lax(v: Any) -> 'UnrecognizedStr': + if isinstance(v, cls): + return v + return cls(str(v)) + + # Use lax_or_strict_schema where strict always fails + # This forces Pydantic to prefer other union members in strict mode + # and only fall back to UnrecognizedStr in lax mode + return core_schema.lax_or_strict_schema( + lax_schema=core_schema.chain_schema([ + core_schema.str_schema(), + core_schema.no_info_plain_validator_function(validate_lax) + ]), + strict_schema=core_schema.none_schema(), # Always fails in strict mode + ) + + +class UnrecognizedInt(int): + @classmethod + def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> core_schema.CoreSchema: + # Make UnrecognizedInt only work in lax mode, not strict mode + # This makes it a "fallback" option when more specific types (like Literals) don't match + def validate_lax(v: Any) -> 'UnrecognizedInt': + if isinstance(v, cls): + return v + return cls(int(v)) + return core_schema.lax_or_strict_schema( + lax_schema=core_schema.chain_schema([ + core_schema.int_schema(), + core_schema.no_info_plain_validator_function(validate_lax) + ]), + strict_schema=core_schema.none_schema(), # Always fails in strict mode + ) diff --git a/src/moderation_api/types/content_submit_params.py b/src/moderation_api/types/content_submit_params.py deleted file mode 100644 index 8ccad09..0000000 --- a/src/moderation_api/types/content_submit_params.py +++ /dev/null @@ -1,596 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Dict, Union, Iterable -from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict - -from .._types import SequenceNotStr -from .._utils import PropertyInfo - -__all__ = [ - "ContentSubmitParams", - "Content", - "ContentText", - "ContentImage", - "ContentVideo", - "ContentAudio", - "ContentObject", - "ContentObjectData", - "ContentObjectDataText", - "ContentObjectDataImage", - "ContentObjectDataVideo", - "ContentObjectDataAudio", - "ClientAction", - "Policy", - "PolicyToxicity", - "PolicyPersonalInformation", - "PolicyToxicitySevere", - "PolicyHate", - "PolicyIllicit", - "PolicyIllicitDrugs", - "PolicyIllicitAlcohol", - "PolicyIllicitFirearms", - "PolicyIllicitTobacco", - "PolicyIllicitGambling", - "PolicyCannabis", - "PolicyAdult", - "PolicyCrypto", - "PolicySexual", - "PolicySexualMinors", - "PolicyFlirtation", - "PolicyProfanity", - "PolicyViolence", - "PolicySelfHarm", - "PolicySpam", - "PolicyLowQualityContent", - "PolicyFaceDetection", - "PolicySelfPromotion", - "PolicyPolitical", - "PolicyReligion", - "PolicyCodeAbuse", - "PolicyUnicodeSpoofing", - "PolicyUnicodeSpoofingSignals", - "PolicyPiiMasking", - "PolicyPiiMaskingEntities", - "PolicyURLMasking", - "PolicyURLMaskingEntities", - "PolicyURLRisk", - "PolicyGuideline", -] - - -class ContentSubmitParams(TypedDict, total=False): - content: Required[Content] - """The content sent for moderation""" - - author_id: Annotated[str, PropertyInfo(alias="authorId")] - """The author of the content.""" - - channel: str - """Provide a channel ID or key. - - Will use the project's default channel if not provided. - """ - - client_action: Annotated[ClientAction, PropertyInfo(alias="clientAction")] - """A recommendation from your own client-side flagging (e.g. - - a banned-IP list or a third-party tool). Feeds the rules engine and can escalate - or override the recommended action. Does not change whether our analysis flagged - the content. - """ - - content_id: Annotated[str, PropertyInfo(alias="contentId")] - """The unique ID of the content in your database.""" - - conversation_id: Annotated[str, PropertyInfo(alias="conversationId")] - """For example the ID of a chat room or a post""" - - do_not_store: Annotated[bool, PropertyInfo(alias="doNotStore")] - """Do not store the content. The content won't enter the review queue""" - - metadata: Dict[str, object] - """Any metadata you want to store with the content""" - - meta_type: Annotated[ - Literal["profile", "message", "post", "comment", "event", "product", "review", "voice", "other"], - PropertyInfo(alias="metaType"), - ] - """The meta type of content being moderated""" - - policies: Iterable[Policy] - """(Enterprise) override the channel policies for this moderation request only.""" - - timestamp: float - """Unix timestamp (in milliseconds) of when the content was created. - - Use if content is not submitted in real-time. - """ - - -class ContentText(TypedDict, total=False): - """Text""" - - text: Required[str] - """The content text""" - - type: Required[Literal["text"]] - - -class ContentImage(TypedDict, total=False): - """Image""" - - type: Required[Literal["image"]] - - data: str - """Base64-encoded image data. - - Either url or data must be provided. Note: base64 images are not stored and will - not appear in the review queue. - """ - - url: str - """A public URL of the image content. Either url or data must be provided.""" - - -class ContentVideo(TypedDict, total=False): - """Video""" - - type: Required[Literal["video"]] - - url: Required[str] - """A public URL of the video content""" - - -class ContentAudio(TypedDict, total=False): - """Audio""" - - type: Required[Literal["audio"]] - - url: Required[str] - """The URL of the audio content""" - - -class ContentObjectDataText(TypedDict, total=False): - """Text""" - - text: Required[str] - """The content text""" - - type: Required[Literal["text"]] - - -class ContentObjectDataImage(TypedDict, total=False): - """Image""" - - type: Required[Literal["image"]] - - data: str - """Base64-encoded image data. - - Either url or data must be provided. Note: base64 images are not stored and will - not appear in the review queue. - """ - - url: str - """A public URL of the image content. Either url or data must be provided.""" - - -class ContentObjectDataVideo(TypedDict, total=False): - """Video""" - - type: Required[Literal["video"]] - - url: Required[str] - """A public URL of the video content""" - - -class ContentObjectDataAudio(TypedDict, total=False): - """Audio""" - - type: Required[Literal["audio"]] - - url: Required[str] - """The URL of the audio content""" - - -ContentObjectData: TypeAlias = Union[ - ContentObjectDataText, ContentObjectDataImage, ContentObjectDataVideo, ContentObjectDataAudio -] - - -class ContentObject(TypedDict, total=False): - """Object""" - - data: Required[Dict[str, ContentObjectData]] - """Values in the object. Can be mixed content types.""" - - type: Required[Literal["object"]] - - -Content: TypeAlias = Union[ContentText, ContentImage, ContentVideo, ContentAudio, ContentObject] - - -class ClientAction(TypedDict, total=False): - """A recommendation from your own client-side flagging (e.g. - - a banned-IP list or a third-party tool). Feeds the rules engine and can escalate or override the recommended action. Does not change whether our analysis flagged the content. - """ - - action: Required[Literal["review", "allow", "reject"]] - """Your recommendation for the content: allow, review, or reject.""" - - behavior: Literal["override", "escalate"] - """How your recommendation combines with ours. - - Defaults to 'escalate', which only applies it when stricter than ours; - 'override' replaces ours outright. - """ - - reason: str - """A human-readable explanation for your recommendation.""" - - source: str - """Where your recommendation came from, e.g. "banned-ip".""" - - -class PolicyToxicity(TypedDict, total=False): - id: Required[Literal["toxicity"]] - - flag: Required[bool] - - threshold: float - - -class PolicyPersonalInformation(TypedDict, total=False): - id: Required[Literal["personal_information"]] - - flag: Required[bool] - - threshold: float - - -class PolicyToxicitySevere(TypedDict, total=False): - id: Required[Literal["toxicity_severe"]] - - flag: Required[bool] - - threshold: float - - -class PolicyHate(TypedDict, total=False): - id: Required[Literal["hate"]] - - flag: Required[bool] - - threshold: float - - -class PolicyIllicit(TypedDict, total=False): - id: Required[Literal["illicit"]] - - flag: Required[bool] - - threshold: float - - -class PolicyIllicitDrugs(TypedDict, total=False): - id: Required[Literal["illicit_drugs"]] - - flag: Required[bool] - - threshold: float - - -class PolicyIllicitAlcohol(TypedDict, total=False): - id: Required[Literal["illicit_alcohol"]] - - flag: Required[bool] - - threshold: float - - -class PolicyIllicitFirearms(TypedDict, total=False): - id: Required[Literal["illicit_firearms"]] - - flag: Required[bool] - - threshold: float - - -class PolicyIllicitTobacco(TypedDict, total=False): - id: Required[Literal["illicit_tobacco"]] - - flag: Required[bool] - - threshold: float - - -class PolicyIllicitGambling(TypedDict, total=False): - id: Required[Literal["illicit_gambling"]] - - flag: Required[bool] - - threshold: float - - -class PolicyCannabis(TypedDict, total=False): - id: Required[Literal["cannabis"]] - - flag: Required[bool] - - threshold: float - - -class PolicyAdult(TypedDict, total=False): - id: Required[Literal["adult"]] - - flag: Required[bool] - - threshold: float - - -class PolicyCrypto(TypedDict, total=False): - id: Required[Literal["crypto"]] - - flag: Required[bool] - - threshold: float - - -class PolicySexual(TypedDict, total=False): - id: Required[Literal["sexual"]] - - flag: Required[bool] - - threshold: float - - -class PolicySexualMinors(TypedDict, total=False): - id: Required[Literal["sexual_minors"]] - - flag: Required[bool] - - threshold: float - - -class PolicyFlirtation(TypedDict, total=False): - id: Required[Literal["flirtation"]] - - flag: Required[bool] - - threshold: float - - -class PolicyProfanity(TypedDict, total=False): - id: Required[Literal["profanity"]] - - flag: Required[bool] - - threshold: float - - -class PolicyViolence(TypedDict, total=False): - id: Required[Literal["violence"]] - - flag: Required[bool] - - threshold: float - - -class PolicySelfHarm(TypedDict, total=False): - id: Required[Literal["self_harm"]] - - flag: Required[bool] - - threshold: float - - -class PolicySpam(TypedDict, total=False): - id: Required[Literal["spam"]] - - flag: Required[bool] - - threshold: float - - -class PolicyLowQualityContent(TypedDict, total=False): - id: Required[Literal["low_quality"]] - - flag: Required[bool] - - min_words: Annotated[int, PropertyInfo(alias="minWords")] - """Flag content with fewer than this many words as low-effort. - - Defaults to 3. Set to disable by omitting. - """ - - threshold: float - - -class PolicyFaceDetection(TypedDict, total=False): - id: Required[Literal["face_detection"]] - - flag: Required[bool] - - comparator: Literal["at_least", "fewer_than"] - """ - Flag images that contain "at least" or "fewer than" the configured number of - faces. Defaults to at_least. - """ - - count: int - """Number of faces the comparator applies to. - - Defaults to 1, so the default rule flags any image containing a face. - """ - - threshold: float - - -class PolicySelfPromotion(TypedDict, total=False): - id: Required[Literal["self_promotion"]] - - flag: Required[bool] - - threshold: float - - -class PolicyPolitical(TypedDict, total=False): - id: Required[Literal["political"]] - - flag: Required[bool] - - threshold: float - - -class PolicyReligion(TypedDict, total=False): - id: Required[Literal["religion"]] - - flag: Required[bool] - - threshold: float - - -class PolicyCodeAbuse(TypedDict, total=False): - id: Required[Literal["code_abuse"]] - - flag: Required[bool] - - threshold: float - - -class PolicyUnicodeSpoofingSignals(TypedDict, total=False): - flag: bool - - -class PolicyUnicodeSpoofing(TypedDict, total=False): - id: Required[Literal["unicode_spoofing"]] - - flag: Required[bool] - - signals: Dict[str, PolicyUnicodeSpoofingSignals] - """Per-signal flag toggles. - - Omitted spoofing signals are enabled; encoding_damage defaults to off because - decode damage (U+FFFD) marks a broken pipeline, not an attack. A disabled signal - is still detected and reported as a label, but does not by itself flag the - policy. - """ - - threshold: float - - -class PolicyPiiMaskingEntities( # type: ignore[call-arg] - TypedDict, - total=False, - extra_items=object, # pyright: ignore[reportGeneralTypeIssues] -): - enable: bool - - flag: bool - - mask: str - - should_mask: Annotated[bool, PropertyInfo(alias="shouldMask")] - - -class PolicyPiiMasking(TypedDict, total=False): - id: Required[Literal["pii"]] - - entities: Required[Dict[str, PolicyPiiMaskingEntities]] - - -class PolicyURLMaskingEntities( # type: ignore[call-arg] - TypedDict, - total=False, - extra_items=object, # pyright: ignore[reportGeneralTypeIssues] -): - enable: bool - - flag: bool - - mask: str - - should_mask: Annotated[bool, PropertyInfo(alias="shouldMask")] - - -class PolicyURLMasking(TypedDict, total=False): - id: Required[Literal["url"]] - - entities: Required[Dict[str, PolicyURLMaskingEntities]] - - -class PolicyURLRisk(TypedDict, total=False): - id: Required[Literal["url_risk"]] - - flag: Required[bool] - - allowlist_wordlist_ids: Annotated[SequenceNotStr[str], PropertyInfo(alias="allowlistWordlistIds")] - """IDs of wordlists whose entries are treated as allowed URL domains. - - Matches short-circuit the risk model and are never flagged. - """ - - blocklist_wordlist_ids: Annotated[SequenceNotStr[str], PropertyInfo(alias="blocklistWordlistIds")] - """IDs of wordlists whose entries are treated as blocked URL domains. - - Matches short-circuit the risk model and are always flagged. Blocklists take - precedence over allowlists. - """ - - flag_link_shorteners: Annotated[bool, PropertyInfo(alias="flagLinkShorteners")] - """ - When true, any URL detected as a free link shortener (bit.ly, t.co, tinyurl, - etc.) is always flagged regardless of risk score. Allowlist matches still win. - """ - - threshold: float - - -class PolicyGuideline(TypedDict, total=False): - id: Required[Literal["guideline"]] - - flag: Required[bool] - - guideline_key: Required[Annotated[str, PropertyInfo(alias="guidelineKey")]] - - instructions: Required[str] - - threshold: float - - -Policy: TypeAlias = Union[ - PolicyToxicity, - PolicyPersonalInformation, - PolicyToxicitySevere, - PolicyHate, - PolicyIllicit, - PolicyIllicitDrugs, - PolicyIllicitAlcohol, - PolicyIllicitFirearms, - PolicyIllicitTobacco, - PolicyIllicitGambling, - PolicyCannabis, - PolicyAdult, - PolicyCrypto, - PolicySexual, - PolicySexualMinors, - PolicyFlirtation, - PolicyProfanity, - PolicyViolence, - PolicySelfHarm, - PolicySpam, - PolicyLowQualityContent, - PolicyFaceDetection, - PolicySelfPromotion, - PolicyPolitical, - PolicyReligion, - PolicyCodeAbuse, - PolicyUnicodeSpoofing, - PolicyPiiMasking, - PolicyURLMasking, - PolicyURLRisk, - PolicyGuideline, -] diff --git a/src/moderation_api/types/content_submit_response.py b/src/moderation_api/types/content_submit_response.py deleted file mode 100644 index 1016880..0000000 --- a/src/moderation_api/types/content_submit_response.py +++ /dev/null @@ -1,476 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Dict, List, Union, Optional -from typing_extensions import Literal, TypeAlias - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = [ - "ContentSubmitResponse", - "Author", - "AuthorBlock", - "AuthorTrustLevel", - "Casebook", - "CasebookTopic", - "Content", - "ContentModifiedModifiedNestedObjectContentContentModifiedModifiedNestedObjectContentItem", - "ContentModifiedModifiedNestedObjectContentContentModifiedModifiedNestedObjectContentItemText", - "ContentModifiedModifiedNestedObjectContentContentModifiedModifiedNestedObjectContentItemImage", - "ContentModifiedModifiedNestedObjectContentContentModifiedModifiedNestedObjectContentItemVideo", - "ContentModifiedModifiedNestedObjectContentContentModifiedModifiedNestedObjectContentItemAudio", - "Evaluation", - "Insight", - "InsightSentimentInsight", - "InsightLanguageInsight", - "Meta", - "Policy", - "PolicyClassifierOutput", - "PolicyClassifierOutputLabel", - "PolicyEntityMatcherOutput", - "PolicyEntityMatcherOutputMatch", - "PolicyEntityMatcherOutputMatchSignals", - "PolicyEntityMatcherOutputMatchSignalsBrandImpersonation", - "Recommendation", - "RecommendationMatchedRule", - "Error", -] - - -class AuthorBlock(BaseModel): - """Block or suspension details, if applicable. Null if the author is enabled.""" - - reason: Optional[str] = None - """The moderators reason why the author was blocked or suspended.""" - - until: Optional[float] = None - """The timestamp until which they are blocked if the author is suspended.""" - - -class AuthorTrustLevel(BaseModel): - level: float - """Author trust level (-1, 0, 1, 2, 3, or 4)""" - - manual: bool - """True if the trust level was set manually by a moderator""" - - -class Author(BaseModel): - """The author of the content if your account has authors enabled. - - Requires you to send authorId when submitting content. - """ - - id: str - """Author ID in Moderation API""" - - block: Optional[AuthorBlock] = None - """Block or suspension details, if applicable. Null if the author is enabled.""" - - status: Literal["enabled", "suspended", "blocked"] - """Current author status""" - - trust_level: AuthorTrustLevel - - external_id: Optional[str] = None - """The author's ID from your system""" - - -class CasebookTopic(BaseModel): - """ - The topic the closest matching case is filed under, or null when it has not been grouped into one yet. - """ - - id: str - - label: str - - -class Casebook(BaseModel): - """ - What your casebook — the record of your past moderation decisions — found for this content, or null when it had nothing close enough to say, when the matching cases disagreed, or when casebook lookups are not switched on for this channel. Reports what the casebook found; whether it decided the outcome is shown in `recommendation`, where a higher-priority rule may have settled the item first. - """ - - agreement: float - """ - How unanimous the matching cases are, from 0 to 1: the share of them that - decided this way, ignoring how many there are. Always at least 0.8 when a ruling - is returned — below that the casebook reports a disagreement instead of picking - a side — so it tells you how clean the consensus was, and is not a threshold to - re-apply yourself. - """ - - case_count: float - """How many of your past cases backed this ruling.""" - - confidence: float - """ - How strongly the casebook holds this ruling, from 0 to 1: the agreement scaled - by how much evidence backs it, so a handful of close, recent cases outweighs one - distant one. Older cases count for less, halving in weight roughly every 180 - days. This is the number to use in rules when you want a strength condition. - """ - - similarity: float - """How close the nearest matching case is, from 0 to 1. - - 1 means the content is identical to something you have already decided. - """ - - topic: Optional[CasebookTopic] = None - """ - The topic the closest matching case is filed under, or null when it has not been - grouped into one yet. - """ - - verdict: Literal["allow", "reject"] - """The ruling your past decisions point to for this content.""" - - -class ContentModifiedModifiedNestedObjectContentContentModifiedModifiedNestedObjectContentItemText(BaseModel): - """Text""" - - text: str - """The content text""" - - type: Literal["text"] - - -class ContentModifiedModifiedNestedObjectContentContentModifiedModifiedNestedObjectContentItemImage(BaseModel): - """Image""" - - type: Literal["image"] - - data: Optional[str] = None - """Base64-encoded image data. - - Either url or data must be provided. Note: base64 images are not stored and will - not appear in the review queue. - """ - - url: Optional[str] = None - """A public URL of the image content. Either url or data must be provided.""" - - -class ContentModifiedModifiedNestedObjectContentContentModifiedModifiedNestedObjectContentItemVideo(BaseModel): - """Video""" - - type: Literal["video"] - - url: str - """A public URL of the video content""" - - -class ContentModifiedModifiedNestedObjectContentContentModifiedModifiedNestedObjectContentItemAudio(BaseModel): - """Audio""" - - type: Literal["audio"] - - url: str - """The URL of the audio content""" - - -ContentModifiedModifiedNestedObjectContentContentModifiedModifiedNestedObjectContentItem: TypeAlias = Union[ - ContentModifiedModifiedNestedObjectContentContentModifiedModifiedNestedObjectContentItemText, - ContentModifiedModifiedNestedObjectContentContentModifiedModifiedNestedObjectContentItemImage, - ContentModifiedModifiedNestedObjectContentContentModifiedModifiedNestedObjectContentItemVideo, - ContentModifiedModifiedNestedObjectContentContentModifiedModifiedNestedObjectContentItemAudio, -] - - -class Content(BaseModel): - """Potentially modified content.""" - - id: str - """The unique identifier for the content. - - Either the contentId provided by you or an autogenerated ID. - """ - - masked: bool - """Whether any values have been masked.""" - - modified: Union[ - str, - Dict[str, object], - Dict[str, ContentModifiedModifiedNestedObjectContentContentModifiedModifiedNestedObjectContentItem], - None, - ] = None - """The modified content, if any.""" - - unicode_cleaned: bool - """ - Whether Unicode spoofing normalization rewrote the content — confusables folded - to their Latin lookalikes, invisible characters and combining-mark abuse - stripped. - """ - - transcript: Optional[str] = None - """The transcribed text from audio content. - - Only present when audio moderation is used and transcript inclusion is enabled - on the channel. - """ - - -class Evaluation(BaseModel): - """The evaluation of the content after running the channel policies.""" - - flag_probability: float - """The probability that the content should be flagged.""" - - flagged: bool - """Whether the content was flagged by any policy.""" - - severity_score: float - """The severity score of the content. - - A higher score indicates more severe content. - """ - - shadow_flagged: Optional[bool] = None - """ - Whether the content would have been flagged by analyze-only policies (flag: - false). - """ - - unicode_spoofed: Optional[bool] = None - """Whether the content was flagged for Unicode spoofing.""" - - -class InsightSentimentInsight(BaseModel): - """Sentiment insight""" - - id: Literal["sentiment"] - - probability: float - - type: Literal["insight"] - - value: Optional[Literal["positive", "neutral", "negative"]] = None - - -class InsightLanguageInsight(BaseModel): - """Language insight""" - - id: Literal["language"] - - probability: float - - type: Literal["insight"] - - value: Optional[str] = None - - -Insight: TypeAlias = Union[InsightSentimentInsight, InsightLanguageInsight] - - -class Meta(BaseModel): - """Metadata about the moderation request""" - - channel_key: str - """The unique key of the channel where the content was handled. - - Either the channel provided by you or automatically routed. - """ - - status: Literal["success", "partial_success"] - - timestamp: float - - usage: float - - processing_time: Optional[str] = None - - -class PolicyClassifierOutputLabel(BaseModel): - id: str - - flagged: bool - - probability: float - - shadow_flagged: Optional[bool] = FieldInfo(alias="shadowFlagged", default=None) - - -class PolicyClassifierOutput(BaseModel): - """Classifier policy.""" - - id: str - """The unique identifier for the classifier output.""" - - flagged: bool - - probability: float - - type: Literal["classifier"] - - data: Optional[Dict[str, object]] = None - """Optional structured data produced by the policy. - - For face detection: { count, faces: [{ confidence, gender, age }] }. - """ - - flagged_fields: Optional[List[str]] = None - """The keys of the flagged fields if submitting an object.""" - - labels: Optional[List[PolicyClassifierOutputLabel]] = None - - -class PolicyEntityMatcherOutputMatchSignalsBrandImpersonation(BaseModel): - brand: str - - method: Literal["registered_domain_token", "subdomain_token"] - - -class PolicyEntityMatcherOutputMatchSignals(BaseModel): - """Observable properties of a URL (URL Risk only). - - Absent for allow/block list matches. - """ - - bot_protection: Optional[bool] = None - - brand_impersonation: Optional[PolicyEntityMatcherOutputMatchSignalsBrandImpersonation] = None - - domain_age_days: Optional[int] = None - - final_url: Optional[str] = None - - has_email_setup: Optional[bool] = None - - has_suspicious_characters: bool - - is_link_shortener: bool - - is_reported: bool - - redirect_count: Optional[int] = None - - -class PolicyEntityMatcherOutputMatch(BaseModel): - match: str - - probability: float - - span: List[int] - - entity_type: Optional[str] = None - """Sub-type of the entity match — e.g. - - the NER key (email, phone, name, …) for PII matches. Absent for URL Risk and - wordlist matches where the type is already encoded in the parent label. - """ - - reasons: Optional[List[str]] = None - """Stable codes explaining why a URL was flagged (URL Risk only).""" - - signals: Optional[PolicyEntityMatcherOutputMatchSignals] = None - """Observable properties of a URL (URL Risk only). - - Absent for allow/block list matches. - """ - - -class PolicyEntityMatcherOutput(BaseModel): - """Entity matcher policy.""" - - id: str - - flagged: bool - - matches: List[PolicyEntityMatcherOutputMatch] - - probability: float - - type: Literal["entity_matcher"] - - flagged_fields: Optional[List[str]] = None - - -Policy: TypeAlias = Union[PolicyClassifierOutput, PolicyEntityMatcherOutput] - - -class RecommendationMatchedRule(BaseModel): - key: str - - name: str - - -class Recommendation(BaseModel): - """The recommendation for the content based on the evaluation.""" - - action: Literal["review", "allow", "reject"] - """The action to take based on the recommendation""" - - reason_codes: List[ - Union[ - Literal[ - "severity_reject", - "severity_review", - "author_block", - "dry_run", - "trusted_allow", - "untrusted_severity", - "rule_match", - "rule_default", - "rule_fallback", - "client_override", - "casebook_match", - ], - str, - ] - ] - """Reason codes for the recommendation. - - Standard codes plus a `rule:` code identifying the rule that produced the - action. Can be used to display a reason to the user. - """ - - matched_rules: Optional[List[RecommendationMatchedRule]] = None - """Rules that matched during evaluation, if rules engine is active.""" - - -class Error(BaseModel): - id: str - - message: str - - -class ContentSubmitResponse(BaseModel): - author: Optional[Author] = None - """The author of the content if your account has authors enabled. - - Requires you to send authorId when submitting content. - """ - - casebook: Optional[Casebook] = None - """ - What your casebook — the record of your past moderation decisions — found for - this content, or null when it had nothing close enough to say, when the matching - cases disagreed, or when casebook lookups are not switched on for this channel. - Reports what the casebook found; whether it decided the outcome is shown in - `recommendation`, where a higher-priority rule may have settled the item first. - """ - - content: Content - """Potentially modified content.""" - - evaluation: Evaluation - """The evaluation of the content after running the channel policies.""" - - insights: List[Insight] - """Results of all insights enabled in the channel.""" - - meta: Meta - """Metadata about the moderation request""" - - policies: List[Policy] - """Results of all policies in the channel. Sorted by highest probability.""" - - recommendation: Recommendation - """The recommendation for the content based on the evaluation.""" - - errors: Optional[List[Error]] = None - """Policies that had errors""" diff --git a/src/moderation_api/types/queue/__init__.py b/src/moderation_api/types/queue/__init__.py deleted file mode 100644 index 58aca4d..0000000 --- a/src/moderation_api/types/queue/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from .item_list_params import ItemListParams as ItemListParams -from .item_list_response import ItemListResponse as ItemListResponse -from .item_resolve_params import ItemResolveParams as ItemResolveParams -from .item_resolve_response import ItemResolveResponse as ItemResolveResponse -from .item_unresolve_params import ItemUnresolveParams as ItemUnresolveParams -from .item_unresolve_response import ItemUnresolveResponse as ItemUnresolveResponse diff --git a/src/moderation_api/types/queue/item_list_params.py b/src/moderation_api/types/queue/item_list_params.py deleted file mode 100644 index b5e032e..0000000 --- a/src/moderation_api/types/queue/item_list_params.py +++ /dev/null @@ -1,36 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal, Annotated, TypedDict - -from ..._utils import PropertyInfo - -__all__ = ["ItemListParams"] - - -class ItemListParams(TypedDict, total=False): - after_date: Annotated[str, PropertyInfo(alias="afterDate")] - - author_id: Annotated[str, PropertyInfo(alias="authorId")] - - before_date: Annotated[str, PropertyInfo(alias="beforeDate")] - - conversation_ids: Annotated[str, PropertyInfo(alias="conversationIds")] - - filtered_action_ids: Annotated[str, PropertyInfo(alias="filteredActionIds")] - - include_resolved: Annotated[str, PropertyInfo(alias="includeResolved")] - - labels: str - - page_number: Annotated[float, PropertyInfo(alias="pageNumber")] - """Page number to fetch""" - - page_size: Annotated[float, PropertyInfo(alias="pageSize")] - """Number of items per page""" - - sort_direction: Annotated[Literal["asc", "desc"], PropertyInfo(alias="sortDirection")] - """Sort direction""" - - sort_field: Annotated[Literal["createdAt", "severity", "reviewedAt"], PropertyInfo(alias="sortField")] diff --git a/src/moderation_api/types/queue/item_list_response.py b/src/moderation_api/types/queue/item_list_response.py deleted file mode 100644 index 2fcb655..0000000 --- a/src/moderation_api/types/queue/item_list_response.py +++ /dev/null @@ -1,90 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from ..._models import BaseModel - -__all__ = ["ItemListResponse", "Item", "ItemLabel", "ItemAction", "Pagination"] - - -class ItemLabel(BaseModel): - flagged: bool - """Whether this label caused a flag""" - - label: str - """Label name""" - - score: float - """Confidence score of the label""" - - -class ItemAction(BaseModel): - id: str - """Action ID""" - - name: str - """Action name""" - - timestamp: float - """Unix timestamp of when the action was taken""" - - comment: Optional[str] = None - """Action comment""" - - reviewer: Optional[str] = None - """Moderator userID""" - - -class Item(BaseModel): - id: str - """Content ID""" - - content: str - """The content to be moderated""" - - flagged: bool - """Whether the item is flagged by any label""" - - labels: List[ItemLabel] - - status: Literal["pending", "resolved"] - """Status of the item""" - - timestamp: float - """Unix timestamp of when the item was created""" - - actions: Optional[List[ItemAction]] = None - """Action IDs taken on this item""" - - author_id: Optional[str] = FieldInfo(alias="authorId", default=None) - """Author ID""" - - content_type: Optional[str] = FieldInfo(alias="contentType", default=None) - """Type of the content object""" - - conversation_id: Optional[str] = FieldInfo(alias="conversationId", default=None) - """Conversation ID""" - - language: Optional[str] = None - """Content language""" - - -class Pagination(BaseModel): - current_page: float = FieldInfo(alias="currentPage") - - has_next_page: bool = FieldInfo(alias="hasNextPage") - - has_previous_page: bool = FieldInfo(alias="hasPreviousPage") - - total_items: float = FieldInfo(alias="totalItems") - - total_pages: float = FieldInfo(alias="totalPages") - - -class ItemListResponse(BaseModel): - items: List[Item] - - pagination: Pagination diff --git a/src/moderation_api/types/queue/item_resolve_params.py b/src/moderation_api/types/queue/item_resolve_params.py deleted file mode 100644 index 16aff11..0000000 --- a/src/moderation_api/types/queue/item_resolve_params.py +++ /dev/null @@ -1,15 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Required, TypedDict - -__all__ = ["ItemResolveParams"] - - -class ItemResolveParams(TypedDict, total=False): - id: Required[str] - """The queue ID""" - - comment: str - """Optional comment""" diff --git a/src/moderation_api/types/queue/item_resolve_response.py b/src/moderation_api/types/queue/item_resolve_response.py deleted file mode 100644 index f89f75a..0000000 --- a/src/moderation_api/types/queue/item_resolve_response.py +++ /dev/null @@ -1,19 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional - -from pydantic import Field as FieldInfo - -from ..._models import BaseModel - -__all__ = ["ItemResolveResponse"] - - -class ItemResolveResponse(BaseModel): - resolved_at: str = FieldInfo(alias="resolvedAt") - """Timestamp when the item was resolved""" - - success: bool - - comment: Optional[str] = None - """Optional comment""" diff --git a/src/moderation_api/types/queue/item_unresolve_params.py b/src/moderation_api/types/queue/item_unresolve_params.py deleted file mode 100644 index 4c97aa5..0000000 --- a/src/moderation_api/types/queue/item_unresolve_params.py +++ /dev/null @@ -1,15 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Required, TypedDict - -__all__ = ["ItemUnresolveParams"] - - -class ItemUnresolveParams(TypedDict, total=False): - id: Required[str] - """The queue ID""" - - comment: str - """Optional reason for unresolving the item""" diff --git a/src/moderation_api/types/queue/item_unresolve_response.py b/src/moderation_api/types/queue/item_unresolve_response.py deleted file mode 100644 index fdf4780..0000000 --- a/src/moderation_api/types/queue/item_unresolve_response.py +++ /dev/null @@ -1,17 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from pydantic import Field as FieldInfo - -from ..._models import BaseModel - -__all__ = ["ItemUnresolveResponse"] - - -class ItemUnresolveResponse(BaseModel): - status: str - """New status of the item""" - - success: bool - - unresolved_at: str = FieldInfo(alias="unresolvedAt") - """Timestamp when the item was unresolved""" diff --git a/src/moderation_api/types/queue_get_stats_params.py b/src/moderation_api/types/queue_get_stats_params.py deleted file mode 100644 index b505325..0000000 --- a/src/moderation_api/types/queue_get_stats_params.py +++ /dev/null @@ -1,14 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["QueueGetStatsParams"] - - -class QueueGetStatsParams(TypedDict, total=False): - within_days: Annotated[str, PropertyInfo(alias="withinDays")] - """Number of days to analyze statistics for""" diff --git a/src/moderation_api/types/queue_get_stats_response.py b/src/moderation_api/types/queue_get_stats_response.py deleted file mode 100644 index 673193e..0000000 --- a/src/moderation_api/types/queue_get_stats_response.py +++ /dev/null @@ -1,110 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = [ - "QueueGetStatsResponse", - "ActionStat", - "ReviewStats", - "TopReviewer", - "TopReviewerTopAction", - "Trends", - "TrendsDailyReviewCount", - "TrendsFlaggedContentTrend", -] - - -class ActionStat(BaseModel): - action_id: str = FieldInfo(alias="actionId") - """ID of the moderation action""" - - action_name: str = FieldInfo(alias="actionName") - """Name of the moderation action""" - - count: float - """Number of times this action was taken""" - - percentage_of_total: float = FieldInfo(alias="percentageOfTotal") - """Percentage this action represents of all actions""" - - -class ReviewStats(BaseModel): - average_time_to_review: float = FieldInfo(alias="averageTimeToReview") - """Average time in milliseconds to review an item""" - - total_pending: float = FieldInfo(alias="totalPending") - """Total number of items pending review""" - - total_reviewed: float = FieldInfo(alias="totalReviewed") - """Total number of items reviewed""" - - -class TopReviewerTopAction(BaseModel): - action_id: str = FieldInfo(alias="actionId") - """Most used action by this reviewer""" - - action_name: str = FieldInfo(alias="actionName") - """Name of the most used action""" - - count: float - """Number of times this action was used""" - - -class TopReviewer(BaseModel): - average_time_per_review: float = FieldInfo(alias="averageTimePerReview") - """Average review time in milliseconds""" - - name: str - """Name of the reviewer""" - - review_count: float = FieldInfo(alias="reviewCount") - """Number of items reviewed""" - - top_actions: List[TopReviewerTopAction] = FieldInfo(alias="topActions") - """Most common actions taken by this reviewer""" - - user_id: str = FieldInfo(alias="userId") - """ID of the reviewer""" - - accuracy_score: Optional[float] = FieldInfo(alias="accuracyScore", default=None) - """Optional accuracy score based on review quality metrics""" - - -class TrendsDailyReviewCount(BaseModel): - count: float - """Number of reviews on this date""" - - date: str - """Date in YYYY-MM-DD format""" - - -class TrendsFlaggedContentTrend(BaseModel): - label: str - """Content flag/label""" - - trend: float - """ - Trend indicator (-1 to 1) showing if this type of flagged content is increasing - or decreasing - """ - - -class Trends(BaseModel): - daily_review_counts: List[TrendsDailyReviewCount] = FieldInfo(alias="dailyReviewCounts") - - flagged_content_trends: List[TrendsFlaggedContentTrend] = FieldInfo(alias="flaggedContentTrends") - - -class QueueGetStatsResponse(BaseModel): - action_stats: List[ActionStat] = FieldInfo(alias="actionStats") - - review_stats: ReviewStats = FieldInfo(alias="reviewStats") - - top_reviewers: List[TopReviewer] = FieldInfo(alias="topReviewers") - """List of top reviewers and their statistics""" - - trends: Trends diff --git a/src/moderation_api/types/queue_retrieve_response.py b/src/moderation_api/types/queue_retrieve_response.py deleted file mode 100644 index e0075f0..0000000 --- a/src/moderation_api/types/queue_retrieve_response.py +++ /dev/null @@ -1,112 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["QueueRetrieveResponse", "Queue", "QueueFilter", "QueueFilterFilterLabel", "QueueFilterMetadataFilter"] - - -class QueueFilterFilterLabel(BaseModel): - label: str - - type: Literal["FLAGGED", "NOT_FLAGGED", "THRESHOLDS", "MATCHED"] - - max_threshold: Optional[float] = FieldInfo(alias="maxThreshold", default=None) - - min_threshold: Optional[float] = FieldInfo(alias="minThreshold", default=None) - - -class QueueFilterMetadataFilter(BaseModel): - key: str - - value: str - - -class QueueFilter(BaseModel): - after_date: Optional[str] = FieldInfo(alias="afterDate", default=None) - - author_id: Optional[str] = FieldInfo(alias="authorID", default=None) - - author_trust_levels: Optional[List[int]] = FieldInfo(alias="authorTrustLevels", default=None) - - before_date: Optional[str] = FieldInfo(alias="beforeDate", default=None) - - casebook_agreement: Optional[Literal["ALL", "OVERRULED", "AGREED"]] = FieldInfo( - alias="casebookAgreement", default=None - ) - - casebook_answer: Optional[Literal["ALL", "ALLOWED", "REJECTED", "NO_ANSWER"]] = FieldInfo( - alias="casebookAnswer", default=None - ) - - check_status: Optional[Literal["all", "checked", "unchecked"]] = FieldInfo(alias="checkStatus", default=None) - - clear_date_window: Optional[bool] = FieldInfo(alias="clearDateWindow", default=None) - - content_id: Optional[str] = FieldInfo(alias="contentID", default=None) - - content_types: Optional[ - List[Literal["profile", "message", "post", "comment", "event", "product", "review", "voice", "other"]] - ] = FieldInfo(alias="contentTypes", default=None) - - conversation_ids: Optional[List[Optional[str]]] = FieldInfo(alias="conversationIds", default=None) - - filtered_action_ids: Optional[List[str]] = FieldInfo(alias="filteredActionIds", default=None) - - filtered_channel_ids: Optional[List[str]] = FieldInfo(alias="filteredChannelIds", default=None) - - filter_labels: Optional[List[QueueFilterFilterLabel]] = FieldInfo(alias="filterLabels", default=None) - - is_flagged: Optional[Literal["ALL", "FLAGGED", "NOT_FLAGGED", "SHADOW_FLAGGED"]] = FieldInfo( - alias="isFlagged", default=None - ) - - labels: Optional[List[str]] = None - - languages: Optional[List[str]] = None - - max_severity: Optional[int] = FieldInfo(alias="maxSeverity", default=None) - - media_types: Optional[List[Literal["text", "image", "video", "object", "audio"]]] = FieldInfo( - alias="mediaTypes", default=None - ) - - metadata_filters: Optional[List[QueueFilterMetadataFilter]] = FieldInfo(alias="metadataFilters", default=None) - - min_severity: Optional[int] = FieldInfo(alias="minSeverity", default=None) - - recommendation_actions: Optional[List[Literal["review", "allow", "reject"]]] = FieldInfo( - alias="recommendationActions", default=None - ) - - search: Optional[List[str]] = None - - within: Optional[float] = None - - within_unit: Optional[Literal["MINUTES", "HOURS", "DAYS", "WEEKS", "MONTHS", "YEARS"]] = FieldInfo( - alias="withinUnit", default=None - ) - - -class Queue(BaseModel): - id: str - - description: str - - filter: QueueFilter - - name: str - - resolved_items_count: float = FieldInfo(alias="resolvedItemsCount") - - total_items_count: float = FieldInfo(alias="totalItemsCount") - - unresolved_items_count: float = FieldInfo(alias="unresolvedItemsCount") - - -class QueueRetrieveResponse(BaseModel): - queue: Queue diff --git a/src/moderation_api/types/webhook_create_params.py b/src/moderation_api/types/webhook_create_params.py deleted file mode 100644 index d1b1753..0000000 --- a/src/moderation_api/types/webhook_create_params.py +++ /dev/null @@ -1,46 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import List, Optional -from typing_extensions import Literal, Required, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["WebhookCreateParams"] - - -class WebhookCreateParams(TypedDict, total=False): - event_types: Required[ - Annotated[ - List[ - Literal[ - "QUEUE_ITEM_NEW", - "QUEUE_ITEM_COMPLETED", - "QUEUE_ITEM_ACTION", - "QUEUE_ITEM_REJECTED", - "QUEUE_ITEM_ALLOWED", - "AUTHOR_BLOCKED", - "AUTHOR_UNBLOCKED", - "AUTHOR_SUSPENDED", - "AUTHOR_UPDATED", - "AUTHOR_TRUST_LEVEL_CHANGED", - "AUTHOR_ACTION", - ] - ], - PropertyInfo(alias="eventTypes"), - ] - ] - """Event types this webhook subscribes to. - - One webhook URL receives all events you list here. - """ - - name: Required[str] - """The webhook's name, used to identify it in the dashboard""" - - url: Required[str] - """The webhook's URL. We'll call this URL when an event occurs.""" - - description: Optional[str] - """The webhook's description""" diff --git a/src/moderation_api/types/webhook_create_response.py b/src/moderation_api/types/webhook_create_response.py deleted file mode 100644 index 48c01b8..0000000 --- a/src/moderation_api/types/webhook_create_response.py +++ /dev/null @@ -1,55 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["WebhookCreateResponse"] - - -class WebhookCreateResponse(BaseModel): - id: str - """The ID of the webhook.""" - - created_at: str = FieldInfo(alias="createdAt") - """The date the webhook was created.""" - - event_types: List[ - Literal[ - "QUEUE_ITEM_NEW", - "QUEUE_ITEM_COMPLETED", - "QUEUE_ITEM_ACTION", - "QUEUE_ITEM_REJECTED", - "QUEUE_ITEM_ALLOWED", - "AUTHOR_BLOCKED", - "AUTHOR_UNBLOCKED", - "AUTHOR_SUSPENDED", - "AUTHOR_UPDATED", - "AUTHOR_TRUST_LEVEL_CHANGED", - "AUTHOR_ACTION", - ] - ] = FieldInfo(alias="eventTypes") - """Event types this webhook subscribes to. - - Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` - instead. - """ - - name: str - """The webhook's name.""" - - payload_version: Literal["V1", "V2"] = FieldInfo(alias="payloadVersion") - """Payload envelope version. - - V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only - via this API. - """ - - url: str - """The URL we call when a subscribed event occurs.""" - - description: Optional[str] = None - """The webhook's description.""" diff --git a/src/moderation_api/types/webhook_delete_response.py b/src/moderation_api/types/webhook_delete_response.py deleted file mode 100644 index b66c864..0000000 --- a/src/moderation_api/types/webhook_delete_response.py +++ /dev/null @@ -1,13 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .._models import BaseModel - -__all__ = ["WebhookDeleteResponse"] - - -class WebhookDeleteResponse(BaseModel): - id: str - """The ID of the webhook.""" - - deleted: bool - """Whether the webhook was deleted.""" diff --git a/src/moderation_api/types/webhook_list_response.py b/src/moderation_api/types/webhook_list_response.py deleted file mode 100644 index d35726f..0000000 --- a/src/moderation_api/types/webhook_list_response.py +++ /dev/null @@ -1,58 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from typing_extensions import Literal, TypeAlias - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["WebhookListResponse", "WebhookListResponseItem"] - - -class WebhookListResponseItem(BaseModel): - id: str - """The ID of the webhook.""" - - created_at: str = FieldInfo(alias="createdAt") - """The date the webhook was created.""" - - event_types: List[ - Literal[ - "QUEUE_ITEM_NEW", - "QUEUE_ITEM_COMPLETED", - "QUEUE_ITEM_ACTION", - "QUEUE_ITEM_REJECTED", - "QUEUE_ITEM_ALLOWED", - "AUTHOR_BLOCKED", - "AUTHOR_UNBLOCKED", - "AUTHOR_SUSPENDED", - "AUTHOR_UPDATED", - "AUTHOR_TRUST_LEVEL_CHANGED", - "AUTHOR_ACTION", - ] - ] = FieldInfo(alias="eventTypes") - """Event types this webhook subscribes to. - - Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` - instead. - """ - - name: str - """The webhook's name.""" - - payload_version: Literal["V1", "V2"] = FieldInfo(alias="payloadVersion") - """Payload envelope version. - - V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only - via this API. - """ - - url: str - """The URL we call when a subscribed event occurs.""" - - description: Optional[str] = None - """The webhook's description.""" - - -WebhookListResponse: TypeAlias = List[WebhookListResponseItem] diff --git a/src/moderation_api/types/webhook_retrieve_response.py b/src/moderation_api/types/webhook_retrieve_response.py deleted file mode 100644 index efde9e3..0000000 --- a/src/moderation_api/types/webhook_retrieve_response.py +++ /dev/null @@ -1,55 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["WebhookRetrieveResponse"] - - -class WebhookRetrieveResponse(BaseModel): - id: str - """The ID of the webhook.""" - - created_at: str = FieldInfo(alias="createdAt") - """The date the webhook was created.""" - - event_types: List[ - Literal[ - "QUEUE_ITEM_NEW", - "QUEUE_ITEM_COMPLETED", - "QUEUE_ITEM_ACTION", - "QUEUE_ITEM_REJECTED", - "QUEUE_ITEM_ALLOWED", - "AUTHOR_BLOCKED", - "AUTHOR_UNBLOCKED", - "AUTHOR_SUSPENDED", - "AUTHOR_UPDATED", - "AUTHOR_TRUST_LEVEL_CHANGED", - "AUTHOR_ACTION", - ] - ] = FieldInfo(alias="eventTypes") - """Event types this webhook subscribes to. - - Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` - instead. - """ - - name: str - """The webhook's name.""" - - payload_version: Literal["V1", "V2"] = FieldInfo(alias="payloadVersion") - """Payload envelope version. - - V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only - via this API. - """ - - url: str - """The URL we call when a subscribed event occurs.""" - - description: Optional[str] = None - """The webhook's description.""" diff --git a/src/moderation_api/types/webhook_secret_retrieve_response.py b/src/moderation_api/types/webhook_secret_retrieve_response.py deleted file mode 100644 index 1b247eb..0000000 --- a/src/moderation_api/types/webhook_secret_retrieve_response.py +++ /dev/null @@ -1,14 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .._models import BaseModel - -__all__ = ["WebhookSecretRetrieveResponse"] - - -class WebhookSecretRetrieveResponse(BaseModel): - secret: str - """The signing secret for this project. - - Every webhook delivery is signed with HMAC-SHA256 over the raw JSON body, - hex-encoded in the `modapi-signature` header. - """ diff --git a/src/moderation_api/types/webhook_update_params.py b/src/moderation_api/types/webhook_update_params.py deleted file mode 100644 index ae25311..0000000 --- a/src/moderation_api/types/webhook_update_params.py +++ /dev/null @@ -1,44 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import List, Optional -from typing_extensions import Literal, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["WebhookUpdateParams"] - - -class WebhookUpdateParams(TypedDict, total=False): - description: Optional[str] - """The webhook's description""" - - event_types: Annotated[ - List[ - Literal[ - "QUEUE_ITEM_NEW", - "QUEUE_ITEM_COMPLETED", - "QUEUE_ITEM_ACTION", - "QUEUE_ITEM_REJECTED", - "QUEUE_ITEM_ALLOWED", - "AUTHOR_BLOCKED", - "AUTHOR_UNBLOCKED", - "AUTHOR_SUSPENDED", - "AUTHOR_UPDATED", - "AUTHOR_TRUST_LEVEL_CHANGED", - "AUTHOR_ACTION", - ] - ], - PropertyInfo(alias="eventTypes"), - ] - """Event types this webhook subscribes to. - - One webhook URL receives all events you list here. - """ - - name: str - """The webhook's name, used to identify it in the dashboard""" - - url: str - """The webhook's URL. We'll call this URL when an event occurs.""" diff --git a/src/moderation_api/types/webhook_update_response.py b/src/moderation_api/types/webhook_update_response.py deleted file mode 100644 index 34604df..0000000 --- a/src/moderation_api/types/webhook_update_response.py +++ /dev/null @@ -1,55 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["WebhookUpdateResponse"] - - -class WebhookUpdateResponse(BaseModel): - id: str - """The ID of the webhook.""" - - created_at: str = FieldInfo(alias="createdAt") - """The date the webhook was created.""" - - event_types: List[ - Literal[ - "QUEUE_ITEM_NEW", - "QUEUE_ITEM_COMPLETED", - "QUEUE_ITEM_ACTION", - "QUEUE_ITEM_REJECTED", - "QUEUE_ITEM_ALLOWED", - "AUTHOR_BLOCKED", - "AUTHOR_UNBLOCKED", - "AUTHOR_SUSPENDED", - "AUTHOR_UPDATED", - "AUTHOR_TRUST_LEVEL_CHANGED", - "AUTHOR_ACTION", - ] - ] = FieldInfo(alias="eventTypes") - """Event types this webhook subscribes to. - - Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` - instead. - """ - - name: str - """The webhook's name.""" - - payload_version: Literal["V1", "V2"] = FieldInfo(alias="payloadVersion") - """Payload envelope version. - - V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only - via this API. - """ - - url: str - """The URL we call when a subscribed event occurs.""" - - description: Optional[str] = None - """The webhook's description.""" diff --git a/src/moderation_api/types/wordlist/__init__.py b/src/moderation_api/types/wordlist/__init__.py deleted file mode 100644 index a12701e..0000000 --- a/src/moderation_api/types/wordlist/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from .word_add_params import WordAddParams as WordAddParams -from .word_add_response import WordAddResponse as WordAddResponse -from .word_remove_params import WordRemoveParams as WordRemoveParams -from .word_remove_response import WordRemoveResponse as WordRemoveResponse diff --git a/src/moderation_api/types/wordlist/word_add_params.py b/src/moderation_api/types/wordlist/word_add_params.py deleted file mode 100644 index 68cc5f0..0000000 --- a/src/moderation_api/types/wordlist/word_add_params.py +++ /dev/null @@ -1,14 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Required, TypedDict - -from ..._types import SequenceNotStr - -__all__ = ["WordAddParams"] - - -class WordAddParams(TypedDict, total=False): - words: Required[SequenceNotStr[str]] - """Array of words to add to the wordlist. Duplicate words will be ignored.""" diff --git a/src/moderation_api/types/wordlist/word_add_response.py b/src/moderation_api/types/wordlist/word_add_response.py deleted file mode 100644 index 7c66c36..0000000 --- a/src/moderation_api/types/wordlist/word_add_response.py +++ /dev/null @@ -1,20 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List - -from pydantic import Field as FieldInfo - -from ..._models import BaseModel - -__all__ = ["WordAddResponse"] - - -class WordAddResponse(BaseModel): - added_count: float = FieldInfo(alias="addedCount") - """Number of words added""" - - added_words: List[str] = FieldInfo(alias="addedWords") - """List of words that were added""" - - total_count: float = FieldInfo(alias="totalCount") - """Total number of words in wordlist""" diff --git a/src/moderation_api/types/wordlist/word_remove_params.py b/src/moderation_api/types/wordlist/word_remove_params.py deleted file mode 100644 index 8573814..0000000 --- a/src/moderation_api/types/wordlist/word_remove_params.py +++ /dev/null @@ -1,14 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Required, TypedDict - -from ..._types import SequenceNotStr - -__all__ = ["WordRemoveParams"] - - -class WordRemoveParams(TypedDict, total=False): - words: Required[SequenceNotStr[str]] - """Array of words to remove from the wordlist""" diff --git a/src/moderation_api/types/wordlist/word_remove_response.py b/src/moderation_api/types/wordlist/word_remove_response.py deleted file mode 100644 index ead396a..0000000 --- a/src/moderation_api/types/wordlist/word_remove_response.py +++ /dev/null @@ -1,20 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List - -from pydantic import Field as FieldInfo - -from ..._models import BaseModel - -__all__ = ["WordRemoveResponse"] - - -class WordRemoveResponse(BaseModel): - removed_count: float = FieldInfo(alias="removedCount") - """Number of words removed""" - - removed_words: List[str] = FieldInfo(alias="removedWords") - """List of words removed""" - - total_count: float = FieldInfo(alias="totalCount") - """Total number of words in wordlist""" diff --git a/src/moderation_api/types/wordlist_get_embedding_status_response.py b/src/moderation_api/types/wordlist_get_embedding_status_response.py deleted file mode 100644 index 7930d48..0000000 --- a/src/moderation_api/types/wordlist_get_embedding_status_response.py +++ /dev/null @@ -1,20 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["WordlistGetEmbeddingStatusResponse"] - - -class WordlistGetEmbeddingStatusResponse(BaseModel): - """Embedding status details""" - - progress: float - """Percentage of words that have been embedded (0-100)""" - - remaining_words: float = FieldInfo(alias="remainingWords") - """Number of words still waiting to be embedded""" - - total_words: float = FieldInfo(alias="totalWords") - """Total number of words in the wordlist""" diff --git a/src/moderation_api/types/wordlist_list_response.py b/src/moderation_api/types/wordlist_list_response.py deleted file mode 100644 index be0db37..0000000 --- a/src/moderation_api/types/wordlist_list_response.py +++ /dev/null @@ -1,30 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from typing_extensions import TypeAlias - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["WordlistListResponse", "WordlistListResponseItem"] - - -class WordlistListResponseItem(BaseModel): - id: str - """Unique identifier of the wordlist""" - - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) - """When the wordlist was created""" - - description: Optional[str] = None - """Description of the wordlist""" - - name: Optional[str] = None - """Name of the wordlist""" - - user_id: Optional[str] = FieldInfo(alias="userId", default=None) - """User who created the wordlist""" - - -WordlistListResponse: TypeAlias = List[WordlistListResponseItem] diff --git a/src/moderation_api/types/wordlist_retrieve_response.py b/src/moderation_api/types/wordlist_retrieve_response.py deleted file mode 100644 index 37e6806..0000000 --- a/src/moderation_api/types/wordlist_retrieve_response.py +++ /dev/null @@ -1,32 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["WordlistRetrieveResponse"] - - -class WordlistRetrieveResponse(BaseModel): - id: str - """ID of the wordlist""" - - created_at: str = FieldInfo(alias="createdAt") - """Creation date of the wordlist""" - - name: Optional[str] = None - """Name of the wordlist""" - - organization_id: str = FieldInfo(alias="organizationId") - """ID of the organization""" - - strict: bool - """Strict mode""" - - user_id: Optional[str] = FieldInfo(alias="userId", default=None) - """ID of the user""" - - words: List[str] - """Words in the wordlist""" diff --git a/src/moderation_api/types/wordlist_update_params.py b/src/moderation_api/types/wordlist_update_params.py deleted file mode 100644 index 4ff90a3..0000000 --- a/src/moderation_api/types/wordlist_update_params.py +++ /dev/null @@ -1,29 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import TypedDict - -from .._types import SequenceNotStr - -__all__ = ["WordlistUpdateParams"] - - -class WordlistUpdateParams(TypedDict, total=False): - description: str - """New description for the wordlist""" - - key: str - """New key for the wordlist""" - - name: str - """New name for the wordlist""" - - strict: bool - """Deprecated. Now using threshold in project settings.""" - - words: SequenceNotStr[str] - """New words for the wordlist. - - Replace the existing words with these new ones. Duplicate words will be ignored. - """ diff --git a/src/moderation_api/types/wordlist_update_response.py b/src/moderation_api/types/wordlist_update_response.py deleted file mode 100644 index e441788..0000000 --- a/src/moderation_api/types/wordlist_update_response.py +++ /dev/null @@ -1,32 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["WordlistUpdateResponse"] - - -class WordlistUpdateResponse(BaseModel): - id: str - """ID of the wordlist""" - - created_at: str = FieldInfo(alias="createdAt") - """Creation date of the wordlist""" - - name: Optional[str] = None - """Name of the wordlist""" - - organization_id: str = FieldInfo(alias="organizationId") - """ID of the organization""" - - strict: bool - """Strict mode""" - - user_id: Optional[str] = FieldInfo(alias="userId", default=None) - """ID of the user""" - - words: List[str] - """Words in the wordlist""" diff --git a/src/moderation_api/utils/__init__.py b/src/moderation_api/utils/__init__.py new file mode 100644 index 0000000..0b27ffc --- /dev/null +++ b/src/moderation_api/utils/__init__.py @@ -0,0 +1,186 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from typing import Any, TYPE_CHECKING, Callable, TypeVar +import asyncio + +from .dynamic_imports import lazy_getattr, lazy_dir + +_T = TypeVar("_T") + + +async def run_sync_in_thread(func: Callable[..., _T], *args) -> _T: + """Run a synchronous function in a thread pool to avoid blocking the event loop.""" + return await asyncio.to_thread(func, *args) + + +if TYPE_CHECKING: + from .annotations import get_discriminator + from .datetimes import parse_datetime, parse_duration + from .enums import OpenEnumMeta + from .unions import parse_open_union + from .headers import get_headers, get_response_headers + from .metadata import ( + FieldMetadata, + find_metadata, + FormMetadata, + HeaderMetadata, + MultipartFormMetadata, + PathParamMetadata, + QueryParamMetadata, + RequestMetadata, + SecurityMetadata, + ) + from .queryparams import get_query_params + from .retries import BackoffStrategy, Retries, retry, retry_async, RetryConfig + from .requestbodies import serialize_request_body, SerializedRequestBody + from .security import get_security, get_security_from_env + + from .serializers import ( + ALLOW_UNKNOWN_UNION_VARIANTS, + get_pydantic_model, + marshal_json, + unmarshal, + unmarshal_json, + serialize_decimal, + serialize_float, + serialize_int, + stream_to_text, + stream_to_text_async, + stream_to_bytes, + stream_to_bytes_async, + validate_const, + validate_decimal, + validate_float, + validate_int, + ) + from .url import generate_url, template_url, remove_suffix + from .values import ( + get_global_from_env, + match_content_type, + match_status_codes, + match_response, + cast_partial, + ) + from .logger import Logger, get_body_content, get_default_logger + +__all__ = [ + "BackoffStrategy", + "FieldMetadata", + "find_metadata", + "FormMetadata", + "generate_url", + "get_body_content", + "get_default_logger", + "get_discriminator", + "parse_datetime", + "parse_duration", + "get_global_from_env", + "get_headers", + "get_pydantic_model", + "get_query_params", + "get_response_headers", + "get_security", + "get_security_from_env", + "HeaderMetadata", + "Logger", + "marshal_json", + "match_content_type", + "match_status_codes", + "match_response", + "MultipartFormMetadata", + "OpenEnumMeta", + "parse_open_union", + "PathParamMetadata", + "QueryParamMetadata", + "remove_suffix", + "Retries", + "retry", + "retry_async", + "RetryConfig", + "RequestMetadata", + "SecurityMetadata", + "serialize_decimal", + "serialize_float", + "serialize_int", + "serialize_request_body", + "SerializedRequestBody", + "stream_to_text", + "stream_to_text_async", + "stream_to_bytes", + "stream_to_bytes_async", + "template_url", + "ALLOW_UNKNOWN_UNION_VARIANTS", + "unmarshal", + "unmarshal_json", + "validate_decimal", + "validate_const", + "validate_float", + "validate_int", + "cast_partial", +] + +_dynamic_imports: dict[str, str] = { + "BackoffStrategy": ".retries", + "FieldMetadata": ".metadata", + "find_metadata": ".metadata", + "FormMetadata": ".metadata", + "generate_url": ".url", + "get_body_content": ".logger", + "get_default_logger": ".logger", + "get_discriminator": ".annotations", + "parse_datetime": ".datetimes", + "parse_duration": ".datetimes", + "get_global_from_env": ".values", + "get_headers": ".headers", + "ALLOW_UNKNOWN_UNION_VARIANTS": ".serializers", + "get_pydantic_model": ".serializers", + "get_query_params": ".queryparams", + "get_response_headers": ".headers", + "get_security": ".security", + "get_security_from_env": ".security", + "HeaderMetadata": ".metadata", + "Logger": ".logger", + "marshal_json": ".serializers", + "match_content_type": ".values", + "match_status_codes": ".values", + "match_response": ".values", + "MultipartFormMetadata": ".metadata", + "OpenEnumMeta": ".enums", + "parse_open_union": ".unions", + "PathParamMetadata": ".metadata", + "QueryParamMetadata": ".metadata", + "remove_suffix": ".url", + "Retries": ".retries", + "retry": ".retries", + "retry_async": ".retries", + "RetryConfig": ".retries", + "RequestMetadata": ".metadata", + "SecurityMetadata": ".metadata", + "serialize_decimal": ".serializers", + "serialize_float": ".serializers", + "serialize_int": ".serializers", + "serialize_request_body": ".requestbodies", + "SerializedRequestBody": ".requestbodies", + "stream_to_text": ".serializers", + "stream_to_text_async": ".serializers", + "stream_to_bytes": ".serializers", + "stream_to_bytes_async": ".serializers", + "template_url": ".url", + "unmarshal": ".serializers", + "unmarshal_json": ".serializers", + "validate_decimal": ".serializers", + "validate_const": ".serializers", + "validate_float": ".serializers", + "validate_int": ".serializers", + "cast_partial": ".values", +} + + +def __getattr__(attr_name: str) -> Any: + return lazy_getattr( + attr_name, package=__package__, dynamic_imports=_dynamic_imports + ) + + +def __dir__(): + return lazy_dir(dynamic_imports=_dynamic_imports) diff --git a/src/moderation_api/utils/annotations.py b/src/moderation_api/utils/annotations.py new file mode 100644 index 0000000..12e0aa4 --- /dev/null +++ b/src/moderation_api/utils/annotations.py @@ -0,0 +1,79 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from enum import Enum +from typing import Any, Optional + + +def get_discriminator(model: Any, fieldname: str, key: str) -> str: + """ + Recursively search for the discriminator attribute in a model. + + Args: + model (Any): The model to search within. + fieldname (str): The name of the field to search for. + key (str): The key to search for in dictionaries. + + Returns: + str: The name of the discriminator attribute. + + Raises: + ValueError: If the discriminator attribute is not found. + """ + upper_fieldname = fieldname.upper() + + def get_field_discriminator(field: Any) -> Optional[str]: + """Search for the discriminator attribute in a given field.""" + + if isinstance(field, dict): + if key in field: + return f"{field[key]}" + + if hasattr(field, fieldname): + attr = getattr(field, fieldname) + if isinstance(attr, Enum): + return f"{attr.value}" + return f"{attr}" + + if hasattr(field, upper_fieldname): + attr = getattr(field, upper_fieldname) + if isinstance(attr, Enum): + return f"{attr.value}" + return f"{attr}" + + return None + + def search_nested_discriminator(obj: Any) -> Optional[str]: + """Recursively search for discriminator in nested structures.""" + # First try direct field lookup + discriminator = get_field_discriminator(obj) + if discriminator is not None: + return discriminator + + # If it's a dict, search in nested values + if isinstance(obj, dict): + for value in obj.values(): + if isinstance(value, list): + # Search in list items + for item in value: + nested_discriminator = search_nested_discriminator(item) + if nested_discriminator is not None: + return nested_discriminator + elif isinstance(value, dict): + # Search in nested dict + nested_discriminator = search_nested_discriminator(value) + if nested_discriminator is not None: + return nested_discriminator + + return None + + if isinstance(model, list): + for field in model: + discriminator = search_nested_discriminator(field) + if discriminator is not None: + return discriminator + + discriminator = search_nested_discriminator(model) + if discriminator is not None: + return discriminator + + raise ValueError(f"Could not find discriminator field {fieldname} in {model}") diff --git a/src/moderation_api/utils/datetimes.py b/src/moderation_api/utils/datetimes.py new file mode 100644 index 0000000..ad634bf --- /dev/null +++ b/src/moderation_api/utils/datetimes.py @@ -0,0 +1,41 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from datetime import datetime, timedelta +import sys + +from pydantic import TypeAdapter + + +def parse_datetime(datetime_string: str) -> datetime: + """ + Convert a RFC 3339 / ISO 8601 formatted string into a datetime object. + Python versions 3.11 and later support parsing RFC 3339 directly with + datetime.fromisoformat(), but for earlier versions, this function + encapsulates the necessary extra logic. + """ + # Python 3.11 and later can parse RFC 3339 directly + if sys.version_info >= (3, 11): + return datetime.fromisoformat(datetime_string) + + # For Python 3.10 and earlier, a common ValueError is trailing 'Z' suffix, + # so fix that upfront. + if datetime_string.endswith("Z"): + datetime_string = datetime_string[:-1] + "+00:00" + + return datetime.fromisoformat(datetime_string) + + +_DURATION_ADAPTER: TypeAdapter[timedelta] = TypeAdapter(timedelta) + + +def parse_duration(duration_string: str) -> timedelta: + """ + Convert an ISO 8601 duration string (e.g. "PT1H30M") into a timedelta. + The standard library has no ISO 8601 duration parser, so this delegates to + pydantic, which is already a runtime dependency of the SDK. + """ + return _DURATION_ADAPTER.validate_python(duration_string) + + +def format_duration(duration: timedelta) -> str: + return _DURATION_ADAPTER.dump_python(duration, mode="json") diff --git a/src/moderation_api/utils/dynamic_imports.py b/src/moderation_api/utils/dynamic_imports.py new file mode 100644 index 0000000..673edf8 --- /dev/null +++ b/src/moderation_api/utils/dynamic_imports.py @@ -0,0 +1,54 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from importlib import import_module +import builtins +import sys + + +def dynamic_import(package, modname, retries=3): + """Import a module relative to package, retrying on KeyError from half-initialized modules.""" + for attempt in range(retries): + try: + return import_module(modname, package) + except KeyError: + sys.modules.pop(modname, None) + if attempt == retries - 1: + break + raise KeyError(f"Failed to import module '{modname}' after {retries} attempts") + + +def lazy_getattr(attr_name, *, package, dynamic_imports, sub_packages=None): + """Module-level __getattr__ that lazily loads from a dynamic_imports mapping. + + Args: + attr_name: The attribute being looked up. + package: The caller's __package__ (for relative imports). + dynamic_imports: Dict mapping attribute names to relative module paths. + sub_packages: Optional list of subpackage names to lazy-load. + """ + module_name = dynamic_imports.get(attr_name) + if module_name is not None: + try: + module = dynamic_import(package, module_name) + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + if sub_packages and attr_name in sub_packages: + return import_module(f".{attr_name}", package) + + raise AttributeError(f"module '{package}' has no attribute '{attr_name}'") + + +def lazy_dir(*, dynamic_imports, sub_packages=None): + """Module-level __dir__ that lists lazily-loadable attributes.""" + lazy_attrs = builtins.list(dynamic_imports.keys()) + if sub_packages: + lazy_attrs.extend(sub_packages) + return builtins.sorted(lazy_attrs) diff --git a/src/moderation_api/utils/enums.py b/src/moderation_api/utils/enums.py new file mode 100644 index 0000000..3324e1b --- /dev/null +++ b/src/moderation_api/utils/enums.py @@ -0,0 +1,134 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +import enum +import sys +from typing import Any + +from pydantic_core import core_schema + + +class OpenEnumMeta(enum.EnumMeta): + # The __call__ method `boundary` kwarg was added in 3.11 and must be present + # for pyright. Refer also: https://github.com/pylint-dev/pylint/issues/9622 + # pylint: disable=unexpected-keyword-arg + # The __call__ method `values` varg must be named for pyright. + # pylint: disable=keyword-arg-before-vararg + + if sys.version_info >= (3, 11): + def __call__( + cls, value, names=None, *values, module=None, qualname=None, type=None, start=1, boundary=None + ): + # The `type` kwarg also happens to be a built-in that pylint flags as + # redeclared. Safe to ignore this lint rule with this scope. + # pylint: disable=redefined-builtin + + if names is not None: + return super().__call__( + value, + names=names, + *values, + module=module, + qualname=qualname, + type=type, + start=start, + boundary=boundary, + ) + + try: + return super().__call__( + value, + names=names, # pyright: ignore[reportArgumentType] + *values, + module=module, + qualname=qualname, + type=type, + start=start, + boundary=boundary, + ) + except ValueError: + return value + else: + def __call__( + cls, value, names=None, *, module=None, qualname=None, type=None, start=1 + ): + # The `type` kwarg also happens to be a built-in that pylint flags as + # redeclared. Safe to ignore this lint rule with this scope. + # pylint: disable=redefined-builtin + + if names is not None: + return super().__call__( + value, + names=names, + module=module, + qualname=qualname, + type=type, + start=start, + ) + + try: + return super().__call__( + value, + names=names, # pyright: ignore[reportArgumentType] + module=module, + qualname=qualname, + type=type, + start=start, + ) + except ValueError: + return value + + def __new__(mcs, name, bases, namespace, **kwargs): + cls = super().__new__(mcs, name, bases, namespace, **kwargs) + + # Add __get_pydantic_core_schema__ to make open enums work correctly + # in union discrimination. In strict mode (used by Pydantic for unions), + # only known enum values match. In lax mode, unknown values are accepted. + def __get_pydantic_core_schema__( + cls_inner: Any, _source_type: Any, _handler: Any + ) -> core_schema.CoreSchema: + # Create a validator that only accepts known enum values (for strict mode) + def validate_strict(v: Any) -> Any: + if isinstance(v, cls_inner): + return v + # Use the parent EnumMeta's __call__ which raises ValueError for unknown values + return enum.EnumMeta.__call__(cls_inner, v) + + # Create a lax validator that accepts unknown values + def validate_lax(v: Any) -> Any: + if isinstance(v, cls_inner): + return v + try: + return enum.EnumMeta.__call__(cls_inner, v) + except ValueError: + # Return the raw value for unknown enum values + return v + + # Determine the base type schema (str or int) + is_int_enum = False + for base in cls_inner.__mro__: + if base is int: + is_int_enum = True + break + if base is str: + break + + base_schema = ( + core_schema.int_schema() + if is_int_enum + else core_schema.str_schema() + ) + + # Use lax_or_strict_schema: + # - strict mode: only known enum values match (raises ValueError for unknown) + # - lax mode: accept any value, return enum member or raw value + return core_schema.lax_or_strict_schema( + lax_schema=core_schema.chain_schema( + [base_schema, core_schema.no_info_plain_validator_function(validate_lax)] + ), + strict_schema=core_schema.chain_schema( + [base_schema, core_schema.no_info_plain_validator_function(validate_strict)] + ), + ) + + setattr(cls, "__get_pydantic_core_schema__", classmethod(__get_pydantic_core_schema__)) + return cls diff --git a/src/moderation_api/utils/eventstreaming.py b/src/moderation_api/utils/eventstreaming.py new file mode 100644 index 0000000..a8d4fe5 --- /dev/null +++ b/src/moderation_api/utils/eventstreaming.py @@ -0,0 +1,326 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +import re +import json +from dataclasses import dataclass, asdict +from typing import ( + Any, + Callable, + Generic, + List, + TypeVar, + Optional, + Generator, + AsyncGenerator, + Tuple, +) +import httpx + +T = TypeVar("T") + + +class EventStream(Generic[T]): + # Holds a reference to the SDK client to avoid it being garbage collected + # and cause termination of the underlying httpx client. + client_ref: Optional[object] + response: httpx.Response + generator: Generator[T, None, None] + _closed: bool + + def __init__( + self, + response: httpx.Response, + decoder: Callable[[str], T], + sentinel: Optional[str] = None, + client_ref: Optional[object] = None, + data_required: bool = True, + ): + self.response = response + self.generator = stream_events( + response, decoder, sentinel, data_required=data_required + ) + self.client_ref = client_ref + self._closed = False + + def __iter__(self): + return self + + def __next__(self): + if self._closed: + raise StopIteration + return next(self.generator) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + def close(self): + self._closed = True + self.response.close() + + +class EventStreamAsync(Generic[T]): + # Holds a reference to the SDK client to avoid it being garbage collected + # and cause termination of the underlying httpx client. + client_ref: Optional[object] + response: httpx.Response + generator: AsyncGenerator[T, None] + _closed: bool + + def __init__( + self, + response: httpx.Response, + decoder: Callable[[str], T], + sentinel: Optional[str] = None, + client_ref: Optional[object] = None, + data_required: bool = True, + ): + self.response = response + self.generator = stream_events_async( + response, decoder, sentinel, data_required=data_required + ) + self.client_ref = client_ref + self._closed = False + + def __aiter__(self): + return self + + async def __anext__(self): + if self._closed: + raise StopAsyncIteration + return await self.generator.__anext__() + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.close() + + async def close(self): + self._closed = True + await self.response.aclose() + + +@dataclass +class ServerEvent: + id: Optional[str] = None + event: Optional[str] = None + data: Any = None + retry: Optional[int] = None + + +MESSAGE_BOUNDARIES = [ + b"\r\n\r\n", + b"\r\n\r", + b"\r\n\n", + b"\r\r\n", + b"\n\r\n", + b"\r\r", + b"\n\r", + b"\n\n", +] +MAX_BOUNDARY_LEN = max(len(b) for b in MESSAGE_BOUNDARIES) + +UTF8_BOM = b"\xef\xbb\xbf" + + +async def stream_events_async( + response: httpx.Response, + decoder: Callable[[str], T], + sentinel: Optional[str] = None, + data_required: bool = True, +) -> AsyncGenerator[T, None]: + try: + buffer = bytearray() + position = 0 + event_id: Optional[str] = None + async for chunk in response.aiter_bytes(): + if len(buffer) == 0 and chunk.startswith(UTF8_BOM): + chunk = chunk[len(UTF8_BOM) :] + old_len = len(buffer) + buffer += chunk + search_start = max(position, old_len - MAX_BOUNDARY_LEN + 1) + for i in range(search_start, len(buffer)): + char = buffer[i : i + 1] + seq: Optional[bytes] = None + if char in [b"\r", b"\n"]: + for boundary in MESSAGE_BOUNDARIES: + seq = _peek_sequence(i, buffer, boundary) + if seq is not None: + break + if seq is None: + continue + + block = buffer[position:i] + position = i + len(seq) + event, discard, event_id = _parse_event( + raw=block, + decoder=decoder, + sentinel=sentinel, + event_id=event_id, + data_required=data_required, + ) + if event is not None: + yield event + if discard: + return + + if position > 0: + buffer = buffer[position:] + position = 0 + + event, discard, _ = _parse_event( + raw=buffer, + decoder=decoder, + sentinel=sentinel, + event_id=event_id, + data_required=data_required, + ) + if event is not None: + yield event + finally: + await response.aclose() + + +def stream_events( + response: httpx.Response, + decoder: Callable[[str], T], + sentinel: Optional[str] = None, + data_required: bool = True, +) -> Generator[T, None, None]: + try: + buffer = bytearray() + position = 0 + event_id: Optional[str] = None + for chunk in response.iter_bytes(): + if len(buffer) == 0 and chunk.startswith(UTF8_BOM): + chunk = chunk[len(UTF8_BOM) :] + old_len = len(buffer) + buffer += chunk + search_start = max(position, old_len - MAX_BOUNDARY_LEN + 1) + for i in range(search_start, len(buffer)): + char = buffer[i : i + 1] + seq: Optional[bytes] = None + if char in [b"\r", b"\n"]: + for boundary in MESSAGE_BOUNDARIES: + seq = _peek_sequence(i, buffer, boundary) + if seq is not None: + break + if seq is None: + continue + + block = buffer[position:i] + position = i + len(seq) + event, discard, event_id = _parse_event( + raw=block, + decoder=decoder, + sentinel=sentinel, + event_id=event_id, + data_required=data_required, + ) + if event is not None: + yield event + if discard: + return + + if position > 0: + buffer = buffer[position:] + position = 0 + + event, discard, _ = _parse_event( + raw=buffer, + decoder=decoder, + sentinel=sentinel, + event_id=event_id, + data_required=data_required, + ) + if event is not None: + yield event + finally: + response.close() + + +def _parse_event( + *, + raw: bytearray, + decoder: Callable[[str], T], + sentinel: Optional[str] = None, + event_id: Optional[str] = None, + data_required: bool = True, +) -> Tuple[Optional[T], bool, Optional[str]]: + block = raw.decode() + lines = re.split(r"\r?\n|\r", block) + publish = False + event = ServerEvent() + data_parts: List[str] = [] + for line in lines: + if not line: + continue + + delim = line.find(":") + if delim == 0: + continue + + field = line + value = "" + if delim > 0: + field = line[0:delim] + value = line[delim + 1 :] if delim < len(line) - 1 else "" + if len(value) and value[0] == " ": + value = value[1:] + + if field == "event": + event.event = value + publish = True + elif field == "data": + data_parts.append(value) + publish = True + elif field == "id": + publish = True + if "\x00" not in value: + event_id = value + elif field == "retry": + if value.isdigit(): + event.retry = int(value) + publish = True + + event.id = event_id + has_data = bool(data_parts) + data = "\n".join(data_parts) + + if sentinel and has_data and data == sentinel: + return None, True, event_id + + # Skip data-less events when data is required + if not has_data and publish and data_required: + return None, False, event_id + + if has_data: + try: + event.data = json.loads(data) + except json.JSONDecodeError: + event.data = data + + out = None + if publish: + out_dict = { + k: v + for k, v in asdict(event).items() + if v is not None or (k == "data" and has_data) + } + out = decoder(json.dumps(out_dict)) + + return out, False, event_id + + +def _peek_sequence(position: int, buffer: bytearray, sequence: bytes): + if len(sequence) > (len(buffer) - position): + return None + + for i, seq in enumerate(sequence): + if buffer[position + i] != seq: + return None + + return sequence diff --git a/src/moderation_api/utils/forms.py b/src/moderation_api/utils/forms.py new file mode 100644 index 0000000..193f264 --- /dev/null +++ b/src/moderation_api/utils/forms.py @@ -0,0 +1,239 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +import io +from typing import ( + Any, + Dict, + get_type_hints, + List, + Tuple, +) +from pydantic import BaseModel +from pydantic.fields import FieldInfo + +from .serializers import marshal_json + +from .metadata import ( + FormMetadata, + MultipartFormMetadata, + find_field_metadata, +) +from .values import _is_set, _val_to_string + + +def _populate_form( + field_name: str, + explode: bool, + obj: Any, + delimiter: str, + form: Dict[str, List[str]], +): + if not _is_set(obj): + return form + + if isinstance(obj, BaseModel): + items = [] + + obj_fields: Dict[str, FieldInfo] = obj.__class__.model_fields + for name in obj_fields: + obj_field = obj_fields[name] + obj_field_name = obj_field.alias if obj_field.alias is not None else name + if obj_field_name == "": + continue + + val = getattr(obj, name) + if not _is_set(val): + continue + + if explode: + form[obj_field_name] = [_val_to_string(val)] + else: + items.append(f"{obj_field_name}{delimiter}{_val_to_string(val)}") + + if len(items) > 0: + form[field_name] = [delimiter.join(items)] + elif isinstance(obj, Dict): + items = [] + for key, value in obj.items(): + if not _is_set(value): + continue + + if explode: + form[key] = [_val_to_string(value)] + else: + items.append(f"{key}{delimiter}{_val_to_string(value)}") + + if len(items) > 0: + form[field_name] = [delimiter.join(items)] + elif isinstance(obj, List): + items = [] + + for value in obj: + if not _is_set(value): + continue + + if explode: + if not field_name in form: + form[field_name] = [] + form[field_name].append(_val_to_string(value)) + else: + items.append(_val_to_string(value)) + + if len(items) > 0: + form[field_name] = [delimiter.join([str(item) for item in items])] + else: + form[field_name] = [_val_to_string(obj)] + + return form + + +def _extract_file_properties(file_obj: Any) -> Tuple[str, Any, Any]: + """Extract file name, content, and content type from a file object.""" + file_fields: Dict[str, FieldInfo] = file_obj.__class__.model_fields + + file_name = "" + content = None + content_type = None + + for file_field_name in file_fields: + file_field = file_fields[file_field_name] + + file_metadata = find_field_metadata(file_field, MultipartFormMetadata) + if file_metadata is None: + continue + + if file_metadata.content: + content = getattr(file_obj, file_field_name, None) + if isinstance(content, io.TextIOBase): + content = content.read().encode( + getattr(content, "encoding", None) or "utf-8" + ) + elif file_field_name == "content_type": + content_type = getattr(file_obj, file_field_name, None) + else: + file_name = getattr(file_obj, file_field_name) + + if file_name == "" or content is None: + raise ValueError("invalid multipart/form-data file") + + return file_name, content, content_type + + +def serialize_multipart_form( + media_type: str, request: Any +) -> Tuple[str, Dict[str, Any], List[Tuple[str, Any]]]: + form: Dict[str, Any] = {} + files: List[Tuple[str, Any]] = [] + + if not isinstance(request, BaseModel): + raise TypeError("invalid request body type") + + request_fields: Dict[str, FieldInfo] = request.__class__.model_fields + request_field_types = get_type_hints(request.__class__) + + for name in request_fields: + field = request_fields[name] + + val = getattr(request, name) + if not _is_set(val): + continue + + field_metadata = find_field_metadata(field, MultipartFormMetadata) + if not field_metadata: + continue + + f_name = field.alias if field.alias else name + + if field_metadata.file: + if isinstance(val, List): + # Handle array of files + array_field_name = f_name + for file_obj in val: + if not _is_set(file_obj): + continue + + file_name, content, content_type = _extract_file_properties( + file_obj + ) + + if content_type is not None: + files.append( + (array_field_name, (file_name, content, content_type)) + ) + else: + files.append((array_field_name, (file_name, content))) + else: + # Handle single file + file_name, content, content_type = _extract_file_properties(val) + + if content_type is not None: + files.append((f_name, (file_name, content, content_type))) + else: + files.append((f_name, (file_name, content))) + elif field_metadata.json: + files.append( + ( + f_name, + ( + None, + marshal_json(val, request_field_types[name]), + "application/json", + ), + ) + ) + else: + if isinstance(val, List): + values = [] + + for value in val: + if not _is_set(value): + continue + values.append(_val_to_string(value)) + + array_field_name = f_name + form[array_field_name] = values + else: + form[f_name] = _val_to_string(val) + return media_type, form, files + + +def serialize_form_data(data: Any) -> Dict[str, Any]: + form: Dict[str, List[str]] = {} + + if isinstance(data, BaseModel): + data_fields: Dict[str, FieldInfo] = data.__class__.model_fields + data_field_types = get_type_hints(data.__class__) + for name in data_fields: + field = data_fields[name] + + val = getattr(data, name) + if not _is_set(val): + continue + + metadata = find_field_metadata(field, FormMetadata) + if metadata is None: + continue + + f_name = field.alias if field.alias is not None else name + + if metadata.json: + form[f_name] = [marshal_json(val, data_field_types[name])] + else: + if metadata.style == "form": + _populate_form( + f_name, + metadata.explode, + val, + ",", + form, + ) + else: + raise ValueError(f"Invalid form style for field {name}") + elif isinstance(data, Dict): + for key, value in data.items(): + if _is_set(value): + form[key] = [_val_to_string(value)] + else: + raise TypeError(f"Invalid request body type {type(data)} for form data") + + return form diff --git a/src/moderation_api/utils/headers.py b/src/moderation_api/utils/headers.py new file mode 100644 index 0000000..37864cb --- /dev/null +++ b/src/moderation_api/utils/headers.py @@ -0,0 +1,136 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from typing import ( + Any, + Dict, + List, + Optional, +) +from httpx import Headers +from pydantic import BaseModel +from pydantic.fields import FieldInfo + +from .metadata import ( + HeaderMetadata, + find_field_metadata, +) + +from .values import _is_set, _populate_from_globals, _val_to_string + + +def get_headers(headers_params: Any, gbls: Optional[Any] = None) -> Dict[str, str]: + headers: Dict[str, str] = {} + + globals_already_populated = [] + if _is_set(headers_params): + globals_already_populated = _populate_headers(headers_params, gbls, headers, []) + if _is_set(gbls): + _populate_headers(gbls, None, headers, globals_already_populated) + + return headers + + +def _populate_headers( + headers_params: Any, + gbls: Any, + header_values: Dict[str, str], + skip_fields: List[str], +) -> List[str]: + globals_already_populated: List[str] = [] + + if not isinstance(headers_params, BaseModel): + return globals_already_populated + + param_fields: Dict[str, FieldInfo] = headers_params.__class__.model_fields + for name in param_fields: + if name in skip_fields: + continue + + field = param_fields[name] + f_name = field.alias if field.alias is not None else name + + metadata = find_field_metadata(field, HeaderMetadata) + if metadata is None: + continue + + value, global_found = _populate_from_globals( + name, getattr(headers_params, name), HeaderMetadata, gbls + ) + if global_found: + globals_already_populated.append(name) + value = _serialize_header(metadata.explode, value) + + if value != "": + header_values[f_name] = value + + return globals_already_populated + + +def _serialize_header(explode: bool, obj: Any) -> str: + if not _is_set(obj): + return "" + + if isinstance(obj, BaseModel): + items = [] + obj_fields: Dict[str, FieldInfo] = obj.__class__.model_fields + for name in obj_fields: + obj_field = obj_fields[name] + obj_param_metadata = find_field_metadata(obj_field, HeaderMetadata) + + if not obj_param_metadata: + continue + + f_name = obj_field.alias if obj_field.alias is not None else name + + val = getattr(obj, name) + if not _is_set(val): + continue + + if explode: + items.append(f"{f_name}={_val_to_string(val)}") + else: + items.append(f_name) + items.append(_val_to_string(val)) + + if len(items) > 0: + return ",".join(items) + elif isinstance(obj, Dict): + items = [] + + for key, value in obj.items(): + if not _is_set(value): + continue + + if explode: + items.append(f"{key}={_val_to_string(value)}") + else: + items.append(key) + items.append(_val_to_string(value)) + + if len(items) > 0: + return ",".join([str(item) for item in items]) + elif isinstance(obj, List): + items = [] + + for value in obj: + if not _is_set(value): + continue + + items.append(_val_to_string(value)) + + if len(items) > 0: + return ",".join(items) + elif _is_set(obj): + return f"{_val_to_string(obj)}" + + return "" + + +def get_response_headers(headers: Headers) -> Dict[str, List[str]]: + res: Dict[str, List[str]] = {} + for k, v in headers.items(): + if not k in res: + res[k] = [] + + res[k].append(v) + return res diff --git a/src/moderation_api/utils/logger.py b/src/moderation_api/utils/logger.py new file mode 100644 index 0000000..fa9c6a5 --- /dev/null +++ b/src/moderation_api/utils/logger.py @@ -0,0 +1,27 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +import httpx +import logging +import os +from typing import Any, Protocol + + +class Logger(Protocol): + def debug(self, msg: str, *args: Any, **kwargs: Any) -> None: + pass + + +class NoOpLogger: + def debug(self, msg: str, *args: Any, **kwargs: Any) -> None: + pass + + +def get_body_content(req: httpx.Request) -> str: + return "" if not hasattr(req, "_content") else str(req.content) + + +def get_default_logger() -> Logger: + if os.getenv("MODAPI_DEBUG"): + logging.basicConfig(level=logging.DEBUG) + return logging.getLogger("moderation_api") + return NoOpLogger() diff --git a/src/moderation_api/utils/metadata.py b/src/moderation_api/utils/metadata.py new file mode 100644 index 0000000..5abddd5 --- /dev/null +++ b/src/moderation_api/utils/metadata.py @@ -0,0 +1,119 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from typing import Optional, Type, TypeVar, Union +from dataclasses import dataclass +from pydantic.fields import FieldInfo + + +T = TypeVar("T") + + +@dataclass +class SecurityMetadata: + option: bool = False + scheme: bool = False + scheme_type: Optional[str] = None + sub_type: Optional[str] = None + field_name: Optional[str] = None + composite: bool = False + + def get_field_name(self, default: str) -> str: + return self.field_name or default + + +@dataclass +class ParamMetadata: + serialization: Optional[str] = None + style: str = "simple" + explode: bool = False + + +@dataclass +class PathParamMetadata(ParamMetadata): + pass + + +@dataclass +class QueryParamMetadata(ParamMetadata): + style: str = "form" + explode: bool = True + + +@dataclass +class HeaderMetadata(ParamMetadata): + pass + + +@dataclass +class RequestMetadata: + media_type: str = "application/octet-stream" + + +@dataclass +class MultipartFormMetadata: + file: bool = False + content: bool = False + json: bool = False + + +@dataclass +class FormMetadata: + json: bool = False + style: str = "form" + explode: bool = True + + +class FieldMetadata: + security: Optional[SecurityMetadata] = None + path: Optional[PathParamMetadata] = None + query: Optional[QueryParamMetadata] = None + header: Optional[HeaderMetadata] = None + request: Optional[RequestMetadata] = None + form: Optional[FormMetadata] = None + multipart: Optional[MultipartFormMetadata] = None + + def __init__( + self, + security: Optional[SecurityMetadata] = None, + path: Optional[Union[PathParamMetadata, bool]] = None, + query: Optional[Union[QueryParamMetadata, bool]] = None, + header: Optional[Union[HeaderMetadata, bool]] = None, + request: Optional[Union[RequestMetadata, bool]] = None, + form: Optional[Union[FormMetadata, bool]] = None, + multipart: Optional[Union[MultipartFormMetadata, bool]] = None, + ): + self.security = security + self.path = PathParamMetadata() if isinstance(path, bool) else path + self.query = QueryParamMetadata() if isinstance(query, bool) else query + self.header = HeaderMetadata() if isinstance(header, bool) else header + self.request = RequestMetadata() if isinstance(request, bool) else request + self.form = FormMetadata() if isinstance(form, bool) else form + self.multipart = ( + MultipartFormMetadata() if isinstance(multipart, bool) else multipart + ) + + +def find_field_metadata(field_info: FieldInfo, metadata_type: Type[T]) -> Optional[T]: + metadata = find_metadata(field_info, FieldMetadata) + if not metadata: + return None + + fields = metadata.__dict__ + + for field in fields: + if isinstance(fields[field], metadata_type): + return fields[field] + + return None + + +def find_metadata(field_info: FieldInfo, metadata_type: Type[T]) -> Optional[T]: + metadata = field_info.metadata + if not metadata: + return None + + for md in metadata: + if isinstance(md, metadata_type): + return md + + return None diff --git a/src/moderation_api/utils/queryparams.py b/src/moderation_api/utils/queryparams.py new file mode 100644 index 0000000..c04e0db --- /dev/null +++ b/src/moderation_api/utils/queryparams.py @@ -0,0 +1,217 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from typing import ( + Any, + Dict, + get_type_hints, + List, + Optional, +) + +from pydantic import BaseModel +from pydantic.fields import FieldInfo + +from .metadata import ( + QueryParamMetadata, + find_field_metadata, +) +from .values import ( + _get_serialized_params, + _is_set, + _populate_from_globals, + _val_to_string, +) +from .forms import _populate_form + + +def get_query_params( + query_params: Any, + gbls: Optional[Any] = None, + allow_empty_value: Optional[List[str]] = None, +) -> Dict[str, List[str]]: + params: Dict[str, List[str]] = {} + + globals_already_populated = _populate_query_params(query_params, gbls, params, [], allow_empty_value) + if _is_set(gbls): + _populate_query_params(gbls, None, params, globals_already_populated, allow_empty_value) + + return params + + +def _populate_query_params( + query_params: Any, + gbls: Any, + query_param_values: Dict[str, List[str]], + skip_fields: List[str], + allow_empty_value: Optional[List[str]] = None, +) -> List[str]: + globals_already_populated: List[str] = [] + + if not isinstance(query_params, BaseModel): + return globals_already_populated + + param_fields: Dict[str, FieldInfo] = query_params.__class__.model_fields + param_field_types = get_type_hints(query_params.__class__) + for name in param_fields: + if name in skip_fields: + continue + + field = param_fields[name] + + metadata = find_field_metadata(field, QueryParamMetadata) + if not metadata: + continue + + value = getattr(query_params, name) if _is_set(query_params) else None + + value, global_found = _populate_from_globals( + name, value, QueryParamMetadata, gbls + ) + if global_found: + globals_already_populated.append(name) + + f_name = field.alias if field.alias is not None else name + + allow_empty_set = set(allow_empty_value or []) + should_include_empty = f_name in allow_empty_set and ( + value is None or value == [] or value == "" + ) + + if should_include_empty: + query_param_values[f_name] = [""] + continue + + serialization = metadata.serialization + if serialization is not None: + serialized_parms = _get_serialized_params( + metadata, f_name, value, param_field_types[name] + ) + for key, value in serialized_parms.items(): + if key in query_param_values: + query_param_values[key].extend(value) + else: + query_param_values[key] = [value] + else: + style = metadata.style + if style == "deepObject": + _populate_deep_object_query_params(f_name, value, query_param_values) + elif style == "form": + _populate_delimited_query_params( + metadata, f_name, value, ",", query_param_values + ) + elif style == "pipeDelimited": + _populate_delimited_query_params( + metadata, f_name, value, "|", query_param_values + ) + else: + raise NotImplementedError( + f"query param style {style} not yet supported" + ) + + return globals_already_populated + + +def _populate_deep_object_query_params( + field_name: str, + obj: Any, + params: Dict[str, List[str]], +): + if not _is_set(obj): + return + + if isinstance(obj, BaseModel): + _populate_deep_object_query_params_basemodel(field_name, obj, params) + elif isinstance(obj, Dict): + _populate_deep_object_query_params_dict(field_name, obj, params) + + +def _populate_deep_object_query_params_basemodel( + prior_params_key: str, + obj: Any, + params: Dict[str, List[str]], +): + if not _is_set(obj) or not isinstance(obj, BaseModel): + return + + obj_fields: Dict[str, FieldInfo] = obj.__class__.model_fields + for name in obj_fields: + obj_field = obj_fields[name] + + f_name = obj_field.alias if obj_field.alias is not None else name + + params_key = f"{prior_params_key}[{f_name}]" + + obj_param_metadata = find_field_metadata(obj_field, QueryParamMetadata) + if not _is_set(obj_param_metadata): + continue + + obj_val = getattr(obj, name) + if not _is_set(obj_val): + continue + + if isinstance(obj_val, BaseModel): + _populate_deep_object_query_params_basemodel(params_key, obj_val, params) + elif isinstance(obj_val, Dict): + _populate_deep_object_query_params_dict(params_key, obj_val, params) + elif isinstance(obj_val, List): + _populate_deep_object_query_params_list(params_key, obj_val, params) + else: + params[params_key] = [_val_to_string(obj_val)] + + +def _populate_deep_object_query_params_dict( + prior_params_key: str, + value: Dict, + params: Dict[str, List[str]], +): + if not _is_set(value): + return + + for key, val in value.items(): + if not _is_set(val): + continue + + params_key = f"{prior_params_key}[{key}]" + + if isinstance(val, BaseModel): + _populate_deep_object_query_params_basemodel(params_key, val, params) + elif isinstance(val, Dict): + _populate_deep_object_query_params_dict(params_key, val, params) + elif isinstance(val, List): + _populate_deep_object_query_params_list(params_key, val, params) + else: + params[params_key] = [_val_to_string(val)] + + +def _populate_deep_object_query_params_list( + params_key: str, + value: List, + params: Dict[str, List[str]], +): + if not _is_set(value): + return + + for val in value: + if not _is_set(val): + continue + + if params.get(params_key) is None: + params[params_key] = [] + + params[params_key].append(_val_to_string(val)) + + +def _populate_delimited_query_params( + metadata: QueryParamMetadata, + field_name: str, + obj: Any, + delimiter: str, + query_param_values: Dict[str, List[str]], +): + _populate_form( + field_name, + metadata.explode, + obj, + delimiter, + query_param_values, + ) diff --git a/src/moderation_api/utils/requestbodies.py b/src/moderation_api/utils/requestbodies.py new file mode 100644 index 0000000..591415a --- /dev/null +++ b/src/moderation_api/utils/requestbodies.py @@ -0,0 +1,67 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +import io +from dataclasses import dataclass +import re +from typing import ( + Any, + Optional, +) + +from .forms import serialize_form_data, serialize_multipart_form + +from .serializers import marshal_json + +SERIALIZATION_METHOD_TO_CONTENT_TYPE = { + "json": "application/json", + "form": "application/x-www-form-urlencoded", + "multipart": "multipart/form-data", + "raw": "application/octet-stream", + "string": "text/plain", +} + + +@dataclass +class SerializedRequestBody: + media_type: Optional[str] = None + content: Optional[Any] = None + data: Optional[Any] = None + files: Optional[Any] = None + + +def serialize_request_body( + request_body: Any, + nullable: bool, + optional: bool, + serialization_method: str, + request_body_type, +) -> Optional[SerializedRequestBody]: + if request_body is None: + if not nullable and optional: + return None + + media_type = SERIALIZATION_METHOD_TO_CONTENT_TYPE[serialization_method] + + serialized_request_body = SerializedRequestBody(media_type) + + if re.match(r"^(application|text)\/([^+]+\+)*json.*", media_type) is not None: + serialized_request_body.content = marshal_json(request_body, request_body_type) + + elif re.match(r"^multipart\/.*", media_type) is not None: + ( + serialized_request_body.media_type, + serialized_request_body.data, + serialized_request_body.files, + ) = serialize_multipart_form(media_type, request_body) + elif re.match(r"^application\/x-www-form-urlencoded.*", media_type) is not None: + serialized_request_body.data = serialize_form_data(request_body) + elif isinstance(request_body, (bytes, bytearray, io.BytesIO, io.BufferedReader)): + serialized_request_body.content = request_body + elif isinstance(request_body, str): + serialized_request_body.content = request_body + else: + raise TypeError( + f"invalid request body type {type(request_body)} for mediaType {media_type}" + ) + + return serialized_request_body diff --git a/src/moderation_api/utils/retries.py b/src/moderation_api/utils/retries.py new file mode 100644 index 0000000..406c1e7 --- /dev/null +++ b/src/moderation_api/utils/retries.py @@ -0,0 +1,372 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +import asyncio +import random +import time +from datetime import datetime +from email.utils import parsedate_to_datetime +from typing import List, Optional + +import httpx + + +class BackoffStrategy: + """Exponential backoff strategy configuration.""" + + initial_interval: int + max_interval: int + exponent: float + max_elapsed_time: int + jitter_ms: Optional[int] + + def __init__( + self, + initial_interval: int, + max_interval: int, + exponent: float, + max_elapsed_time: int, + jitter_ms: Optional[int] = None, + ): + """Initialize a backoff strategy. + + Args: + initial_interval: Initial retry interval in milliseconds. + max_interval: Maximum retry interval in milliseconds. + exponent: Base of the exponential backoff; the interval grows as + ``initial_interval * exponent ** retries``. + max_elapsed_time: Maximum total elapsed time in milliseconds. + jitter_ms: Additive jitter bound in milliseconds. When set, adds a random + value in ``[0, jitter_ms]`` to each computed backoff interval (default + ``+[0, 1s]``). + + Note: + When a response carries a ``Retry-After`` or ``retry-after-ms`` header, + that delay is used as-is and the sleep-shaping parameters + (``initial_interval``, ``max_interval``, ``exponent``, ``jitter_ms``) are + ignored for that attempt. + """ + if jitter_ms is not None and jitter_ms < 0: + raise ValueError("jitter_ms must be >= 0") + self.initial_interval = initial_interval + self.max_interval = max_interval + self.exponent = exponent + self.max_elapsed_time = max_elapsed_time + self.jitter_ms = jitter_ms + + +class RetryConfig: + """Runtime retry configuration.""" + + strategy: str + backoff: BackoffStrategy + retry_connection_errors: bool + status_codes_override: Optional[List[str]] + + def __init__( + self, + strategy: str, + backoff: BackoffStrategy, + retry_connection_errors: bool, + status_codes_override: Optional[List[str]] = None, + ): + """Initialize a retry configuration. + + Args: + strategy: Retry strategy: ``"none"`` or ``"backoff"``. + backoff: Backoff parameters. + retry_connection_errors: Whether to also retry transport-level connection errors. + status_codes_override: Retryable HTTP status codes that take precedence over the + per-operation defaults when non-empty. + """ + self.strategy = strategy + self.backoff = backoff + self.retry_connection_errors = retry_connection_errors + self.status_codes_override = status_codes_override + + +class Retries: + config: RetryConfig + status_codes: List[str] + + def __init__(self, config: RetryConfig, status_codes: List[str]): + self.config = config + self.status_codes = config.status_codes_override or status_codes + + +class TemporaryError(Exception): + response: httpx.Response + retry_after: Optional[int] + + def __init__(self, response: httpx.Response): + self.response = response + self.retry_after = _parse_retry_after_header(response) + + +class PermanentError(Exception): + inner: Exception + + def __init__(self, inner: Exception): + self.inner = inner + + +_TRANSPORT_ERROR_NAMES = frozenset({"NetworkError", "TimeoutException"}) +_TRANSPORT_ERROR_BASES = frozenset({"TransportError", "RequestError", "HTTPError"}) + + +def _is_transport_error(exception: BaseException) -> bool: + """Report whether an exception is a connection or timeout failure.""" + if isinstance(exception, (httpx.NetworkError, httpx.TimeoutException)): + return True + names = {base.__name__ for base in type(exception).__mro__} + return bool(names & _TRANSPORT_ERROR_NAMES) and _TRANSPORT_ERROR_BASES <= names + + +def _parse_retry_after_header(response: httpx.Response) -> Optional[int]: + """Parse Retry-After header from response. + + Returns: + Retry interval in milliseconds, or None if header is missing or invalid. + """ + retry_after_header = response.headers.get("retry-after") + if not retry_after_header: + return None + + try: + seconds = float(retry_after_header) + return round(seconds * 1000) + except ValueError: + pass + + try: + retry_date = parsedate_to_datetime(retry_after_header) + delta = (retry_date - datetime.now(retry_date.tzinfo)).total_seconds() + return round(max(0, delta) * 1000) + except (ValueError, TypeError): + pass + + return None + + +def _parse_retry_after_ms_header(response: httpx.Response) -> Optional[int]: + retry_after_ms_header = response.headers.get("retry-after-ms") + if not retry_after_ms_header: + return None + + try: + milliseconds = float(retry_after_ms_header) + if milliseconds >= 0: + return round(milliseconds) + except (OverflowError, ValueError): + pass + + return None + + +def _get_sleep_interval( + exception: Exception, + initial_interval: int, + max_interval: int, + exponent: float, + retries: int, + jitter_ms: Optional[int] = None, +) -> float: + """Get sleep interval for retry with exponential backoff. + + Args: + exception: The exception that triggered the retry. + initial_interval: Initial retry interval in milliseconds. + max_interval: Maximum retry interval in milliseconds. + exponent: Base for exponential backoff calculation. + retries: Current retry attempt count. + jitter_ms: Additive jitter bound in ms; see ``BackoffStrategy.jitter_ms``. + + Returns: + Sleep interval in seconds. + """ + if ( + isinstance(exception, TemporaryError) + and exception.retry_after is not None + and exception.retry_after > 0 + ): + return exception.retry_after / 1000 + + sleep = (initial_interval / 1000) * exponent**retries + if jitter_ms is not None: + sleep += random.uniform(0, jitter_ms / 1000) + else: + sleep += random.uniform(0, 1) + return min(sleep, max_interval / 1000) + + +def retry(func, retries: Retries): + if retries.config.strategy == "backoff": + + def do_request() -> httpx.Response: + res: httpx.Response + try: + res = func() + + for code in retries.status_codes: + if "X" in code.upper(): + code_range = int(code[0]) + + status_major = res.status_code / 100 + + if code_range <= status_major < code_range + 1: + raise TemporaryError(res) + else: + parsed_code = int(code) + + if res.status_code == parsed_code: + raise TemporaryError(res) + except TemporaryError: + raise + except Exception as exception: + if ( + _is_transport_error(exception) + and retries.config.retry_connection_errors + ): + raise + + raise PermanentError(exception) from exception + + return res + + return retry_with_backoff( + do_request, + retries.config.backoff.initial_interval, + retries.config.backoff.max_interval, + retries.config.backoff.exponent, + retries.config.backoff.max_elapsed_time, + retries.config.backoff.jitter_ms, + ) + + return func() + + +async def retry_async(func, retries: Retries): + if retries.config.strategy == "backoff": + + async def do_request() -> httpx.Response: + res: httpx.Response + try: + res = await func() + + for code in retries.status_codes: + if "X" in code.upper(): + code_range = int(code[0]) + + status_major = res.status_code / 100 + + if code_range <= status_major < code_range + 1: + raise TemporaryError(res) + else: + parsed_code = int(code) + + if res.status_code == parsed_code: + raise TemporaryError(res) + except TemporaryError: + raise + except Exception as exception: + if ( + _is_transport_error(exception) + and retries.config.retry_connection_errors + ): + raise + + raise PermanentError(exception) from exception + + return res + + return await retry_with_backoff_async( + do_request, + retries.config.backoff.initial_interval, + retries.config.backoff.max_interval, + retries.config.backoff.exponent, + retries.config.backoff.max_elapsed_time, + retries.config.backoff.jitter_ms, + ) + + return await func() + + +def retry_with_backoff( + func, + initial_interval=500, + max_interval=60000, + exponent=1.5, + max_elapsed_time=3600000, + jitter_ms=None, +): + start = round(time.time() * 1000) + retries = 0 + + while True: + try: + return func() + except PermanentError as exception: + raise exception.inner + except Exception as exception: # pylint: disable=broad-exception-caught + now = round(time.time() * 1000) + if now - start > max_elapsed_time: + if isinstance(exception, TemporaryError): + return exception.response + + raise + + if isinstance(exception, TemporaryError): + retry_after_ms = _parse_retry_after_ms_header(exception.response) + if retry_after_ms is not None: + exception.retry_after = retry_after_ms + exception.response.close() + sleep = _get_sleep_interval( + exception, + initial_interval, + max_interval, + exponent, + retries, + jitter_ms=jitter_ms, + ) + time.sleep(sleep) + retries += 1 + + +async def retry_with_backoff_async( + func, + initial_interval=500, + max_interval=60000, + exponent=1.5, + max_elapsed_time=3600000, + jitter_ms=None, +): + start = round(time.time() * 1000) + retries = 0 + + while True: + try: + return await func() + except PermanentError as exception: + raise exception.inner + except Exception as exception: # pylint: disable=broad-exception-caught + now = round(time.time() * 1000) + if now - start > max_elapsed_time: + if isinstance(exception, TemporaryError): + return exception.response + + raise + + if isinstance(exception, TemporaryError): + retry_after_ms = _parse_retry_after_ms_header(exception.response) + if retry_after_ms is not None: + exception.retry_after = retry_after_ms + await exception.response.aclose() + sleep = _get_sleep_interval( + exception, + initial_interval, + max_interval, + exponent, + retries, + jitter_ms=jitter_ms, + ) + await asyncio.sleep(sleep) + retries += 1 diff --git a/src/moderation_api/utils/security.py b/src/moderation_api/utils/security.py new file mode 100644 index 0000000..d203115 --- /dev/null +++ b/src/moderation_api/utils/security.py @@ -0,0 +1,215 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +import base64 + +from typing import ( + Any, + Dict, + List, + Optional, + Tuple, +) +from pydantic import BaseModel +from pydantic.fields import FieldInfo + +from .metadata import ( + SecurityMetadata, + find_field_metadata, +) +import os + + +def get_security( + security: Any, allowed_fields: Optional[List[str]] = None +) -> Tuple[Dict[str, str], Dict[str, List[str]]]: + headers: Dict[str, str] = {} + query_params: Dict[str, List[str]] = {} + + if security is None: + return headers, query_params + + if not isinstance(security, BaseModel): + raise TypeError("security must be a pydantic model") + + sec_fields: Dict[str, FieldInfo] = security.__class__.model_fields + sec_field_names = ( + list(sec_fields.keys()) if allowed_fields is None else allowed_fields + ) + + for name in sec_field_names: + if name not in sec_fields: + continue + + sec_field = sec_fields[name] + + value = getattr(security, name) + if value is None: + continue + + metadata = find_field_metadata(sec_field, SecurityMetadata) + if metadata is None: + continue + if metadata.option: + _parse_security_option(headers, query_params, value) + return headers, query_params + if metadata.scheme: + # Special case for basic auth or custom auth which could be a flattened model + if metadata.sub_type in ["basic", "custom"] and not isinstance( + value, BaseModel + ): + _parse_security_scheme(headers, query_params, metadata, name, security) + else: + _parse_security_scheme(headers, query_params, metadata, name, value) + + if not metadata.composite: + return headers, query_params + + return headers, query_params + + +def get_security_from_env(security: Any, security_class: Any) -> Optional[BaseModel]: + if security is not None: + return security + + if not issubclass(security_class, BaseModel): + raise TypeError("security_class must be a pydantic model class") + + security_dict: Any = {} + + if os.getenv("MODAPI_SECRET_KEY"): + security_dict["secret_key"] = os.getenv("MODAPI_SECRET_KEY") + + return security_class(**security_dict) if security_dict else None + + +def _parse_security_option( + headers: Dict[str, str], query_params: Dict[str, List[str]], option: Any +): + if not isinstance(option, BaseModel): + raise TypeError("security option must be a pydantic model") + + opt_fields: Dict[str, FieldInfo] = option.__class__.model_fields + + for name in opt_fields: + opt_field = opt_fields[name] + + metadata = find_field_metadata(opt_field, SecurityMetadata) + if metadata is None or not metadata.scheme: + continue + + value = getattr(option, name) + if ( + metadata.scheme_type == "http" + and metadata.sub_type == "basic" + and not isinstance(value, BaseModel) + ): + _parse_basic_auth_scheme(headers, option) + return + + _parse_security_scheme(headers, query_params, metadata, name, value) + + +def _parse_security_scheme( + headers: Dict[str, str], + query_params: Dict[str, List[str]], + scheme_metadata: SecurityMetadata, + field_name: str, + scheme: Any, +): + scheme_type = scheme_metadata.scheme_type + sub_type = scheme_metadata.sub_type + + if isinstance(scheme, BaseModel): + if scheme_type == "http": + if sub_type == "basic": + _parse_basic_auth_scheme(headers, scheme) + return + if sub_type == "custom": + return + + scheme_fields: Dict[str, FieldInfo] = scheme.__class__.model_fields + for name in scheme_fields: + scheme_field = scheme_fields[name] + + metadata = find_field_metadata(scheme_field, SecurityMetadata) + if metadata is None or metadata.field_name is None: + continue + + value = getattr(scheme, name) + + _parse_security_scheme_value( + headers, query_params, scheme_metadata, metadata, name, value + ) + else: + _parse_security_scheme_value( + headers, query_params, scheme_metadata, scheme_metadata, field_name, scheme + ) + + +def _parse_security_scheme_value( + headers: Dict[str, str], + query_params: Dict[str, List[str]], + scheme_metadata: SecurityMetadata, + security_metadata: SecurityMetadata, + field_name: str, + value: Any, +): + scheme_type = scheme_metadata.scheme_type + sub_type = scheme_metadata.sub_type + + header_name = security_metadata.get_field_name(field_name) + + if scheme_type == "apiKey": + if sub_type == "header": + headers[header_name] = value + elif sub_type == "query": + query_params[header_name] = [value] + else: + raise ValueError("sub type {sub_type} not supported") + elif scheme_type == "openIdConnect": + headers[header_name] = _apply_bearer(value) + elif scheme_type == "oauth2": + if sub_type != "client_credentials": + headers[header_name] = _apply_bearer(value) + elif scheme_type == "http": + if sub_type == "bearer": + headers[header_name] = _apply_bearer(value) + elif sub_type == "basic": + headers[header_name] = value + elif sub_type == "custom": + return + else: + raise ValueError("sub type {sub_type} not supported") + else: + raise ValueError("scheme type {scheme_type} not supported") + + +def _apply_bearer(token: str) -> str: + return token.lower().startswith("bearer ") and token or f"Bearer {token}" + + +def _parse_basic_auth_scheme(headers: Dict[str, str], scheme: Any): + username = "" + password = "" + + if not isinstance(scheme, BaseModel): + raise TypeError("basic auth scheme must be a pydantic model") + + scheme_fields: Dict[str, FieldInfo] = scheme.__class__.model_fields + for name in scheme_fields: + scheme_field = scheme_fields[name] + + metadata = find_field_metadata(scheme_field, SecurityMetadata) + if metadata is None or metadata.field_name is None: + continue + + field_name = metadata.field_name + value = getattr(scheme, name) + + if field_name == "username": + username = value + if field_name == "password": + password = value + + data = f"{username}:{password}".encode() + headers["Authorization"] = f"Basic {base64.b64encode(data).decode()}" diff --git a/src/moderation_api/utils/serializers.py b/src/moderation_api/utils/serializers.py new file mode 100644 index 0000000..5e57d36 --- /dev/null +++ b/src/moderation_api/utils/serializers.py @@ -0,0 +1,326 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from decimal import Decimal +import functools +import json +import typing +from typing import Any, Dict, Iterable, List, Mapping, Tuple, Union, get_args +import typing_extensions +from typing_extensions import get_origin + +import httpx +from pydantic import ConfigDict, create_model +from pydantic_core import from_json + +from ..types.basemodel import BaseModel, Nullable, OptionalNullable, Unset + + +def serialize_decimal(as_str: bool): + def serialize(d): + if d is None: + return None + if isinstance(d, Unset): + return d + + if not isinstance(d, Decimal): + raise ValueError("Expected Decimal object") + + return str(d) if as_str else float(d) + + return serialize + + +def validate_decimal(d): + if d is None: + return None + + if isinstance(d, (Decimal, Unset)): + return d + + if not isinstance(d, (str, int, float)): + raise ValueError("Expected string, int or float") + + return Decimal(str(d)) + + +def serialize_float(as_str: bool): + def serialize(f): + if f is None: + return None + if isinstance(f, Unset): + return f + + if not isinstance(f, float): + raise ValueError("Expected float") + + return str(f) if as_str else f + + return serialize + + +def validate_float(f): + if f is None: + return None + + if isinstance(f, (float, Unset)): + return f + + if not isinstance(f, str): + raise ValueError("Expected string") + + return float(f) + + +def serialize_int(as_str: bool): + def serialize(i): + if i is None: + return None + if isinstance(i, Unset): + return i + + if not isinstance(i, int): + raise ValueError("Expected int") + + return str(i) if as_str else i + + return serialize + + +def validate_int(b): + if b is None: + return None + + if isinstance(b, (int, Unset)): + return b + + if not isinstance(b, str): + raise ValueError("Expected string") + + return int(b) + + +def validate_const(v): + def validate(c): + if c is None: + return None + + if v != c: + raise ValueError(f"Expected {v}") + + return c + + return validate + + +ALLOW_UNKNOWN_UNION_VARIANTS = "speakeasy_allow_unknown_union_variants" +"""Validation-context key enabling the Unknown fallback on open discriminated +unions. The SDK sets it when deserializing server responses; validation +without it (e.g. of user-constructed request payloads) stays strict. Pass +``context={ALLOW_UNKNOWN_UNION_VARIANTS: True}`` to ``model_validate`` to +opt in when parsing response payloads manually.""" + + +def unmarshal_json(raw, typ: Any) -> Any: + return unmarshal( + from_json(raw), typ, coerce_iterables=False, allow_unknown_union_variants=True + ) + + +def unmarshal( + val, + typ: Any, + coerce_iterables: bool = True, + allow_unknown_union_variants: bool = False, +) -> Any: + if coerce_iterables: + val = _coerce_iterables_for_type(val, typ) + unmarshaller = create_model( + "Unmarshaller", + body=(typ, ...), + __config__=ConfigDict(populate_by_name=True, arbitrary_types_allowed=True), + ) + + if allow_unknown_union_variants: + m = unmarshaller.model_validate( + {"body": val}, context={ALLOW_UNKNOWN_UNION_VARIANTS: True} + ) + else: + m = unmarshaller(body=val) + + # pyright: ignore[reportAttributeAccessIssue] + return m.body # type: ignore + + +def marshal_json(val, typ): + if is_nullable(typ) and val is None: + return "null" + + marshaller = create_model( + "Marshaller", + body=(typ, ...), + __config__=ConfigDict(populate_by_name=True, arbitrary_types_allowed=True), + ) + + m = marshaller(body=val) + + d = m.model_dump(by_alias=True, mode="json", exclude_none=True) + + if len(d) == 0: + return "" + + return json.dumps(d[next(iter(d))], separators=(",", ":")) + + +def is_nullable(field): + origin = get_origin(field) + if origin is Nullable or origin is OptionalNullable: + return True + + if not origin is Union or type(None) not in get_args(field): + return False + + for arg in get_args(field): + if get_origin(arg) is Nullable or get_origin(arg) is OptionalNullable: + return True + + return False + + +def is_union(obj: object) -> bool: + """ + Returns True if the given object is a typing.Union or typing_extensions.Union. + """ + return any( + obj is typing_obj for typing_obj in _get_typing_objects_by_name_of("Union") + ) + + +def stream_to_text(stream: httpx.Response) -> str: + return "".join(stream.iter_text()) + + +async def stream_to_text_async(stream: httpx.Response) -> str: + return "".join([chunk async for chunk in stream.aiter_text()]) + + +def stream_to_bytes(stream: httpx.Response) -> bytes: + return stream.content + + +async def stream_to_bytes_async(stream: httpx.Response) -> bytes: + return await stream.aread() + + +def get_pydantic_model(data: Any, typ: Any) -> Any: + if not _contains_pydantic_model(data): + return unmarshal(data, typ) + + return _coerce_iterables_for_type(data, typ) + + +def _coerce_iterables_for_type(data: Any, typ: Any) -> Any: + if data is None or isinstance(data, (BaseModel, Unset)): + return data + + typ = _resolve_type_alias(typ) + origin = get_origin(typ) + + if _is_annotated_type(origin): + args = get_args(typ) + return _coerce_iterables_for_type(data, args[0]) if args else data + + if is_union(origin): + for arg in (arg for arg in get_args(typ) if arg is not type(None)): + coerced = _coerce_iterables_for_type(data, arg) + if coerced is not data: + return coerced + return data + + if _is_list_type(typ): + item_type = get_args(typ)[0] if get_args(typ) else Any + if isinstance(data, (str, bytes, bytearray, Mapping)): + return data + if isinstance(data, Iterable): + return [_coerce_iterables_for_type(item, item_type) for item in data] + return data + + if _is_mapping_type(typ): + value_type = get_args(typ)[1] if len(get_args(typ)) > 1 else Any + if isinstance(data, Mapping): + return { + key: _coerce_iterables_for_type(value, value_type) + for key, value in data.items() + } + return data + + if _is_pydantic_model_type(typ) and isinstance(data, Mapping): + coerced = None + for field_name, field in typ.model_fields.items(): + field_type = field.annotation + for key in (field_name, field.alias): + if key is not None and key in data: + value = data[key] if coerced is None else coerced[key] + coerced_value = _coerce_iterables_for_type(value, field_type) + if coerced_value is not value: + if coerced is None: + coerced = dict(data) + coerced[key] = coerced_value + return coerced if coerced is not None else data + + return data + + +def _resolve_type_alias(typ: Any) -> Any: + return getattr(typ, "__value__", typ) + + +def _is_annotated_type(origin: Any) -> bool: + return any( + origin is typing_obj + for typing_obj in _get_typing_objects_by_name_of("Annotated") + ) + + +def _is_list_type(typ: Any) -> bool: + typ = _resolve_type_alias(typ) + return typ is list or get_origin(typ) is list + + +def _is_mapping_type(typ: Any) -> bool: + typ = _resolve_type_alias(typ) + origin = get_origin(typ) + mapping_origin = get_origin(Mapping[Any, Any]) + return typ in (dict, Dict, Mapping) or origin in (dict, Mapping, mapping_origin) + + +def _is_pydantic_model_type(typ: Any) -> bool: + return isinstance(typ, type) and issubclass(typ, BaseModel) + + +def _contains_pydantic_model(data: Any) -> bool: + if isinstance(data, BaseModel): + return True + if isinstance(data, List): + return any(_contains_pydantic_model(item) for item in data) + if isinstance(data, Dict): + return any(_contains_pydantic_model(value) for value in data.values()) + + return False + + +@functools.cache +def _get_typing_objects_by_name_of(name: str) -> Tuple[Any, ...]: + """ + Get typing objects by name from typing and typing_extensions. + Reference: https://typing-extensions.readthedocs.io/en/latest/#runtime-use-of-types + """ + result = tuple( + getattr(module, name) + for module in (typing, typing_extensions) + if hasattr(module, name) + ) + if not result: + raise ValueError( + f"Neither typing nor typing_extensions has an object called {name!r}" + ) + return result diff --git a/src/moderation_api/utils/unions.py b/src/moderation_api/utils/unions.py new file mode 100644 index 0000000..45248ff --- /dev/null +++ b/src/moderation_api/utils/unions.py @@ -0,0 +1,58 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from typing import Any, Mapping + +from pydantic import BaseModel, TypeAdapter, ValidationError, ValidationInfo + + +def parse_open_union( + v: Any, + info: ValidationInfo, + *, + disc_key: str, + variants: dict[str, Any], + unknown_cls: type, + union_name: str, +) -> Any: + """Parse an open discriminated union value with forward-compatibility. + + Known discriminator values are dispatched to their variant types. + + The Unknown fallback only applies when the validation context carries + ALLOW_UNKNOWN_UNION_VARIANTS, which the SDK sets when deserializing + server responses. There, unknown discriminator values — or known + discriminator values whose payload fails variant validation (e.g. a + partial variant emitted by a newer server) — produce an instance of the + fallback class, preserving the raw payload for inspection. Without the + flag (e.g. user-constructed request payloads), invalid values raise so + mistakes surface locally instead of being sent to the server. + + Non-dict values and dicts missing the discriminator deliberately raise + instead of falling back, so pydantic can try sibling branches of an + enclosing union (e.g. None in Optional[...]). + """ + # pylint: disable=import-outside-toplevel + from .serializers import ALLOW_UNKNOWN_UNION_VARIANTS + + if isinstance(v, BaseModel): + return v + if not isinstance(v, dict) or disc_key not in v: + raise ValueError(f"{union_name}: expected object with '{disc_key}' field") + context = info.context + fallback_allowed = isinstance(context, Mapping) and bool( + context.get(ALLOW_UNKNOWN_UNION_VARIANTS) + ) + disc = v[disc_key] + variant_cls = variants.get(disc) + if variant_cls is None: + if fallback_allowed: + return unknown_cls(raw=v) + raise ValueError(f"{union_name}: unrecognized {disc_key} value {disc!r}") + try: + if isinstance(variant_cls, type) and issubclass(variant_cls, BaseModel): + return variant_cls.model_validate(v, context=info.context) + return TypeAdapter(variant_cls).validate_python(v, context=info.context) + except ValidationError: + if not fallback_allowed: + raise + return unknown_cls(raw=v) diff --git a/src/moderation_api/utils/unmarshal_json_response.py b/src/moderation_api/utils/unmarshal_json_response.py new file mode 100644 index 0000000..e7cf06a --- /dev/null +++ b/src/moderation_api/utils/unmarshal_json_response.py @@ -0,0 +1,38 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from typing import Any, Optional, Type, TypeVar, overload + +import httpx + +from .serializers import unmarshal_json +from moderation_api import errors + +T = TypeVar("T") + + +@overload +def unmarshal_json_response( + typ: Type[T], http_res: httpx.Response, body: Optional[str] = None +) -> T: ... + + +@overload +def unmarshal_json_response( + typ: Any, http_res: httpx.Response, body: Optional[str] = None +) -> Any: ... + + +def unmarshal_json_response( + typ: Any, http_res: httpx.Response, body: Optional[str] = None +) -> Any: + if body is None: + body = http_res.text + try: + return unmarshal_json(body, typ) + except Exception as e: + raise errors.ResponseValidationError( + "Response validation failed", + http_res, + e, + body, + ) from e diff --git a/src/moderation_api/utils/url.py b/src/moderation_api/utils/url.py new file mode 100644 index 0000000..8eeaa7b --- /dev/null +++ b/src/moderation_api/utils/url.py @@ -0,0 +1,154 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from typing import ( + Any, + Dict, + get_type_hints, + List, + Optional, + Union, + get_args, + get_origin, +) +from pydantic import BaseModel +from pydantic.fields import FieldInfo + +from .metadata import ( + PathParamMetadata, + find_field_metadata, +) +from .values import ( + _get_serialized_params, + _is_set, + _populate_from_globals, + _val_to_string, +) + + +def generate_url( + server_url: str, + path: str, + path_params: Any, + gbls: Optional[Any] = None, +) -> str: + path_param_values: Dict[str, str] = {} + + globals_already_populated = _populate_path_params( + path_params, gbls, path_param_values, [] + ) + if _is_set(gbls): + _populate_path_params(gbls, None, path_param_values, globals_already_populated) + + for key, value in path_param_values.items(): + path = path.replace("{" + key + "}", value, 1) + + return remove_suffix(server_url, "/") + path + + +def _populate_path_params( + path_params: Any, + gbls: Any, + path_param_values: Dict[str, str], + skip_fields: List[str], +) -> List[str]: + globals_already_populated: List[str] = [] + + if not isinstance(path_params, BaseModel): + return globals_already_populated + + path_param_fields: Dict[str, FieldInfo] = path_params.__class__.model_fields + path_param_field_types = get_type_hints(path_params.__class__) + for name in path_param_fields: + if name in skip_fields: + continue + + field = path_param_fields[name] + + param_metadata = find_field_metadata(field, PathParamMetadata) + if param_metadata is None: + continue + + param = getattr(path_params, name) if _is_set(path_params) else None + param, global_found = _populate_from_globals( + name, param, PathParamMetadata, gbls + ) + if global_found: + globals_already_populated.append(name) + + if not _is_set(param): + continue + + f_name = field.alias if field.alias is not None else name + serialization = param_metadata.serialization + if serialization is not None: + serialized_params = _get_serialized_params( + param_metadata, f_name, param, path_param_field_types[name] + ) + for key, value in serialized_params.items(): + path_param_values[key] = value + else: + pp_vals: List[str] = [] + if param_metadata.style == "simple": + if isinstance(param, List): + for pp_val in param: + if not _is_set(pp_val): + continue + pp_vals.append(_val_to_string(pp_val)) + path_param_values[f_name] = ",".join(pp_vals) + elif isinstance(param, Dict): + for pp_key in param: + if not _is_set(param[pp_key]): + continue + if param_metadata.explode: + pp_vals.append(f"{pp_key}={_val_to_string(param[pp_key])}") + else: + pp_vals.append(f"{pp_key},{_val_to_string(param[pp_key])}") + path_param_values[f_name] = ",".join(pp_vals) + elif isinstance(param, BaseModel): + param_fields: Dict[str, FieldInfo] = param.__class__.model_fields + for name in param_fields: + param_field = param_fields[name] + + param_value_metadata = find_field_metadata( + param_field, PathParamMetadata + ) + if param_value_metadata is None: + continue + + param_name = ( + param_field.alias if param_field.alias is not None else name + ) + + param_field_val = getattr(param, name) + if not _is_set(param_field_val): + continue + if param_metadata.explode: + pp_vals.append( + f"{param_name}={_val_to_string(param_field_val)}" + ) + else: + pp_vals.append( + f"{param_name},{_val_to_string(param_field_val)}" + ) + path_param_values[f_name] = ",".join(pp_vals) + else: + path_param_values[f_name] = _val_to_string(param) + + return globals_already_populated + + +def is_optional(field): + return get_origin(field) is Union and type(None) in get_args(field) + + +def template_url(url_with_params: str, params: Dict[str, str]) -> str: + for key, value in params.items(): + url_with_params = url_with_params.replace("{" + key + "}", value) + + return url_with_params + + +def remove_suffix(input_string, suffix): + if suffix and input_string.endswith(suffix): + return input_string[: -len(suffix)] + return input_string diff --git a/src/moderation_api/utils/values.py b/src/moderation_api/utils/values.py new file mode 100644 index 0000000..094755b --- /dev/null +++ b/src/moderation_api/utils/values.py @@ -0,0 +1,142 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from datetime import datetime, timedelta +from enum import Enum +from email.message import Message +from functools import partial +import os +from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar, Union, cast + +from httpx import Response +from pydantic import BaseModel +from pydantic.fields import FieldInfo + +from ..types.basemodel import Unset + +from .datetimes import format_duration +from .serializers import marshal_json + +from .metadata import ParamMetadata, find_field_metadata + + +def match_content_type(content_type: str, pattern: str) -> bool: + if pattern in (content_type, "*", "*/*"): + return True + + msg = Message() + msg["content-type"] = content_type + media_type = msg.get_content_type() + + if media_type == pattern: + return True + + parts = media_type.split("/") + if len(parts) == 2: + if pattern in (f"{parts[0]}/*", f"*/{parts[1]}"): + return True + + return False + + +def match_status_codes(status_codes: List[str], status_code: int) -> bool: + if "default" in status_codes: + return True + + for code in status_codes: + if code == str(status_code): + return True + + if code.endswith("XX") and code.startswith(str(status_code)[:1]): + return True + return False + + +T = TypeVar("T") + + +def cast_partial(typ): + return partial(cast, typ) + + +def get_global_from_env( + value: Optional[T], env_key: str, type_cast: Callable[[str], T] +) -> Optional[T]: + if value is not None: + return value + env_value = os.getenv(env_key) + if env_value is not None: + try: + return type_cast(env_value) + except ValueError: + pass + return None + + +def match_response( + response: Response, code: Union[str, List[str]], content_type: str +) -> bool: + codes = code if isinstance(code, list) else [code] + return match_status_codes(codes, response.status_code) and match_content_type( + response.headers.get("content-type", "application/octet-stream"), content_type + ) + + +def _populate_from_globals( + param_name: str, value: Any, param_metadata_type: type, gbls: Any +) -> Tuple[Any, bool]: + if gbls is None: + return value, False + + if not isinstance(gbls, BaseModel): + raise TypeError("globals must be a pydantic model") + + global_fields: Dict[str, FieldInfo] = gbls.__class__.model_fields + found = False + for name in global_fields: + field = global_fields[name] + if name is not param_name: + continue + + found = True + + if value is not None: + return value, True + + global_value = getattr(gbls, name) + + param_metadata = find_field_metadata(field, param_metadata_type) + if param_metadata is None: + return value, True + + return global_value, True + + return value, found + + +def _val_to_string(val) -> str: + if isinstance(val, bool): + return str(val).lower() + if isinstance(val, datetime): + return str(val.isoformat().replace("+00:00", "Z")) + if isinstance(val, timedelta): + return format_duration(val) + if isinstance(val, Enum): + return str(val.value) + + return str(val) + + +def _get_serialized_params( + metadata: ParamMetadata, field_name: str, obj: Any, typ: type +) -> Dict[str, str]: + params: Dict[str, str] = {} + + serialization = metadata.serialization + if serialization == "json": + params[field_name] = marshal_json(obj, typ) + + return params + + +def _is_set(value: Any) -> bool: + return value is not None and not isinstance(value, Unset) diff --git a/src/moderation_api/webhook_signature.py b/src/moderation_api/webhook_signature.py new file mode 100644 index 0000000..800b5d1 --- /dev/null +++ b/src/moderation_api/webhook_signature.py @@ -0,0 +1,67 @@ +"""Verify webhook deliveries from Moderation API. + +Every delivery is signed with HMAC-SHA256 over the raw request body, using +your project's webhook signing secret. The hex-encoded signature is in the +`modapi-signature` header. + +Maintained by hand in moderation-api/sdk-generator (custom/python), not +generated. +""" + +import hashlib +import hmac +import json +import os +from typing import Any, Optional, Union + +SIGNATURE_HEADER = "modapi-signature" +SECRET_ENV_VAR = "MODAPI_WEBHOOK_SECRET" + + +class WebhookSignatureError(Exception): + """The webhook signature is missing or does not match the payload.""" + + +def _to_bytes(value: Union[str, bytes]) -> bytes: + return value.encode("utf-8") if isinstance(value, str) else value + + +def compute_signature(payload: Union[str, bytes], secret: str) -> str: + """Return the hex HMAC-SHA256 signature for a raw payload.""" + return hmac.new(_to_bytes(secret), _to_bytes(payload), hashlib.sha256).hexdigest() + + +def _secret(secret: Optional[str]) -> str: + if secret: + return secret + return os.environ.get(SECRET_ENV_VAR) or "" + + +def verify_signature( + payload: Union[str, bytes], signature: str, secret: Optional[str] = None +) -> bool: + """Return True if `signature` matches the raw `payload`. + + `secret` defaults to the MODAPI_WEBHOOK_SECRET environment variable. + """ + secret = _secret(secret) + if not signature or not secret: + return False + expected = compute_signature(payload, secret) + return hmac.compare_digest(expected, signature.strip().lower()) + + +def construct_event( + payload: Union[str, bytes], signature: str, secret: Optional[str] = None +) -> Any: + """Verify a delivery and return the parsed event. + + Pass the raw request body exactly as received and the value of the + `modapi-signature` header. `secret` defaults to the MODAPI_WEBHOOK_SECRET + environment variable. + + Raises WebhookSignatureError when the signature does not match. + """ + if not verify_signature(payload, signature, secret): + raise WebhookSignatureError("Webhook signature does not match the payload") + return json.loads(payload) diff --git a/src/moderation_api/webhooks.py b/src/moderation_api/webhooks.py new file mode 100644 index 0000000..3169fb6 --- /dev/null +++ b/src/moderation_api/webhooks.py @@ -0,0 +1,1181 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .basesdk import BaseSDK +from jsonpath import JSONPath +from moderation_api import errors, models, utils +from moderation_api._hooks import HookContext +from moderation_api.types import OptionalNullable, UNSET +from moderation_api.utils import get_security_from_env +from moderation_api.utils.unmarshal_json_response import unmarshal_json_response +from typing import Any, Awaitable, Dict, Iterable, List, Mapping, Optional, Union + + +class Webhooks(BaseSDK): + def list( + self, + *, + page: Optional[int] = 1, + page_size: Optional[int] = 20, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> Optional[models.ListWebhooksResponse]: + r"""List webhooks + + List the webhooks of the project. The list is paginated. + + :param page: + :param page_size: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.ListWebhooksRequest( + page=page, + page_size=page_size, + ) + + req = self._build_request( + method="GET", + path="/webhooks", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="listWebhooks", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Webhooks"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + def next_func() -> Optional[models.ListWebhooksResponse]: + body = utils.unmarshal_json(http_res.text, Union[Dict[Any, Any], List[Any]]) + + page = request.page if isinstance(request.page, int) else 1 + next_page = page + 1 + + num_pages = JSONPath("$.pagination.totalPages").parse(body) + if len(num_pages) == 0 or num_pages[0] <= page: + return None + + if not http_res.text: + return None + results = JSONPath("$.data").parse(body) + if len(results) == 0 or len(results[0]) == 0: + return None + limit_ = request.page_size if isinstance(request.page_size, int) else 20 + if len(results[0]) < limit_: + return None + + return self.list( + page=next_page, + page_size=page_size, + retries=retries, + server_url=server_url, + timeout_ms=timeout_ms, + http_headers=http_headers, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return models.ListWebhooksResponse( + result=unmarshal_json_response( + models.ListWebhooksResponseBody, http_res + ), + next=next_func, + ) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def list_async( + self, + *, + page: Optional[int] = 1, + page_size: Optional[int] = 20, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> Optional[models.ListWebhooksResponse]: + r"""List webhooks + + List the webhooks of the project. The list is paginated. + + :param page: + :param page_size: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.ListWebhooksRequest( + page=page, + page_size=page_size, + ) + + req = self._build_request_async( + method="GET", + path="/webhooks", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="listWebhooks", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Webhooks"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + def next_func() -> Awaitable[Optional[models.ListWebhooksResponse]]: + body = utils.unmarshal_json(http_res.text, Union[Dict[Any, Any], List[Any]]) + + async def empty_result(): + return None + + page = request.page if isinstance(request.page, int) else 1 + next_page = page + 1 + + num_pages = JSONPath("$.pagination.totalPages").parse(body) + if len(num_pages) == 0 or num_pages[0] <= page: + return empty_result() + + if not http_res.text: + return empty_result() + results = JSONPath("$.data").parse(body) + if len(results) == 0 or len(results[0]) == 0: + return empty_result() + limit_ = request.page_size if isinstance(request.page_size, int) else 20 + if len(results[0]) < limit_: + return empty_result() + + return self.list_async( + page=next_page, + page_size=page_size, + retries=retries, + server_url=server_url, + timeout_ms=timeout_ms, + http_headers=http_headers, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return models.ListWebhooksResponse( + result=unmarshal_json_response( + models.ListWebhooksResponseBody, http_res + ), + next=next_func, + ) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def create( + self, + *, + name: str, + url: str, + event_types: Iterable[models.CreateWebhookEventTypeRequest], + description: OptionalNullable[str] = UNSET, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.CreateWebhookResponse: + r"""Create a webhook + + Create a webhook subscribed to one or more event types. Deliveries use the v2 envelope and are signed with the project signing secret (see the signing secret endpoint). + + :param name: The webhook's name, used to identify it in the dashboard + :param url: The webhook's URL. We'll call this URL when an event occurs. + :param event_types: Event types this webhook subscribes to. One webhook URL receives all events you list here. + :param description: The webhook's description + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.CreateWebhookRequest( + name=name, + description=description, + url=url, + event_types=utils.unmarshal( + event_types, List[models.CreateWebhookEventTypeRequest] + ), + ) + + req = self._build_request( + method="POST", + path="/webhooks", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request, False, False, "json", models.CreateWebhookRequest + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="createWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Webhooks"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.CreateWebhookResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def create_async( + self, + *, + name: str, + url: str, + event_types: Iterable[models.CreateWebhookEventTypeRequest], + description: OptionalNullable[str] = UNSET, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.CreateWebhookResponse: + r"""Create a webhook + + Create a webhook subscribed to one or more event types. Deliveries use the v2 envelope and are signed with the project signing secret (see the signing secret endpoint). + + :param name: The webhook's name, used to identify it in the dashboard + :param url: The webhook's URL. We'll call this URL when an event occurs. + :param event_types: Event types this webhook subscribes to. One webhook URL receives all events you list here. + :param description: The webhook's description + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.CreateWebhookRequest( + name=name, + description=description, + url=url, + event_types=utils.unmarshal( + event_types, List[models.CreateWebhookEventTypeRequest] + ), + ) + + req = self._build_request_async( + method="POST", + path="/webhooks", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request, False, False, "json", models.CreateWebhookRequest + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="createWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Webhooks"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.CreateWebhookResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def retrieve( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.GetWebhookResponse: + r"""Get a webhook + + Get a webhook by ID. + + :param id: The ID of the webhook to get. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetWebhookRequest( + id=id, + ) + + req = self._build_request( + method="GET", + path="/webhooks/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Webhooks"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.GetWebhookResponse, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def retrieve_async( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.GetWebhookResponse: + r"""Get a webhook + + Get a webhook by ID. + + :param id: The ID of the webhook to get. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetWebhookRequest( + id=id, + ) + + req = self._build_request_async( + method="GET", + path="/webhooks/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Webhooks"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.GetWebhookResponse, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def update( + self, + *, + id: str, + name: Optional[str] = None, + description: OptionalNullable[str] = UNSET, + url: Optional[str] = None, + event_types: Optional[Iterable[models.UpdateWebhookEventTypeRequest]] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.UpdateWebhookResponse: + r"""Update a webhook + + Update a webhook. Legacy v1 webhooks are read-only: delete them and create a new webhook instead. + + :param id: The ID of the webhook to update. + :param name: The webhook's name, used to identify it in the dashboard + :param description: The webhook's description + :param url: The webhook's URL. We'll call this URL when an event occurs. + :param event_types: Event types this webhook subscribes to. One webhook URL receives all events you list here. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.UpdateWebhookRequest( + id=id, + body=models.UpdateWebhookRequestBody( + name=name, + description=description, + url=url, + event_types=utils.unmarshal( + event_types, Optional[List[models.UpdateWebhookEventTypeRequest]] + ), + ), + ) + + req = self._build_request( + method="PUT", + path="/webhooks/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.UpdateWebhookRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="updateWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Webhooks"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.UpdateWebhookResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def update_async( + self, + *, + id: str, + name: Optional[str] = None, + description: OptionalNullable[str] = UNSET, + url: Optional[str] = None, + event_types: Optional[Iterable[models.UpdateWebhookEventTypeRequest]] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.UpdateWebhookResponse: + r"""Update a webhook + + Update a webhook. Legacy v1 webhooks are read-only: delete them and create a new webhook instead. + + :param id: The ID of the webhook to update. + :param name: The webhook's name, used to identify it in the dashboard + :param description: The webhook's description + :param url: The webhook's URL. We'll call this URL when an event occurs. + :param event_types: Event types this webhook subscribes to. One webhook URL receives all events you list here. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.UpdateWebhookRequest( + id=id, + body=models.UpdateWebhookRequestBody( + name=name, + description=description, + url=url, + event_types=utils.unmarshal( + event_types, Optional[List[models.UpdateWebhookEventTypeRequest]] + ), + ), + ) + + req = self._build_request_async( + method="PUT", + path="/webhooks/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.UpdateWebhookRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="updateWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Webhooks"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.UpdateWebhookResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def delete( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.DeleteWebhookResponse: + r"""Delete a webhook + + Delete a webhook. + + :param id: The ID of the webhook to delete. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.DeleteWebhookRequest( + id=id, + ) + + req = self._build_request( + method="DELETE", + path="/webhooks/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="deleteWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Webhooks"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.DeleteWebhookResponse, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def delete_async( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.DeleteWebhookResponse: + r"""Delete a webhook + + Delete a webhook. + + :param id: The ID of the webhook to delete. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.DeleteWebhookRequest( + id=id, + ) + + req = self._build_request_async( + method="DELETE", + path="/webhooks/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="deleteWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Webhooks"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.DeleteWebhookResponse, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) diff --git a/src/moderation_api/webhooksecret.py b/src/moderation_api/webhooksecret.py new file mode 100644 index 0000000..9c52eb0 --- /dev/null +++ b/src/moderation_api/webhooksecret.py @@ -0,0 +1,195 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .basesdk import BaseSDK +from moderation_api import errors, models, utils +from moderation_api._hooks import HookContext +from moderation_api.types import OptionalNullable, UNSET +from moderation_api.utils import get_security_from_env +from moderation_api.utils.unmarshal_json_response import unmarshal_json_response +from typing import Any, Mapping, Optional + + +class WebhookSecret(BaseSDK): + def retrieve( + self, + *, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.GetWebhookSecretResponse: + r"""Get the webhook signing secret + + Get the signing secret used to sign webhook deliveries for this project, creating one if none exists yet. Verify deliveries by comparing the `modapi-signature` header to HMAC-SHA256(raw request body, secret) hex-encoded. + + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + req = self._build_request( + method="GET", + path="/webhook-secret", + base_url=base_url, + url_variables=url_variables, + request=None, + request_body_required=False, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getWebhookSecret", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Webhooks"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.GetWebhookSecretResponse, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def retrieve_async( + self, + *, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.GetWebhookSecretResponse: + r"""Get the webhook signing secret + + Get the signing secret used to sign webhook deliveries for this project, creating one if none exists yet. Verify deliveries by comparing the `modapi-signature` header to HMAC-SHA256(raw request body, secret) hex-encoded. + + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + req = self._build_request_async( + method="GET", + path="/webhook-secret", + base_url=base_url, + url_variables=url_variables, + request=None, + request_body_required=False, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getWebhookSecret", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Webhooks"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.GetWebhookSecretResponse, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) diff --git a/src/moderation_api/wordlist.py b/src/moderation_api/wordlist.py new file mode 100644 index 0000000..4628b45 --- /dev/null +++ b/src/moderation_api/wordlist.py @@ -0,0 +1,1061 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .basesdk import BaseSDK +from .sdkconfiguration import SDKConfiguration +from jsonpath import JSONPath +from moderation_api import errors, models, utils +from moderation_api._hooks import HookContext +from moderation_api.types import OptionalNullable, UNSET +from moderation_api.utils import get_security_from_env +from moderation_api.utils.unmarshal_json_response import unmarshal_json_response +from moderation_api.words import Words +from typing import Any, Awaitable, Dict, Iterable, List, Mapping, Optional, Union + + +class Wordlist(BaseSDK): + words: Words + + def __init__( + self, sdk_config: SDKConfiguration, parent_ref: Optional[object] = None + ) -> None: + BaseSDK.__init__(self, sdk_config, parent_ref=parent_ref) + self.sdk_configuration = sdk_config + self._init_sdks() + + def _init_sdks(self): + self.words = Words(self.sdk_configuration, parent_ref=self.parent_ref) + + def list( + self, + *, + page: Optional[int] = 1, + page_size: Optional[int] = 20, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> Optional[models.ListWordlistsResponse]: + r"""List wordlists + + List the wordlists of the organization. The list is paginated. + + :param page: + :param page_size: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.ListWordlistsRequest( + page=page, + page_size=page_size, + ) + + req = self._build_request( + method="GET", + path="/wordlist", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="listWordlists", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Wordlists"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + def next_func() -> Optional[models.ListWordlistsResponse]: + body = utils.unmarshal_json(http_res.text, Union[Dict[Any, Any], List[Any]]) + + page = request.page if isinstance(request.page, int) else 1 + next_page = page + 1 + + num_pages = JSONPath("$.pagination.totalPages").parse(body) + if len(num_pages) == 0 or num_pages[0] <= page: + return None + + if not http_res.text: + return None + results = JSONPath("$.data").parse(body) + if len(results) == 0 or len(results[0]) == 0: + return None + limit_ = request.page_size if isinstance(request.page_size, int) else 20 + if len(results[0]) < limit_: + return None + + return self.list( + page=next_page, + page_size=page_size, + retries=retries, + server_url=server_url, + timeout_ms=timeout_ms, + http_headers=http_headers, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return models.ListWordlistsResponse( + result=unmarshal_json_response( + models.ListWordlistsResponseBody, http_res + ), + next=next_func, + ) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def list_async( + self, + *, + page: Optional[int] = 1, + page_size: Optional[int] = 20, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> Optional[models.ListWordlistsResponse]: + r"""List wordlists + + List the wordlists of the organization. The list is paginated. + + :param page: + :param page_size: + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.ListWordlistsRequest( + page=page, + page_size=page_size, + ) + + req = self._build_request_async( + method="GET", + path="/wordlist", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=False, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="listWordlists", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Wordlists"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + def next_func() -> Awaitable[Optional[models.ListWordlistsResponse]]: + body = utils.unmarshal_json(http_res.text, Union[Dict[Any, Any], List[Any]]) + + async def empty_result(): + return None + + page = request.page if isinstance(request.page, int) else 1 + next_page = page + 1 + + num_pages = JSONPath("$.pagination.totalPages").parse(body) + if len(num_pages) == 0 or num_pages[0] <= page: + return empty_result() + + if not http_res.text: + return empty_result() + results = JSONPath("$.data").parse(body) + if len(results) == 0 or len(results[0]) == 0: + return empty_result() + limit_ = request.page_size if isinstance(request.page_size, int) else 20 + if len(results[0]) < limit_: + return empty_result() + + return self.list_async( + page=next_page, + page_size=page_size, + retries=retries, + server_url=server_url, + timeout_ms=timeout_ms, + http_headers=http_headers, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return models.ListWordlistsResponse( + result=unmarshal_json_response( + models.ListWordlistsResponseBody, http_res + ), + next=next_func, + ) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def retrieve( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.GetWordlistResponse: + r"""Get wordlist + + Get a specific wordlist by ID + + :param id: ID of the wordlist to get + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetWordlistRequest( + id=id, + ) + + req = self._build_request( + method="GET", + path="/wordlist/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getWordlist", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Wordlists"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.GetWordlistResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def retrieve_async( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.GetWordlistResponse: + r"""Get wordlist + + Get a specific wordlist by ID + + :param id: ID of the wordlist to get + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetWordlistRequest( + id=id, + ) + + req = self._build_request_async( + method="GET", + path="/wordlist/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getWordlist", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Wordlists"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.GetWordlistResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def update( + self, + *, + id: str, + name: Optional[str] = None, + key: Optional[str] = None, + description: Optional[str] = None, + words: Optional[Iterable[str]] = None, + strict: Optional[bool] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.UpdateWordlistResponse: + r"""Update wordlist + + Update a wordlist + + :param id: ID of the wordlist to update + :param name: New name for the wordlist + :param key: New key for the wordlist + :param description: New description for the wordlist + :param words: New words for the wordlist. Replace the existing words with these new ones. Duplicate words will be ignored. + :param strict: Deprecated. Now using threshold in project settings. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.UpdateWordlistRequest( + id=id, + body=models.UpdateWordlistRequestBody( + name=name, + key=key, + description=description, + words=utils.unmarshal(words, Optional[List[str]]), + strict=strict, + ), + ) + + req = self._build_request( + method="PUT", + path="/wordlist/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.UpdateWordlistRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="updateWordlist", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Wordlists"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.UpdateWordlistResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def update_async( + self, + *, + id: str, + name: Optional[str] = None, + key: Optional[str] = None, + description: Optional[str] = None, + words: Optional[Iterable[str]] = None, + strict: Optional[bool] = None, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.UpdateWordlistResponse: + r"""Update wordlist + + Update a wordlist + + :param id: ID of the wordlist to update + :param name: New name for the wordlist + :param key: New key for the wordlist + :param description: New description for the wordlist + :param words: New words for the wordlist. Replace the existing words with these new ones. Duplicate words will be ignored. + :param strict: Deprecated. Now using threshold in project settings. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.UpdateWordlistRequest( + id=id, + body=models.UpdateWordlistRequestBody( + name=name, + key=key, + description=description, + words=utils.unmarshal(words, Optional[List[str]]), + strict=strict, + ), + ) + + req = self._build_request_async( + method="PUT", + path="/wordlist/{id}", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.UpdateWordlistRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="updateWordlist", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Wordlists"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.UpdateWordlistResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def get_embedding_status( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.GetWordlistEmbeddingStatusResponse: + r"""Get embedding status + + Get the current embedding progress status for a wordlist + + :param id: ID of the wordlist to check embedding status for + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetWordlistEmbeddingStatusRequest( + id=id, + ) + + req = self._build_request( + method="GET", + path="/wordlist/{id}/embedding-status", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getWordlistEmbeddingStatus", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Wordlists"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response( + models.GetWordlistEmbeddingStatusResponse, http_res + ) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def get_embedding_status_async( + self, + *, + id: str, + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.GetWordlistEmbeddingStatusResponse: + r"""Get embedding status + + Get the current embedding progress status for a wordlist + + :param id: ID of the wordlist to check embedding status for + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.GetWordlistEmbeddingStatusRequest( + id=id, + ) + + req = self._build_request_async( + method="GET", + path="/wordlist/{id}/embedding-status", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getWordlistEmbeddingStatus", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Wordlists"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response( + models.GetWordlistEmbeddingStatusResponse, http_res + ) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "403", "application/json"): + response_data = unmarshal_json_response( + errors.PermissionDeniedErrorData, http_res + ) + raise errors.PermissionDeniedError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "500", "application/json"): + response_data = unmarshal_json_response( + errors.InternalServerErrorData, http_res + ) + raise errors.InternalServerError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) diff --git a/src/moderation_api/words.py b/src/moderation_api/words.py new file mode 100644 index 0000000..cefa1a0 --- /dev/null +++ b/src/moderation_api/words.py @@ -0,0 +1,461 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .basesdk import BaseSDK +from moderation_api import errors, models, utils +from moderation_api._hooks import HookContext +from moderation_api.types import OptionalNullable, UNSET +from moderation_api.utils import get_security_from_env +from moderation_api.utils.unmarshal_json_response import unmarshal_json_response +from typing import Any, Iterable, List, Mapping, Optional + + +class Words(BaseSDK): + def add( + self, + *, + id: str, + words: Iterable[str], + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.AddWordlistWordsResponse: + r"""Add words to wordlist + + Add words to an existing wordlist + + :param id: ID of the wordlist to add words to + :param words: Array of words to add to the wordlist. Duplicate words will be ignored. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.AddWordlistWordsRequest( + id=id, + body=models.AddWordlistWordsRequestBody( + words=utils.unmarshal(words, List[str]), + ), + ) + + req = self._build_request( + method="POST", + path="/wordlist/{id}/words", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.AddWordlistWordsRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="addWordlistWords", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Wordlists"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.AddWordlistWordsResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def add_async( + self, + *, + id: str, + words: Iterable[str], + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.AddWordlistWordsResponse: + r"""Add words to wordlist + + Add words to an existing wordlist + + :param id: ID of the wordlist to add words to + :param words: Array of words to add to the wordlist. Duplicate words will be ignored. + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.AddWordlistWordsRequest( + id=id, + body=models.AddWordlistWordsRequestBody( + words=utils.unmarshal(words, List[str]), + ), + ) + + req = self._build_request_async( + method="POST", + path="/wordlist/{id}/words", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=True, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + get_serialized_body=lambda: utils.serialize_request_body( + request.body, False, False, "json", models.AddWordlistWordsRequestBody + ), + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="addWordlistWords", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Wordlists"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.AddWordlistWordsResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + def remove( + self, + *, + id: str, + words: Iterable[str], + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.RemoveWordlistWordsResponse: + r"""Remove words from wordlist + + Remove words from an existing wordlist + + :param id: ID of the wordlist to remove words from + :param words: Array of words to remove from the wordlist + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.RemoveWordlistWordsRequest( + id=id, + words=utils.unmarshal(words, List[str]), + ) + + req = self._build_request( + method="DELETE", + path="/wordlist/{id}/words", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = self.do_request( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="removeWordlistWords", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Wordlists"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.RemoveWordlistWordsResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = utils.stream_to_text(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) + + async def remove_async( + self, + *, + id: str, + words: Iterable[str], + retries: OptionalNullable[utils.RetryConfig] = UNSET, + server_url: Optional[str] = None, + timeout_ms: Optional[int] = None, + http_headers: Optional[Mapping[str, str]] = None, + ) -> models.RemoveWordlistWordsResponse: + r"""Remove words from wordlist + + Remove words from an existing wordlist + + :param id: ID of the wordlist to remove words from + :param words: Array of words to remove from the wordlist + :param retries: Override the default retry configuration for this method + :param server_url: Override the default server URL for this method + :param timeout_ms: Override the default request timeout configuration for this method in milliseconds + :param http_headers: Additional headers to set or replace on requests. + """ + base_url = None + url_variables = None + if timeout_ms is None: + timeout_ms = self.sdk_configuration.timeout_ms + + if server_url is not None: + base_url = server_url + else: + base_url = self._get_url(base_url, url_variables) + + request = models.RemoveWordlistWordsRequest( + id=id, + words=utils.unmarshal(words, List[str]), + ) + + req = self._build_request_async( + method="DELETE", + path="/wordlist/{id}/words", + base_url=base_url, + url_variables=url_variables, + request=request, + request_body_required=False, + request_has_path_params=True, + request_has_query_params=True, + user_agent_header="user-agent", + accept_header_value="application/json", + http_headers=http_headers, + security=self.sdk_configuration.security, + allow_empty_value=None, + timeout_ms=timeout_ms, + ) + + if retries == UNSET: + if self.sdk_configuration.retry_config is not UNSET: + retries = self.sdk_configuration.retry_config + + retry_config = None + if isinstance(retries, utils.RetryConfig): + retry_config = (retries, ["429", "500", "502", "503", "504"]) + + http_res = await self.do_request_async( + hook_ctx=HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="removeWordlistWords", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + tags=["Wordlists"], + extensions=None, + ), + request=req, + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), + retry_config=retry_config, + ) + + response_data: Any = None + if utils.match_response(http_res, "200", "application/json"): + return unmarshal_json_response(models.RemoveWordlistWordsResponse, http_res) + if utils.match_response(http_res, "400", "application/json"): + response_data = unmarshal_json_response( + errors.BadRequestErrorData, http_res + ) + raise errors.BadRequestError(response_data, http_res) + if utils.match_response(http_res, "401", "application/json"): + response_data = unmarshal_json_response( + errors.AuthenticationErrorData, http_res + ) + raise errors.AuthenticationError(response_data, http_res) + if utils.match_response(http_res, "404", "application/json"): + response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res) + raise errors.NotFoundError(response_data, http_res) + if utils.match_response(http_res, "429", "application/json"): + response_data = unmarshal_json_response(errors.RateLimitErrorData, http_res) + raise errors.RateLimitError(response_data, http_res) + if utils.match_response(http_res, "4XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + if utils.match_response(http_res, "5XX", "*"): + http_res_text = await utils.stream_to_text_async(http_res) + raise errors.ModerationAPIDefaultError( + "API error occurred", http_res, http_res_text + ) + + raise errors.ModerationAPIDefaultError("Unexpected response received", http_res) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index fd8019a..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/__init__.py b/tests/api_resources/__init__.py deleted file mode 100644 index fd8019a..0000000 --- a/tests/api_resources/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/actions/__init__.py b/tests/api_resources/actions/__init__.py deleted file mode 100644 index fd8019a..0000000 --- a/tests/api_resources/actions/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/actions/test_execute.py b/tests/api_resources/actions/test_execute.py deleted file mode 100644 index b91b7da..0000000 --- a/tests/api_resources/actions/test_execute.py +++ /dev/null @@ -1,245 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from tests.utils import assert_matches_type -from moderation_api import ModerationAPI, AsyncModerationAPI -from moderation_api.types.actions import ( - ExecuteExecuteResponse, - ExecuteExecuteByIDResponse, -) - -# pyright: reportDeprecated=false - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestExecute: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_execute(self, client: ModerationAPI) -> None: - execute = client.actions.execute.execute( - action_key="actionKey", - ) - assert_matches_type(ExecuteExecuteResponse, execute, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_execute_with_all_params(self, client: ModerationAPI) -> None: - execute = client.actions.execute.execute( - action_key="actionKey", - author_ids=["string"], - content_ids=["string"], - duration=0, - queue_id="queueId", - value="value", - ) - assert_matches_type(ExecuteExecuteResponse, execute, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_execute(self, client: ModerationAPI) -> None: - response = client.actions.execute.with_raw_response.execute( - action_key="actionKey", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - execute = response.parse() - assert_matches_type(ExecuteExecuteResponse, execute, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_execute(self, client: ModerationAPI) -> None: - with client.actions.execute.with_streaming_response.execute( - action_key="actionKey", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - execute = response.parse() - assert_matches_type(ExecuteExecuteResponse, execute, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_execute_by_id(self, client: ModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - execute = client.actions.execute.execute_by_id( - action_id="actionId", - ) - - assert_matches_type(ExecuteExecuteByIDResponse, execute, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_execute_by_id_with_all_params(self, client: ModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - execute = client.actions.execute.execute_by_id( - action_id="actionId", - author_ids=["string"], - content_ids=["string"], - queue_id="queueId", - value="value", - ) - - assert_matches_type(ExecuteExecuteByIDResponse, execute, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_execute_by_id(self, client: ModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - response = client.actions.execute.with_raw_response.execute_by_id( - action_id="actionId", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - execute = response.parse() - assert_matches_type(ExecuteExecuteByIDResponse, execute, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_execute_by_id(self, client: ModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - with client.actions.execute.with_streaming_response.execute_by_id( - action_id="actionId", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - execute = response.parse() - assert_matches_type(ExecuteExecuteByIDResponse, execute, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_execute_by_id(self, client: ModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - with pytest.raises(ValueError, match=r"Expected a non-empty value for `action_id` but received ''"): - client.actions.execute.with_raw_response.execute_by_id( - action_id="", - ) - - -class TestAsyncExecute: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_execute(self, async_client: AsyncModerationAPI) -> None: - execute = await async_client.actions.execute.execute( - action_key="actionKey", - ) - assert_matches_type(ExecuteExecuteResponse, execute, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_execute_with_all_params(self, async_client: AsyncModerationAPI) -> None: - execute = await async_client.actions.execute.execute( - action_key="actionKey", - author_ids=["string"], - content_ids=["string"], - duration=0, - queue_id="queueId", - value="value", - ) - assert_matches_type(ExecuteExecuteResponse, execute, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_execute(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.actions.execute.with_raw_response.execute( - action_key="actionKey", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - execute = await response.parse() - assert_matches_type(ExecuteExecuteResponse, execute, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_execute(self, async_client: AsyncModerationAPI) -> None: - async with async_client.actions.execute.with_streaming_response.execute( - action_key="actionKey", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - execute = await response.parse() - assert_matches_type(ExecuteExecuteResponse, execute, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_execute_by_id(self, async_client: AsyncModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - execute = await async_client.actions.execute.execute_by_id( - action_id="actionId", - ) - - assert_matches_type(ExecuteExecuteByIDResponse, execute, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_execute_by_id_with_all_params(self, async_client: AsyncModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - execute = await async_client.actions.execute.execute_by_id( - action_id="actionId", - author_ids=["string"], - content_ids=["string"], - queue_id="queueId", - value="value", - ) - - assert_matches_type(ExecuteExecuteByIDResponse, execute, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_execute_by_id(self, async_client: AsyncModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - response = await async_client.actions.execute.with_raw_response.execute_by_id( - action_id="actionId", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - execute = await response.parse() - assert_matches_type(ExecuteExecuteByIDResponse, execute, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_execute_by_id(self, async_client: AsyncModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - async with async_client.actions.execute.with_streaming_response.execute_by_id( - action_id="actionId", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - execute = await response.parse() - assert_matches_type(ExecuteExecuteByIDResponse, execute, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_execute_by_id(self, async_client: AsyncModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - with pytest.raises(ValueError, match=r"Expected a non-empty value for `action_id` but received ''"): - await async_client.actions.execute.with_raw_response.execute_by_id( - action_id="", - ) diff --git a/tests/api_resources/queue/__init__.py b/tests/api_resources/queue/__init__.py deleted file mode 100644 index fd8019a..0000000 --- a/tests/api_resources/queue/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/queue/test_items.py b/tests/api_resources/queue/test_items.py deleted file mode 100644 index 9b83358..0000000 --- a/tests/api_resources/queue/test_items.py +++ /dev/null @@ -1,398 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from tests.utils import assert_matches_type -from moderation_api import ModerationAPI, AsyncModerationAPI -from moderation_api.types.queue import ( - ItemListResponse, - ItemResolveResponse, - ItemUnresolveResponse, -) - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestItems: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_list(self, client: ModerationAPI) -> None: - item = client.queue.items.list( - id="id", - ) - assert_matches_type(ItemListResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_list_with_all_params(self, client: ModerationAPI) -> None: - item = client.queue.items.list( - id="id", - after_date="afterDate", - author_id="authorId", - before_date="beforeDate", - conversation_ids="conversationIds", - filtered_action_ids="filteredActionIds", - include_resolved="includeResolved", - labels="labels", - page_number=0, - page_size=0, - sort_direction="asc", - sort_field="createdAt", - ) - assert_matches_type(ItemListResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_list(self, client: ModerationAPI) -> None: - response = client.queue.items.with_raw_response.list( - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - item = response.parse() - assert_matches_type(ItemListResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_list(self, client: ModerationAPI) -> None: - with client.queue.items.with_streaming_response.list( - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - item = response.parse() - assert_matches_type(ItemListResponse, item, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_list(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.queue.items.with_raw_response.list( - id="", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_resolve(self, client: ModerationAPI) -> None: - item = client.queue.items.resolve( - item_id="itemId", - id="id", - ) - assert_matches_type(ItemResolveResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_resolve_with_all_params(self, client: ModerationAPI) -> None: - item = client.queue.items.resolve( - item_id="itemId", - id="id", - comment="comment", - ) - assert_matches_type(ItemResolveResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_resolve(self, client: ModerationAPI) -> None: - response = client.queue.items.with_raw_response.resolve( - item_id="itemId", - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - item = response.parse() - assert_matches_type(ItemResolveResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_resolve(self, client: ModerationAPI) -> None: - with client.queue.items.with_streaming_response.resolve( - item_id="itemId", - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - item = response.parse() - assert_matches_type(ItemResolveResponse, item, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_resolve(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.queue.items.with_raw_response.resolve( - item_id="itemId", - id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `item_id` but received ''"): - client.queue.items.with_raw_response.resolve( - item_id="", - id="id", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_unresolve(self, client: ModerationAPI) -> None: - item = client.queue.items.unresolve( - item_id="itemId", - id="id", - ) - assert_matches_type(ItemUnresolveResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_unresolve_with_all_params(self, client: ModerationAPI) -> None: - item = client.queue.items.unresolve( - item_id="itemId", - id="id", - comment="comment", - ) - assert_matches_type(ItemUnresolveResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_unresolve(self, client: ModerationAPI) -> None: - response = client.queue.items.with_raw_response.unresolve( - item_id="itemId", - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - item = response.parse() - assert_matches_type(ItemUnresolveResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_unresolve(self, client: ModerationAPI) -> None: - with client.queue.items.with_streaming_response.unresolve( - item_id="itemId", - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - item = response.parse() - assert_matches_type(ItemUnresolveResponse, item, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_unresolve(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.queue.items.with_raw_response.unresolve( - item_id="itemId", - id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `item_id` but received ''"): - client.queue.items.with_raw_response.unresolve( - item_id="", - id="id", - ) - - -class TestAsyncItems: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_list(self, async_client: AsyncModerationAPI) -> None: - item = await async_client.queue.items.list( - id="id", - ) - assert_matches_type(ItemListResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncModerationAPI) -> None: - item = await async_client.queue.items.list( - id="id", - after_date="afterDate", - author_id="authorId", - before_date="beforeDate", - conversation_ids="conversationIds", - filtered_action_ids="filteredActionIds", - include_resolved="includeResolved", - labels="labels", - page_number=0, - page_size=0, - sort_direction="asc", - sort_field="createdAt", - ) - assert_matches_type(ItemListResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_list(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.queue.items.with_raw_response.list( - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - item = await response.parse() - assert_matches_type(ItemListResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_list(self, async_client: AsyncModerationAPI) -> None: - async with async_client.queue.items.with_streaming_response.list( - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - item = await response.parse() - assert_matches_type(ItemListResponse, item, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_list(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.queue.items.with_raw_response.list( - id="", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_resolve(self, async_client: AsyncModerationAPI) -> None: - item = await async_client.queue.items.resolve( - item_id="itemId", - id="id", - ) - assert_matches_type(ItemResolveResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_resolve_with_all_params(self, async_client: AsyncModerationAPI) -> None: - item = await async_client.queue.items.resolve( - item_id="itemId", - id="id", - comment="comment", - ) - assert_matches_type(ItemResolveResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_resolve(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.queue.items.with_raw_response.resolve( - item_id="itemId", - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - item = await response.parse() - assert_matches_type(ItemResolveResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_resolve(self, async_client: AsyncModerationAPI) -> None: - async with async_client.queue.items.with_streaming_response.resolve( - item_id="itemId", - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - item = await response.parse() - assert_matches_type(ItemResolveResponse, item, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_resolve(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.queue.items.with_raw_response.resolve( - item_id="itemId", - id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `item_id` but received ''"): - await async_client.queue.items.with_raw_response.resolve( - item_id="", - id="id", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_unresolve(self, async_client: AsyncModerationAPI) -> None: - item = await async_client.queue.items.unresolve( - item_id="itemId", - id="id", - ) - assert_matches_type(ItemUnresolveResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_unresolve_with_all_params(self, async_client: AsyncModerationAPI) -> None: - item = await async_client.queue.items.unresolve( - item_id="itemId", - id="id", - comment="comment", - ) - assert_matches_type(ItemUnresolveResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_unresolve(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.queue.items.with_raw_response.unresolve( - item_id="itemId", - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - item = await response.parse() - assert_matches_type(ItemUnresolveResponse, item, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_unresolve(self, async_client: AsyncModerationAPI) -> None: - async with async_client.queue.items.with_streaming_response.unresolve( - item_id="itemId", - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - item = await response.parse() - assert_matches_type(ItemUnresolveResponse, item, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_unresolve(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.queue.items.with_raw_response.unresolve( - item_id="itemId", - id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `item_id` but received ''"): - await async_client.queue.items.with_raw_response.unresolve( - item_id="", - id="id", - ) diff --git a/tests/api_resources/test_account.py b/tests/api_resources/test_account.py deleted file mode 100644 index a27dc17..0000000 --- a/tests/api_resources/test_account.py +++ /dev/null @@ -1,80 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from tests.utils import assert_matches_type -from moderation_api import ModerationAPI, AsyncModerationAPI -from moderation_api.types import AccountListResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestAccount: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_list(self, client: ModerationAPI) -> None: - account = client.account.list() - assert_matches_type(AccountListResponse, account, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_list(self, client: ModerationAPI) -> None: - response = client.account.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - account = response.parse() - assert_matches_type(AccountListResponse, account, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_list(self, client: ModerationAPI) -> None: - with client.account.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - account = response.parse() - assert_matches_type(AccountListResponse, account, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncAccount: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_list(self, async_client: AsyncModerationAPI) -> None: - account = await async_client.account.list() - assert_matches_type(AccountListResponse, account, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_list(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.account.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - account = await response.parse() - assert_matches_type(AccountListResponse, account, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_list(self, async_client: AsyncModerationAPI) -> None: - async with async_client.account.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - account = await response.parse() - assert_matches_type(AccountListResponse, account, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_actions.py b/tests/api_resources/test_actions.py deleted file mode 100644 index ed7f9a0..0000000 --- a/tests/api_resources/test_actions.py +++ /dev/null @@ -1,496 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from tests.utils import assert_matches_type -from moderation_api import ModerationAPI, AsyncModerationAPI -from moderation_api.types import ( - ActionListResponse, - ActionCreateResponse, - ActionDeleteResponse, - ActionUpdateResponse, - ActionRetrieveResponse, -) - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestActions: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_create(self, client: ModerationAPI) -> None: - action = client.actions.create( - name="name", - ) - assert_matches_type(ActionCreateResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_create_with_all_params(self, client: ModerationAPI) -> None: - action = client.actions.create( - name="name", - built_in=True, - description="description", - filter_in_queue_ids=["string"], - free_text=True, - key="key", - position="ALL_QUEUES", - possible_values=[{"value": "value"}], - queue_behaviour="REMOVE", - type="AUTHOR_BLOCK", - value_required=True, - ) - assert_matches_type(ActionCreateResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_create(self, client: ModerationAPI) -> None: - response = client.actions.with_raw_response.create( - name="name", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - action = response.parse() - assert_matches_type(ActionCreateResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_create(self, client: ModerationAPI) -> None: - with client.actions.with_streaming_response.create( - name="name", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - action = response.parse() - assert_matches_type(ActionCreateResponse, action, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_retrieve(self, client: ModerationAPI) -> None: - action = client.actions.retrieve( - "id", - ) - assert_matches_type(ActionRetrieveResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_retrieve(self, client: ModerationAPI) -> None: - response = client.actions.with_raw_response.retrieve( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - action = response.parse() - assert_matches_type(ActionRetrieveResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_retrieve(self, client: ModerationAPI) -> None: - with client.actions.with_streaming_response.retrieve( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - action = response.parse() - assert_matches_type(ActionRetrieveResponse, action, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_retrieve(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.actions.with_raw_response.retrieve( - "", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_update(self, client: ModerationAPI) -> None: - action = client.actions.update( - id="id", - ) - assert_matches_type(ActionUpdateResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_update_with_all_params(self, client: ModerationAPI) -> None: - action = client.actions.update( - id="id", - built_in=True, - description="description", - filter_in_queue_ids=["string"], - free_text=True, - key="key", - name="name", - position="ALL_QUEUES", - possible_values=[{"value": "value"}], - queue_behaviour="REMOVE", - type="AUTHOR_BLOCK", - value_required=True, - ) - assert_matches_type(ActionUpdateResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_update(self, client: ModerationAPI) -> None: - response = client.actions.with_raw_response.update( - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - action = response.parse() - assert_matches_type(ActionUpdateResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_update(self, client: ModerationAPI) -> None: - with client.actions.with_streaming_response.update( - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - action = response.parse() - assert_matches_type(ActionUpdateResponse, action, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_update(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.actions.with_raw_response.update( - id="", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_list(self, client: ModerationAPI) -> None: - action = client.actions.list() - assert_matches_type(ActionListResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_list_with_all_params(self, client: ModerationAPI) -> None: - action = client.actions.list( - queue_id="queueId", - ) - assert_matches_type(ActionListResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_list(self, client: ModerationAPI) -> None: - response = client.actions.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - action = response.parse() - assert_matches_type(ActionListResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_list(self, client: ModerationAPI) -> None: - with client.actions.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - action = response.parse() - assert_matches_type(ActionListResponse, action, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_delete(self, client: ModerationAPI) -> None: - action = client.actions.delete( - "id", - ) - assert_matches_type(ActionDeleteResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_delete(self, client: ModerationAPI) -> None: - response = client.actions.with_raw_response.delete( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - action = response.parse() - assert_matches_type(ActionDeleteResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_delete(self, client: ModerationAPI) -> None: - with client.actions.with_streaming_response.delete( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - action = response.parse() - assert_matches_type(ActionDeleteResponse, action, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_delete(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.actions.with_raw_response.delete( - "", - ) - - -class TestAsyncActions: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_create(self, async_client: AsyncModerationAPI) -> None: - action = await async_client.actions.create( - name="name", - ) - assert_matches_type(ActionCreateResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncModerationAPI) -> None: - action = await async_client.actions.create( - name="name", - built_in=True, - description="description", - filter_in_queue_ids=["string"], - free_text=True, - key="key", - position="ALL_QUEUES", - possible_values=[{"value": "value"}], - queue_behaviour="REMOVE", - type="AUTHOR_BLOCK", - value_required=True, - ) - assert_matches_type(ActionCreateResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_create(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.actions.with_raw_response.create( - name="name", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - action = await response.parse() - assert_matches_type(ActionCreateResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_create(self, async_client: AsyncModerationAPI) -> None: - async with async_client.actions.with_streaming_response.create( - name="name", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - action = await response.parse() - assert_matches_type(ActionCreateResponse, action, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_retrieve(self, async_client: AsyncModerationAPI) -> None: - action = await async_client.actions.retrieve( - "id", - ) - assert_matches_type(ActionRetrieveResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.actions.with_raw_response.retrieve( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - action = await response.parse() - assert_matches_type(ActionRetrieveResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncModerationAPI) -> None: - async with async_client.actions.with_streaming_response.retrieve( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - action = await response.parse() - assert_matches_type(ActionRetrieveResponse, action, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_retrieve(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.actions.with_raw_response.retrieve( - "", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_update(self, async_client: AsyncModerationAPI) -> None: - action = await async_client.actions.update( - id="id", - ) - assert_matches_type(ActionUpdateResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncModerationAPI) -> None: - action = await async_client.actions.update( - id="id", - built_in=True, - description="description", - filter_in_queue_ids=["string"], - free_text=True, - key="key", - name="name", - position="ALL_QUEUES", - possible_values=[{"value": "value"}], - queue_behaviour="REMOVE", - type="AUTHOR_BLOCK", - value_required=True, - ) - assert_matches_type(ActionUpdateResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_update(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.actions.with_raw_response.update( - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - action = await response.parse() - assert_matches_type(ActionUpdateResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_update(self, async_client: AsyncModerationAPI) -> None: - async with async_client.actions.with_streaming_response.update( - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - action = await response.parse() - assert_matches_type(ActionUpdateResponse, action, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_update(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.actions.with_raw_response.update( - id="", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_list(self, async_client: AsyncModerationAPI) -> None: - action = await async_client.actions.list() - assert_matches_type(ActionListResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncModerationAPI) -> None: - action = await async_client.actions.list( - queue_id="queueId", - ) - assert_matches_type(ActionListResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_list(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.actions.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - action = await response.parse() - assert_matches_type(ActionListResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_list(self, async_client: AsyncModerationAPI) -> None: - async with async_client.actions.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - action = await response.parse() - assert_matches_type(ActionListResponse, action, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_delete(self, async_client: AsyncModerationAPI) -> None: - action = await async_client.actions.delete( - "id", - ) - assert_matches_type(ActionDeleteResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_delete(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.actions.with_raw_response.delete( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - action = await response.parse() - assert_matches_type(ActionDeleteResponse, action, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_delete(self, async_client: AsyncModerationAPI) -> None: - async with async_client.actions.with_streaming_response.delete( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - action = await response.parse() - assert_matches_type(ActionDeleteResponse, action, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_delete(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.actions.with_raw_response.delete( - "", - ) diff --git a/tests/api_resources/test_auth.py b/tests/api_resources/test_auth.py deleted file mode 100644 index ab76fc5..0000000 --- a/tests/api_resources/test_auth.py +++ /dev/null @@ -1,154 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from tests.utils import assert_matches_type -from moderation_api import ModerationAPI, AsyncModerationAPI -from moderation_api.types import AuthCreateResponse, AuthRetrieveResponse - -# pyright: reportDeprecated=false - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestAuth: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_create(self, client: ModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - auth = client.auth.create() - - assert_matches_type(AuthCreateResponse, auth, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_create(self, client: ModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - response = client.auth.with_raw_response.create() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth = response.parse() - assert_matches_type(AuthCreateResponse, auth, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_create(self, client: ModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - with client.auth.with_streaming_response.create() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth = response.parse() - assert_matches_type(AuthCreateResponse, auth, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_retrieve(self, client: ModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - auth = client.auth.retrieve() - - assert_matches_type(AuthRetrieveResponse, auth, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_retrieve(self, client: ModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - response = client.auth.with_raw_response.retrieve() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth = response.parse() - assert_matches_type(AuthRetrieveResponse, auth, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_retrieve(self, client: ModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - with client.auth.with_streaming_response.retrieve() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth = response.parse() - assert_matches_type(AuthRetrieveResponse, auth, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncAuth: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_create(self, async_client: AsyncModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - auth = await async_client.auth.create() - - assert_matches_type(AuthCreateResponse, auth, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_create(self, async_client: AsyncModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - response = await async_client.auth.with_raw_response.create() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth = await response.parse() - assert_matches_type(AuthCreateResponse, auth, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_create(self, async_client: AsyncModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - async with async_client.auth.with_streaming_response.create() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth = await response.parse() - assert_matches_type(AuthCreateResponse, auth, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_retrieve(self, async_client: AsyncModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - auth = await async_client.auth.retrieve() - - assert_matches_type(AuthRetrieveResponse, auth, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - response = await async_client.auth.with_raw_response.retrieve() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth = await response.parse() - assert_matches_type(AuthRetrieveResponse, auth, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncModerationAPI) -> None: - with pytest.warns(DeprecationWarning): - async with async_client.auth.with_streaming_response.retrieve() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth = await response.parse() - assert_matches_type(AuthRetrieveResponse, auth, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_authors.py b/tests/api_resources/test_authors.py deleted file mode 100644 index 0639c7f..0000000 --- a/tests/api_resources/test_authors.py +++ /dev/null @@ -1,522 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from tests.utils import assert_matches_type -from moderation_api import ModerationAPI, AsyncModerationAPI -from moderation_api.types import ( - AuthorListResponse, - AuthorCreateResponse, - AuthorDeleteResponse, - AuthorUpdateResponse, - AuthorRetrieveResponse, -) - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestAuthors: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_create(self, client: ModerationAPI) -> None: - author = client.authors.create( - external_id="external_id", - ) - assert_matches_type(AuthorCreateResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_create_with_all_params(self, client: ModerationAPI) -> None: - author = client.authors.create( - external_id="external_id", - company="company", - email="dev@stainless.com", - external_link="https://example.com", - first_seen=0, - last_seen=0, - manual_trust_level=-1, - metadata={ - "email_verified": True, - "identity_verified": True, - "is_paying_customer": True, - "phone_verified": True, - }, - name="name", - profile_picture="https://example.com", - ) - assert_matches_type(AuthorCreateResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_create(self, client: ModerationAPI) -> None: - response = client.authors.with_raw_response.create( - external_id="external_id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - author = response.parse() - assert_matches_type(AuthorCreateResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_create(self, client: ModerationAPI) -> None: - with client.authors.with_streaming_response.create( - external_id="external_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - author = response.parse() - assert_matches_type(AuthorCreateResponse, author, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_retrieve(self, client: ModerationAPI) -> None: - author = client.authors.retrieve( - "id", - ) - assert_matches_type(AuthorRetrieveResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_retrieve(self, client: ModerationAPI) -> None: - response = client.authors.with_raw_response.retrieve( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - author = response.parse() - assert_matches_type(AuthorRetrieveResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_retrieve(self, client: ModerationAPI) -> None: - with client.authors.with_streaming_response.retrieve( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - author = response.parse() - assert_matches_type(AuthorRetrieveResponse, author, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_retrieve(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.authors.with_raw_response.retrieve( - "", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_update(self, client: ModerationAPI) -> None: - author = client.authors.update( - id="id", - ) - assert_matches_type(AuthorUpdateResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_update_with_all_params(self, client: ModerationAPI) -> None: - author = client.authors.update( - id="id", - company="company", - email="dev@stainless.com", - external_link="https://example.com", - first_seen=0, - last_seen=0, - manual_trust_level=-1, - metadata={ - "email_verified": True, - "identity_verified": True, - "is_paying_customer": True, - "phone_verified": True, - }, - name="name", - profile_picture="https://example.com", - ) - assert_matches_type(AuthorUpdateResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_update(self, client: ModerationAPI) -> None: - response = client.authors.with_raw_response.update( - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - author = response.parse() - assert_matches_type(AuthorUpdateResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_update(self, client: ModerationAPI) -> None: - with client.authors.with_streaming_response.update( - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - author = response.parse() - assert_matches_type(AuthorUpdateResponse, author, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_update(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.authors.with_raw_response.update( - id="", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_list(self, client: ModerationAPI) -> None: - author = client.authors.list() - assert_matches_type(AuthorListResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_list_with_all_params(self, client: ModerationAPI) -> None: - author = client.authors.list( - content_types="contentTypes", - last_active_date="lastActiveDate", - member_since_date="memberSinceDate", - page_number=0, - page_size=0, - sort_by="trustLevel", - sort_direction="asc", - ) - assert_matches_type(AuthorListResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_list(self, client: ModerationAPI) -> None: - response = client.authors.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - author = response.parse() - assert_matches_type(AuthorListResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_list(self, client: ModerationAPI) -> None: - with client.authors.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - author = response.parse() - assert_matches_type(AuthorListResponse, author, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_delete(self, client: ModerationAPI) -> None: - author = client.authors.delete( - "id", - ) - assert_matches_type(AuthorDeleteResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_delete(self, client: ModerationAPI) -> None: - response = client.authors.with_raw_response.delete( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - author = response.parse() - assert_matches_type(AuthorDeleteResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_delete(self, client: ModerationAPI) -> None: - with client.authors.with_streaming_response.delete( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - author = response.parse() - assert_matches_type(AuthorDeleteResponse, author, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_delete(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.authors.with_raw_response.delete( - "", - ) - - -class TestAsyncAuthors: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_create(self, async_client: AsyncModerationAPI) -> None: - author = await async_client.authors.create( - external_id="external_id", - ) - assert_matches_type(AuthorCreateResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncModerationAPI) -> None: - author = await async_client.authors.create( - external_id="external_id", - company="company", - email="dev@stainless.com", - external_link="https://example.com", - first_seen=0, - last_seen=0, - manual_trust_level=-1, - metadata={ - "email_verified": True, - "identity_verified": True, - "is_paying_customer": True, - "phone_verified": True, - }, - name="name", - profile_picture="https://example.com", - ) - assert_matches_type(AuthorCreateResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_create(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.authors.with_raw_response.create( - external_id="external_id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - author = await response.parse() - assert_matches_type(AuthorCreateResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_create(self, async_client: AsyncModerationAPI) -> None: - async with async_client.authors.with_streaming_response.create( - external_id="external_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - author = await response.parse() - assert_matches_type(AuthorCreateResponse, author, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_retrieve(self, async_client: AsyncModerationAPI) -> None: - author = await async_client.authors.retrieve( - "id", - ) - assert_matches_type(AuthorRetrieveResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.authors.with_raw_response.retrieve( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - author = await response.parse() - assert_matches_type(AuthorRetrieveResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncModerationAPI) -> None: - async with async_client.authors.with_streaming_response.retrieve( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - author = await response.parse() - assert_matches_type(AuthorRetrieveResponse, author, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_retrieve(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.authors.with_raw_response.retrieve( - "", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_update(self, async_client: AsyncModerationAPI) -> None: - author = await async_client.authors.update( - id="id", - ) - assert_matches_type(AuthorUpdateResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncModerationAPI) -> None: - author = await async_client.authors.update( - id="id", - company="company", - email="dev@stainless.com", - external_link="https://example.com", - first_seen=0, - last_seen=0, - manual_trust_level=-1, - metadata={ - "email_verified": True, - "identity_verified": True, - "is_paying_customer": True, - "phone_verified": True, - }, - name="name", - profile_picture="https://example.com", - ) - assert_matches_type(AuthorUpdateResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_update(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.authors.with_raw_response.update( - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - author = await response.parse() - assert_matches_type(AuthorUpdateResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_update(self, async_client: AsyncModerationAPI) -> None: - async with async_client.authors.with_streaming_response.update( - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - author = await response.parse() - assert_matches_type(AuthorUpdateResponse, author, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_update(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.authors.with_raw_response.update( - id="", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_list(self, async_client: AsyncModerationAPI) -> None: - author = await async_client.authors.list() - assert_matches_type(AuthorListResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncModerationAPI) -> None: - author = await async_client.authors.list( - content_types="contentTypes", - last_active_date="lastActiveDate", - member_since_date="memberSinceDate", - page_number=0, - page_size=0, - sort_by="trustLevel", - sort_direction="asc", - ) - assert_matches_type(AuthorListResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_list(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.authors.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - author = await response.parse() - assert_matches_type(AuthorListResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_list(self, async_client: AsyncModerationAPI) -> None: - async with async_client.authors.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - author = await response.parse() - assert_matches_type(AuthorListResponse, author, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_delete(self, async_client: AsyncModerationAPI) -> None: - author = await async_client.authors.delete( - "id", - ) - assert_matches_type(AuthorDeleteResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_delete(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.authors.with_raw_response.delete( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - author = await response.parse() - assert_matches_type(AuthorDeleteResponse, author, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_delete(self, async_client: AsyncModerationAPI) -> None: - async with async_client.authors.with_streaming_response.delete( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - author = await response.parse() - assert_matches_type(AuthorDeleteResponse, author, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_delete(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.authors.with_raw_response.delete( - "", - ) diff --git a/tests/api_resources/test_content.py b/tests/api_resources/test_content.py deleted file mode 100644 index 3693af5..0000000 --- a/tests/api_resources/test_content.py +++ /dev/null @@ -1,242 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from tests.utils import assert_matches_type -from moderation_api import ModerationAPI, AsyncModerationAPI -from moderation_api.types import ContentSubmitResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestContent: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_stream(self, client: ModerationAPI) -> None: - content = client.content.stream( - sec_web_socket_protocol="moderationapi.v1", - ) - assert content is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_stream(self, client: ModerationAPI) -> None: - response = client.content.with_raw_response.stream( - sec_web_socket_protocol="moderationapi.v1", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - content = response.parse() - assert content is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_stream(self, client: ModerationAPI) -> None: - with client.content.with_streaming_response.stream( - sec_web_socket_protocol="moderationapi.v1", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - content = response.parse() - assert content is None - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_submit(self, client: ModerationAPI) -> None: - content = client.content.submit( - content={ - "text": "text", - "type": "text", - }, - ) - assert_matches_type(ContentSubmitResponse, content, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_submit_with_all_params(self, client: ModerationAPI) -> None: - content = client.content.submit( - content={ - "text": "text", - "type": "text", - }, - author_id="authorId", - channel="channel", - client_action={ - "action": "review", - "behavior": "override", - "reason": "reason", - "source": "source", - }, - content_id="contentId", - conversation_id="conversationId", - do_not_store=True, - metadata={"foo": "bar"}, - meta_type="profile", - policies=[ - { - "id": "toxicity", - "flag": True, - "threshold": 0, - } - ], - timestamp=0, - ) - assert_matches_type(ContentSubmitResponse, content, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_submit(self, client: ModerationAPI) -> None: - response = client.content.with_raw_response.submit( - content={ - "text": "text", - "type": "text", - }, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - content = response.parse() - assert_matches_type(ContentSubmitResponse, content, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_submit(self, client: ModerationAPI) -> None: - with client.content.with_streaming_response.submit( - content={ - "text": "text", - "type": "text", - }, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - content = response.parse() - assert_matches_type(ContentSubmitResponse, content, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncContent: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_stream(self, async_client: AsyncModerationAPI) -> None: - content = await async_client.content.stream( - sec_web_socket_protocol="moderationapi.v1", - ) - assert content is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_stream(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.content.with_raw_response.stream( - sec_web_socket_protocol="moderationapi.v1", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - content = await response.parse() - assert content is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_stream(self, async_client: AsyncModerationAPI) -> None: - async with async_client.content.with_streaming_response.stream( - sec_web_socket_protocol="moderationapi.v1", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - content = await response.parse() - assert content is None - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_submit(self, async_client: AsyncModerationAPI) -> None: - content = await async_client.content.submit( - content={ - "text": "text", - "type": "text", - }, - ) - assert_matches_type(ContentSubmitResponse, content, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_submit_with_all_params(self, async_client: AsyncModerationAPI) -> None: - content = await async_client.content.submit( - content={ - "text": "text", - "type": "text", - }, - author_id="authorId", - channel="channel", - client_action={ - "action": "review", - "behavior": "override", - "reason": "reason", - "source": "source", - }, - content_id="contentId", - conversation_id="conversationId", - do_not_store=True, - metadata={"foo": "bar"}, - meta_type="profile", - policies=[ - { - "id": "toxicity", - "flag": True, - "threshold": 0, - } - ], - timestamp=0, - ) - assert_matches_type(ContentSubmitResponse, content, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_submit(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.content.with_raw_response.submit( - content={ - "text": "text", - "type": "text", - }, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - content = await response.parse() - assert_matches_type(ContentSubmitResponse, content, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_submit(self, async_client: AsyncModerationAPI) -> None: - async with async_client.content.with_streaming_response.submit( - content={ - "text": "text", - "type": "text", - }, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - content = await response.parse() - assert_matches_type(ContentSubmitResponse, content, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_queue.py b/tests/api_resources/test_queue.py deleted file mode 100644 index 5d555d8..0000000 --- a/tests/api_resources/test_queue.py +++ /dev/null @@ -1,210 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from tests.utils import assert_matches_type -from moderation_api import ModerationAPI, AsyncModerationAPI -from moderation_api.types import QueueGetStatsResponse, QueueRetrieveResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestQueue: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_retrieve(self, client: ModerationAPI) -> None: - queue = client.queue.retrieve( - "id", - ) - assert_matches_type(QueueRetrieveResponse, queue, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_retrieve(self, client: ModerationAPI) -> None: - response = client.queue.with_raw_response.retrieve( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - queue = response.parse() - assert_matches_type(QueueRetrieveResponse, queue, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_retrieve(self, client: ModerationAPI) -> None: - with client.queue.with_streaming_response.retrieve( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - queue = response.parse() - assert_matches_type(QueueRetrieveResponse, queue, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_retrieve(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.queue.with_raw_response.retrieve( - "", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_get_stats(self, client: ModerationAPI) -> None: - queue = client.queue.get_stats( - id="id", - ) - assert_matches_type(QueueGetStatsResponse, queue, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_get_stats_with_all_params(self, client: ModerationAPI) -> None: - queue = client.queue.get_stats( - id="id", - within_days="withinDays", - ) - assert_matches_type(QueueGetStatsResponse, queue, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_get_stats(self, client: ModerationAPI) -> None: - response = client.queue.with_raw_response.get_stats( - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - queue = response.parse() - assert_matches_type(QueueGetStatsResponse, queue, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_get_stats(self, client: ModerationAPI) -> None: - with client.queue.with_streaming_response.get_stats( - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - queue = response.parse() - assert_matches_type(QueueGetStatsResponse, queue, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_get_stats(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.queue.with_raw_response.get_stats( - id="", - ) - - -class TestAsyncQueue: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_retrieve(self, async_client: AsyncModerationAPI) -> None: - queue = await async_client.queue.retrieve( - "id", - ) - assert_matches_type(QueueRetrieveResponse, queue, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.queue.with_raw_response.retrieve( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - queue = await response.parse() - assert_matches_type(QueueRetrieveResponse, queue, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncModerationAPI) -> None: - async with async_client.queue.with_streaming_response.retrieve( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - queue = await response.parse() - assert_matches_type(QueueRetrieveResponse, queue, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_retrieve(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.queue.with_raw_response.retrieve( - "", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_get_stats(self, async_client: AsyncModerationAPI) -> None: - queue = await async_client.queue.get_stats( - id="id", - ) - assert_matches_type(QueueGetStatsResponse, queue, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_get_stats_with_all_params(self, async_client: AsyncModerationAPI) -> None: - queue = await async_client.queue.get_stats( - id="id", - within_days="withinDays", - ) - assert_matches_type(QueueGetStatsResponse, queue, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_get_stats(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.queue.with_raw_response.get_stats( - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - queue = await response.parse() - assert_matches_type(QueueGetStatsResponse, queue, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_get_stats(self, async_client: AsyncModerationAPI) -> None: - async with async_client.queue.with_streaming_response.get_stats( - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - queue = await response.parse() - assert_matches_type(QueueGetStatsResponse, queue, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_get_stats(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.queue.with_raw_response.get_stats( - id="", - ) diff --git a/tests/api_resources/test_webhook_secret.py b/tests/api_resources/test_webhook_secret.py deleted file mode 100644 index 0d8f0fc..0000000 --- a/tests/api_resources/test_webhook_secret.py +++ /dev/null @@ -1,80 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from tests.utils import assert_matches_type -from moderation_api import ModerationAPI, AsyncModerationAPI -from moderation_api.types import WebhookSecretRetrieveResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestWebhookSecret: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_retrieve(self, client: ModerationAPI) -> None: - webhook_secret = client.webhook_secret.retrieve() - assert_matches_type(WebhookSecretRetrieveResponse, webhook_secret, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_retrieve(self, client: ModerationAPI) -> None: - response = client.webhook_secret.with_raw_response.retrieve() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - webhook_secret = response.parse() - assert_matches_type(WebhookSecretRetrieveResponse, webhook_secret, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_retrieve(self, client: ModerationAPI) -> None: - with client.webhook_secret.with_streaming_response.retrieve() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - webhook_secret = response.parse() - assert_matches_type(WebhookSecretRetrieveResponse, webhook_secret, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncWebhookSecret: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_retrieve(self, async_client: AsyncModerationAPI) -> None: - webhook_secret = await async_client.webhook_secret.retrieve() - assert_matches_type(WebhookSecretRetrieveResponse, webhook_secret, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.webhook_secret.with_raw_response.retrieve() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - webhook_secret = await response.parse() - assert_matches_type(WebhookSecretRetrieveResponse, webhook_secret, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncModerationAPI) -> None: - async with async_client.webhook_secret.with_streaming_response.retrieve() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - webhook_secret = await response.parse() - assert_matches_type(WebhookSecretRetrieveResponse, webhook_secret, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_webhooks.py b/tests/api_resources/test_webhooks.py deleted file mode 100644 index 33ea5bd..0000000 --- a/tests/api_resources/test_webhooks.py +++ /dev/null @@ -1,464 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from tests.utils import assert_matches_type -from moderation_api import ModerationAPI, AsyncModerationAPI -from moderation_api.types import ( - WebhookListResponse, - WebhookCreateResponse, - WebhookDeleteResponse, - WebhookUpdateResponse, - WebhookRetrieveResponse, -) - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestWebhooks: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_create(self, client: ModerationAPI) -> None: - webhook = client.webhooks.create( - event_types=["QUEUE_ITEM_NEW"], - name="x", - url="https://example.com", - ) - assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_create_with_all_params(self, client: ModerationAPI) -> None: - webhook = client.webhooks.create( - event_types=["QUEUE_ITEM_NEW"], - name="x", - url="https://example.com", - description="description", - ) - assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_create(self, client: ModerationAPI) -> None: - response = client.webhooks.with_raw_response.create( - event_types=["QUEUE_ITEM_NEW"], - name="x", - url="https://example.com", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - webhook = response.parse() - assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_create(self, client: ModerationAPI) -> None: - with client.webhooks.with_streaming_response.create( - event_types=["QUEUE_ITEM_NEW"], - name="x", - url="https://example.com", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - webhook = response.parse() - assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_retrieve(self, client: ModerationAPI) -> None: - webhook = client.webhooks.retrieve( - "id", - ) - assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_retrieve(self, client: ModerationAPI) -> None: - response = client.webhooks.with_raw_response.retrieve( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - webhook = response.parse() - assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_retrieve(self, client: ModerationAPI) -> None: - with client.webhooks.with_streaming_response.retrieve( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - webhook = response.parse() - assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_retrieve(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.webhooks.with_raw_response.retrieve( - "", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_update(self, client: ModerationAPI) -> None: - webhook = client.webhooks.update( - id="id", - ) - assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_update_with_all_params(self, client: ModerationAPI) -> None: - webhook = client.webhooks.update( - id="id", - description="description", - event_types=["QUEUE_ITEM_NEW"], - name="x", - url="https://example.com", - ) - assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_update(self, client: ModerationAPI) -> None: - response = client.webhooks.with_raw_response.update( - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - webhook = response.parse() - assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_update(self, client: ModerationAPI) -> None: - with client.webhooks.with_streaming_response.update( - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - webhook = response.parse() - assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_update(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.webhooks.with_raw_response.update( - id="", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_list(self, client: ModerationAPI) -> None: - webhook = client.webhooks.list() - assert_matches_type(WebhookListResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_list(self, client: ModerationAPI) -> None: - response = client.webhooks.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - webhook = response.parse() - assert_matches_type(WebhookListResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_list(self, client: ModerationAPI) -> None: - with client.webhooks.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - webhook = response.parse() - assert_matches_type(WebhookListResponse, webhook, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_delete(self, client: ModerationAPI) -> None: - webhook = client.webhooks.delete( - "id", - ) - assert_matches_type(WebhookDeleteResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_delete(self, client: ModerationAPI) -> None: - response = client.webhooks.with_raw_response.delete( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - webhook = response.parse() - assert_matches_type(WebhookDeleteResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_delete(self, client: ModerationAPI) -> None: - with client.webhooks.with_streaming_response.delete( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - webhook = response.parse() - assert_matches_type(WebhookDeleteResponse, webhook, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_delete(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.webhooks.with_raw_response.delete( - "", - ) - - -class TestAsyncWebhooks: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_create(self, async_client: AsyncModerationAPI) -> None: - webhook = await async_client.webhooks.create( - event_types=["QUEUE_ITEM_NEW"], - name="x", - url="https://example.com", - ) - assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncModerationAPI) -> None: - webhook = await async_client.webhooks.create( - event_types=["QUEUE_ITEM_NEW"], - name="x", - url="https://example.com", - description="description", - ) - assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_create(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.webhooks.with_raw_response.create( - event_types=["QUEUE_ITEM_NEW"], - name="x", - url="https://example.com", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - webhook = await response.parse() - assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_create(self, async_client: AsyncModerationAPI) -> None: - async with async_client.webhooks.with_streaming_response.create( - event_types=["QUEUE_ITEM_NEW"], - name="x", - url="https://example.com", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - webhook = await response.parse() - assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_retrieve(self, async_client: AsyncModerationAPI) -> None: - webhook = await async_client.webhooks.retrieve( - "id", - ) - assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.webhooks.with_raw_response.retrieve( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - webhook = await response.parse() - assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncModerationAPI) -> None: - async with async_client.webhooks.with_streaming_response.retrieve( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - webhook = await response.parse() - assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_retrieve(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.webhooks.with_raw_response.retrieve( - "", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_update(self, async_client: AsyncModerationAPI) -> None: - webhook = await async_client.webhooks.update( - id="id", - ) - assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncModerationAPI) -> None: - webhook = await async_client.webhooks.update( - id="id", - description="description", - event_types=["QUEUE_ITEM_NEW"], - name="x", - url="https://example.com", - ) - assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_update(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.webhooks.with_raw_response.update( - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - webhook = await response.parse() - assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_update(self, async_client: AsyncModerationAPI) -> None: - async with async_client.webhooks.with_streaming_response.update( - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - webhook = await response.parse() - assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_update(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.webhooks.with_raw_response.update( - id="", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_list(self, async_client: AsyncModerationAPI) -> None: - webhook = await async_client.webhooks.list() - assert_matches_type(WebhookListResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_list(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.webhooks.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - webhook = await response.parse() - assert_matches_type(WebhookListResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_list(self, async_client: AsyncModerationAPI) -> None: - async with async_client.webhooks.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - webhook = await response.parse() - assert_matches_type(WebhookListResponse, webhook, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_delete(self, async_client: AsyncModerationAPI) -> None: - webhook = await async_client.webhooks.delete( - "id", - ) - assert_matches_type(WebhookDeleteResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_delete(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.webhooks.with_raw_response.delete( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - webhook = await response.parse() - assert_matches_type(WebhookDeleteResponse, webhook, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_delete(self, async_client: AsyncModerationAPI) -> None: - async with async_client.webhooks.with_streaming_response.delete( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - webhook = await response.parse() - assert_matches_type(WebhookDeleteResponse, webhook, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_delete(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.webhooks.with_raw_response.delete( - "", - ) diff --git a/tests/api_resources/test_wordlist.py b/tests/api_resources/test_wordlist.py deleted file mode 100644 index d1726cd..0000000 --- a/tests/api_resources/test_wordlist.py +++ /dev/null @@ -1,363 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from tests.utils import assert_matches_type -from moderation_api import ModerationAPI, AsyncModerationAPI -from moderation_api.types import ( - WordlistListResponse, - WordlistUpdateResponse, - WordlistRetrieveResponse, - WordlistGetEmbeddingStatusResponse, -) - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestWordlist: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_retrieve(self, client: ModerationAPI) -> None: - wordlist = client.wordlist.retrieve( - "id", - ) - assert_matches_type(WordlistRetrieveResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_retrieve(self, client: ModerationAPI) -> None: - response = client.wordlist.with_raw_response.retrieve( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - wordlist = response.parse() - assert_matches_type(WordlistRetrieveResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_retrieve(self, client: ModerationAPI) -> None: - with client.wordlist.with_streaming_response.retrieve( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - wordlist = response.parse() - assert_matches_type(WordlistRetrieveResponse, wordlist, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_retrieve(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.wordlist.with_raw_response.retrieve( - "", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_update(self, client: ModerationAPI) -> None: - wordlist = client.wordlist.update( - id="id", - ) - assert_matches_type(WordlistUpdateResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_update_with_all_params(self, client: ModerationAPI) -> None: - wordlist = client.wordlist.update( - id="id", - description="description", - key="key", - name="name", - strict=True, - words=["string"], - ) - assert_matches_type(WordlistUpdateResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_update(self, client: ModerationAPI) -> None: - response = client.wordlist.with_raw_response.update( - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - wordlist = response.parse() - assert_matches_type(WordlistUpdateResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_update(self, client: ModerationAPI) -> None: - with client.wordlist.with_streaming_response.update( - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - wordlist = response.parse() - assert_matches_type(WordlistUpdateResponse, wordlist, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_update(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.wordlist.with_raw_response.update( - id="", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_list(self, client: ModerationAPI) -> None: - wordlist = client.wordlist.list() - assert_matches_type(WordlistListResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_list(self, client: ModerationAPI) -> None: - response = client.wordlist.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - wordlist = response.parse() - assert_matches_type(WordlistListResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_list(self, client: ModerationAPI) -> None: - with client.wordlist.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - wordlist = response.parse() - assert_matches_type(WordlistListResponse, wordlist, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_get_embedding_status(self, client: ModerationAPI) -> None: - wordlist = client.wordlist.get_embedding_status( - "id", - ) - assert_matches_type(WordlistGetEmbeddingStatusResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_get_embedding_status(self, client: ModerationAPI) -> None: - response = client.wordlist.with_raw_response.get_embedding_status( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - wordlist = response.parse() - assert_matches_type(WordlistGetEmbeddingStatusResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_get_embedding_status(self, client: ModerationAPI) -> None: - with client.wordlist.with_streaming_response.get_embedding_status( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - wordlist = response.parse() - assert_matches_type(WordlistGetEmbeddingStatusResponse, wordlist, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_get_embedding_status(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.wordlist.with_raw_response.get_embedding_status( - "", - ) - - -class TestAsyncWordlist: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_retrieve(self, async_client: AsyncModerationAPI) -> None: - wordlist = await async_client.wordlist.retrieve( - "id", - ) - assert_matches_type(WordlistRetrieveResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.wordlist.with_raw_response.retrieve( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - wordlist = await response.parse() - assert_matches_type(WordlistRetrieveResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncModerationAPI) -> None: - async with async_client.wordlist.with_streaming_response.retrieve( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - wordlist = await response.parse() - assert_matches_type(WordlistRetrieveResponse, wordlist, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_retrieve(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.wordlist.with_raw_response.retrieve( - "", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_update(self, async_client: AsyncModerationAPI) -> None: - wordlist = await async_client.wordlist.update( - id="id", - ) - assert_matches_type(WordlistUpdateResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncModerationAPI) -> None: - wordlist = await async_client.wordlist.update( - id="id", - description="description", - key="key", - name="name", - strict=True, - words=["string"], - ) - assert_matches_type(WordlistUpdateResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_update(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.wordlist.with_raw_response.update( - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - wordlist = await response.parse() - assert_matches_type(WordlistUpdateResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_update(self, async_client: AsyncModerationAPI) -> None: - async with async_client.wordlist.with_streaming_response.update( - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - wordlist = await response.parse() - assert_matches_type(WordlistUpdateResponse, wordlist, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_update(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.wordlist.with_raw_response.update( - id="", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_list(self, async_client: AsyncModerationAPI) -> None: - wordlist = await async_client.wordlist.list() - assert_matches_type(WordlistListResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_list(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.wordlist.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - wordlist = await response.parse() - assert_matches_type(WordlistListResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_list(self, async_client: AsyncModerationAPI) -> None: - async with async_client.wordlist.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - wordlist = await response.parse() - assert_matches_type(WordlistListResponse, wordlist, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_get_embedding_status(self, async_client: AsyncModerationAPI) -> None: - wordlist = await async_client.wordlist.get_embedding_status( - "id", - ) - assert_matches_type(WordlistGetEmbeddingStatusResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_get_embedding_status(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.wordlist.with_raw_response.get_embedding_status( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - wordlist = await response.parse() - assert_matches_type(WordlistGetEmbeddingStatusResponse, wordlist, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_get_embedding_status(self, async_client: AsyncModerationAPI) -> None: - async with async_client.wordlist.with_streaming_response.get_embedding_status( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - wordlist = await response.parse() - assert_matches_type(WordlistGetEmbeddingStatusResponse, wordlist, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_get_embedding_status(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.wordlist.with_raw_response.get_embedding_status( - "", - ) diff --git a/tests/api_resources/wordlist/__init__.py b/tests/api_resources/wordlist/__init__.py deleted file mode 100644 index fd8019a..0000000 --- a/tests/api_resources/wordlist/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/wordlist/test_words.py b/tests/api_resources/wordlist/test_words.py deleted file mode 100644 index 03f04b2..0000000 --- a/tests/api_resources/wordlist/test_words.py +++ /dev/null @@ -1,208 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from tests.utils import assert_matches_type -from moderation_api import ModerationAPI, AsyncModerationAPI -from moderation_api.types.wordlist import WordAddResponse, WordRemoveResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestWords: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_add(self, client: ModerationAPI) -> None: - word = client.wordlist.words.add( - id="id", - words=["string"], - ) - assert_matches_type(WordAddResponse, word, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_add(self, client: ModerationAPI) -> None: - response = client.wordlist.words.with_raw_response.add( - id="id", - words=["string"], - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - word = response.parse() - assert_matches_type(WordAddResponse, word, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_add(self, client: ModerationAPI) -> None: - with client.wordlist.words.with_streaming_response.add( - id="id", - words=["string"], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - word = response.parse() - assert_matches_type(WordAddResponse, word, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_add(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.wordlist.words.with_raw_response.add( - id="", - words=["string"], - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_remove(self, client: ModerationAPI) -> None: - word = client.wordlist.words.remove( - id="id", - words=["string"], - ) - assert_matches_type(WordRemoveResponse, word, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_remove(self, client: ModerationAPI) -> None: - response = client.wordlist.words.with_raw_response.remove( - id="id", - words=["string"], - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - word = response.parse() - assert_matches_type(WordRemoveResponse, word, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_remove(self, client: ModerationAPI) -> None: - with client.wordlist.words.with_streaming_response.remove( - id="id", - words=["string"], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - word = response.parse() - assert_matches_type(WordRemoveResponse, word, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_remove(self, client: ModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.wordlist.words.with_raw_response.remove( - id="", - words=["string"], - ) - - -class TestAsyncWords: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_add(self, async_client: AsyncModerationAPI) -> None: - word = await async_client.wordlist.words.add( - id="id", - words=["string"], - ) - assert_matches_type(WordAddResponse, word, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_add(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.wordlist.words.with_raw_response.add( - id="id", - words=["string"], - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - word = await response.parse() - assert_matches_type(WordAddResponse, word, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_add(self, async_client: AsyncModerationAPI) -> None: - async with async_client.wordlist.words.with_streaming_response.add( - id="id", - words=["string"], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - word = await response.parse() - assert_matches_type(WordAddResponse, word, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_add(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.wordlist.words.with_raw_response.add( - id="", - words=["string"], - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_remove(self, async_client: AsyncModerationAPI) -> None: - word = await async_client.wordlist.words.remove( - id="id", - words=["string"], - ) - assert_matches_type(WordRemoveResponse, word, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_remove(self, async_client: AsyncModerationAPI) -> None: - response = await async_client.wordlist.words.with_raw_response.remove( - id="id", - words=["string"], - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - word = await response.parse() - assert_matches_type(WordRemoveResponse, word, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_remove(self, async_client: AsyncModerationAPI) -> None: - async with async_client.wordlist.words.with_streaming_response.remove( - id="id", - words=["string"], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - word = await response.parse() - assert_matches_type(WordRemoveResponse, word, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_remove(self, async_client: AsyncModerationAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.wordlist.words.with_raw_response.remove( - id="", - words=["string"], - ) diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index b95291f..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,84 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -import logging -from typing import TYPE_CHECKING, Iterator, AsyncIterator - -import httpx -import pytest -from pytest_asyncio import is_async_test - -from moderation_api import ModerationAPI, AsyncModerationAPI, DefaultAioHttpClient -from moderation_api._utils import is_dict - -if TYPE_CHECKING: - from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] - -pytest.register_assert_rewrite("tests.utils") - -logging.getLogger("moderation_api").setLevel(logging.DEBUG) - - -# automatically add `pytest.mark.asyncio()` to all of our async tests -# so we don't have to add that boilerplate everywhere -def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: - pytest_asyncio_tests = (item for item in items if is_async_test(item)) - session_scope_marker = pytest.mark.asyncio(loop_scope="session") - for async_test in pytest_asyncio_tests: - async_test.add_marker(session_scope_marker, append=False) - - # We skip tests that use both the aiohttp client and respx_mock as respx_mock - # doesn't support custom transports. - for item in items: - if "async_client" not in item.fixturenames or "respx_mock" not in item.fixturenames: - continue - - if not hasattr(item, "callspec"): - continue - - async_client_param = item.callspec.params.get("async_client") - if is_dict(async_client_param) and async_client_param.get("http_client") == "aiohttp": - item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx_mock")) - - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - -secret_key = "My Secret Key" - - -@pytest.fixture(scope="session") -def client(request: FixtureRequest) -> Iterator[ModerationAPI]: - strict = getattr(request, "param", True) - if not isinstance(strict, bool): - raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - - with ModerationAPI(base_url=base_url, secret_key=secret_key, _strict_response_validation=strict) as client: - yield client - - -@pytest.fixture(scope="session") -async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncModerationAPI]: - param = getattr(request, "param", True) - - # defaults - strict = True - http_client: None | httpx.AsyncClient = None - - if isinstance(param, bool): - strict = param - elif is_dict(param): - strict = param.get("strict", True) - assert isinstance(strict, bool) - - http_client_type = param.get("http_client", "httpx") - if http_client_type == "aiohttp": - http_client = DefaultAioHttpClient() - else: - raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") - - async with AsyncModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=strict, http_client=http_client - ) as client: - yield client diff --git a/tests/sample_file.txt b/tests/sample_file.txt deleted file mode 100644 index af5626b..0000000 --- a/tests/sample_file.txt +++ /dev/null @@ -1 +0,0 @@ -Hello, world! diff --git a/tests/test_client.py b/tests/test_client.py deleted file mode 100644 index a327d51..0000000 --- a/tests/test_client.py +++ /dev/null @@ -1,2042 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import gc -import os -import sys -import json -import asyncio -import inspect -import dataclasses -import tracemalloc -from typing import Any, Union, TypeVar, Callable, Iterable, Iterator, Optional, Coroutine, cast -from unittest import mock -from typing_extensions import Literal, AsyncIterator, override - -import httpx -import pytest -from respx import MockRouter -from pydantic import ValidationError - -from moderation_api import ModerationAPI, AsyncModerationAPI, APIResponseValidationError -from moderation_api._types import Omit -from moderation_api._utils import asyncify -from moderation_api._models import BaseModel, FinalRequestOptions -from moderation_api._exceptions import APIStatusError, APITimeoutError, ModerationAPIError, APIResponseValidationError -from moderation_api._base_client import ( - DEFAULT_TIMEOUT, - HTTPX_DEFAULT_TIMEOUT, - BaseClient, - OtherPlatform, - DefaultHttpxClient, - DefaultAsyncHttpxClient, - get_platform, - make_request_options, -) - -from .utils import update_env - -T = TypeVar("T") -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -secret_key = "My Secret Key" - - -def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]: - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - url = httpx.URL(request.url) - return dict(url.params) - - -def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float: - return 0.1 - - -def mirror_request_content(request: httpx.Request) -> httpx.Response: - return httpx.Response(200, content=request.content) - - -# note: we can't use the httpx.MockTransport class as it consumes the request -# body itself, which means we can't test that the body is read lazily -class MockTransport(httpx.BaseTransport, httpx.AsyncBaseTransport): - def __init__( - self, - handler: Callable[[httpx.Request], httpx.Response] - | Callable[[httpx.Request], Coroutine[Any, Any, httpx.Response]], - ) -> None: - self.handler = handler - - @override - def handle_request( - self, - request: httpx.Request, - ) -> httpx.Response: - assert not inspect.iscoroutinefunction(self.handler), "handler must not be a coroutine function" - assert inspect.isfunction(self.handler), "handler must be a function" - return self.handler(request) - - @override - async def handle_async_request( - self, - request: httpx.Request, - ) -> httpx.Response: - assert inspect.iscoroutinefunction(self.handler), "handler must be a coroutine function" - return await self.handler(request) - - -@dataclasses.dataclass -class Counter: - value: int = 0 - - -def _make_sync_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> Iterator[T]: - for item in iterable: - if counter: - counter.value += 1 - yield item - - -async def _make_async_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> AsyncIterator[T]: - for item in iterable: - if counter: - counter.value += 1 - yield item - - -def _get_open_connections(client: ModerationAPI | AsyncModerationAPI) -> int: - transport = client._client._transport - assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport) - - pool = transport._pool - return len(pool._requests) - - -class TestModerationAPI: - @pytest.mark.respx(base_url=base_url) - def test_raw_response(self, respx_mock: MockRouter, client: ModerationAPI) -> None: - respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - - response = client.post("/foo", cast_to=httpx.Response) - assert response.status_code == 200 - assert isinstance(response, httpx.Response) - assert response.json() == {"foo": "bar"} - - @pytest.mark.respx(base_url=base_url) - def test_raw_response_for_binary(self, respx_mock: MockRouter, client: ModerationAPI) -> None: - respx_mock.post("/foo").mock( - return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') - ) - - response = client.post("/foo", cast_to=httpx.Response) - assert response.status_code == 200 - assert isinstance(response, httpx.Response) - assert response.json() == {"foo": "bar"} - - def test_copy(self, client: ModerationAPI) -> None: - copied = client.copy() - assert id(copied) != id(client) - - copied = client.copy(secret_key="another My Secret Key") - assert copied.secret_key == "another My Secret Key" - assert client.secret_key == "My Secret Key" - - def test_copy_default_options(self, client: ModerationAPI) -> None: - # options that have a default are overridden correctly - copied = client.copy(max_retries=7) - assert copied.max_retries == 7 - assert client.max_retries == 2 - - copied2 = copied.copy(max_retries=6) - assert copied2.max_retries == 6 - assert copied.max_retries == 7 - - # timeout - assert isinstance(client.timeout, httpx.Timeout) - copied = client.copy(timeout=None) - assert copied.timeout is None - assert isinstance(client.timeout, httpx.Timeout) - - def test_copy_default_headers(self) -> None: - client = ModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} - ) - assert client.default_headers["X-Foo"] == "bar" - - # does not override the already given value when not specified - copied = client.copy() - assert copied.default_headers["X-Foo"] == "bar" - - # merges already given headers - copied = client.copy(default_headers={"X-Bar": "stainless"}) - assert copied.default_headers["X-Foo"] == "bar" - assert copied.default_headers["X-Bar"] == "stainless" - - # uses new values for any already given headers - copied = client.copy(default_headers={"X-Foo": "stainless"}) - assert copied.default_headers["X-Foo"] == "stainless" - - # set_default_headers - - # completely overrides already set values - copied = client.copy(set_default_headers={}) - assert copied.default_headers.get("X-Foo") is None - - copied = client.copy(set_default_headers={"X-Bar": "Robert"}) - assert copied.default_headers["X-Bar"] == "Robert" - - with pytest.raises( - ValueError, - match="`default_headers` and `set_default_headers` arguments are mutually exclusive", - ): - client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) - client.close() - - def test_copy_default_query(self) -> None: - client = ModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=True, default_query={"foo": "bar"} - ) - assert _get_params(client)["foo"] == "bar" - - # does not override the already given value when not specified - copied = client.copy() - assert _get_params(copied)["foo"] == "bar" - - # merges already given params - copied = client.copy(default_query={"bar": "stainless"}) - params = _get_params(copied) - assert params["foo"] == "bar" - assert params["bar"] == "stainless" - - # uses new values for any already given headers - copied = client.copy(default_query={"foo": "stainless"}) - assert _get_params(copied)["foo"] == "stainless" - - # set_default_query - - # completely overrides already set values - copied = client.copy(set_default_query={}) - assert _get_params(copied) == {} - - copied = client.copy(set_default_query={"bar": "Robert"}) - assert _get_params(copied)["bar"] == "Robert" - - with pytest.raises( - ValueError, - # TODO: update - match="`default_query` and `set_default_query` arguments are mutually exclusive", - ): - client.copy(set_default_query={}, default_query={"foo": "Bar"}) - - client.close() - - def test_copy_signature(self, client: ModerationAPI) -> None: - # ensure the same parameters that can be passed to the client are defined in the `.copy()` method - init_signature = inspect.signature( - # mypy doesn't like that we access the `__init__` property. - client.__init__, # type: ignore[misc] - ) - copy_signature = inspect.signature(client.copy) - exclude_params = {"transport", "proxies", "_strict_response_validation"} - - for name in init_signature.parameters.keys(): - if name in exclude_params: - continue - - copy_param = copy_signature.parameters.get(name) - assert copy_param is not None, f"copy() signature is missing the {name} param" - - @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") - def test_copy_build_request(self, client: ModerationAPI) -> None: - options = FinalRequestOptions(method="get", url="/foo") - - def build_request(options: FinalRequestOptions) -> None: - client_copy = client.copy() - client_copy._build_request(options) - - # ensure that the machinery is warmed up before tracing starts. - build_request(options) - gc.collect() - - tracemalloc.start(1000) - - snapshot_before = tracemalloc.take_snapshot() - - ITERATIONS = 10 - for _ in range(ITERATIONS): - build_request(options) - - gc.collect() - snapshot_after = tracemalloc.take_snapshot() - - tracemalloc.stop() - - def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff) -> None: - if diff.count == 0: - # Avoid false positives by considering only leaks (i.e. allocations that persist). - return - - if diff.count % ITERATIONS != 0: - # Avoid false positives by considering only leaks that appear per iteration. - return - - for frame in diff.traceback: - if any( - frame.filename.endswith(fragment) - for fragment in [ - # to_raw_response_wrapper leaks through the @functools.wraps() decorator. - # - # removing the decorator fixes the leak for reasons we don't understand. - "moderation_api/_legacy_response.py", - "moderation_api/_response.py", - # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason. - "moderation_api/_compat.py", - # Standard library leaks we don't care about. - "/logging/__init__.py", - ] - ): - return - - leaks.append(diff) - - leaks: list[tracemalloc.StatisticDiff] = [] - for diff in snapshot_after.compare_to(snapshot_before, "traceback"): - add_leak(leaks, diff) - if leaks: - for leak in leaks: - print("MEMORY LEAK:", leak) - for frame in leak.traceback: - print(frame) - raise AssertionError() - - def test_request_timeout(self, client: ModerationAPI) -> None: - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == DEFAULT_TIMEOUT - - request = client._build_request(FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0))) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == httpx.Timeout(100.0) - - def test_client_timeout_option(self) -> None: - client = ModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=True, timeout=httpx.Timeout(0) - ) - - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == httpx.Timeout(0) - - client.close() - - def test_http_client_timeout_option(self) -> None: - # custom timeout given to the httpx client should be used - with httpx.Client(timeout=None) as http_client: - client = ModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=True, http_client=http_client - ) - - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == httpx.Timeout(None) - - client.close() - - # no timeout given to the httpx client should not use the httpx default - with httpx.Client() as http_client: - client = ModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=True, http_client=http_client - ) - - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == DEFAULT_TIMEOUT - - client.close() - - # explicitly passing the default timeout currently results in it being ignored - with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: - client = ModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=True, http_client=http_client - ) - - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == DEFAULT_TIMEOUT # our default - - client.close() - - async def test_invalid_http_client(self) -> None: - with pytest.raises(TypeError, match="Invalid `http_client` arg"): - async with httpx.AsyncClient() as http_client: - ModerationAPI( - base_url=base_url, - secret_key=secret_key, - _strict_response_validation=True, - http_client=cast(Any, http_client), - ) - - def test_default_headers_option(self) -> None: - test_client = ModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} - ) - request = test_client._build_request(FinalRequestOptions(method="get", url="/foo")) - assert request.headers.get("x-foo") == "bar" - assert request.headers.get("x-stainless-lang") == "python" - - test_client2 = ModerationAPI( - base_url=base_url, - secret_key=secret_key, - _strict_response_validation=True, - default_headers={ - "X-Foo": "stainless", - "X-Stainless-Lang": "my-overriding-header", - }, - ) - request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo")) - assert request.headers.get("x-foo") == "stainless" - assert request.headers.get("x-stainless-lang") == "my-overriding-header" - - test_client.close() - test_client2.close() - - def test_validate_headers(self) -> None: - client = ModerationAPI(base_url=base_url, secret_key=secret_key, _strict_response_validation=True) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - assert request.headers.get("Authorization") == f"Bearer {secret_key}" - - with pytest.raises(ModerationAPIError): - with update_env(**{"MODAPI_SECRET_KEY": Omit()}): - client2 = ModerationAPI(base_url=base_url, secret_key=None, _strict_response_validation=True) - _ = client2 - - def test_default_query_option(self) -> None: - client = ModerationAPI( - base_url=base_url, - secret_key=secret_key, - _strict_response_validation=True, - default_query={"query_param": "bar"}, - ) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - url = httpx.URL(request.url) - assert dict(url.params) == {"query_param": "bar"} - - request = client._build_request( - FinalRequestOptions( - method="get", - url="/foo", - params={"foo": "baz", "query_param": "overridden"}, - ) - ) - url = httpx.URL(request.url) - assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} - - client.close() - - def test_hardcoded_query_params_in_url(self, client: ModerationAPI) -> None: - request = client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true")) - url = httpx.URL(request.url) - assert dict(url.params) == {"beta": "true"} - - request = client._build_request( - FinalRequestOptions( - method="get", - url="/foo?beta=true", - params={"limit": "10", "page": "abc"}, - ) - ) - url = httpx.URL(request.url) - assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"} - - request = client._build_request( - FinalRequestOptions( - method="get", - url="/files/a%2Fb?beta=true", - params={"limit": "10"}, - ) - ) - assert request.url.raw_path == b"/files/a%2Fb?beta=true&limit=10" - - def test_request_extra_json(self, client: ModerationAPI) -> None: - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - json_data={"foo": "bar"}, - extra_json={"baz": False}, - ), - ) - data = json.loads(request.content.decode("utf-8")) - assert data == {"foo": "bar", "baz": False} - - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - extra_json={"baz": False}, - ), - ) - data = json.loads(request.content.decode("utf-8")) - assert data == {"baz": False} - - # `extra_json` takes priority over `json_data` when keys clash - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - json_data={"foo": "bar", "baz": True}, - extra_json={"baz": None}, - ), - ) - data = json.loads(request.content.decode("utf-8")) - assert data == {"foo": "bar", "baz": None} - - def test_request_extra_headers(self, client: ModerationAPI) -> None: - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - **make_request_options(extra_headers={"X-Foo": "Foo"}), - ), - ) - assert request.headers.get("X-Foo") == "Foo" - - # `extra_headers` takes priority over `default_headers` when keys clash - request = client.with_options(default_headers={"X-Bar": "true"})._build_request( - FinalRequestOptions( - method="post", - url="/foo", - **make_request_options( - extra_headers={"X-Bar": "false"}, - ), - ), - ) - assert request.headers.get("X-Bar") == "false" - - def test_request_extra_query(self, client: ModerationAPI) -> None: - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - **make_request_options( - extra_query={"my_query_param": "Foo"}, - ), - ), - ) - params = dict(request.url.params) - assert params == {"my_query_param": "Foo"} - - # if both `query` and `extra_query` are given, they are merged - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - **make_request_options( - query={"bar": "1"}, - extra_query={"foo": "2"}, - ), - ), - ) - params = dict(request.url.params) - assert params == {"bar": "1", "foo": "2"} - - # `extra_query` takes priority over `query` when keys clash - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - **make_request_options( - query={"foo": "1"}, - extra_query={"foo": "2"}, - ), - ), - ) - params = dict(request.url.params) - assert params == {"foo": "2"} - - def test_multipart_repeating_array(self, client: ModerationAPI) -> None: - request = client._build_request( - FinalRequestOptions.construct( - method="post", - url="/foo", - headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, - json_data={"array": ["foo", "bar"]}, - files=[("foo.txt", b"hello world")], - ) - ) - - assert request.read().split(b"\r\n") == [ - b"--6b7ba517decee4a450543ea6ae821c82", - b'Content-Disposition: form-data; name="array[]"', - b"", - b"foo", - b"--6b7ba517decee4a450543ea6ae821c82", - b'Content-Disposition: form-data; name="array[]"', - b"", - b"bar", - b"--6b7ba517decee4a450543ea6ae821c82", - b'Content-Disposition: form-data; name="foo.txt"; filename="upload"', - b"Content-Type: application/octet-stream", - b"", - b"hello world", - b"--6b7ba517decee4a450543ea6ae821c82--", - b"", - ] - - @pytest.mark.respx(base_url=base_url) - def test_binary_content_upload(self, respx_mock: MockRouter, client: ModerationAPI) -> None: - respx_mock.post("/upload").mock(side_effect=mirror_request_content) - - file_content = b"Hello, this is a test file." - - response = client.post( - "/upload", - content=file_content, - cast_to=httpx.Response, - options={"headers": {"Content-Type": "application/octet-stream"}}, - ) - - assert response.status_code == 200 - assert response.request.headers["Content-Type"] == "application/octet-stream" - assert response.content == file_content - - def test_binary_content_upload_with_iterator(self) -> None: - file_content = b"Hello, this is a test file." - counter = Counter() - iterator = _make_sync_iterator([file_content], counter=counter) - - def mock_handler(request: httpx.Request) -> httpx.Response: - assert counter.value == 0, "the request body should not have been read" - return httpx.Response(200, content=request.read()) - - with ModerationAPI( - base_url=base_url, - secret_key=secret_key, - _strict_response_validation=True, - http_client=httpx.Client(transport=MockTransport(handler=mock_handler)), - ) as client: - response = client.post( - "/upload", - content=iterator, - cast_to=httpx.Response, - options={"headers": {"Content-Type": "application/octet-stream"}}, - ) - - assert response.status_code == 200 - assert response.request.headers["Content-Type"] == "application/octet-stream" - assert response.content == file_content - assert counter.value == 1 - - @pytest.mark.respx(base_url=base_url) - def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: ModerationAPI) -> None: - respx_mock.post("/upload").mock(side_effect=mirror_request_content) - - file_content = b"Hello, this is a test file." - - with pytest.deprecated_call( - match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead." - ): - response = client.post( - "/upload", - body=file_content, - cast_to=httpx.Response, - options={"headers": {"Content-Type": "application/octet-stream"}}, - ) - - assert response.status_code == 200 - assert response.request.headers["Content-Type"] == "application/octet-stream" - assert response.content == file_content - - @pytest.mark.respx(base_url=base_url) - def test_basic_union_response(self, respx_mock: MockRouter, client: ModerationAPI) -> None: - class Model1(BaseModel): - name: str - - class Model2(BaseModel): - foo: str - - respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - - response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) - assert isinstance(response, Model2) - assert response.foo == "bar" - - @pytest.mark.respx(base_url=base_url) - def test_union_response_different_types(self, respx_mock: MockRouter, client: ModerationAPI) -> None: - """Union of objects with the same field name using a different type""" - - class Model1(BaseModel): - foo: int - - class Model2(BaseModel): - foo: str - - respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - - response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) - assert isinstance(response, Model2) - assert response.foo == "bar" - - respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1})) - - response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) - assert isinstance(response, Model1) - assert response.foo == 1 - - @pytest.mark.respx(base_url=base_url) - def test_non_application_json_content_type_for_json_data( - self, respx_mock: MockRouter, client: ModerationAPI - ) -> None: - """ - Response that sets Content-Type to something other than application/json but returns json data - """ - - class Model(BaseModel): - foo: int - - respx_mock.get("/foo").mock( - return_value=httpx.Response( - 200, - content=json.dumps({"foo": 2}), - headers={"Content-Type": "application/text"}, - ) - ) - - response = client.get("/foo", cast_to=Model) - assert isinstance(response, Model) - assert response.foo == 2 - - def test_base_url_setter(self) -> None: - client = ModerationAPI( - base_url="https://example.com/from_init", secret_key=secret_key, _strict_response_validation=True - ) - assert client.base_url == "https://example.com/from_init/" - - client.base_url = "https://example.com/from_setter" # type: ignore[assignment] - - assert client.base_url == "https://example.com/from_setter/" - - client.close() - - def test_base_url_env(self) -> None: - with update_env(MODERATION_API_BASE_URL="http://localhost:5000/from/env"): - client = ModerationAPI(secret_key=secret_key, _strict_response_validation=True) - assert client.base_url == "http://localhost:5000/from/env/" - - @pytest.mark.parametrize( - "client", - [ - ModerationAPI( - base_url="http://localhost:5000/custom/path/", secret_key=secret_key, _strict_response_validation=True - ), - ModerationAPI( - base_url="http://localhost:5000/custom/path/", - secret_key=secret_key, - _strict_response_validation=True, - http_client=httpx.Client(), - ), - ], - ids=["standard", "custom http client"], - ) - def test_base_url_trailing_slash(self, client: ModerationAPI) -> None: - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - json_data={"foo": "bar"}, - ), - ) - assert request.url == "http://localhost:5000/custom/path/foo" - client.close() - - @pytest.mark.parametrize( - "client", - [ - ModerationAPI( - base_url="http://localhost:5000/custom/path/", secret_key=secret_key, _strict_response_validation=True - ), - ModerationAPI( - base_url="http://localhost:5000/custom/path/", - secret_key=secret_key, - _strict_response_validation=True, - http_client=httpx.Client(), - ), - ], - ids=["standard", "custom http client"], - ) - def test_base_url_no_trailing_slash(self, client: ModerationAPI) -> None: - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - json_data={"foo": "bar"}, - ), - ) - assert request.url == "http://localhost:5000/custom/path/foo" - client.close() - - @pytest.mark.parametrize( - "client", - [ - ModerationAPI( - base_url="http://localhost:5000/custom/path/", secret_key=secret_key, _strict_response_validation=True - ), - ModerationAPI( - base_url="http://localhost:5000/custom/path/", - secret_key=secret_key, - _strict_response_validation=True, - http_client=httpx.Client(), - ), - ], - ids=["standard", "custom http client"], - ) - def test_absolute_request_url(self, client: ModerationAPI) -> None: - request = client._build_request( - FinalRequestOptions( - method="post", - url="https://myapi.com/foo", - json_data={"foo": "bar"}, - ), - ) - assert request.url == "https://myapi.com/foo" - client.close() - - def test_copied_client_does_not_close_http(self) -> None: - test_client = ModerationAPI(base_url=base_url, secret_key=secret_key, _strict_response_validation=True) - assert not test_client.is_closed() - - copied = test_client.copy() - assert copied is not test_client - - del copied - - assert not test_client.is_closed() - - def test_client_context_manager(self) -> None: - test_client = ModerationAPI(base_url=base_url, secret_key=secret_key, _strict_response_validation=True) - with test_client as c2: - assert c2 is test_client - assert not c2.is_closed() - assert not test_client.is_closed() - assert test_client.is_closed() - - @pytest.mark.respx(base_url=base_url) - def test_client_response_validation_error(self, respx_mock: MockRouter, client: ModerationAPI) -> None: - class Model(BaseModel): - foo: str - - respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}})) - - with pytest.raises(APIResponseValidationError) as exc: - client.get("/foo", cast_to=Model) - - assert isinstance(exc.value.__cause__, ValidationError) - - def test_client_max_retries_validation(self) -> None: - with pytest.raises(TypeError, match=r"max_retries cannot be None"): - ModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=True, max_retries=cast(Any, None) - ) - - @pytest.mark.respx(base_url=base_url) - def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: - class Model(BaseModel): - name: str - - respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - - strict_client = ModerationAPI(base_url=base_url, secret_key=secret_key, _strict_response_validation=True) - - with pytest.raises(APIResponseValidationError): - strict_client.get("/foo", cast_to=Model) - - non_strict_client = ModerationAPI(base_url=base_url, secret_key=secret_key, _strict_response_validation=False) - - response = non_strict_client.get("/foo", cast_to=Model) - assert isinstance(response, str) # type: ignore[unreachable] - - strict_client.close() - non_strict_client.close() - - @pytest.mark.parametrize( - "remaining_retries,retry_after,timeout", - [ - [3, "20", 20], - [3, "0", 0.5], - [3, "-10", 0.5], - [3, "60", 60], - [3, "61", 0.5], - [3, "Fri, 29 Sep 2023 16:26:57 GMT", 20], - [3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5], - [3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5], - [3, "Fri, 29 Sep 2023 16:27:37 GMT", 60], - [3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5], - [3, "99999999999999999999999999999999999", 0.5], - [3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5], - [3, "", 0.5], - [2, "", 0.5 * 2.0], - [1, "", 0.5 * 4.0], - [-1100, "", 8], # test large number potentially overflowing - ], - ) - @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) - def test_parse_retry_after_header( - self, remaining_retries: int, retry_after: str, timeout: float, client: ModerationAPI - ) -> None: - headers = httpx.Headers({"retry-after": retry_after}) - options = FinalRequestOptions(method="get", url="/foo", max_retries=3) - calculated = client._calculate_retry_timeout(remaining_retries, options, headers) - assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType] - - @mock.patch("moderation_api._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) - def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: ModerationAPI) -> None: - respx_mock.post("/moderate").mock(side_effect=httpx.TimeoutException("Test timeout error")) - - with pytest.raises(APITimeoutError): - client.content.with_streaming_response.submit( - content={ - "text": "text", - "type": "text", - } - ).__enter__() - - assert _get_open_connections(client) == 0 - - @mock.patch("moderation_api._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) - def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: ModerationAPI) -> None: - respx_mock.post("/moderate").mock(return_value=httpx.Response(500)) - - with pytest.raises(APIStatusError): - client.content.with_streaming_response.submit( - content={ - "text": "text", - "type": "text", - } - ).__enter__() - assert _get_open_connections(client) == 0 - - @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) - @mock.patch("moderation_api._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) - @pytest.mark.parametrize("failure_mode", ["status", "exception"]) - def test_retries_taken( - self, - client: ModerationAPI, - failures_before_success: int, - failure_mode: Literal["status", "exception"], - respx_mock: MockRouter, - ) -> None: - client = client.with_options(max_retries=4) - - nb_retries = 0 - - def retry_handler(_request: httpx.Request) -> httpx.Response: - nonlocal nb_retries - if nb_retries < failures_before_success: - nb_retries += 1 - if failure_mode == "exception": - raise RuntimeError("oops") - return httpx.Response(500) - return httpx.Response(200) - - respx_mock.post("/moderate").mock(side_effect=retry_handler) - - response = client.content.with_raw_response.submit( - content={ - "text": "text", - "type": "text", - } - ) - - assert response.retries_taken == failures_before_success - assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success - - @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) - @mock.patch("moderation_api._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) - def test_omit_retry_count_header( - self, client: ModerationAPI, failures_before_success: int, respx_mock: MockRouter - ) -> None: - client = client.with_options(max_retries=4) - - nb_retries = 0 - - def retry_handler(_request: httpx.Request) -> httpx.Response: - nonlocal nb_retries - if nb_retries < failures_before_success: - nb_retries += 1 - return httpx.Response(500) - return httpx.Response(200) - - respx_mock.post("/moderate").mock(side_effect=retry_handler) - - response = client.content.with_raw_response.submit( - content={ - "text": "text", - "type": "text", - }, - extra_headers={"x-stainless-retry-count": Omit()}, - ) - - assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 - - @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) - @mock.patch("moderation_api._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) - def test_overwrite_retry_count_header( - self, client: ModerationAPI, failures_before_success: int, respx_mock: MockRouter - ) -> None: - client = client.with_options(max_retries=4) - - nb_retries = 0 - - def retry_handler(_request: httpx.Request) -> httpx.Response: - nonlocal nb_retries - if nb_retries < failures_before_success: - nb_retries += 1 - return httpx.Response(500) - return httpx.Response(200) - - respx_mock.post("/moderate").mock(side_effect=retry_handler) - - response = client.content.with_raw_response.submit( - content={ - "text": "text", - "type": "text", - }, - extra_headers={"x-stainless-retry-count": "42"}, - ) - - assert response.http_request.headers.get("x-stainless-retry-count") == "42" - - def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: - # Test that the proxy environment variables are set correctly - monkeypatch.setenv("HTTPS_PROXY", "https://example.org") - # Delete in case our environment has any proxy env vars set - monkeypatch.delenv("HTTP_PROXY", raising=False) - monkeypatch.delenv("ALL_PROXY", raising=False) - monkeypatch.delenv("NO_PROXY", raising=False) - monkeypatch.delenv("http_proxy", raising=False) - monkeypatch.delenv("https_proxy", raising=False) - monkeypatch.delenv("all_proxy", raising=False) - monkeypatch.delenv("no_proxy", raising=False) - - client = DefaultHttpxClient() - - mounts = tuple(client._mounts.items()) - assert len(mounts) == 1 - assert mounts[0][0].pattern == "https://" - - @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") - def test_default_client_creation(self) -> None: - # Ensure that the client can be initialized without any exceptions - DefaultHttpxClient( - verify=True, - cert=None, - trust_env=True, - http1=True, - http2=False, - limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), - ) - - @pytest.mark.respx(base_url=base_url) - def test_follow_redirects(self, respx_mock: MockRouter, client: ModerationAPI) -> None: - # Test that the default follow_redirects=True allows following redirects - respx_mock.post("/redirect").mock( - return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) - ) - respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) - - response = client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) - assert response.status_code == 200 - assert response.json() == {"status": "ok"} - - @pytest.mark.respx(base_url=base_url) - def test_follow_redirects_disabled(self, respx_mock: MockRouter, client: ModerationAPI) -> None: - # Test that follow_redirects=False prevents following redirects - respx_mock.post("/redirect").mock( - return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) - ) - - with pytest.raises(APIStatusError) as exc_info: - client.post("/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response) - - assert exc_info.value.response.status_code == 302 - assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" - - -class TestAsyncModerationAPI: - @pytest.mark.respx(base_url=base_url) - async def test_raw_response(self, respx_mock: MockRouter, async_client: AsyncModerationAPI) -> None: - respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - - response = await async_client.post("/foo", cast_to=httpx.Response) - assert response.status_code == 200 - assert isinstance(response, httpx.Response) - assert response.json() == {"foo": "bar"} - - @pytest.mark.respx(base_url=base_url) - async def test_raw_response_for_binary(self, respx_mock: MockRouter, async_client: AsyncModerationAPI) -> None: - respx_mock.post("/foo").mock( - return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') - ) - - response = await async_client.post("/foo", cast_to=httpx.Response) - assert response.status_code == 200 - assert isinstance(response, httpx.Response) - assert response.json() == {"foo": "bar"} - - def test_copy(self, async_client: AsyncModerationAPI) -> None: - copied = async_client.copy() - assert id(copied) != id(async_client) - - copied = async_client.copy(secret_key="another My Secret Key") - assert copied.secret_key == "another My Secret Key" - assert async_client.secret_key == "My Secret Key" - - def test_copy_default_options(self, async_client: AsyncModerationAPI) -> None: - # options that have a default are overridden correctly - copied = async_client.copy(max_retries=7) - assert copied.max_retries == 7 - assert async_client.max_retries == 2 - - copied2 = copied.copy(max_retries=6) - assert copied2.max_retries == 6 - assert copied.max_retries == 7 - - # timeout - assert isinstance(async_client.timeout, httpx.Timeout) - copied = async_client.copy(timeout=None) - assert copied.timeout is None - assert isinstance(async_client.timeout, httpx.Timeout) - - async def test_copy_default_headers(self) -> None: - client = AsyncModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} - ) - assert client.default_headers["X-Foo"] == "bar" - - # does not override the already given value when not specified - copied = client.copy() - assert copied.default_headers["X-Foo"] == "bar" - - # merges already given headers - copied = client.copy(default_headers={"X-Bar": "stainless"}) - assert copied.default_headers["X-Foo"] == "bar" - assert copied.default_headers["X-Bar"] == "stainless" - - # uses new values for any already given headers - copied = client.copy(default_headers={"X-Foo": "stainless"}) - assert copied.default_headers["X-Foo"] == "stainless" - - # set_default_headers - - # completely overrides already set values - copied = client.copy(set_default_headers={}) - assert copied.default_headers.get("X-Foo") is None - - copied = client.copy(set_default_headers={"X-Bar": "Robert"}) - assert copied.default_headers["X-Bar"] == "Robert" - - with pytest.raises( - ValueError, - match="`default_headers` and `set_default_headers` arguments are mutually exclusive", - ): - client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) - await client.close() - - async def test_copy_default_query(self) -> None: - client = AsyncModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=True, default_query={"foo": "bar"} - ) - assert _get_params(client)["foo"] == "bar" - - # does not override the already given value when not specified - copied = client.copy() - assert _get_params(copied)["foo"] == "bar" - - # merges already given params - copied = client.copy(default_query={"bar": "stainless"}) - params = _get_params(copied) - assert params["foo"] == "bar" - assert params["bar"] == "stainless" - - # uses new values for any already given headers - copied = client.copy(default_query={"foo": "stainless"}) - assert _get_params(copied)["foo"] == "stainless" - - # set_default_query - - # completely overrides already set values - copied = client.copy(set_default_query={}) - assert _get_params(copied) == {} - - copied = client.copy(set_default_query={"bar": "Robert"}) - assert _get_params(copied)["bar"] == "Robert" - - with pytest.raises( - ValueError, - # TODO: update - match="`default_query` and `set_default_query` arguments are mutually exclusive", - ): - client.copy(set_default_query={}, default_query={"foo": "Bar"}) - - await client.close() - - def test_copy_signature(self, async_client: AsyncModerationAPI) -> None: - # ensure the same parameters that can be passed to the client are defined in the `.copy()` method - init_signature = inspect.signature( - # mypy doesn't like that we access the `__init__` property. - async_client.__init__, # type: ignore[misc] - ) - copy_signature = inspect.signature(async_client.copy) - exclude_params = {"transport", "proxies", "_strict_response_validation"} - - for name in init_signature.parameters.keys(): - if name in exclude_params: - continue - - copy_param = copy_signature.parameters.get(name) - assert copy_param is not None, f"copy() signature is missing the {name} param" - - @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") - def test_copy_build_request(self, async_client: AsyncModerationAPI) -> None: - options = FinalRequestOptions(method="get", url="/foo") - - def build_request(options: FinalRequestOptions) -> None: - client_copy = async_client.copy() - client_copy._build_request(options) - - # ensure that the machinery is warmed up before tracing starts. - build_request(options) - gc.collect() - - tracemalloc.start(1000) - - snapshot_before = tracemalloc.take_snapshot() - - ITERATIONS = 10 - for _ in range(ITERATIONS): - build_request(options) - - gc.collect() - snapshot_after = tracemalloc.take_snapshot() - - tracemalloc.stop() - - def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff) -> None: - if diff.count == 0: - # Avoid false positives by considering only leaks (i.e. allocations that persist). - return - - if diff.count % ITERATIONS != 0: - # Avoid false positives by considering only leaks that appear per iteration. - return - - for frame in diff.traceback: - if any( - frame.filename.endswith(fragment) - for fragment in [ - # to_raw_response_wrapper leaks through the @functools.wraps() decorator. - # - # removing the decorator fixes the leak for reasons we don't understand. - "moderation_api/_legacy_response.py", - "moderation_api/_response.py", - # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason. - "moderation_api/_compat.py", - # Standard library leaks we don't care about. - "/logging/__init__.py", - ] - ): - return - - leaks.append(diff) - - leaks: list[tracemalloc.StatisticDiff] = [] - for diff in snapshot_after.compare_to(snapshot_before, "traceback"): - add_leak(leaks, diff) - if leaks: - for leak in leaks: - print("MEMORY LEAK:", leak) - for frame in leak.traceback: - print(frame) - raise AssertionError() - - async def test_request_timeout(self, async_client: AsyncModerationAPI) -> None: - request = async_client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == DEFAULT_TIMEOUT - - request = async_client._build_request( - FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0)) - ) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == httpx.Timeout(100.0) - - async def test_client_timeout_option(self) -> None: - client = AsyncModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=True, timeout=httpx.Timeout(0) - ) - - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == httpx.Timeout(0) - - await client.close() - - async def test_http_client_timeout_option(self) -> None: - # custom timeout given to the httpx client should be used - async with httpx.AsyncClient(timeout=None) as http_client: - client = AsyncModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=True, http_client=http_client - ) - - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == httpx.Timeout(None) - - await client.close() - - # no timeout given to the httpx client should not use the httpx default - async with httpx.AsyncClient() as http_client: - client = AsyncModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=True, http_client=http_client - ) - - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == DEFAULT_TIMEOUT - - await client.close() - - # explicitly passing the default timeout currently results in it being ignored - async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: - client = AsyncModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=True, http_client=http_client - ) - - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == DEFAULT_TIMEOUT # our default - - await client.close() - - def test_invalid_http_client(self) -> None: - with pytest.raises(TypeError, match="Invalid `http_client` arg"): - with httpx.Client() as http_client: - AsyncModerationAPI( - base_url=base_url, - secret_key=secret_key, - _strict_response_validation=True, - http_client=cast(Any, http_client), - ) - - async def test_default_headers_option(self) -> None: - test_client = AsyncModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} - ) - request = test_client._build_request(FinalRequestOptions(method="get", url="/foo")) - assert request.headers.get("x-foo") == "bar" - assert request.headers.get("x-stainless-lang") == "python" - - test_client2 = AsyncModerationAPI( - base_url=base_url, - secret_key=secret_key, - _strict_response_validation=True, - default_headers={ - "X-Foo": "stainless", - "X-Stainless-Lang": "my-overriding-header", - }, - ) - request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo")) - assert request.headers.get("x-foo") == "stainless" - assert request.headers.get("x-stainless-lang") == "my-overriding-header" - - await test_client.close() - await test_client2.close() - - def test_validate_headers(self) -> None: - client = AsyncModerationAPI(base_url=base_url, secret_key=secret_key, _strict_response_validation=True) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - assert request.headers.get("Authorization") == f"Bearer {secret_key}" - - with pytest.raises(ModerationAPIError): - with update_env(**{"MODAPI_SECRET_KEY": Omit()}): - client2 = AsyncModerationAPI(base_url=base_url, secret_key=None, _strict_response_validation=True) - _ = client2 - - async def test_default_query_option(self) -> None: - client = AsyncModerationAPI( - base_url=base_url, - secret_key=secret_key, - _strict_response_validation=True, - default_query={"query_param": "bar"}, - ) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - url = httpx.URL(request.url) - assert dict(url.params) == {"query_param": "bar"} - - request = client._build_request( - FinalRequestOptions( - method="get", - url="/foo", - params={"foo": "baz", "query_param": "overridden"}, - ) - ) - url = httpx.URL(request.url) - assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} - - await client.close() - - async def test_hardcoded_query_params_in_url(self, async_client: AsyncModerationAPI) -> None: - request = async_client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true")) - url = httpx.URL(request.url) - assert dict(url.params) == {"beta": "true"} - - request = async_client._build_request( - FinalRequestOptions( - method="get", - url="/foo?beta=true", - params={"limit": "10", "page": "abc"}, - ) - ) - url = httpx.URL(request.url) - assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"} - - request = async_client._build_request( - FinalRequestOptions( - method="get", - url="/files/a%2Fb?beta=true", - params={"limit": "10"}, - ) - ) - assert request.url.raw_path == b"/files/a%2Fb?beta=true&limit=10" - - def test_request_extra_json(self, client: ModerationAPI) -> None: - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - json_data={"foo": "bar"}, - extra_json={"baz": False}, - ), - ) - data = json.loads(request.content.decode("utf-8")) - assert data == {"foo": "bar", "baz": False} - - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - extra_json={"baz": False}, - ), - ) - data = json.loads(request.content.decode("utf-8")) - assert data == {"baz": False} - - # `extra_json` takes priority over `json_data` when keys clash - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - json_data={"foo": "bar", "baz": True}, - extra_json={"baz": None}, - ), - ) - data = json.loads(request.content.decode("utf-8")) - assert data == {"foo": "bar", "baz": None} - - def test_request_extra_headers(self, client: ModerationAPI) -> None: - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - **make_request_options(extra_headers={"X-Foo": "Foo"}), - ), - ) - assert request.headers.get("X-Foo") == "Foo" - - # `extra_headers` takes priority over `default_headers` when keys clash - request = client.with_options(default_headers={"X-Bar": "true"})._build_request( - FinalRequestOptions( - method="post", - url="/foo", - **make_request_options( - extra_headers={"X-Bar": "false"}, - ), - ), - ) - assert request.headers.get("X-Bar") == "false" - - def test_request_extra_query(self, client: ModerationAPI) -> None: - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - **make_request_options( - extra_query={"my_query_param": "Foo"}, - ), - ), - ) - params = dict(request.url.params) - assert params == {"my_query_param": "Foo"} - - # if both `query` and `extra_query` are given, they are merged - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - **make_request_options( - query={"bar": "1"}, - extra_query={"foo": "2"}, - ), - ), - ) - params = dict(request.url.params) - assert params == {"bar": "1", "foo": "2"} - - # `extra_query` takes priority over `query` when keys clash - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - **make_request_options( - query={"foo": "1"}, - extra_query={"foo": "2"}, - ), - ), - ) - params = dict(request.url.params) - assert params == {"foo": "2"} - - def test_multipart_repeating_array(self, async_client: AsyncModerationAPI) -> None: - request = async_client._build_request( - FinalRequestOptions.construct( - method="post", - url="/foo", - headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, - json_data={"array": ["foo", "bar"]}, - files=[("foo.txt", b"hello world")], - ) - ) - - assert request.read().split(b"\r\n") == [ - b"--6b7ba517decee4a450543ea6ae821c82", - b'Content-Disposition: form-data; name="array[]"', - b"", - b"foo", - b"--6b7ba517decee4a450543ea6ae821c82", - b'Content-Disposition: form-data; name="array[]"', - b"", - b"bar", - b"--6b7ba517decee4a450543ea6ae821c82", - b'Content-Disposition: form-data; name="foo.txt"; filename="upload"', - b"Content-Type: application/octet-stream", - b"", - b"hello world", - b"--6b7ba517decee4a450543ea6ae821c82--", - b"", - ] - - @pytest.mark.respx(base_url=base_url) - async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncModerationAPI) -> None: - respx_mock.post("/upload").mock(side_effect=mirror_request_content) - - file_content = b"Hello, this is a test file." - - response = await async_client.post( - "/upload", - content=file_content, - cast_to=httpx.Response, - options={"headers": {"Content-Type": "application/octet-stream"}}, - ) - - assert response.status_code == 200 - assert response.request.headers["Content-Type"] == "application/octet-stream" - assert response.content == file_content - - async def test_binary_content_upload_with_asynciterator(self) -> None: - file_content = b"Hello, this is a test file." - counter = Counter() - iterator = _make_async_iterator([file_content], counter=counter) - - async def mock_handler(request: httpx.Request) -> httpx.Response: - assert counter.value == 0, "the request body should not have been read" - return httpx.Response(200, content=await request.aread()) - - async with AsyncModerationAPI( - base_url=base_url, - secret_key=secret_key, - _strict_response_validation=True, - http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)), - ) as client: - response = await client.post( - "/upload", - content=iterator, - cast_to=httpx.Response, - options={"headers": {"Content-Type": "application/octet-stream"}}, - ) - - assert response.status_code == 200 - assert response.request.headers["Content-Type"] == "application/octet-stream" - assert response.content == file_content - assert counter.value == 1 - - @pytest.mark.respx(base_url=base_url) - async def test_binary_content_upload_with_body_is_deprecated( - self, respx_mock: MockRouter, async_client: AsyncModerationAPI - ) -> None: - respx_mock.post("/upload").mock(side_effect=mirror_request_content) - - file_content = b"Hello, this is a test file." - - with pytest.deprecated_call( - match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead." - ): - response = await async_client.post( - "/upload", - body=file_content, - cast_to=httpx.Response, - options={"headers": {"Content-Type": "application/octet-stream"}}, - ) - - assert response.status_code == 200 - assert response.request.headers["Content-Type"] == "application/octet-stream" - assert response.content == file_content - - @pytest.mark.respx(base_url=base_url) - async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncModerationAPI) -> None: - class Model1(BaseModel): - name: str - - class Model2(BaseModel): - foo: str - - respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - - response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) - assert isinstance(response, Model2) - assert response.foo == "bar" - - @pytest.mark.respx(base_url=base_url) - async def test_union_response_different_types( - self, respx_mock: MockRouter, async_client: AsyncModerationAPI - ) -> None: - """Union of objects with the same field name using a different type""" - - class Model1(BaseModel): - foo: int - - class Model2(BaseModel): - foo: str - - respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - - response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) - assert isinstance(response, Model2) - assert response.foo == "bar" - - respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1})) - - response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) - assert isinstance(response, Model1) - assert response.foo == 1 - - @pytest.mark.respx(base_url=base_url) - async def test_non_application_json_content_type_for_json_data( - self, respx_mock: MockRouter, async_client: AsyncModerationAPI - ) -> None: - """ - Response that sets Content-Type to something other than application/json but returns json data - """ - - class Model(BaseModel): - foo: int - - respx_mock.get("/foo").mock( - return_value=httpx.Response( - 200, - content=json.dumps({"foo": 2}), - headers={"Content-Type": "application/text"}, - ) - ) - - response = await async_client.get("/foo", cast_to=Model) - assert isinstance(response, Model) - assert response.foo == 2 - - async def test_base_url_setter(self) -> None: - client = AsyncModerationAPI( - base_url="https://example.com/from_init", secret_key=secret_key, _strict_response_validation=True - ) - assert client.base_url == "https://example.com/from_init/" - - client.base_url = "https://example.com/from_setter" # type: ignore[assignment] - - assert client.base_url == "https://example.com/from_setter/" - - await client.close() - - async def test_base_url_env(self) -> None: - with update_env(MODERATION_API_BASE_URL="http://localhost:5000/from/env"): - client = AsyncModerationAPI(secret_key=secret_key, _strict_response_validation=True) - assert client.base_url == "http://localhost:5000/from/env/" - - @pytest.mark.parametrize( - "client", - [ - AsyncModerationAPI( - base_url="http://localhost:5000/custom/path/", secret_key=secret_key, _strict_response_validation=True - ), - AsyncModerationAPI( - base_url="http://localhost:5000/custom/path/", - secret_key=secret_key, - _strict_response_validation=True, - http_client=httpx.AsyncClient(), - ), - ], - ids=["standard", "custom http client"], - ) - async def test_base_url_trailing_slash(self, client: AsyncModerationAPI) -> None: - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - json_data={"foo": "bar"}, - ), - ) - assert request.url == "http://localhost:5000/custom/path/foo" - await client.close() - - @pytest.mark.parametrize( - "client", - [ - AsyncModerationAPI( - base_url="http://localhost:5000/custom/path/", secret_key=secret_key, _strict_response_validation=True - ), - AsyncModerationAPI( - base_url="http://localhost:5000/custom/path/", - secret_key=secret_key, - _strict_response_validation=True, - http_client=httpx.AsyncClient(), - ), - ], - ids=["standard", "custom http client"], - ) - async def test_base_url_no_trailing_slash(self, client: AsyncModerationAPI) -> None: - request = client._build_request( - FinalRequestOptions( - method="post", - url="/foo", - json_data={"foo": "bar"}, - ), - ) - assert request.url == "http://localhost:5000/custom/path/foo" - await client.close() - - @pytest.mark.parametrize( - "client", - [ - AsyncModerationAPI( - base_url="http://localhost:5000/custom/path/", secret_key=secret_key, _strict_response_validation=True - ), - AsyncModerationAPI( - base_url="http://localhost:5000/custom/path/", - secret_key=secret_key, - _strict_response_validation=True, - http_client=httpx.AsyncClient(), - ), - ], - ids=["standard", "custom http client"], - ) - async def test_absolute_request_url(self, client: AsyncModerationAPI) -> None: - request = client._build_request( - FinalRequestOptions( - method="post", - url="https://myapi.com/foo", - json_data={"foo": "bar"}, - ), - ) - assert request.url == "https://myapi.com/foo" - await client.close() - - async def test_copied_client_does_not_close_http(self) -> None: - test_client = AsyncModerationAPI(base_url=base_url, secret_key=secret_key, _strict_response_validation=True) - assert not test_client.is_closed() - - copied = test_client.copy() - assert copied is not test_client - - del copied - - await asyncio.sleep(0.2) - assert not test_client.is_closed() - - async def test_client_context_manager(self) -> None: - test_client = AsyncModerationAPI(base_url=base_url, secret_key=secret_key, _strict_response_validation=True) - async with test_client as c2: - assert c2 is test_client - assert not c2.is_closed() - assert not test_client.is_closed() - assert test_client.is_closed() - - @pytest.mark.respx(base_url=base_url) - async def test_client_response_validation_error( - self, respx_mock: MockRouter, async_client: AsyncModerationAPI - ) -> None: - class Model(BaseModel): - foo: str - - respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}})) - - with pytest.raises(APIResponseValidationError) as exc: - await async_client.get("/foo", cast_to=Model) - - assert isinstance(exc.value.__cause__, ValidationError) - - async def test_client_max_retries_validation(self) -> None: - with pytest.raises(TypeError, match=r"max_retries cannot be None"): - AsyncModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=True, max_retries=cast(Any, None) - ) - - @pytest.mark.respx(base_url=base_url) - async def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: - class Model(BaseModel): - name: str - - respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - - strict_client = AsyncModerationAPI(base_url=base_url, secret_key=secret_key, _strict_response_validation=True) - - with pytest.raises(APIResponseValidationError): - await strict_client.get("/foo", cast_to=Model) - - non_strict_client = AsyncModerationAPI( - base_url=base_url, secret_key=secret_key, _strict_response_validation=False - ) - - response = await non_strict_client.get("/foo", cast_to=Model) - assert isinstance(response, str) # type: ignore[unreachable] - - await strict_client.close() - await non_strict_client.close() - - @pytest.mark.parametrize( - "remaining_retries,retry_after,timeout", - [ - [3, "20", 20], - [3, "0", 0.5], - [3, "-10", 0.5], - [3, "60", 60], - [3, "61", 0.5], - [3, "Fri, 29 Sep 2023 16:26:57 GMT", 20], - [3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5], - [3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5], - [3, "Fri, 29 Sep 2023 16:27:37 GMT", 60], - [3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5], - [3, "99999999999999999999999999999999999", 0.5], - [3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5], - [3, "", 0.5], - [2, "", 0.5 * 2.0], - [1, "", 0.5 * 4.0], - [-1100, "", 8], # test large number potentially overflowing - ], - ) - @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) - async def test_parse_retry_after_header( - self, remaining_retries: int, retry_after: str, timeout: float, async_client: AsyncModerationAPI - ) -> None: - headers = httpx.Headers({"retry-after": retry_after}) - options = FinalRequestOptions(method="get", url="/foo", max_retries=3) - calculated = async_client._calculate_retry_timeout(remaining_retries, options, headers) - assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType] - - @mock.patch("moderation_api._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) - async def test_retrying_timeout_errors_doesnt_leak( - self, respx_mock: MockRouter, async_client: AsyncModerationAPI - ) -> None: - respx_mock.post("/moderate").mock(side_effect=httpx.TimeoutException("Test timeout error")) - - with pytest.raises(APITimeoutError): - await async_client.content.with_streaming_response.submit( - content={ - "text": "text", - "type": "text", - } - ).__aenter__() - - assert _get_open_connections(async_client) == 0 - - @mock.patch("moderation_api._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) - async def test_retrying_status_errors_doesnt_leak( - self, respx_mock: MockRouter, async_client: AsyncModerationAPI - ) -> None: - respx_mock.post("/moderate").mock(return_value=httpx.Response(500)) - - with pytest.raises(APIStatusError): - await async_client.content.with_streaming_response.submit( - content={ - "text": "text", - "type": "text", - } - ).__aenter__() - assert _get_open_connections(async_client) == 0 - - @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) - @mock.patch("moderation_api._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) - @pytest.mark.parametrize("failure_mode", ["status", "exception"]) - async def test_retries_taken( - self, - async_client: AsyncModerationAPI, - failures_before_success: int, - failure_mode: Literal["status", "exception"], - respx_mock: MockRouter, - ) -> None: - client = async_client.with_options(max_retries=4) - - nb_retries = 0 - - def retry_handler(_request: httpx.Request) -> httpx.Response: - nonlocal nb_retries - if nb_retries < failures_before_success: - nb_retries += 1 - if failure_mode == "exception": - raise RuntimeError("oops") - return httpx.Response(500) - return httpx.Response(200) - - respx_mock.post("/moderate").mock(side_effect=retry_handler) - - response = await client.content.with_raw_response.submit( - content={ - "text": "text", - "type": "text", - } - ) - - assert response.retries_taken == failures_before_success - assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success - - @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) - @mock.patch("moderation_api._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) - async def test_omit_retry_count_header( - self, async_client: AsyncModerationAPI, failures_before_success: int, respx_mock: MockRouter - ) -> None: - client = async_client.with_options(max_retries=4) - - nb_retries = 0 - - def retry_handler(_request: httpx.Request) -> httpx.Response: - nonlocal nb_retries - if nb_retries < failures_before_success: - nb_retries += 1 - return httpx.Response(500) - return httpx.Response(200) - - respx_mock.post("/moderate").mock(side_effect=retry_handler) - - response = await client.content.with_raw_response.submit( - content={ - "text": "text", - "type": "text", - }, - extra_headers={"x-stainless-retry-count": Omit()}, - ) - - assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 - - @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) - @mock.patch("moderation_api._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) - async def test_overwrite_retry_count_header( - self, async_client: AsyncModerationAPI, failures_before_success: int, respx_mock: MockRouter - ) -> None: - client = async_client.with_options(max_retries=4) - - nb_retries = 0 - - def retry_handler(_request: httpx.Request) -> httpx.Response: - nonlocal nb_retries - if nb_retries < failures_before_success: - nb_retries += 1 - return httpx.Response(500) - return httpx.Response(200) - - respx_mock.post("/moderate").mock(side_effect=retry_handler) - - response = await client.content.with_raw_response.submit( - content={ - "text": "text", - "type": "text", - }, - extra_headers={"x-stainless-retry-count": "42"}, - ) - - assert response.http_request.headers.get("x-stainless-retry-count") == "42" - - async def test_get_platform(self) -> None: - platform = await asyncify(get_platform)() - assert isinstance(platform, (str, OtherPlatform)) - - async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: - # Test that the proxy environment variables are set correctly - monkeypatch.setenv("HTTPS_PROXY", "https://example.org") - # Delete in case our environment has any proxy env vars set - monkeypatch.delenv("HTTP_PROXY", raising=False) - monkeypatch.delenv("ALL_PROXY", raising=False) - monkeypatch.delenv("NO_PROXY", raising=False) - monkeypatch.delenv("http_proxy", raising=False) - monkeypatch.delenv("https_proxy", raising=False) - monkeypatch.delenv("all_proxy", raising=False) - monkeypatch.delenv("no_proxy", raising=False) - - client = DefaultAsyncHttpxClient() - - mounts = tuple(client._mounts.items()) - assert len(mounts) == 1 - assert mounts[0][0].pattern == "https://" - - @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") - async def test_default_client_creation(self) -> None: - # Ensure that the client can be initialized without any exceptions - DefaultAsyncHttpxClient( - verify=True, - cert=None, - trust_env=True, - http1=True, - http2=False, - limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), - ) - - @pytest.mark.respx(base_url=base_url) - async def test_follow_redirects(self, respx_mock: MockRouter, async_client: AsyncModerationAPI) -> None: - # Test that the default follow_redirects=True allows following redirects - respx_mock.post("/redirect").mock( - return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) - ) - respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) - - response = await async_client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) - assert response.status_code == 200 - assert response.json() == {"status": "ok"} - - @pytest.mark.respx(base_url=base_url) - async def test_follow_redirects_disabled(self, respx_mock: MockRouter, async_client: AsyncModerationAPI) -> None: - # Test that follow_redirects=False prevents following redirects - respx_mock.post("/redirect").mock( - return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) - ) - - with pytest.raises(APIStatusError) as exc_info: - await async_client.post( - "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response - ) - - assert exc_info.value.response.status_code == 302 - assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py deleted file mode 100644 index 44ee04b..0000000 --- a/tests/test_extract_files.py +++ /dev/null @@ -1,91 +0,0 @@ -from __future__ import annotations - -from typing import Sequence - -import pytest - -from moderation_api._types import FileTypes, ArrayFormat -from moderation_api._utils import extract_files - - -def test_removes_files_from_input() -> None: - query = {"foo": "bar"} - assert extract_files(query, paths=[]) == [] - assert query == {"foo": "bar"} - - query2 = {"foo": b"Bar", "hello": "world"} - assert extract_files(query2, paths=[["foo"]]) == [("foo", b"Bar")] - assert query2 == {"hello": "world"} - - query3 = {"foo": {"foo": {"bar": b"Bar"}}, "hello": "world"} - assert extract_files(query3, paths=[["foo", "foo", "bar"]]) == [("foo[foo][bar]", b"Bar")] - assert query3 == {"foo": {"foo": {}}, "hello": "world"} - - query4 = {"foo": {"bar": b"Bar", "baz": "foo"}, "hello": "world"} - assert extract_files(query4, paths=[["foo", "bar"]]) == [("foo[bar]", b"Bar")] - assert query4 == {"hello": "world", "foo": {"baz": "foo"}} - - -def test_multiple_files() -> None: - query = {"documents": [{"file": b"My first file"}, {"file": b"My second file"}]} - assert extract_files(query, paths=[["documents", "", "file"]]) == [ - ("documents[][file]", b"My first file"), - ("documents[][file]", b"My second file"), - ] - assert query == {"documents": [{}, {}]} - - -def test_top_level_file_array() -> None: - query = {"files": [b"file one", b"file two"], "title": "hello"} - assert extract_files(query, paths=[["files", ""]]) == [("files[]", b"file one"), ("files[]", b"file two")] - assert query == {"title": "hello"} - - -@pytest.mark.parametrize( - "query,paths,expected", - [ - [ - {"foo": {"bar": "baz"}}, - [["foo", "", "bar"]], - [], - ], - [ - {"foo": ["bar", "baz"]}, - [["foo", "bar"]], - [], - ], - [ - {"foo": {"bar": "baz"}}, - [["foo", "foo"]], - [], - ], - ], - ids=["dict expecting array", "array expecting dict", "unknown keys"], -) -def test_ignores_incorrect_paths( - query: dict[str, object], - paths: Sequence[Sequence[str]], - expected: list[tuple[str, FileTypes]], -) -> None: - assert extract_files(query, paths=paths) == expected - - -@pytest.mark.parametrize( - "array_format,expected_top_level,expected_nested", - [ - ("brackets", [("files[]", b"a"), ("files[]", b"b")], [("items[][file]", b"a"), ("items[][file]", b"b")]), - ("repeat", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]), - ("comma", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]), - ("indices", [("files[0]", b"a"), ("files[1]", b"b")], [("items[0][file]", b"a"), ("items[1][file]", b"b")]), - ], -) -def test_array_format_controls_file_field_names( - array_format: ArrayFormat, - expected_top_level: list[tuple[str, FileTypes]], - expected_nested: list[tuple[str, FileTypes]], -) -> None: - top_level = {"files": [b"a", b"b"]} - assert extract_files(top_level, paths=[["files", ""]], array_format=array_format) == expected_top_level - - nested = {"items": [{"file": b"a"}, {"file": b"b"}]} - assert extract_files(nested, paths=[["items", "", "file"]], array_format=array_format) == expected_nested diff --git a/tests/test_files.py b/tests/test_files.py deleted file mode 100644 index ffebeac..0000000 --- a/tests/test_files.py +++ /dev/null @@ -1,148 +0,0 @@ -from pathlib import Path - -import anyio -import pytest -from dirty_equals import IsDict, IsList, IsBytes, IsTuple - -from moderation_api._files import to_httpx_files, deepcopy_with_paths, async_to_httpx_files -from moderation_api._utils import extract_files - -readme_path = Path(__file__).parent.parent.joinpath("README.md") - - -def test_pathlib_includes_file_name() -> None: - result = to_httpx_files({"file": readme_path}) - print(result) - assert result == IsDict({"file": IsTuple("README.md", IsBytes())}) - - -def test_tuple_input() -> None: - result = to_httpx_files([("file", readme_path)]) - print(result) - assert result == IsList(IsTuple("file", IsTuple("README.md", IsBytes()))) - - -@pytest.mark.asyncio -async def test_async_pathlib_includes_file_name() -> None: - result = await async_to_httpx_files({"file": readme_path}) - print(result) - assert result == IsDict({"file": IsTuple("README.md", IsBytes())}) - - -@pytest.mark.asyncio -async def test_async_supports_anyio_path() -> None: - result = await async_to_httpx_files({"file": anyio.Path(readme_path)}) - print(result) - assert result == IsDict({"file": IsTuple("README.md", IsBytes())}) - - -@pytest.mark.asyncio -async def test_async_tuple_input() -> None: - result = await async_to_httpx_files([("file", readme_path)]) - print(result) - assert result == IsList(IsTuple("file", IsTuple("README.md", IsBytes()))) - - -def test_string_not_allowed() -> None: - with pytest.raises(TypeError, match="Expected file types input to be a FileContent type or to be a tuple"): - to_httpx_files( - { - "file": "foo", # type: ignore - } - ) - - -def assert_different_identities(obj1: object, obj2: object) -> None: - assert obj1 == obj2 - assert obj1 is not obj2 - - -class TestDeepcopyWithPaths: - def test_copies_top_level_dict(self) -> None: - original = {"file": b"data", "other": "value"} - result = deepcopy_with_paths(original, [["file"]]) - assert_different_identities(result, original) - - def test_file_value_is_same_reference(self) -> None: - file_bytes = b"contents" - original = {"file": file_bytes} - result = deepcopy_with_paths(original, [["file"]]) - assert_different_identities(result, original) - assert result["file"] is file_bytes - - def test_list_popped_wholesale(self) -> None: - files = [b"f1", b"f2"] - original = {"files": files, "title": "t"} - result = deepcopy_with_paths(original, [["files", ""]]) - assert_different_identities(result, original) - result_files = result["files"] - assert isinstance(result_files, list) - assert_different_identities(result_files, files) - - def test_nested_array_path_copies_list_and_elements(self) -> None: - elem1 = {"file": b"f1", "extra": 1} - elem2 = {"file": b"f2", "extra": 2} - original = {"items": [elem1, elem2]} - result = deepcopy_with_paths(original, [["items", "", "file"]]) - assert_different_identities(result, original) - result_items = result["items"] - assert isinstance(result_items, list) - assert_different_identities(result_items, original["items"]) - assert_different_identities(result_items[0], elem1) - assert_different_identities(result_items[1], elem2) - - def test_empty_paths_returns_same_object(self) -> None: - original = {"foo": "bar"} - result = deepcopy_with_paths(original, []) - assert result is original - - def test_multiple_paths(self) -> None: - f1 = b"file1" - f2 = b"file2" - original = {"a": f1, "b": f2, "c": "unchanged"} - result = deepcopy_with_paths(original, [["a"], ["b"]]) - assert_different_identities(result, original) - assert result["a"] is f1 - assert result["b"] is f2 - assert result["c"] is original["c"] - - def test_extract_files_does_not_mutate_original_top_level(self) -> None: - file_bytes = b"contents" - original = {"file": file_bytes, "other": "value"} - - copied = deepcopy_with_paths(original, [["file"]]) - extracted = extract_files(copied, paths=[["file"]]) - - assert extracted == [("file", file_bytes)] - assert original == {"file": file_bytes, "other": "value"} - assert copied == {"other": "value"} - - def test_extract_files_does_not_mutate_original_nested_array_path(self) -> None: - file1 = b"f1" - file2 = b"f2" - original = { - "items": [ - {"file": file1, "extra": 1}, - {"file": file2, "extra": 2}, - ], - "title": "example", - } - - copied = deepcopy_with_paths(original, [["items", "", "file"]]) - extracted = extract_files(copied, paths=[["items", "", "file"]]) - - assert [entry for _, entry in extracted] == [file1, file2] - assert original == { - "items": [ - {"file": file1, "extra": 1}, - {"file": file2, "extra": 2}, - ], - "title": "example", - } - assert copied == { - "items": [ - {"extra": 1}, - {"extra": 2}, - ], - "title": "example", - } diff --git a/tests/test_models.py b/tests/test_models.py deleted file mode 100644 index 5dbb021..0000000 --- a/tests/test_models.py +++ /dev/null @@ -1,1017 +0,0 @@ -import json -from typing import TYPE_CHECKING, Any, Dict, List, Union, Iterable, Optional, cast -from datetime import datetime, timezone -from collections import deque -from typing_extensions import Literal, Annotated, TypedDict, TypeAliasType - -import pytest -import pydantic -from pydantic import Field - -from moderation_api._utils import PropertyInfo -from moderation_api._compat import PYDANTIC_V1, parse_obj, model_dump, model_json -from moderation_api._models import DISCRIMINATOR_CACHE, BaseModel, EagerIterable, construct_type - - -class BasicModel(BaseModel): - foo: str - - -@pytest.mark.parametrize("value", ["hello", 1], ids=["correct type", "mismatched"]) -def test_basic(value: object) -> None: - m = BasicModel.construct(foo=value) - assert m.foo == value - - -def test_directly_nested_model() -> None: - class NestedModel(BaseModel): - nested: BasicModel - - m = NestedModel.construct(nested={"foo": "Foo!"}) - assert m.nested.foo == "Foo!" - - # mismatched types - m = NestedModel.construct(nested="hello!") - assert cast(Any, m.nested) == "hello!" - - -def test_optional_nested_model() -> None: - class NestedModel(BaseModel): - nested: Optional[BasicModel] - - m1 = NestedModel.construct(nested=None) - assert m1.nested is None - - m2 = NestedModel.construct(nested={"foo": "bar"}) - assert m2.nested is not None - assert m2.nested.foo == "bar" - - # mismatched types - m3 = NestedModel.construct(nested={"foo"}) - assert isinstance(cast(Any, m3.nested), set) - assert cast(Any, m3.nested) == {"foo"} - - -def test_list_nested_model() -> None: - class NestedModel(BaseModel): - nested: List[BasicModel] - - m = NestedModel.construct(nested=[{"foo": "bar"}, {"foo": "2"}]) - assert m.nested is not None - assert isinstance(m.nested, list) - assert len(m.nested) == 2 - assert m.nested[0].foo == "bar" - assert m.nested[1].foo == "2" - - # mismatched types - m = NestedModel.construct(nested=True) - assert cast(Any, m.nested) is True - - m = NestedModel.construct(nested=[False]) - assert cast(Any, m.nested) == [False] - - -def test_optional_list_nested_model() -> None: - class NestedModel(BaseModel): - nested: Optional[List[BasicModel]] - - m1 = NestedModel.construct(nested=[{"foo": "bar"}, {"foo": "2"}]) - assert m1.nested is not None - assert isinstance(m1.nested, list) - assert len(m1.nested) == 2 - assert m1.nested[0].foo == "bar" - assert m1.nested[1].foo == "2" - - m2 = NestedModel.construct(nested=None) - assert m2.nested is None - - # mismatched types - m3 = NestedModel.construct(nested={1}) - assert cast(Any, m3.nested) == {1} - - m4 = NestedModel.construct(nested=[False]) - assert cast(Any, m4.nested) == [False] - - -def test_list_optional_items_nested_model() -> None: - class NestedModel(BaseModel): - nested: List[Optional[BasicModel]] - - m = NestedModel.construct(nested=[None, {"foo": "bar"}]) - assert m.nested is not None - assert isinstance(m.nested, list) - assert len(m.nested) == 2 - assert m.nested[0] is None - assert m.nested[1] is not None - assert m.nested[1].foo == "bar" - - # mismatched types - m3 = NestedModel.construct(nested="foo") - assert cast(Any, m3.nested) == "foo" - - m4 = NestedModel.construct(nested=[False]) - assert cast(Any, m4.nested) == [False] - - -def test_list_mismatched_type() -> None: - class NestedModel(BaseModel): - nested: List[str] - - m = NestedModel.construct(nested=False) - assert cast(Any, m.nested) is False - - -def test_raw_dictionary() -> None: - class NestedModel(BaseModel): - nested: Dict[str, str] - - m = NestedModel.construct(nested={"hello": "world"}) - assert m.nested == {"hello": "world"} - - # mismatched types - m = NestedModel.construct(nested=False) - assert cast(Any, m.nested) is False - - -def test_nested_dictionary_model() -> None: - class NestedModel(BaseModel): - nested: Dict[str, BasicModel] - - m = NestedModel.construct(nested={"hello": {"foo": "bar"}}) - assert isinstance(m.nested, dict) - assert m.nested["hello"].foo == "bar" - - # mismatched types - m = NestedModel.construct(nested={"hello": False}) - assert cast(Any, m.nested["hello"]) is False - - -def test_unknown_fields() -> None: - m1 = BasicModel.construct(foo="foo", unknown=1) - assert m1.foo == "foo" - assert cast(Any, m1).unknown == 1 - - m2 = BasicModel.construct(foo="foo", unknown={"foo_bar": True}) - assert m2.foo == "foo" - assert cast(Any, m2).unknown == {"foo_bar": True} - - assert model_dump(m2) == {"foo": "foo", "unknown": {"foo_bar": True}} - - -def test_strict_validation_unknown_fields() -> None: - class Model(BaseModel): - foo: str - - model = parse_obj(Model, dict(foo="hello!", user="Robert")) - assert model.foo == "hello!" - assert cast(Any, model).user == "Robert" - - assert model_dump(model) == {"foo": "hello!", "user": "Robert"} - - -def test_aliases() -> None: - class Model(BaseModel): - my_field: int = Field(alias="myField") - - m = Model.construct(myField=1) - assert m.my_field == 1 - - # mismatched types - m = Model.construct(myField={"hello": False}) - assert cast(Any, m.my_field) == {"hello": False} - - -def test_repr() -> None: - model = BasicModel(foo="bar") - assert str(model) == "BasicModel(foo='bar')" - assert repr(model) == "BasicModel(foo='bar')" - - -def test_repr_nested_model() -> None: - class Child(BaseModel): - name: str - age: int - - class Parent(BaseModel): - name: str - child: Child - - model = Parent(name="Robert", child=Child(name="Foo", age=5)) - assert str(model) == "Parent(name='Robert', child=Child(name='Foo', age=5))" - assert repr(model) == "Parent(name='Robert', child=Child(name='Foo', age=5))" - - -def test_optional_list() -> None: - class Submodel(BaseModel): - name: str - - class Model(BaseModel): - items: Optional[List[Submodel]] - - m = Model.construct(items=None) - assert m.items is None - - m = Model.construct(items=[]) - assert m.items == [] - - m = Model.construct(items=[{"name": "Robert"}]) - assert m.items is not None - assert len(m.items) == 1 - assert m.items[0].name == "Robert" - - -def test_nested_union_of_models() -> None: - class Submodel1(BaseModel): - bar: bool - - class Submodel2(BaseModel): - thing: str - - class Model(BaseModel): - foo: Union[Submodel1, Submodel2] - - m = Model.construct(foo={"thing": "hello"}) - assert isinstance(m.foo, Submodel2) - assert m.foo.thing == "hello" - - -def test_nested_union_of_mixed_types() -> None: - class Submodel1(BaseModel): - bar: bool - - class Model(BaseModel): - foo: Union[Submodel1, Literal[True], Literal["CARD_HOLDER"]] - - m = Model.construct(foo=True) - assert m.foo is True - - m = Model.construct(foo="CARD_HOLDER") - assert m.foo == "CARD_HOLDER" - - m = Model.construct(foo={"bar": False}) - assert isinstance(m.foo, Submodel1) - assert m.foo.bar is False - - -def test_nested_union_multiple_variants() -> None: - class Submodel1(BaseModel): - bar: bool - - class Submodel2(BaseModel): - thing: str - - class Submodel3(BaseModel): - foo: int - - class Model(BaseModel): - foo: Union[Submodel1, Submodel2, None, Submodel3] - - m = Model.construct(foo={"thing": "hello"}) - assert isinstance(m.foo, Submodel2) - assert m.foo.thing == "hello" - - m = Model.construct(foo=None) - assert m.foo is None - - m = Model.construct() - assert m.foo is None - - m = Model.construct(foo={"foo": "1"}) - assert isinstance(m.foo, Submodel3) - assert m.foo.foo == 1 - - -def test_nested_union_invalid_data() -> None: - class Submodel1(BaseModel): - level: int - - class Submodel2(BaseModel): - name: str - - class Model(BaseModel): - foo: Union[Submodel1, Submodel2] - - m = Model.construct(foo=True) - assert cast(bool, m.foo) is True - - m = Model.construct(foo={"name": 3}) - if PYDANTIC_V1: - assert isinstance(m.foo, Submodel2) - assert m.foo.name == "3" - else: - assert isinstance(m.foo, Submodel1) - assert m.foo.name == 3 # type: ignore - - -def test_list_of_unions() -> None: - class Submodel1(BaseModel): - level: int - - class Submodel2(BaseModel): - name: str - - class Model(BaseModel): - items: List[Union[Submodel1, Submodel2]] - - m = Model.construct(items=[{"level": 1}, {"name": "Robert"}]) - assert len(m.items) == 2 - assert isinstance(m.items[0], Submodel1) - assert m.items[0].level == 1 - assert isinstance(m.items[1], Submodel2) - assert m.items[1].name == "Robert" - - m = Model.construct(items=[{"level": -1}, 156]) - assert len(m.items) == 2 - assert isinstance(m.items[0], Submodel1) - assert m.items[0].level == -1 - assert cast(Any, m.items[1]) == 156 - - -def test_union_of_lists() -> None: - class SubModel1(BaseModel): - level: int - - class SubModel2(BaseModel): - name: str - - class Model(BaseModel): - items: Union[List[SubModel1], List[SubModel2]] - - # with one valid entry - m = Model.construct(items=[{"name": "Robert"}]) - assert len(m.items) == 1 - assert isinstance(m.items[0], SubModel2) - assert m.items[0].name == "Robert" - - # with two entries pointing to different types - m = Model.construct(items=[{"level": 1}, {"name": "Robert"}]) - assert len(m.items) == 2 - assert isinstance(m.items[0], SubModel1) - assert m.items[0].level == 1 - assert isinstance(m.items[1], SubModel1) - assert cast(Any, m.items[1]).name == "Robert" - - # with two entries pointing to *completely* different types - m = Model.construct(items=[{"level": -1}, 156]) - assert len(m.items) == 2 - assert isinstance(m.items[0], SubModel1) - assert m.items[0].level == -1 - assert cast(Any, m.items[1]) == 156 - - -def test_dict_of_union() -> None: - class SubModel1(BaseModel): - name: str - - class SubModel2(BaseModel): - foo: str - - class Model(BaseModel): - data: Dict[str, Union[SubModel1, SubModel2]] - - m = Model.construct(data={"hello": {"name": "there"}, "foo": {"foo": "bar"}}) - assert len(list(m.data.keys())) == 2 - assert isinstance(m.data["hello"], SubModel1) - assert m.data["hello"].name == "there" - assert isinstance(m.data["foo"], SubModel2) - assert m.data["foo"].foo == "bar" - - # TODO: test mismatched type - - -def test_double_nested_union() -> None: - class SubModel1(BaseModel): - name: str - - class SubModel2(BaseModel): - bar: str - - class Model(BaseModel): - data: Dict[str, List[Union[SubModel1, SubModel2]]] - - m = Model.construct(data={"foo": [{"bar": "baz"}, {"name": "Robert"}]}) - assert len(m.data["foo"]) == 2 - - entry1 = m.data["foo"][0] - assert isinstance(entry1, SubModel2) - assert entry1.bar == "baz" - - entry2 = m.data["foo"][1] - assert isinstance(entry2, SubModel1) - assert entry2.name == "Robert" - - # TODO: test mismatched type - - -def test_union_of_dict() -> None: - class SubModel1(BaseModel): - name: str - - class SubModel2(BaseModel): - foo: str - - class Model(BaseModel): - data: Union[Dict[str, SubModel1], Dict[str, SubModel2]] - - m = Model.construct(data={"hello": {"name": "there"}, "foo": {"foo": "bar"}}) - assert len(list(m.data.keys())) == 2 - assert isinstance(m.data["hello"], SubModel1) - assert m.data["hello"].name == "there" - assert isinstance(m.data["foo"], SubModel1) - assert cast(Any, m.data["foo"]).foo == "bar" - - -def test_iso8601_datetime() -> None: - class Model(BaseModel): - created_at: datetime - - expected = datetime(2019, 12, 27, 18, 11, 19, 117000, tzinfo=timezone.utc) - - if PYDANTIC_V1: - expected_json = '{"created_at": "2019-12-27T18:11:19.117000+00:00"}' - else: - expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}' - - model = Model.construct(created_at="2019-12-27T18:11:19.117Z") - assert model.created_at == expected - assert model_json(model) == expected_json - - model = parse_obj(Model, dict(created_at="2019-12-27T18:11:19.117Z")) - assert model.created_at == expected - assert model_json(model) == expected_json - - -def test_does_not_coerce_int() -> None: - class Model(BaseModel): - bar: int - - assert Model.construct(bar=1).bar == 1 - assert Model.construct(bar=10.9).bar == 10.9 - assert Model.construct(bar="19").bar == "19" # type: ignore[comparison-overlap] - assert Model.construct(bar=False).bar is False - - -def test_int_to_float_safe_conversion() -> None: - class Model(BaseModel): - float_field: float - - m = Model.construct(float_field=10) - assert m.float_field == 10.0 - assert isinstance(m.float_field, float) - - m = Model.construct(float_field=10.12) - assert m.float_field == 10.12 - assert isinstance(m.float_field, float) - - # number too big - m = Model.construct(float_field=2**53 + 1) - assert m.float_field == 2**53 + 1 - assert isinstance(m.float_field, int) - - -def test_deprecated_alias() -> None: - class Model(BaseModel): - resource_id: str = Field(alias="model_id") - - @property - def model_id(self) -> str: - return self.resource_id - - m = Model.construct(model_id="id") - assert m.model_id == "id" - assert m.resource_id == "id" - assert m.resource_id is m.model_id - - m = parse_obj(Model, {"model_id": "id"}) - assert m.model_id == "id" - assert m.resource_id == "id" - assert m.resource_id is m.model_id - - -def test_omitted_fields() -> None: - class Model(BaseModel): - resource_id: Optional[str] = None - - m = Model.construct() - assert m.resource_id is None - assert "resource_id" not in m.model_fields_set - - m = Model.construct(resource_id=None) - assert m.resource_id is None - assert "resource_id" in m.model_fields_set - - m = Model.construct(resource_id="foo") - assert m.resource_id == "foo" - assert "resource_id" in m.model_fields_set - - -def test_to_dict() -> None: - class Model(BaseModel): - foo: Optional[str] = Field(alias="FOO", default=None) - - m = Model(FOO="hello") - assert m.to_dict() == {"FOO": "hello"} - assert m.to_dict(use_api_names=False) == {"foo": "hello"} - - m2 = Model() - assert m2.to_dict() == {} - assert m2.to_dict(exclude_unset=False) == {"FOO": None} - assert m2.to_dict(exclude_unset=False, exclude_none=True) == {} - assert m2.to_dict(exclude_unset=False, exclude_defaults=True) == {} - - m3 = Model(FOO=None) - assert m3.to_dict() == {"FOO": None} - assert m3.to_dict(exclude_none=True) == {} - assert m3.to_dict(exclude_defaults=True) == {} - - class Model2(BaseModel): - created_at: datetime - - time_str = "2024-03-21T11:39:01.275859" - m4 = Model2.construct(created_at=time_str) - assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)} - assert m4.to_dict(mode="json") == {"created_at": time_str} - - if PYDANTIC_V1: - with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): - m.to_dict(warnings=False) - - -def test_forwards_compat_model_dump_method() -> None: - class Model(BaseModel): - foo: Optional[str] = Field(alias="FOO", default=None) - - m = Model(FOO="hello") - assert m.model_dump() == {"foo": "hello"} - assert m.model_dump(include={"bar"}) == {} - assert m.model_dump(exclude={"foo"}) == {} - assert m.model_dump(by_alias=True) == {"FOO": "hello"} - - m2 = Model() - assert m2.model_dump() == {"foo": None} - assert m2.model_dump(exclude_unset=True) == {} - assert m2.model_dump(exclude_none=True) == {} - assert m2.model_dump(exclude_defaults=True) == {} - - m3 = Model(FOO=None) - assert m3.model_dump() == {"foo": None} - assert m3.model_dump(exclude_none=True) == {} - - if PYDANTIC_V1: - with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): - m.model_dump(round_trip=True) - - with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): - m.model_dump(warnings=False) - - -def test_compat_method_no_error_for_warnings() -> None: - class Model(BaseModel): - foo: Optional[str] - - m = Model(foo="hello") - assert isinstance(model_dump(m, warnings=False), dict) - - -def test_to_json() -> None: - class Model(BaseModel): - foo: Optional[str] = Field(alias="FOO", default=None) - - m = Model(FOO="hello") - assert json.loads(m.to_json()) == {"FOO": "hello"} - assert json.loads(m.to_json(use_api_names=False)) == {"foo": "hello"} - - if PYDANTIC_V1: - assert m.to_json(indent=None) == '{"FOO": "hello"}' - else: - assert m.to_json(indent=None) == '{"FOO":"hello"}' - - m2 = Model() - assert json.loads(m2.to_json()) == {} - assert json.loads(m2.to_json(exclude_unset=False)) == {"FOO": None} - assert json.loads(m2.to_json(exclude_unset=False, exclude_none=True)) == {} - assert json.loads(m2.to_json(exclude_unset=False, exclude_defaults=True)) == {} - - m3 = Model(FOO=None) - assert json.loads(m3.to_json()) == {"FOO": None} - assert json.loads(m3.to_json(exclude_none=True)) == {} - - if PYDANTIC_V1: - with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): - m.to_json(warnings=False) - - -def test_forwards_compat_model_dump_json_method() -> None: - class Model(BaseModel): - foo: Optional[str] = Field(alias="FOO", default=None) - - m = Model(FOO="hello") - assert json.loads(m.model_dump_json()) == {"foo": "hello"} - assert json.loads(m.model_dump_json(include={"bar"})) == {} - assert json.loads(m.model_dump_json(include={"foo"})) == {"foo": "hello"} - assert json.loads(m.model_dump_json(by_alias=True)) == {"FOO": "hello"} - - assert m.model_dump_json(indent=2) == '{\n "foo": "hello"\n}' - - m2 = Model() - assert json.loads(m2.model_dump_json()) == {"foo": None} - assert json.loads(m2.model_dump_json(exclude_unset=True)) == {} - assert json.loads(m2.model_dump_json(exclude_none=True)) == {} - assert json.loads(m2.model_dump_json(exclude_defaults=True)) == {} - - m3 = Model(FOO=None) - assert json.loads(m3.model_dump_json()) == {"foo": None} - assert json.loads(m3.model_dump_json(exclude_none=True)) == {} - - if PYDANTIC_V1: - with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): - m.model_dump_json(round_trip=True) - - with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): - m.model_dump_json(warnings=False) - - -def test_type_compat() -> None: - # our model type can be assigned to Pydantic's model type - - def takes_pydantic(model: pydantic.BaseModel) -> None: # noqa: ARG001 - ... - - class OurModel(BaseModel): - foo: Optional[str] = None - - takes_pydantic(OurModel()) - - -def test_annotated_types() -> None: - class Model(BaseModel): - value: str - - m = construct_type( - value={"value": "foo"}, - type_=cast(Any, Annotated[Model, "random metadata"]), - ) - assert isinstance(m, Model) - assert m.value == "foo" - - -def test_discriminated_unions_invalid_data() -> None: - class A(BaseModel): - type: Literal["a"] - - data: str - - class B(BaseModel): - type: Literal["b"] - - data: int - - m = construct_type( - value={"type": "b", "data": "foo"}, - type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]), - ) - assert isinstance(m, B) - assert m.type == "b" - assert m.data == "foo" # type: ignore[comparison-overlap] - - m = construct_type( - value={"type": "a", "data": 100}, - type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]), - ) - assert isinstance(m, A) - assert m.type == "a" - if PYDANTIC_V1: - # pydantic v1 automatically converts inputs to strings - # if the expected type is a str - assert m.data == "100" - else: - assert m.data == 100 # type: ignore[comparison-overlap] - - -def test_discriminated_unions_unknown_variant() -> None: - class A(BaseModel): - type: Literal["a"] - - data: str - - class B(BaseModel): - type: Literal["b"] - - data: int - - m = construct_type( - value={"type": "c", "data": None, "new_thing": "bar"}, - type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]), - ) - - # just chooses the first variant - assert isinstance(m, A) - assert m.type == "c" # type: ignore[comparison-overlap] - assert m.data == None # type: ignore[unreachable] - assert m.new_thing == "bar" - - -def test_discriminated_unions_invalid_data_nested_unions() -> None: - class A(BaseModel): - type: Literal["a"] - - data: str - - class B(BaseModel): - type: Literal["b"] - - data: int - - class C(BaseModel): - type: Literal["c"] - - data: bool - - m = construct_type( - value={"type": "b", "data": "foo"}, - type_=cast(Any, Annotated[Union[Union[A, B], C], PropertyInfo(discriminator="type")]), - ) - assert isinstance(m, B) - assert m.type == "b" - assert m.data == "foo" # type: ignore[comparison-overlap] - - m = construct_type( - value={"type": "c", "data": "foo"}, - type_=cast(Any, Annotated[Union[Union[A, B], C], PropertyInfo(discriminator="type")]), - ) - assert isinstance(m, C) - assert m.type == "c" - assert m.data == "foo" # type: ignore[comparison-overlap] - - -def test_discriminated_unions_with_aliases_invalid_data() -> None: - class A(BaseModel): - foo_type: Literal["a"] = Field(alias="type") - - data: str - - class B(BaseModel): - foo_type: Literal["b"] = Field(alias="type") - - data: int - - m = construct_type( - value={"type": "b", "data": "foo"}, - type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="foo_type")]), - ) - assert isinstance(m, B) - assert m.foo_type == "b" - assert m.data == "foo" # type: ignore[comparison-overlap] - - m = construct_type( - value={"type": "a", "data": 100}, - type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="foo_type")]), - ) - assert isinstance(m, A) - assert m.foo_type == "a" - if PYDANTIC_V1: - # pydantic v1 automatically converts inputs to strings - # if the expected type is a str - assert m.data == "100" - else: - assert m.data == 100 # type: ignore[comparison-overlap] - - -def test_discriminated_unions_overlapping_discriminators_invalid_data() -> None: - class A(BaseModel): - type: Literal["a"] - - data: bool - - class B(BaseModel): - type: Literal["a"] - - data: int - - m = construct_type( - value={"type": "a", "data": "foo"}, - type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]), - ) - assert isinstance(m, B) - assert m.type == "a" - assert m.data == "foo" # type: ignore[comparison-overlap] - - -def test_discriminated_unions_invalid_data_uses_cache() -> None: - class A(BaseModel): - type: Literal["a"] - - data: str - - class B(BaseModel): - type: Literal["b"] - - data: int - - UnionType = cast(Any, Union[A, B]) - - assert not DISCRIMINATOR_CACHE.get(UnionType) - - m = construct_type( - value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")]) - ) - assert isinstance(m, B) - assert m.type == "b" - assert m.data == "foo" # type: ignore[comparison-overlap] - - discriminator = DISCRIMINATOR_CACHE.get(UnionType) - assert discriminator is not None - - m = construct_type( - value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")]) - ) - assert isinstance(m, B) - assert m.type == "b" - assert m.data == "foo" # type: ignore[comparison-overlap] - - # if the discriminator details object stays the same between invocations then - # we hit the cache - assert DISCRIMINATOR_CACHE.get(UnionType) is discriminator - - -@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") -def test_type_alias_type() -> None: - Alias = TypeAliasType("Alias", str) # pyright: ignore - - class Model(BaseModel): - alias: Alias - union: Union[int, Alias] - - m = construct_type(value={"alias": "foo", "union": "bar"}, type_=Model) - assert isinstance(m, Model) - assert isinstance(m.alias, str) - assert m.alias == "foo" - assert isinstance(m.union, str) - assert m.union == "bar" - - -@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") -def test_field_named_cls() -> None: - class Model(BaseModel): - cls: str - - m = construct_type(value={"cls": "foo"}, type_=Model) - assert isinstance(m, Model) - assert isinstance(m.cls, str) - - -def test_discriminated_union_case() -> None: - class A(BaseModel): - type: Literal["a"] - - data: bool - - class B(BaseModel): - type: Literal["b"] - - data: List[Union[A, object]] - - class ModelA(BaseModel): - type: Literal["modelA"] - - data: int - - class ModelB(BaseModel): - type: Literal["modelB"] - - required: str - - data: Union[A, B] - - # when constructing ModelA | ModelB, value data doesn't match ModelB exactly - missing `required` - m = construct_type( - value={"type": "modelB", "data": {"type": "a", "data": True}}, - type_=cast(Any, Annotated[Union[ModelA, ModelB], PropertyInfo(discriminator="type")]), - ) - - assert isinstance(m, ModelB) - - -def test_nested_discriminated_union() -> None: - class InnerType1(BaseModel): - type: Literal["type_1"] - - class InnerModel(BaseModel): - inner_value: str - - class InnerType2(BaseModel): - type: Literal["type_2"] - some_inner_model: InnerModel - - class Type1(BaseModel): - base_type: Literal["base_type_1"] - value: Annotated[ - Union[ - InnerType1, - InnerType2, - ], - PropertyInfo(discriminator="type"), - ] - - class Type2(BaseModel): - base_type: Literal["base_type_2"] - - T = Annotated[ - Union[ - Type1, - Type2, - ], - PropertyInfo(discriminator="base_type"), - ] - - model = construct_type( - type_=T, - value={ - "base_type": "base_type_1", - "value": { - "type": "type_2", - }, - }, - ) - assert isinstance(model, Type1) - assert isinstance(model.value, InnerType2) - - -@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2 for now") -def test_extra_properties() -> None: - class Item(BaseModel): - prop: int - - class Model(BaseModel): - __pydantic_extra__: Dict[str, Item] = Field(init=False) # pyright: ignore[reportIncompatibleVariableOverride] - - other: str - - if TYPE_CHECKING: - - def __getattr__(self, attr: str) -> Item: ... - - model = construct_type( - type_=Model, - value={ - "a": {"prop": 1}, - "other": "foo", - }, - ) - assert isinstance(model, Model) - assert model.a.prop == 1 - assert isinstance(model.a, Item) - assert model.other == "foo" - - -# NOTE: Workaround for Pydantic Iterable behavior. -# Iterable fields are replaced with a ValidatorIterator and may be consumed -# during serialization, which can cause subsequent dumps to return empty data. -# See: https://github.com/pydantic/pydantic/issues/9541 -@pytest.mark.parametrize( - "data, expected_validated", - [ - ([1, 2, 3], [1, 2, 3]), - ((1, 2, 3), (1, 2, 3)), - (set([1, 2, 3]), set([1, 2, 3])), - (iter([1, 2, 3]), [1, 2, 3]), - ([], []), - ((x for x in [1, 2, 3]), [1, 2, 3]), - (map(lambda x: x, [1, 2, 3]), [1, 2, 3]), - (frozenset([1, 2, 3]), frozenset([1, 2, 3])), - (deque([1, 2, 3]), deque([1, 2, 3])), - ], - ids=["list", "tuple", "set", "iterator", "empty", "generator", "map", "frozenset", "deque"], -) -@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2") -def test_iterable_construction(data: Iterable[int], expected_validated: Iterable[int]) -> None: - class TypeWithIterable(TypedDict): - items: EagerIterable[int] - - class Model(BaseModel): - data: TypeWithIterable - - m = Model.model_validate({"data": {"items": data}}) - assert m.data["items"] == expected_validated - - # Verify repeated dumps don't lose data (the original bug) - assert m.model_dump()["data"]["items"] == list(expected_validated) - assert m.model_dump()["data"]["items"] == list(expected_validated) - - -@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2") -def test_iterable_construction_str_falls_back_to_list() -> None: - # str is iterable (over chars), but str(list_of_chars) produces the list's repr - # rather than reconstructing a string from items. We special-case str to fall - # back to list instead of attempting reconstruction. - class TypeWithIterable(TypedDict): - items: EagerIterable[str] - - class Model(BaseModel): - data: TypeWithIterable - - m = Model.model_validate({"data": {"items": "hello"}}) - - # falls back to list of chars rather than calling str(["h", "e", "l", "l", "o"]) - assert m.data["items"] == ["h", "e", "l", "l", "o"] - assert m.model_dump()["data"]["items"] == ["h", "e", "l", "l", "o"] diff --git a/tests/test_qs.py b/tests/test_qs.py deleted file mode 100644 index 030e8c2..0000000 --- a/tests/test_qs.py +++ /dev/null @@ -1,78 +0,0 @@ -from typing import Any, cast -from functools import partial -from urllib.parse import unquote - -import pytest - -from moderation_api._qs import Querystring, stringify - - -def test_empty() -> None: - assert stringify({}) == "" - assert stringify({"a": {}}) == "" - assert stringify({"a": {"b": {"c": {}}}}) == "" - - -def test_basic() -> None: - assert stringify({"a": 1}) == "a=1" - assert stringify({"a": "b"}) == "a=b" - assert stringify({"a": True}) == "a=true" - assert stringify({"a": False}) == "a=false" - assert stringify({"a": 1.23456}) == "a=1.23456" - assert stringify({"a": None}) == "" - - -@pytest.mark.parametrize("method", ["class", "function"]) -def test_nested_dotted(method: str) -> None: - if method == "class": - serialise = Querystring(nested_format="dots").stringify - else: - serialise = partial(stringify, nested_format="dots") - - assert unquote(serialise({"a": {"b": "c"}})) == "a.b=c" - assert unquote(serialise({"a": {"b": "c", "d": "e", "f": "g"}})) == "a.b=c&a.d=e&a.f=g" - assert unquote(serialise({"a": {"b": {"c": {"d": "e"}}}})) == "a.b.c.d=e" - assert unquote(serialise({"a": {"b": True}})) == "a.b=true" - - -def test_nested_brackets() -> None: - assert unquote(stringify({"a": {"b": "c"}})) == "a[b]=c" - assert unquote(stringify({"a": {"b": "c", "d": "e", "f": "g"}})) == "a[b]=c&a[d]=e&a[f]=g" - assert unquote(stringify({"a": {"b": {"c": {"d": "e"}}}})) == "a[b][c][d]=e" - assert unquote(stringify({"a": {"b": True}})) == "a[b]=true" - - -@pytest.mark.parametrize("method", ["class", "function"]) -def test_array_comma(method: str) -> None: - if method == "class": - serialise = Querystring(array_format="comma").stringify - else: - serialise = partial(stringify, array_format="comma") - - assert unquote(serialise({"in": ["foo", "bar"]})) == "in=foo,bar" - assert unquote(serialise({"a": {"b": [True, False]}})) == "a[b]=true,false" - assert unquote(serialise({"a": {"b": [True, False, None, True]}})) == "a[b]=true,false,true" - - -def test_array_repeat() -> None: - assert unquote(stringify({"in": ["foo", "bar"]})) == "in=foo&in=bar" - assert unquote(stringify({"a": {"b": [True, False]}})) == "a[b]=true&a[b]=false" - assert unquote(stringify({"a": {"b": [True, False, None, True]}})) == "a[b]=true&a[b]=false&a[b]=true" - assert unquote(stringify({"in": ["foo", {"b": {"c": ["d", "e"]}}]})) == "in=foo&in[b][c]=d&in[b][c]=e" - - -@pytest.mark.parametrize("method", ["class", "function"]) -def test_array_brackets(method: str) -> None: - if method == "class": - serialise = Querystring(array_format="brackets").stringify - else: - serialise = partial(stringify, array_format="brackets") - - assert unquote(serialise({"in": ["foo", "bar"]})) == "in[]=foo&in[]=bar" - assert unquote(serialise({"a": {"b": [True, False]}})) == "a[b][]=true&a[b][]=false" - assert unquote(serialise({"a": {"b": [True, False, None, True]}})) == "a[b][]=true&a[b][]=false&a[b][]=true" - - -def test_unknown_array_format() -> None: - with pytest.raises(NotImplementedError, match="Unknown array_format value: foo, choose from comma, repeat"): - stringify({"a": ["foo", "bar"]}, array_format=cast(Any, "foo")) diff --git a/tests/test_required_args.py b/tests/test_required_args.py deleted file mode 100644 index 575dac6..0000000 --- a/tests/test_required_args.py +++ /dev/null @@ -1,111 +0,0 @@ -from __future__ import annotations - -import pytest - -from moderation_api._utils import required_args - - -def test_too_many_positional_params() -> None: - @required_args(["a"]) - def foo(a: str | None = None) -> str | None: - return a - - with pytest.raises(TypeError, match=r"foo\(\) takes 1 argument\(s\) but 2 were given"): - foo("a", "b") # type: ignore - - -def test_positional_param() -> None: - @required_args(["a"]) - def foo(a: str | None = None) -> str | None: - return a - - assert foo("a") == "a" - assert foo(None) is None - assert foo(a="b") == "b" - - with pytest.raises(TypeError, match="Missing required argument: 'a'"): - foo() - - -def test_keyword_only_param() -> None: - @required_args(["a"]) - def foo(*, a: str | None = None) -> str | None: - return a - - assert foo(a="a") == "a" - assert foo(a=None) is None - assert foo(a="b") == "b" - - with pytest.raises(TypeError, match="Missing required argument: 'a'"): - foo() - - -def test_multiple_params() -> None: - @required_args(["a", "b", "c"]) - def foo(a: str = "", *, b: str = "", c: str = "") -> str | None: - return f"{a} {b} {c}" - - assert foo(a="a", b="b", c="c") == "a b c" - - error_message = r"Missing required arguments.*" - - with pytest.raises(TypeError, match=error_message): - foo() - - with pytest.raises(TypeError, match=error_message): - foo(a="a") - - with pytest.raises(TypeError, match=error_message): - foo(b="b") - - with pytest.raises(TypeError, match=error_message): - foo(c="c") - - with pytest.raises(TypeError, match=r"Missing required argument: 'a'"): - foo(b="a", c="c") - - with pytest.raises(TypeError, match=r"Missing required argument: 'b'"): - foo("a", c="c") - - -def test_multiple_variants() -> None: - @required_args(["a"], ["b"]) - def foo(*, a: str | None = None, b: str | None = None) -> str | None: - return a if a is not None else b - - assert foo(a="foo") == "foo" - assert foo(b="bar") == "bar" - assert foo(a=None) is None - assert foo(b=None) is None - - # TODO: this error message could probably be improved - with pytest.raises( - TypeError, - match=r"Missing required arguments; Expected either \('a'\) or \('b'\) arguments to be given", - ): - foo() - - -def test_multiple_params_multiple_variants() -> None: - @required_args(["a", "b"], ["c"]) - def foo(*, a: str | None = None, b: str | None = None, c: str | None = None) -> str | None: - if a is not None: - return a - if b is not None: - return b - return c - - error_message = r"Missing required arguments; Expected either \('a' and 'b'\) or \('c'\) arguments to be given" - - with pytest.raises(TypeError, match=error_message): - foo(a="foo") - - with pytest.raises(TypeError, match=error_message): - foo(b="bar") - - with pytest.raises(TypeError, match=error_message): - foo() - - assert foo(a=None, b="bar") == "bar" - assert foo(c=None) is None - assert foo(c="foo") == "foo" diff --git a/tests/test_response.py b/tests/test_response.py deleted file mode 100644 index c75e4d0..0000000 --- a/tests/test_response.py +++ /dev/null @@ -1,277 +0,0 @@ -import json -from typing import Any, List, Union, cast -from typing_extensions import Annotated - -import httpx -import pytest -import pydantic - -from moderation_api import BaseModel, ModerationAPI, AsyncModerationAPI -from moderation_api._response import ( - APIResponse, - BaseAPIResponse, - AsyncAPIResponse, - BinaryAPIResponse, - AsyncBinaryAPIResponse, - extract_response_type, -) -from moderation_api._streaming import Stream -from moderation_api._base_client import FinalRequestOptions - - -class ConcreteBaseAPIResponse(APIResponse[bytes]): ... - - -class ConcreteAPIResponse(APIResponse[List[str]]): ... - - -class ConcreteAsyncAPIResponse(APIResponse[httpx.Response]): ... - - -def test_extract_response_type_direct_classes() -> None: - assert extract_response_type(BaseAPIResponse[str]) == str - assert extract_response_type(APIResponse[str]) == str - assert extract_response_type(AsyncAPIResponse[str]) == str - - -def test_extract_response_type_direct_class_missing_type_arg() -> None: - with pytest.raises( - RuntimeError, - match="Expected type to have a type argument at index 0 but it did not", - ): - extract_response_type(AsyncAPIResponse) - - -def test_extract_response_type_concrete_subclasses() -> None: - assert extract_response_type(ConcreteBaseAPIResponse) == bytes - assert extract_response_type(ConcreteAPIResponse) == List[str] - assert extract_response_type(ConcreteAsyncAPIResponse) == httpx.Response - - -def test_extract_response_type_binary_response() -> None: - assert extract_response_type(BinaryAPIResponse) == bytes - assert extract_response_type(AsyncBinaryAPIResponse) == bytes - - -class PydanticModel(pydantic.BaseModel): ... - - -def test_response_parse_mismatched_basemodel(client: ModerationAPI) -> None: - response = APIResponse( - raw=httpx.Response(200, content=b"foo"), - client=client, - stream=False, - stream_cls=None, - cast_to=str, - options=FinalRequestOptions.construct(method="get", url="/foo"), - ) - - with pytest.raises( - TypeError, - match="Pydantic models must subclass our base model type, e.g. `from moderation_api import BaseModel`", - ): - response.parse(to=PydanticModel) - - -@pytest.mark.asyncio -async def test_async_response_parse_mismatched_basemodel(async_client: AsyncModerationAPI) -> None: - response = AsyncAPIResponse( - raw=httpx.Response(200, content=b"foo"), - client=async_client, - stream=False, - stream_cls=None, - cast_to=str, - options=FinalRequestOptions.construct(method="get", url="/foo"), - ) - - with pytest.raises( - TypeError, - match="Pydantic models must subclass our base model type, e.g. `from moderation_api import BaseModel`", - ): - await response.parse(to=PydanticModel) - - -def test_response_parse_custom_stream(client: ModerationAPI) -> None: - response = APIResponse( - raw=httpx.Response(200, content=b"foo"), - client=client, - stream=True, - stream_cls=None, - cast_to=str, - options=FinalRequestOptions.construct(method="get", url="/foo"), - ) - - stream = response.parse(to=Stream[int]) - assert stream._cast_to == int - - -@pytest.mark.asyncio -async def test_async_response_parse_custom_stream(async_client: AsyncModerationAPI) -> None: - response = AsyncAPIResponse( - raw=httpx.Response(200, content=b"foo"), - client=async_client, - stream=True, - stream_cls=None, - cast_to=str, - options=FinalRequestOptions.construct(method="get", url="/foo"), - ) - - stream = await response.parse(to=Stream[int]) - assert stream._cast_to == int - - -class CustomModel(BaseModel): - foo: str - bar: int - - -def test_response_parse_custom_model(client: ModerationAPI) -> None: - response = APIResponse( - raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), - client=client, - stream=False, - stream_cls=None, - cast_to=str, - options=FinalRequestOptions.construct(method="get", url="/foo"), - ) - - obj = response.parse(to=CustomModel) - assert obj.foo == "hello!" - assert obj.bar == 2 - - -@pytest.mark.asyncio -async def test_async_response_parse_custom_model(async_client: AsyncModerationAPI) -> None: - response = AsyncAPIResponse( - raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), - client=async_client, - stream=False, - stream_cls=None, - cast_to=str, - options=FinalRequestOptions.construct(method="get", url="/foo"), - ) - - obj = await response.parse(to=CustomModel) - assert obj.foo == "hello!" - assert obj.bar == 2 - - -def test_response_parse_annotated_type(client: ModerationAPI) -> None: - response = APIResponse( - raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), - client=client, - stream=False, - stream_cls=None, - cast_to=str, - options=FinalRequestOptions.construct(method="get", url="/foo"), - ) - - obj = response.parse( - to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]), - ) - assert obj.foo == "hello!" - assert obj.bar == 2 - - -async def test_async_response_parse_annotated_type(async_client: AsyncModerationAPI) -> None: - response = AsyncAPIResponse( - raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), - client=async_client, - stream=False, - stream_cls=None, - cast_to=str, - options=FinalRequestOptions.construct(method="get", url="/foo"), - ) - - obj = await response.parse( - to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]), - ) - assert obj.foo == "hello!" - assert obj.bar == 2 - - -@pytest.mark.parametrize( - "content, expected", - [ - ("false", False), - ("true", True), - ("False", False), - ("True", True), - ("TrUe", True), - ("FalSe", False), - ], -) -def test_response_parse_bool(client: ModerationAPI, content: str, expected: bool) -> None: - response = APIResponse( - raw=httpx.Response(200, content=content), - client=client, - stream=False, - stream_cls=None, - cast_to=str, - options=FinalRequestOptions.construct(method="get", url="/foo"), - ) - - result = response.parse(to=bool) - assert result is expected - - -@pytest.mark.parametrize( - "content, expected", - [ - ("false", False), - ("true", True), - ("False", False), - ("True", True), - ("TrUe", True), - ("FalSe", False), - ], -) -async def test_async_response_parse_bool(client: AsyncModerationAPI, content: str, expected: bool) -> None: - response = AsyncAPIResponse( - raw=httpx.Response(200, content=content), - client=client, - stream=False, - stream_cls=None, - cast_to=str, - options=FinalRequestOptions.construct(method="get", url="/foo"), - ) - - result = await response.parse(to=bool) - assert result is expected - - -class OtherModel(BaseModel): - a: str - - -@pytest.mark.parametrize("client", [False], indirect=True) # loose validation -def test_response_parse_expect_model_union_non_json_content(client: ModerationAPI) -> None: - response = APIResponse( - raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), - client=client, - stream=False, - stream_cls=None, - cast_to=str, - options=FinalRequestOptions.construct(method="get", url="/foo"), - ) - - obj = response.parse(to=cast(Any, Union[CustomModel, OtherModel])) - assert isinstance(obj, str) - assert obj == "foo" - - -@pytest.mark.asyncio -@pytest.mark.parametrize("async_client", [False], indirect=True) # loose validation -async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncModerationAPI) -> None: - response = AsyncAPIResponse( - raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), - client=async_client, - stream=False, - stream_cls=None, - cast_to=str, - options=FinalRequestOptions.construct(method="get", url="/foo"), - ) - - obj = await response.parse(to=cast(Any, Union[CustomModel, OtherModel])) - assert isinstance(obj, str) - assert obj == "foo" diff --git a/tests/test_streaming.py b/tests/test_streaming.py deleted file mode 100644 index 4555ec4..0000000 --- a/tests/test_streaming.py +++ /dev/null @@ -1,252 +0,0 @@ -from __future__ import annotations - -from typing import Iterator, AsyncIterator - -import httpx -import pytest - -from moderation_api import ModerationAPI, AsyncModerationAPI -from moderation_api._streaming import Stream, AsyncStream, ServerSentEvent - - -@pytest.mark.asyncio -@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_basic(sync: bool, client: ModerationAPI, async_client: AsyncModerationAPI) -> None: - def body() -> Iterator[bytes]: - yield b"event: completion\n" - yield b'data: {"foo":true}\n' - yield b"\n" - - iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) - - sse = await iter_next(iterator) - assert sse.event == "completion" - assert sse.json() == {"foo": True} - - await assert_empty_iter(iterator) - - -@pytest.mark.asyncio -@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_data_missing_event(sync: bool, client: ModerationAPI, async_client: AsyncModerationAPI) -> None: - def body() -> Iterator[bytes]: - yield b'data: {"foo":true}\n' - yield b"\n" - - iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) - - sse = await iter_next(iterator) - assert sse.event is None - assert sse.json() == {"foo": True} - - await assert_empty_iter(iterator) - - -@pytest.mark.asyncio -@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_event_missing_data(sync: bool, client: ModerationAPI, async_client: AsyncModerationAPI) -> None: - def body() -> Iterator[bytes]: - yield b"event: ping\n" - yield b"\n" - - iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) - - sse = await iter_next(iterator) - assert sse.event == "ping" - assert sse.data == "" - - await assert_empty_iter(iterator) - - -@pytest.mark.asyncio -@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_events(sync: bool, client: ModerationAPI, async_client: AsyncModerationAPI) -> None: - def body() -> Iterator[bytes]: - yield b"event: ping\n" - yield b"\n" - yield b"event: completion\n" - yield b"\n" - - iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) - - sse = await iter_next(iterator) - assert sse.event == "ping" - assert sse.data == "" - - sse = await iter_next(iterator) - assert sse.event == "completion" - assert sse.data == "" - - await assert_empty_iter(iterator) - - -@pytest.mark.asyncio -@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_events_with_data(sync: bool, client: ModerationAPI, async_client: AsyncModerationAPI) -> None: - def body() -> Iterator[bytes]: - yield b"event: ping\n" - yield b'data: {"foo":true}\n' - yield b"\n" - yield b"event: completion\n" - yield b'data: {"bar":false}\n' - yield b"\n" - - iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) - - sse = await iter_next(iterator) - assert sse.event == "ping" - assert sse.json() == {"foo": True} - - sse = await iter_next(iterator) - assert sse.event == "completion" - assert sse.json() == {"bar": False} - - await assert_empty_iter(iterator) - - -@pytest.mark.asyncio -@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_data_lines_with_empty_line( - sync: bool, client: ModerationAPI, async_client: AsyncModerationAPI -) -> None: - def body() -> Iterator[bytes]: - yield b"event: ping\n" - yield b"data: {\n" - yield b'data: "foo":\n' - yield b"data: \n" - yield b"data:\n" - yield b"data: true}\n" - yield b"\n\n" - - iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) - - sse = await iter_next(iterator) - assert sse.event == "ping" - assert sse.json() == {"foo": True} - assert sse.data == '{\n"foo":\n\n\ntrue}' - - await assert_empty_iter(iterator) - - -@pytest.mark.asyncio -@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_data_json_escaped_double_new_line( - sync: bool, client: ModerationAPI, async_client: AsyncModerationAPI -) -> None: - def body() -> Iterator[bytes]: - yield b"event: ping\n" - yield b'data: {"foo": "my long\\n\\ncontent"}' - yield b"\n\n" - - iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) - - sse = await iter_next(iterator) - assert sse.event == "ping" - assert sse.json() == {"foo": "my long\n\ncontent"} - - await assert_empty_iter(iterator) - - -@pytest.mark.asyncio -@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_data_lines(sync: bool, client: ModerationAPI, async_client: AsyncModerationAPI) -> None: - def body() -> Iterator[bytes]: - yield b"event: ping\n" - yield b"data: {\n" - yield b'data: "foo":\n' - yield b"data: true}\n" - yield b"\n\n" - - iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) - - sse = await iter_next(iterator) - assert sse.event == "ping" - assert sse.json() == {"foo": True} - - await assert_empty_iter(iterator) - - -@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_special_new_line_character( - sync: bool, - client: ModerationAPI, - async_client: AsyncModerationAPI, -) -> None: - def body() -> Iterator[bytes]: - yield b'data: {"content":" culpa"}\n' - yield b"\n" - yield b'data: {"content":" \xe2\x80\xa8"}\n' - yield b"\n" - yield b'data: {"content":"foo"}\n' - yield b"\n" - - iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) - - sse = await iter_next(iterator) - assert sse.event is None - assert sse.json() == {"content": " culpa"} - - sse = await iter_next(iterator) - assert sse.event is None - assert sse.json() == {"content": " 
"} - - sse = await iter_next(iterator) - assert sse.event is None - assert sse.json() == {"content": "foo"} - - await assert_empty_iter(iterator) - - -@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multi_byte_character_multiple_chunks( - sync: bool, - client: ModerationAPI, - async_client: AsyncModerationAPI, -) -> None: - def body() -> Iterator[bytes]: - yield b'data: {"content":"' - # bytes taken from the string 'известни' and arbitrarily split - # so that some multi-byte characters span multiple chunks - yield b"\xd0" - yield b"\xb8\xd0\xb7\xd0" - yield b"\xb2\xd0\xb5\xd1\x81\xd1\x82\xd0\xbd\xd0\xb8" - yield b'"}\n' - yield b"\n" - - iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) - - sse = await iter_next(iterator) - assert sse.event is None - assert sse.json() == {"content": "известни"} - - -async def to_aiter(iter: Iterator[bytes]) -> AsyncIterator[bytes]: - for chunk in iter: - yield chunk - - -async def iter_next(iter: Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]) -> ServerSentEvent: - if isinstance(iter, AsyncIterator): - return await iter.__anext__() - - return next(iter) - - -async def assert_empty_iter(iter: Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]) -> None: - with pytest.raises((StopAsyncIteration, RuntimeError)): - await iter_next(iter) - - -def make_event_iterator( - content: Iterator[bytes], - *, - sync: bool, - client: ModerationAPI, - async_client: AsyncModerationAPI, -) -> Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]: - if sync: - return Stream(cast_to=object, client=client, response=httpx.Response(200, content=content))._iter_events() - - return AsyncStream( - cast_to=object, client=async_client, response=httpx.Response(200, content=to_aiter(content)) - )._iter_events() diff --git a/tests/test_transform.py b/tests/test_transform.py deleted file mode 100644 index d281f3d..0000000 --- a/tests/test_transform.py +++ /dev/null @@ -1,460 +0,0 @@ -from __future__ import annotations - -import io -import pathlib -from typing import Any, Dict, List, Union, TypeVar, Iterable, Optional, cast -from datetime import date, datetime -from typing_extensions import Required, Annotated, TypedDict - -import pytest - -from moderation_api._types import Base64FileInput, omit, not_given -from moderation_api._utils import ( - PropertyInfo, - transform as _transform, - parse_datetime, - async_transform as _async_transform, -) -from moderation_api._compat import PYDANTIC_V1 -from moderation_api._models import BaseModel - -_T = TypeVar("_T") - -SAMPLE_FILE_PATH = pathlib.Path(__file__).parent.joinpath("sample_file.txt") - - -async def transform( - data: _T, - expected_type: object, - use_async: bool, -) -> _T: - if use_async: - return await _async_transform(data, expected_type=expected_type) - - return _transform(data, expected_type=expected_type) - - -parametrize = pytest.mark.parametrize("use_async", [False, True], ids=["sync", "async"]) - - -class Foo1(TypedDict): - foo_bar: Annotated[str, PropertyInfo(alias="fooBar")] - - -@parametrize -@pytest.mark.asyncio -async def test_top_level_alias(use_async: bool) -> None: - assert await transform({"foo_bar": "hello"}, expected_type=Foo1, use_async=use_async) == {"fooBar": "hello"} - - -class Foo2(TypedDict): - bar: Bar2 - - -class Bar2(TypedDict): - this_thing: Annotated[int, PropertyInfo(alias="this__thing")] - baz: Annotated[Baz2, PropertyInfo(alias="Baz")] - - -class Baz2(TypedDict): - my_baz: Annotated[str, PropertyInfo(alias="myBaz")] - - -@parametrize -@pytest.mark.asyncio -async def test_recursive_typeddict(use_async: bool) -> None: - assert await transform({"bar": {"this_thing": 1}}, Foo2, use_async) == {"bar": {"this__thing": 1}} - assert await transform({"bar": {"baz": {"my_baz": "foo"}}}, Foo2, use_async) == {"bar": {"Baz": {"myBaz": "foo"}}} - - -class Foo3(TypedDict): - things: List[Bar3] - - -class Bar3(TypedDict): - my_field: Annotated[str, PropertyInfo(alias="myField")] - - -@parametrize -@pytest.mark.asyncio -async def test_list_of_typeddict(use_async: bool) -> None: - result = await transform({"things": [{"my_field": "foo"}, {"my_field": "foo2"}]}, Foo3, use_async) - assert result == {"things": [{"myField": "foo"}, {"myField": "foo2"}]} - - -class Foo4(TypedDict): - foo: Union[Bar4, Baz4] - - -class Bar4(TypedDict): - foo_bar: Annotated[str, PropertyInfo(alias="fooBar")] - - -class Baz4(TypedDict): - foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] - - -@parametrize -@pytest.mark.asyncio -async def test_union_of_typeddict(use_async: bool) -> None: - assert await transform({"foo": {"foo_bar": "bar"}}, Foo4, use_async) == {"foo": {"fooBar": "bar"}} - assert await transform({"foo": {"foo_baz": "baz"}}, Foo4, use_async) == {"foo": {"fooBaz": "baz"}} - assert await transform({"foo": {"foo_baz": "baz", "foo_bar": "bar"}}, Foo4, use_async) == { - "foo": {"fooBaz": "baz", "fooBar": "bar"} - } - - -class Foo5(TypedDict): - foo: Annotated[Union[Bar4, List[Baz4]], PropertyInfo(alias="FOO")] - - -class Bar5(TypedDict): - foo_bar: Annotated[str, PropertyInfo(alias="fooBar")] - - -class Baz5(TypedDict): - foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] - - -@parametrize -@pytest.mark.asyncio -async def test_union_of_list(use_async: bool) -> None: - assert await transform({"foo": {"foo_bar": "bar"}}, Foo5, use_async) == {"FOO": {"fooBar": "bar"}} - assert await transform( - { - "foo": [ - {"foo_baz": "baz"}, - {"foo_baz": "baz"}, - ] - }, - Foo5, - use_async, - ) == {"FOO": [{"fooBaz": "baz"}, {"fooBaz": "baz"}]} - - -class Foo6(TypedDict): - bar: Annotated[str, PropertyInfo(alias="Bar")] - - -@parametrize -@pytest.mark.asyncio -async def test_includes_unknown_keys(use_async: bool) -> None: - assert await transform({"bar": "bar", "baz_": {"FOO": 1}}, Foo6, use_async) == { - "Bar": "bar", - "baz_": {"FOO": 1}, - } - - -class Foo7(TypedDict): - bar: Annotated[List[Bar7], PropertyInfo(alias="bAr")] - foo: Bar7 - - -class Bar7(TypedDict): - foo: str - - -@parametrize -@pytest.mark.asyncio -async def test_ignores_invalid_input(use_async: bool) -> None: - assert await transform({"bar": ""}, Foo7, use_async) == {"bAr": ""} - assert await transform({"foo": ""}, Foo7, use_async) == {"foo": ""} - - -class DatetimeDict(TypedDict, total=False): - foo: Annotated[datetime, PropertyInfo(format="iso8601")] - - bar: Annotated[Optional[datetime], PropertyInfo(format="iso8601")] - - required: Required[Annotated[Optional[datetime], PropertyInfo(format="iso8601")]] - - list_: Required[Annotated[Optional[List[datetime]], PropertyInfo(format="iso8601")]] - - union: Annotated[Union[int, datetime], PropertyInfo(format="iso8601")] - - -class DateDict(TypedDict, total=False): - foo: Annotated[date, PropertyInfo(format="iso8601")] - - -class DatetimeModel(BaseModel): - foo: datetime - - -class DateModel(BaseModel): - foo: Optional[date] - - -@parametrize -@pytest.mark.asyncio -async def test_iso8601_format(use_async: bool) -> None: - dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - tz = "+00:00" if PYDANTIC_V1 else "Z" - assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] - assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692" + tz} # type: ignore[comparison-overlap] - - dt = dt.replace(tzinfo=None) - assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap] - assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap] - - assert await transform({"foo": None}, DateDict, use_async) == {"foo": None} # type: ignore[comparison-overlap] - assert await transform(DateModel(foo=None), Any, use_async) == {"foo": None} # type: ignore - assert await transform({"foo": date.fromisoformat("2023-02-23")}, DateDict, use_async) == {"foo": "2023-02-23"} # type: ignore[comparison-overlap] - assert await transform(DateModel(foo=date.fromisoformat("2023-02-23")), DateDict, use_async) == { - "foo": "2023-02-23" - } # type: ignore[comparison-overlap] - - -@parametrize -@pytest.mark.asyncio -async def test_optional_iso8601_format(use_async: bool) -> None: - dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - assert await transform({"bar": dt}, DatetimeDict, use_async) == {"bar": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] - - assert await transform({"bar": None}, DatetimeDict, use_async) == {"bar": None} - - -@parametrize -@pytest.mark.asyncio -async def test_required_iso8601_format(use_async: bool) -> None: - dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - assert await transform({"required": dt}, DatetimeDict, use_async) == { - "required": "2023-02-23T14:16:36.337692+00:00" - } # type: ignore[comparison-overlap] - - assert await transform({"required": None}, DatetimeDict, use_async) == {"required": None} - - -@parametrize -@pytest.mark.asyncio -async def test_union_datetime(use_async: bool) -> None: - dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - assert await transform({"union": dt}, DatetimeDict, use_async) == { # type: ignore[comparison-overlap] - "union": "2023-02-23T14:16:36.337692+00:00" - } - - assert await transform({"union": "foo"}, DatetimeDict, use_async) == {"union": "foo"} - - -@parametrize -@pytest.mark.asyncio -async def test_nested_list_iso6801_format(use_async: bool) -> None: - dt1 = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - dt2 = parse_datetime("2022-01-15T06:34:23Z") - assert await transform({"list_": [dt1, dt2]}, DatetimeDict, use_async) == { # type: ignore[comparison-overlap] - "list_": ["2023-02-23T14:16:36.337692+00:00", "2022-01-15T06:34:23+00:00"] - } - - -@parametrize -@pytest.mark.asyncio -async def test_datetime_custom_format(use_async: bool) -> None: - dt = parse_datetime("2022-01-15T06:34:23Z") - - result = await transform(dt, Annotated[datetime, PropertyInfo(format="custom", format_template="%H")], use_async) - assert result == "06" # type: ignore[comparison-overlap] - - -class DateDictWithRequiredAlias(TypedDict, total=False): - required_prop: Required[Annotated[date, PropertyInfo(format="iso8601", alias="prop")]] - - -@parametrize -@pytest.mark.asyncio -async def test_datetime_with_alias(use_async: bool) -> None: - assert await transform({"required_prop": None}, DateDictWithRequiredAlias, use_async) == {"prop": None} # type: ignore[comparison-overlap] - assert await transform( - {"required_prop": date.fromisoformat("2023-02-23")}, DateDictWithRequiredAlias, use_async - ) == {"prop": "2023-02-23"} # type: ignore[comparison-overlap] - - -class MyModel(BaseModel): - foo: str - - -@parametrize -@pytest.mark.asyncio -async def test_pydantic_model_to_dictionary(use_async: bool) -> None: - assert cast(Any, await transform(MyModel(foo="hi!"), Any, use_async)) == {"foo": "hi!"} - assert cast(Any, await transform(MyModel.construct(foo="hi!"), Any, use_async)) == {"foo": "hi!"} - - -@parametrize -@pytest.mark.asyncio -async def test_pydantic_empty_model(use_async: bool) -> None: - assert cast(Any, await transform(MyModel.construct(), Any, use_async)) == {} - - -@parametrize -@pytest.mark.asyncio -async def test_pydantic_unknown_field(use_async: bool) -> None: - assert cast(Any, await transform(MyModel.construct(my_untyped_field=True), Any, use_async)) == { - "my_untyped_field": True - } - - -@parametrize -@pytest.mark.asyncio -async def test_pydantic_mismatched_types(use_async: bool) -> None: - model = MyModel.construct(foo=True) - if PYDANTIC_V1: - params = await transform(model, Any, use_async) - else: - with pytest.warns(UserWarning): - params = await transform(model, Any, use_async) - assert cast(Any, params) == {"foo": True} - - -@parametrize -@pytest.mark.asyncio -async def test_pydantic_mismatched_object_type(use_async: bool) -> None: - model = MyModel.construct(foo=MyModel.construct(hello="world")) - if PYDANTIC_V1: - params = await transform(model, Any, use_async) - else: - with pytest.warns(UserWarning): - params = await transform(model, Any, use_async) - assert cast(Any, params) == {"foo": {"hello": "world"}} - - -class ModelNestedObjects(BaseModel): - nested: MyModel - - -@parametrize -@pytest.mark.asyncio -async def test_pydantic_nested_objects(use_async: bool) -> None: - model = ModelNestedObjects.construct(nested={"foo": "stainless"}) - assert isinstance(model.nested, MyModel) - assert cast(Any, await transform(model, Any, use_async)) == {"nested": {"foo": "stainless"}} - - -class ModelWithDefaultField(BaseModel): - foo: str - with_none_default: Union[str, None] = None - with_str_default: str = "foo" - - -@parametrize -@pytest.mark.asyncio -async def test_pydantic_default_field(use_async: bool) -> None: - # should be excluded when defaults are used - model = ModelWithDefaultField.construct() - assert model.with_none_default is None - assert model.with_str_default == "foo" - assert cast(Any, await transform(model, Any, use_async)) == {} - - # should be included when the default value is explicitly given - model = ModelWithDefaultField.construct(with_none_default=None, with_str_default="foo") - assert model.with_none_default is None - assert model.with_str_default == "foo" - assert cast(Any, await transform(model, Any, use_async)) == {"with_none_default": None, "with_str_default": "foo"} - - # should be included when a non-default value is explicitly given - model = ModelWithDefaultField.construct(with_none_default="bar", with_str_default="baz") - assert model.with_none_default == "bar" - assert model.with_str_default == "baz" - assert cast(Any, await transform(model, Any, use_async)) == {"with_none_default": "bar", "with_str_default": "baz"} - - -class TypedDictIterableUnion(TypedDict): - foo: Annotated[Union[Bar8, Iterable[Baz8]], PropertyInfo(alias="FOO")] - - -class Bar8(TypedDict): - foo_bar: Annotated[str, PropertyInfo(alias="fooBar")] - - -class Baz8(TypedDict): - foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] - - -@parametrize -@pytest.mark.asyncio -async def test_iterable_of_dictionaries(use_async: bool) -> None: - assert await transform({"foo": [{"foo_baz": "bar"}]}, TypedDictIterableUnion, use_async) == { - "FOO": [{"fooBaz": "bar"}] - } - assert cast(Any, await transform({"foo": ({"foo_baz": "bar"},)}, TypedDictIterableUnion, use_async)) == { - "FOO": [{"fooBaz": "bar"}] - } - - def my_iter() -> Iterable[Baz8]: - yield {"foo_baz": "hello"} - yield {"foo_baz": "world"} - - assert await transform({"foo": my_iter()}, TypedDictIterableUnion, use_async) == { - "FOO": [{"fooBaz": "hello"}, {"fooBaz": "world"}] - } - - -@parametrize -@pytest.mark.asyncio -async def test_dictionary_items(use_async: bool) -> None: - class DictItems(TypedDict): - foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] - - assert await transform({"foo": {"foo_baz": "bar"}}, Dict[str, DictItems], use_async) == {"foo": {"fooBaz": "bar"}} - - -class TypedDictIterableUnionStr(TypedDict): - foo: Annotated[Union[str, Iterable[Baz8]], PropertyInfo(alias="FOO")] - - -@parametrize -@pytest.mark.asyncio -async def test_iterable_union_str(use_async: bool) -> None: - assert await transform({"foo": "bar"}, TypedDictIterableUnionStr, use_async) == {"FOO": "bar"} - assert cast(Any, await transform(iter([{"foo_baz": "bar"}]), Union[str, Iterable[Baz8]], use_async)) == [ - {"fooBaz": "bar"} - ] - - -class TypedDictBase64Input(TypedDict): - foo: Annotated[Union[str, Base64FileInput], PropertyInfo(format="base64")] - - -@parametrize -@pytest.mark.asyncio -async def test_base64_file_input(use_async: bool) -> None: - # strings are left as-is - assert await transform({"foo": "bar"}, TypedDictBase64Input, use_async) == {"foo": "bar"} - - # pathlib.Path is automatically converted to base64 - assert await transform({"foo": SAMPLE_FILE_PATH}, TypedDictBase64Input, use_async) == { - "foo": "SGVsbG8sIHdvcmxkIQo=" - } # type: ignore[comparison-overlap] - - # io instances are automatically converted to base64 - assert await transform({"foo": io.StringIO("Hello, world!")}, TypedDictBase64Input, use_async) == { - "foo": "SGVsbG8sIHdvcmxkIQ==" - } # type: ignore[comparison-overlap] - assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == { - "foo": "SGVsbG8sIHdvcmxkIQ==" - } # type: ignore[comparison-overlap] - - -@parametrize -@pytest.mark.asyncio -async def test_transform_skipping(use_async: bool) -> None: - # lists of ints are left as-is - data = [1, 2, 3] - assert await transform(data, List[int], use_async) is data - - # iterables of ints are converted to a list - data = iter([1, 2, 3]) - assert await transform(data, Iterable[int], use_async) == [1, 2, 3] - - -@parametrize -@pytest.mark.asyncio -async def test_strips_notgiven(use_async: bool) -> None: - assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} - assert await transform({"foo_bar": not_given}, Foo1, use_async) == {} - - -@parametrize -@pytest.mark.asyncio -async def test_strips_omit(use_async: bool) -> None: - assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} - assert await transform({"foo_bar": omit}, Foo1, use_async) == {} diff --git a/tests/test_utils/test_datetime_parse.py b/tests/test_utils/test_datetime_parse.py deleted file mode 100644 index 0cb4651..0000000 --- a/tests/test_utils/test_datetime_parse.py +++ /dev/null @@ -1,110 +0,0 @@ -""" -Copied from https://github.com/pydantic/pydantic/blob/v1.10.22/tests/test_datetime_parse.py -with modifications so it works without pydantic v1 imports. -""" - -from typing import Type, Union -from datetime import date, datetime, timezone, timedelta - -import pytest - -from moderation_api._utils import parse_date, parse_datetime - - -def create_tz(minutes: int) -> timezone: - return timezone(timedelta(minutes=minutes)) - - -@pytest.mark.parametrize( - "value,result", - [ - # Valid inputs - ("1494012444.883309", date(2017, 5, 5)), - (b"1494012444.883309", date(2017, 5, 5)), - (1_494_012_444.883_309, date(2017, 5, 5)), - ("1494012444", date(2017, 5, 5)), - (1_494_012_444, date(2017, 5, 5)), - (0, date(1970, 1, 1)), - ("2012-04-23", date(2012, 4, 23)), - (b"2012-04-23", date(2012, 4, 23)), - ("2012-4-9", date(2012, 4, 9)), - (date(2012, 4, 9), date(2012, 4, 9)), - (datetime(2012, 4, 9, 12, 15), date(2012, 4, 9)), - # Invalid inputs - ("x20120423", ValueError), - ("2012-04-56", ValueError), - (19_999_999_999, date(2603, 10, 11)), # just before watershed - (20_000_000_001, date(1970, 8, 20)), # just after watershed - (1_549_316_052, date(2019, 2, 4)), # nowish in s - (1_549_316_052_104, date(2019, 2, 4)), # nowish in ms - (1_549_316_052_104_324, date(2019, 2, 4)), # nowish in μs - (1_549_316_052_104_324_096, date(2019, 2, 4)), # nowish in ns - ("infinity", date(9999, 12, 31)), - ("inf", date(9999, 12, 31)), - (float("inf"), date(9999, 12, 31)), - ("infinity ", date(9999, 12, 31)), - (int("1" + "0" * 100), date(9999, 12, 31)), - (1e1000, date(9999, 12, 31)), - ("-infinity", date(1, 1, 1)), - ("-inf", date(1, 1, 1)), - ("nan", ValueError), - ], -) -def test_date_parsing(value: Union[str, bytes, int, float], result: Union[date, Type[Exception]]) -> None: - if type(result) == type and issubclass(result, Exception): # pyright: ignore[reportUnnecessaryIsInstance] - with pytest.raises(result): - parse_date(value) - else: - assert parse_date(value) == result - - -@pytest.mark.parametrize( - "value,result", - [ - # Valid inputs - # values in seconds - ("1494012444.883309", datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)), - (1_494_012_444.883_309, datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)), - ("1494012444", datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), - (b"1494012444", datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), - (1_494_012_444, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), - # values in ms - ("1494012444000.883309", datetime(2017, 5, 5, 19, 27, 24, 883, tzinfo=timezone.utc)), - ("-1494012444000.883309", datetime(1922, 8, 29, 4, 32, 35, 999117, tzinfo=timezone.utc)), - (1_494_012_444_000, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), - ("2012-04-23T09:15:00", datetime(2012, 4, 23, 9, 15)), - ("2012-4-9 4:8:16", datetime(2012, 4, 9, 4, 8, 16)), - ("2012-04-23T09:15:00Z", datetime(2012, 4, 23, 9, 15, 0, 0, timezone.utc)), - ("2012-4-9 4:8:16-0320", datetime(2012, 4, 9, 4, 8, 16, 0, create_tz(-200))), - ("2012-04-23T10:20:30.400+02:30", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(150))), - ("2012-04-23T10:20:30.400+02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(120))), - ("2012-04-23T10:20:30.400-02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))), - (b"2012-04-23T10:20:30.400-02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))), - (datetime(2017, 5, 5), datetime(2017, 5, 5)), - (0, datetime(1970, 1, 1, 0, 0, 0, tzinfo=timezone.utc)), - # Invalid inputs - ("x20120423091500", ValueError), - ("2012-04-56T09:15:90", ValueError), - ("2012-04-23T11:05:00-25:00", ValueError), - (19_999_999_999, datetime(2603, 10, 11, 11, 33, 19, tzinfo=timezone.utc)), # just before watershed - (20_000_000_001, datetime(1970, 8, 20, 11, 33, 20, 1000, tzinfo=timezone.utc)), # just after watershed - (1_549_316_052, datetime(2019, 2, 4, 21, 34, 12, 0, tzinfo=timezone.utc)), # nowish in s - (1_549_316_052_104, datetime(2019, 2, 4, 21, 34, 12, 104_000, tzinfo=timezone.utc)), # nowish in ms - (1_549_316_052_104_324, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in μs - (1_549_316_052_104_324_096, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in ns - ("infinity", datetime(9999, 12, 31, 23, 59, 59, 999999)), - ("inf", datetime(9999, 12, 31, 23, 59, 59, 999999)), - ("inf ", datetime(9999, 12, 31, 23, 59, 59, 999999)), - (1e50, datetime(9999, 12, 31, 23, 59, 59, 999999)), - (float("inf"), datetime(9999, 12, 31, 23, 59, 59, 999999)), - ("-infinity", datetime(1, 1, 1, 0, 0)), - ("-inf", datetime(1, 1, 1, 0, 0)), - ("nan", ValueError), - ], -) -def test_datetime_parsing(value: Union[str, bytes, int, float], result: Union[datetime, Type[Exception]]) -> None: - if type(result) == type and issubclass(result, Exception): # pyright: ignore[reportUnnecessaryIsInstance] - with pytest.raises(result): - parse_datetime(value) - else: - assert parse_datetime(value) == result diff --git a/tests/test_utils/test_json.py b/tests/test_utils/test_json.py deleted file mode 100644 index ab6e1cb..0000000 --- a/tests/test_utils/test_json.py +++ /dev/null @@ -1,126 +0,0 @@ -from __future__ import annotations - -import datetime -from typing import Union - -import pydantic - -from moderation_api import _compat -from moderation_api._utils._json import openapi_dumps - - -class TestOpenapiDumps: - def test_basic(self) -> None: - data = {"key": "value", "number": 42} - json_bytes = openapi_dumps(data) - assert json_bytes == b'{"key":"value","number":42}' - - def test_datetime_serialization(self) -> None: - dt = datetime.datetime(2023, 1, 1, 12, 0, 0) - data = {"datetime": dt} - json_bytes = openapi_dumps(data) - assert json_bytes == b'{"datetime":"2023-01-01T12:00:00"}' - - def test_pydantic_model_serialization(self) -> None: - class User(pydantic.BaseModel): - first_name: str - last_name: str - age: int - - model_instance = User(first_name="John", last_name="Kramer", age=83) - data = {"model": model_instance} - json_bytes = openapi_dumps(data) - assert json_bytes == b'{"model":{"first_name":"John","last_name":"Kramer","age":83}}' - - def test_pydantic_model_with_default_values(self) -> None: - class User(pydantic.BaseModel): - name: str - role: str = "user" - active: bool = True - score: int = 0 - - model_instance = User(name="Alice") - data = {"model": model_instance} - json_bytes = openapi_dumps(data) - assert json_bytes == b'{"model":{"name":"Alice"}}' - - def test_pydantic_model_with_default_values_overridden(self) -> None: - class User(pydantic.BaseModel): - name: str - role: str = "user" - active: bool = True - - model_instance = User(name="Bob", role="admin", active=False) - data = {"model": model_instance} - json_bytes = openapi_dumps(data) - assert json_bytes == b'{"model":{"name":"Bob","role":"admin","active":false}}' - - def test_pydantic_model_with_alias(self) -> None: - class User(pydantic.BaseModel): - first_name: str = pydantic.Field(alias="firstName") - last_name: str = pydantic.Field(alias="lastName") - - model_instance = User(firstName="John", lastName="Doe") - data = {"model": model_instance} - json_bytes = openapi_dumps(data) - assert json_bytes == b'{"model":{"firstName":"John","lastName":"Doe"}}' - - def test_pydantic_model_with_alias_and_default(self) -> None: - class User(pydantic.BaseModel): - user_name: str = pydantic.Field(alias="userName") - user_role: str = pydantic.Field(default="member", alias="userRole") - is_active: bool = pydantic.Field(default=True, alias="isActive") - - model_instance = User(userName="charlie") - data = {"model": model_instance} - json_bytes = openapi_dumps(data) - assert json_bytes == b'{"model":{"userName":"charlie"}}' - - model_with_overrides = User(userName="diana", userRole="admin", isActive=False) - data = {"model": model_with_overrides} - json_bytes = openapi_dumps(data) - assert json_bytes == b'{"model":{"userName":"diana","userRole":"admin","isActive":false}}' - - def test_pydantic_model_with_nested_models_and_defaults(self) -> None: - class Address(pydantic.BaseModel): - street: str - city: str = "Unknown" - - class User(pydantic.BaseModel): - name: str - address: Address - verified: bool = False - - if _compat.PYDANTIC_V1: - # to handle forward references in Pydantic v1 - User.update_forward_refs(**locals()) # type: ignore[reportDeprecated] - - address = Address(street="123 Main St") - user = User(name="Diana", address=address) - data = {"user": user} - json_bytes = openapi_dumps(data) - assert json_bytes == b'{"user":{"name":"Diana","address":{"street":"123 Main St"}}}' - - address_with_city = Address(street="456 Oak Ave", city="Boston") - user_verified = User(name="Eve", address=address_with_city, verified=True) - data = {"user": user_verified} - json_bytes = openapi_dumps(data) - assert ( - json_bytes == b'{"user":{"name":"Eve","address":{"street":"456 Oak Ave","city":"Boston"},"verified":true}}' - ) - - def test_pydantic_model_with_optional_fields(self) -> None: - class User(pydantic.BaseModel): - name: str - email: Union[str, None] - phone: Union[str, None] - - model_with_none = User(name="Eve", email=None, phone=None) - data = {"model": model_with_none} - json_bytes = openapi_dumps(data) - assert json_bytes == b'{"model":{"name":"Eve","email":null,"phone":null}}' - - model_with_values = User(name="Frank", email="frank@example.com", phone=None) - data = {"model": model_with_values} - json_bytes = openapi_dumps(data) - assert json_bytes == b'{"model":{"name":"Frank","email":"frank@example.com","phone":null}}' diff --git a/tests/test_utils/test_path.py b/tests/test_utils/test_path.py deleted file mode 100644 index 7e6d85a..0000000 --- a/tests/test_utils/test_path.py +++ /dev/null @@ -1,89 +0,0 @@ -from __future__ import annotations - -from typing import Any - -import pytest - -from moderation_api._utils._path import path_template - - -@pytest.mark.parametrize( - "template, kwargs, expected", - [ - ("/v1/{id}", dict(id="abc"), "/v1/abc"), - ("/v1/{a}/{b}", dict(a="x", b="y"), "/v1/x/y"), - ("/v1/{a}{b}/path/{c}?val={d}#{e}", dict(a="x", b="y", c="z", d="u", e="v"), "/v1/xy/path/z?val=u#v"), - ("/{w}/{w}", dict(w="echo"), "/echo/echo"), - ("/v1/static", {}, "/v1/static"), - ("", {}, ""), - ("/v1/?q={n}&count=10", dict(n=42), "/v1/?q=42&count=10"), - ("/v1/{v}", dict(v=None), "/v1/null"), - ("/v1/{v}", dict(v=True), "/v1/true"), - ("/v1/{v}", dict(v=False), "/v1/false"), - ("/v1/{v}", dict(v=".hidden"), "/v1/.hidden"), # dot prefix ok - ("/v1/{v}", dict(v="file.txt"), "/v1/file.txt"), # dot in middle ok - ("/v1/{v}", dict(v="..."), "/v1/..."), # triple dot ok - ("/v1/{a}{b}", dict(a=".", b="txt"), "/v1/.txt"), # dot var combining with adjacent to be ok - ("/items?q={v}#{f}", dict(v=".", f=".."), "/items?q=.#.."), # dots in query/fragment are fine - ( - "/v1/{a}?query={b}", - dict(a="../../other/endpoint", b="a&bad=true"), - "/v1/..%2F..%2Fother%2Fendpoint?query=a%26bad%3Dtrue", - ), - ("/v1/{val}", dict(val="a/b/c"), "/v1/a%2Fb%2Fc"), - ("/v1/{val}", dict(val="a/b/c?query=value"), "/v1/a%2Fb%2Fc%3Fquery=value"), - ("/v1/{val}", dict(val="a/b/c?query=value&bad=true"), "/v1/a%2Fb%2Fc%3Fquery=value&bad=true"), - ("/v1/{val}", dict(val="%20"), "/v1/%2520"), # escapes escape sequences in input - # Query: slash and ? are safe, # is not - ("/items?q={v}", dict(v="a/b"), "/items?q=a/b"), - ("/items?q={v}", dict(v="a?b"), "/items?q=a?b"), - ("/items?q={v}", dict(v="a#b"), "/items?q=a%23b"), - ("/items?q={v}", dict(v="a b"), "/items?q=a%20b"), - # Fragment: slash and ? are safe - ("/docs#{v}", dict(v="a/b"), "/docs#a/b"), - ("/docs#{v}", dict(v="a?b"), "/docs#a?b"), - # Path: slash, ? and # are all encoded - ("/v1/{v}", dict(v="a/b"), "/v1/a%2Fb"), - ("/v1/{v}", dict(v="a?b"), "/v1/a%3Fb"), - ("/v1/{v}", dict(v="a#b"), "/v1/a%23b"), - # same var encoded differently by component - ( - "/v1/{v}?q={v}#{v}", - dict(v="a/b?c#d"), - "/v1/a%2Fb%3Fc%23d?q=a/b?c%23d#a/b?c%23d", - ), - ("/v1/{val}", dict(val="x?admin=true"), "/v1/x%3Fadmin=true"), # query injection - ("/v1/{val}", dict(val="x#admin"), "/v1/x%23admin"), # fragment injection - ], -) -def test_interpolation(template: str, kwargs: dict[str, Any], expected: str) -> None: - assert path_template(template, **kwargs) == expected - - -def test_missing_kwarg_raises_key_error() -> None: - with pytest.raises(KeyError, match="org_id"): - path_template("/v1/{org_id}") - - -@pytest.mark.parametrize( - "template, kwargs", - [ - ("{a}/path", dict(a=".")), - ("{a}/path", dict(a="..")), - ("/v1/{a}", dict(a=".")), - ("/v1/{a}", dict(a="..")), - ("/v1/{a}/path", dict(a=".")), - ("/v1/{a}/path", dict(a="..")), - ("/v1/{a}{b}", dict(a=".", b=".")), # adjacent vars → ".." - ("/v1/{a}.", dict(a=".")), # var + static → ".." - ("/v1/{a}{b}", dict(a="", b=".")), # empty + dot → "." - ("/v1/%2e/{x}", dict(x="ok")), # encoded dot in static text - ("/v1/%2e./{x}", dict(x="ok")), # mixed encoded ".." in static - ("/v1/.%2E/{x}", dict(x="ok")), # mixed encoded ".." in static - ("/v1/{v}?q=1", dict(v="..")), - ("/v1/{v}#frag", dict(v="..")), - ], -) -def test_dot_segment_rejected(template: str, kwargs: dict[str, Any]) -> None: - with pytest.raises(ValueError, match="dot-segment"): - path_template(template, **kwargs) diff --git a/tests/test_utils/test_proxy.py b/tests/test_utils/test_proxy.py deleted file mode 100644 index 955248d..0000000 --- a/tests/test_utils/test_proxy.py +++ /dev/null @@ -1,34 +0,0 @@ -import operator -from typing import Any -from typing_extensions import override - -from moderation_api._utils import LazyProxy - - -class RecursiveLazyProxy(LazyProxy[Any]): - @override - def __load__(self) -> Any: - return self - - def __call__(self, *_args: Any, **_kwds: Any) -> Any: - raise RuntimeError("This should never be called!") - - -def test_recursive_proxy() -> None: - proxy = RecursiveLazyProxy() - assert repr(proxy) == "RecursiveLazyProxy" - assert str(proxy) == "RecursiveLazyProxy" - assert dir(proxy) == [] - assert type(proxy).__name__ == "RecursiveLazyProxy" - assert type(operator.attrgetter("name.foo.bar.baz")(proxy)).__name__ == "RecursiveLazyProxy" - - -def test_isinstance_does_not_error() -> None: - class AlwaysErrorProxy(LazyProxy[Any]): - @override - def __load__(self) -> Any: - raise RuntimeError("Mocking missing dependency") - - proxy = AlwaysErrorProxy() - assert not isinstance(proxy, dict) - assert isinstance(proxy, LazyProxy) diff --git a/tests/test_utils/test_typing.py b/tests/test_utils/test_typing.py deleted file mode 100644 index bcb2325..0000000 --- a/tests/test_utils/test_typing.py +++ /dev/null @@ -1,73 +0,0 @@ -from __future__ import annotations - -from typing import Generic, TypeVar, cast - -from moderation_api._utils import extract_type_var_from_base - -_T = TypeVar("_T") -_T2 = TypeVar("_T2") -_T3 = TypeVar("_T3") - - -class BaseGeneric(Generic[_T]): ... - - -class SubclassGeneric(BaseGeneric[_T]): ... - - -class BaseGenericMultipleTypeArgs(Generic[_T, _T2, _T3]): ... - - -class SubclassGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T, _T2, _T3]): ... - - -class SubclassDifferentOrderGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T2, _T, _T3]): ... - - -def test_extract_type_var() -> None: - assert ( - extract_type_var_from_base( - BaseGeneric[int], - index=0, - generic_bases=cast("tuple[type, ...]", (BaseGeneric,)), - ) - == int - ) - - -def test_extract_type_var_generic_subclass() -> None: - assert ( - extract_type_var_from_base( - SubclassGeneric[int], - index=0, - generic_bases=cast("tuple[type, ...]", (BaseGeneric,)), - ) - == int - ) - - -def test_extract_type_var_multiple() -> None: - typ = BaseGenericMultipleTypeArgs[int, str, None] - - generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,)) - assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int - assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str - assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None) - - -def test_extract_type_var_generic_subclass_multiple() -> None: - typ = SubclassGenericMultipleTypeArgs[int, str, None] - - generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,)) - assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int - assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str - assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None) - - -def test_extract_type_var_generic_subclass_different_ordering_multiple() -> None: - typ = SubclassDifferentOrderGenericMultipleTypeArgs[int, str, None] - - generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,)) - assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int - assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str - assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None) diff --git a/tests/utils.py b/tests/utils.py deleted file mode 100644 index 58383d8..0000000 --- a/tests/utils.py +++ /dev/null @@ -1,167 +0,0 @@ -from __future__ import annotations - -import os -import inspect -import traceback -import contextlib -from typing import Any, TypeVar, Iterator, Sequence, cast -from datetime import date, datetime -from typing_extensions import Literal, get_args, get_origin, assert_type - -from moderation_api._types import Omit, NoneType -from moderation_api._utils import ( - is_dict, - is_list, - is_list_type, - is_union_type, - extract_type_arg, - is_sequence_type, - is_annotated_type, - is_type_alias_type, -) -from moderation_api._compat import PYDANTIC_V1, field_outer_type, get_model_fields -from moderation_api._models import BaseModel - -BaseModelT = TypeVar("BaseModelT", bound=BaseModel) - - -def assert_matches_model(model: type[BaseModelT], value: BaseModelT, *, path: list[str]) -> bool: - for name, field in get_model_fields(model).items(): - field_value = getattr(value, name) - if PYDANTIC_V1: - # in v1 nullability was structured differently - # https://docs.pydantic.dev/2.0/migration/#required-optional-and-nullable-fields - allow_none = getattr(field, "allow_none", False) - else: - allow_none = False - - assert_matches_type( - field_outer_type(field), - field_value, - path=[*path, name], - allow_none=allow_none, - ) - - return True - - -# Note: the `path` argument is only used to improve error messages when `--showlocals` is used -def assert_matches_type( - type_: Any, - value: object, - *, - path: list[str], - allow_none: bool = False, -) -> None: - if is_type_alias_type(type_): - type_ = type_.__value__ - - # unwrap `Annotated[T, ...]` -> `T` - if is_annotated_type(type_): - type_ = extract_type_arg(type_, 0) - - if allow_none and value is None: - return - - if type_ is None or type_ is NoneType: - assert value is None - return - - origin = get_origin(type_) or type_ - - if is_list_type(type_): - return _assert_list_type(type_, value) - - if is_sequence_type(type_): - assert isinstance(value, Sequence) - inner_type = get_args(type_)[0] - for entry in value: # type: ignore - assert_type(inner_type, entry) # type: ignore - return - - if origin == str: - assert isinstance(value, str) - elif origin == int: - assert isinstance(value, int) - elif origin == bool: - assert isinstance(value, bool) - elif origin == float: - assert isinstance(value, float) - elif origin == bytes: - assert isinstance(value, bytes) - elif origin == datetime: - assert isinstance(value, datetime) - elif origin == date: - assert isinstance(value, date) - elif origin == object: - # nothing to do here, the expected type is unknown - pass - elif origin == Literal: - assert value in get_args(type_) - elif origin == dict: - assert is_dict(value) - - args = get_args(type_) - key_type = args[0] - items_type = args[1] - - for key, item in value.items(): - assert_matches_type(key_type, key, path=[*path, ""]) - assert_matches_type(items_type, item, path=[*path, ""]) - elif is_union_type(type_): - variants = get_args(type_) - - try: - none_index = variants.index(type(None)) - except ValueError: - pass - else: - # special case Optional[T] for better error messages - if len(variants) == 2: - if value is None: - # valid - return - - return assert_matches_type(type_=variants[not none_index], value=value, path=path) - - for i, variant in enumerate(variants): - try: - assert_matches_type(variant, value, path=[*path, f"variant {i}"]) - return - except AssertionError: - traceback.print_exc() - continue - - raise AssertionError("Did not match any variants") - elif issubclass(origin, BaseModel): - assert isinstance(value, type_) - assert assert_matches_model(type_, cast(Any, value), path=path) - elif inspect.isclass(origin) and origin.__name__ == "HttpxBinaryResponseContent": - assert value.__class__.__name__ == "HttpxBinaryResponseContent" - else: - assert None, f"Unhandled field type: {type_}" - - -def _assert_list_type(type_: type[object], value: object) -> None: - assert is_list(value) - - inner_type = get_args(type_)[0] - for entry in value: - assert_type(inner_type, entry) # type: ignore - - -@contextlib.contextmanager -def update_env(**new_env: str | Omit) -> Iterator[None]: - old = os.environ.copy() - - try: - for name, value in new_env.items(): - if isinstance(value, Omit): - os.environ.pop(name, None) - else: - os.environ[name] = value - - yield None - finally: - os.environ.clear() - os.environ.update(old) diff --git a/uv.lock b/uv.lock deleted file mode 100644 index d6a2516..0000000 --- a/uv.lock +++ /dev/null @@ -1,1829 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.9" -resolution-markers = [ - "python_full_version >= '3.14' and extra != 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2'", - "python_full_version < '3.10' and extra != 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and extra == 'group-14-moderation-api-pydantic-v1' and extra != 'group-14-moderation-api-pydantic-v2'", - "python_full_version < '3.10' and extra == 'group-14-moderation-api-pydantic-v1' and extra != 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and extra != 'group-14-moderation-api-pydantic-v1' and extra != 'group-14-moderation-api-pydantic-v2'", - "python_full_version < '3.10' and extra != 'group-14-moderation-api-pydantic-v1' and extra != 'group-14-moderation-api-pydantic-v2'", -] -conflicts = [[ - { package = "moderation-api", group = "pydantic-v1" }, - { package = "moderation-api", group = "pydantic-v2" }, -]] - -[[package]] -name = "aiohappyeyeballs" -version = "2.6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, -] - -[[package]] -name = "aiohttp" -version = "3.13.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohappyeyeballs" }, - { name = "aiosignal" }, - { name = "async-timeout", marker = "python_full_version < '3.11' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "attrs" }, - { name = "frozenlist" }, - { name = "multidict" }, - { name = "propcache" }, - { name = "yarl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/36/d6/5aec9313ee6ea9c7cde8b891b69f4ff4001416867104580670a31daeba5b/aiohttp-3.13.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a372fd5afd301b3a89582817fdcdb6c34124787c70dbcc616f259013e7eef7", size = 738950, upload-time = "2026-01-03T17:29:13.002Z" }, - { url = "https://files.pythonhosted.org/packages/68/03/8fa90a7e6d11ff20a18837a8e2b5dd23db01aabc475aa9271c8ad33299f5/aiohttp-3.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:147e422fd1223005c22b4fe080f5d93ced44460f5f9c105406b753612b587821", size = 496099, upload-time = "2026-01-03T17:29:15.268Z" }, - { url = "https://files.pythonhosted.org/packages/d2/23/b81f744d402510a8366b74eb420fc0cc1170d0c43daca12d10814df85f10/aiohttp-3.13.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:859bd3f2156e81dd01432f5849fc73e2243d4a487c4fd26609b1299534ee1845", size = 491072, upload-time = "2026-01-03T17:29:16.922Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e1/56d1d1c0dd334cd203dd97706ce004c1aa24b34a813b0b8daf3383039706/aiohttp-3.13.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dca68018bf48c251ba17c72ed479f4dafe9dbd5a73707ad8d28a38d11f3d42af", size = 1671588, upload-time = "2026-01-03T17:29:18.539Z" }, - { url = "https://files.pythonhosted.org/packages/5f/34/8d7f962604f4bc2b4e39eb1220dac7d4e4cba91fb9ba0474b4ecd67db165/aiohttp-3.13.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fee0c6bc7db1de362252affec009707a17478a00ec69f797d23ca256e36d5940", size = 1640334, upload-time = "2026-01-03T17:29:21.028Z" }, - { url = "https://files.pythonhosted.org/packages/94/1d/fcccf2c668d87337ddeef9881537baee13c58d8f01f12ba8a24215f2b804/aiohttp-3.13.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c048058117fd649334d81b4b526e94bde3ccaddb20463a815ced6ecbb7d11160", size = 1722656, upload-time = "2026-01-03T17:29:22.531Z" }, - { url = "https://files.pythonhosted.org/packages/aa/98/c6f3b081c4c606bc1e5f2ec102e87d6411c73a9ef3616fea6f2d5c98c062/aiohttp-3.13.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:215a685b6fbbfcf71dfe96e3eba7a6f58f10da1dfdf4889c7dd856abe430dca7", size = 1817625, upload-time = "2026-01-03T17:29:24.276Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c0/cfcc3d2e11b477f86e1af2863f3858c8850d751ce8dc39c4058a072c9e54/aiohttp-3.13.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2c184bb1fe2cbd2cefba613e9db29a5ab559323f994b6737e370d3da0ac455", size = 1672604, upload-time = "2026-01-03T17:29:26.099Z" }, - { url = "https://files.pythonhosted.org/packages/1e/77/6b4ffcbcac4c6a5d041343a756f34a6dd26174ae07f977a64fe028dda5b0/aiohttp-3.13.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75ca857eba4e20ce9f546cd59c7007b33906a4cd48f2ff6ccf1ccfc3b646f279", size = 1554370, upload-time = "2026-01-03T17:29:28.121Z" }, - { url = "https://files.pythonhosted.org/packages/f2/f0/e3ddfa93f17d689dbe014ba048f18e0c9f9b456033b70e94349a2e9048be/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81e97251d9298386c2b7dbeb490d3d1badbdc69107fb8c9299dd04eb39bddc0e", size = 1642023, upload-time = "2026-01-03T17:29:30.002Z" }, - { url = "https://files.pythonhosted.org/packages/eb/45/c14019c9ec60a8e243d06d601b33dcc4fd92379424bde3021725859d7f99/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0e2d366af265797506f0283487223146af57815b388623f0357ef7eac9b209d", size = 1649680, upload-time = "2026-01-03T17:29:31.782Z" }, - { url = "https://files.pythonhosted.org/packages/9c/fd/09c9451dae5aa5c5ed756df95ff9ef549d45d4be663bafd1e4954fd836f0/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4e239d501f73d6db1522599e14b9b321a7e3b1de66ce33d53a765d975e9f4808", size = 1692407, upload-time = "2026-01-03T17:29:33.392Z" }, - { url = "https://files.pythonhosted.org/packages/a6/81/938bc2ec33c10efd6637ccb3d22f9f3160d08e8f3aa2587a2c2d5ab578eb/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0db318f7a6f065d84cb1e02662c526294450b314a02bd9e2a8e67f0d8564ce40", size = 1543047, upload-time = "2026-01-03T17:29:34.855Z" }, - { url = "https://files.pythonhosted.org/packages/f7/23/80488ee21c8d567c83045e412e1d9b7077d27171591a4eb7822586e8c06a/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:bfc1cc2fe31a6026a8a88e4ecfb98d7f6b1fec150cfd708adbfd1d2f42257c29", size = 1715264, upload-time = "2026-01-03T17:29:36.389Z" }, - { url = "https://files.pythonhosted.org/packages/e2/83/259a8da6683182768200b368120ab3deff5370bed93880fb9a3a86299f34/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af71fff7bac6bb7508956696dce8f6eec2bbb045eceb40343944b1ae62b5ef11", size = 1657275, upload-time = "2026-01-03T17:29:38.162Z" }, - { url = "https://files.pythonhosted.org/packages/3f/4f/2c41f800a0b560785c10fb316216ac058c105f9be50bdc6a285de88db625/aiohttp-3.13.3-cp310-cp310-win32.whl", hash = "sha256:37da61e244d1749798c151421602884db5270faf479cf0ef03af0ff68954c9dd", size = 434053, upload-time = "2026-01-03T17:29:40.074Z" }, - { url = "https://files.pythonhosted.org/packages/80/df/29cd63c7ecfdb65ccc12f7d808cac4fa2a19544660c06c61a4a48462de0c/aiohttp-3.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:7e63f210bc1b57ef699035f2b4b6d9ce096b5914414a49b0997c839b2bd2223c", size = 456687, upload-time = "2026-01-03T17:29:41.819Z" }, - { url = "https://files.pythonhosted.org/packages/f1/4c/a164164834f03924d9a29dc3acd9e7ee58f95857e0b467f6d04298594ebb/aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b", size = 746051, upload-time = "2026-01-03T17:29:43.287Z" }, - { url = "https://files.pythonhosted.org/packages/82/71/d5c31390d18d4f58115037c432b7e0348c60f6f53b727cad33172144a112/aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64", size = 499234, upload-time = "2026-01-03T17:29:44.822Z" }, - { url = "https://files.pythonhosted.org/packages/0e/c9/741f8ac91e14b1d2e7100690425a5b2b919a87a5075406582991fb7de920/aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea", size = 494979, upload-time = "2026-01-03T17:29:46.405Z" }, - { url = "https://files.pythonhosted.org/packages/75/b5/31d4d2e802dfd59f74ed47eba48869c1c21552c586d5e81a9d0d5c2ad640/aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a", size = 1748297, upload-time = "2026-01-03T17:29:48.083Z" }, - { url = "https://files.pythonhosted.org/packages/1a/3e/eefad0ad42959f226bb79664826883f2687d602a9ae2941a18e0484a74d3/aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540", size = 1707172, upload-time = "2026-01-03T17:29:49.648Z" }, - { url = "https://files.pythonhosted.org/packages/c5/3a/54a64299fac2891c346cdcf2aa6803f994a2e4beeaf2e5a09dcc54acc842/aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b", size = 1805405, upload-time = "2026-01-03T17:29:51.244Z" }, - { url = "https://files.pythonhosted.org/packages/6c/70/ddc1b7169cf64075e864f64595a14b147a895a868394a48f6a8031979038/aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3", size = 1899449, upload-time = "2026-01-03T17:29:53.938Z" }, - { url = "https://files.pythonhosted.org/packages/a1/7e/6815aab7d3a56610891c76ef79095677b8b5be6646aaf00f69b221765021/aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1", size = 1748444, upload-time = "2026-01-03T17:29:55.484Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f2/073b145c4100da5511f457dc0f7558e99b2987cf72600d42b559db856fbc/aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3", size = 1606038, upload-time = "2026-01-03T17:29:57.179Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c1/778d011920cae03ae01424ec202c513dc69243cf2db303965615b81deeea/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440", size = 1724156, upload-time = "2026-01-03T17:29:58.914Z" }, - { url = "https://files.pythonhosted.org/packages/0e/cb/3419eabf4ec1e9ec6f242c32b689248365a1cf621891f6f0386632525494/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7", size = 1722340, upload-time = "2026-01-03T17:30:01.962Z" }, - { url = "https://files.pythonhosted.org/packages/7a/e5/76cf77bdbc435bf233c1f114edad39ed4177ccbfab7c329482b179cff4f4/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c", size = 1783041, upload-time = "2026-01-03T17:30:03.609Z" }, - { url = "https://files.pythonhosted.org/packages/9d/d4/dd1ca234c794fd29c057ce8c0566b8ef7fd6a51069de5f06fa84b9a1971c/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51", size = 1596024, upload-time = "2026-01-03T17:30:05.132Z" }, - { url = "https://files.pythonhosted.org/packages/55/58/4345b5f26661a6180afa686c473620c30a66afdf120ed3dd545bbc809e85/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4", size = 1804590, upload-time = "2026-01-03T17:30:07.135Z" }, - { url = "https://files.pythonhosted.org/packages/7b/06/05950619af6c2df7e0a431d889ba2813c9f0129cec76f663e547a5ad56f2/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29", size = 1740355, upload-time = "2026-01-03T17:30:09.083Z" }, - { url = "https://files.pythonhosted.org/packages/3e/80/958f16de79ba0422d7c1e284b2abd0c84bc03394fbe631d0a39ffa10e1eb/aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239", size = 433701, upload-time = "2026-01-03T17:30:10.869Z" }, - { url = "https://files.pythonhosted.org/packages/dc/f2/27cdf04c9851712d6c1b99df6821a6623c3c9e55956d4b1e318c337b5a48/aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f", size = 457678, upload-time = "2026-01-03T17:30:12.719Z" }, - { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" }, - { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" }, - { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29", size = 1737839, upload-time = "2026-01-03T17:30:19.422Z" }, - { url = "https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3", size = 1703932, upload-time = "2026-01-03T17:30:21.756Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d", size = 1771906, upload-time = "2026-01-03T17:30:23.932Z" }, - { url = "https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463", size = 1871020, upload-time = "2026-01-03T17:30:26Z" }, - { url = "https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc", size = 1755181, upload-time = "2026-01-03T17:30:27.554Z" }, - { url = "https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf", size = 1561794, upload-time = "2026-01-03T17:30:29.254Z" }, - { url = "https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033", size = 1697900, upload-time = "2026-01-03T17:30:31.033Z" }, - { url = "https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f", size = 1728239, upload-time = "2026-01-03T17:30:32.703Z" }, - { url = "https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679", size = 1740527, upload-time = "2026-01-03T17:30:34.695Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423", size = 1554489, upload-time = "2026-01-03T17:30:36.864Z" }, - { url = "https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce", size = 1767852, upload-time = "2026-01-03T17:30:39.433Z" }, - { url = "https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a", size = 1722379, upload-time = "2026-01-03T17:30:41.081Z" }, - { url = "https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046", size = 428253, upload-time = "2026-01-03T17:30:42.644Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57", size = 455407, upload-time = "2026-01-03T17:30:44.195Z" }, - { url = "https://files.pythonhosted.org/packages/97/8a/12ca489246ca1faaf5432844adbfce7ff2cc4997733e0af120869345643a/aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c", size = 734190, upload-time = "2026-01-03T17:30:45.832Z" }, - { url = "https://files.pythonhosted.org/packages/32/08/de43984c74ed1fca5c014808963cc83cb00d7bb06af228f132d33862ca76/aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9", size = 491783, upload-time = "2026-01-03T17:30:47.466Z" }, - { url = "https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3", size = 490704, upload-time = "2026-01-03T17:30:49.373Z" }, - { url = "https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf", size = 1720652, upload-time = "2026-01-03T17:30:50.974Z" }, - { url = "https://files.pythonhosted.org/packages/f7/7e/917fe18e3607af92657e4285498f500dca797ff8c918bd7d90b05abf6c2a/aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6", size = 1692014, upload-time = "2026-01-03T17:30:52.729Z" }, - { url = "https://files.pythonhosted.org/packages/71/b6/cefa4cbc00d315d68973b671cf105b21a609c12b82d52e5d0c9ae61d2a09/aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d", size = 1759777, upload-time = "2026-01-03T17:30:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e3/e06ee07b45e59e6d81498b591fc589629be1553abb2a82ce33efe2a7b068/aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261", size = 1861276, upload-time = "2026-01-03T17:30:56.512Z" }, - { url = "https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0", size = 1743131, upload-time = "2026-01-03T17:30:58.256Z" }, - { url = "https://files.pythonhosted.org/packages/04/98/3d21dde21889b17ca2eea54fdcff21b27b93f45b7bb94ca029c31ab59dc3/aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730", size = 1556863, upload-time = "2026-01-03T17:31:00.445Z" }, - { url = "https://files.pythonhosted.org/packages/9e/84/da0c3ab1192eaf64782b03971ab4055b475d0db07b17eff925e8c93b3aa5/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91", size = 1682793, upload-time = "2026-01-03T17:31:03.024Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0f/5802ada182f575afa02cbd0ec5180d7e13a402afb7c2c03a9aa5e5d49060/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3", size = 1716676, upload-time = "2026-01-03T17:31:04.842Z" }, - { url = "https://files.pythonhosted.org/packages/3f/8c/714d53bd8b5a4560667f7bbbb06b20c2382f9c7847d198370ec6526af39c/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4", size = 1733217, upload-time = "2026-01-03T17:31:06.868Z" }, - { url = "https://files.pythonhosted.org/packages/7d/79/e2176f46d2e963facea939f5be2d26368ce543622be6f00a12844d3c991f/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998", size = 1552303, upload-time = "2026-01-03T17:31:08.958Z" }, - { url = "https://files.pythonhosted.org/packages/ab/6a/28ed4dea1759916090587d1fe57087b03e6c784a642b85ef48217b0277ae/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0", size = 1763673, upload-time = "2026-01-03T17:31:10.676Z" }, - { url = "https://files.pythonhosted.org/packages/e8/35/4a3daeb8b9fab49240d21c04d50732313295e4bd813a465d840236dd0ce1/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591", size = 1721120, upload-time = "2026-01-03T17:31:12.575Z" }, - { url = "https://files.pythonhosted.org/packages/bc/9f/d643bb3c5fb99547323e635e251c609fbbc660d983144cfebec529e09264/aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf", size = 427383, upload-time = "2026-01-03T17:31:14.382Z" }, - { url = "https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e", size = 453899, upload-time = "2026-01-03T17:31:15.958Z" }, - { url = "https://files.pythonhosted.org/packages/99/36/5b6514a9f5d66f4e2597e40dea2e3db271e023eb7a5d22defe96ba560996/aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808", size = 737238, upload-time = "2026-01-03T17:31:17.909Z" }, - { url = "https://files.pythonhosted.org/packages/f7/49/459327f0d5bcd8c6c9ca69e60fdeebc3622861e696490d8674a6d0cb90a6/aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415", size = 492292, upload-time = "2026-01-03T17:31:19.919Z" }, - { url = "https://files.pythonhosted.org/packages/e8/0b/b97660c5fd05d3495b4eb27f2d0ef18dc1dc4eff7511a9bf371397ff0264/aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f", size = 493021, upload-time = "2026-01-03T17:31:21.636Z" }, - { url = "https://files.pythonhosted.org/packages/54/d4/438efabdf74e30aeceb890c3290bbaa449780583b1270b00661126b8aae4/aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6", size = 1717263, upload-time = "2026-01-03T17:31:23.296Z" }, - { url = "https://files.pythonhosted.org/packages/71/f2/7bddc7fd612367d1459c5bcf598a9e8f7092d6580d98de0e057eb42697ad/aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687", size = 1669107, upload-time = "2026-01-03T17:31:25.334Z" }, - { url = "https://files.pythonhosted.org/packages/00/5a/1aeaecca40e22560f97610a329e0e5efef5e0b5afdf9f857f0d93839ab2e/aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26", size = 1760196, upload-time = "2026-01-03T17:31:27.394Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f8/0ff6992bea7bd560fc510ea1c815f87eedd745fe035589c71ce05612a19a/aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a", size = 1843591, upload-time = "2026-01-03T17:31:29.238Z" }, - { url = "https://files.pythonhosted.org/packages/e3/d1/e30e537a15f53485b61f5be525f2157da719819e8377298502aebac45536/aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1", size = 1720277, upload-time = "2026-01-03T17:31:31.053Z" }, - { url = "https://files.pythonhosted.org/packages/84/45/23f4c451d8192f553d38d838831ebbc156907ea6e05557f39563101b7717/aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25", size = 1548575, upload-time = "2026-01-03T17:31:32.87Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ed/0a42b127a43712eda7807e7892c083eadfaf8429ca8fb619662a530a3aab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603", size = 1679455, upload-time = "2026-01-03T17:31:34.76Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b5/c05f0c2b4b4fe2c9d55e73b6d3ed4fd6c9dc2684b1d81cbdf77e7fad9adb/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a", size = 1687417, upload-time = "2026-01-03T17:31:36.699Z" }, - { url = "https://files.pythonhosted.org/packages/c9/6b/915bc5dad66aef602b9e459b5a973529304d4e89ca86999d9d75d80cbd0b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926", size = 1729968, upload-time = "2026-01-03T17:31:38.622Z" }, - { url = "https://files.pythonhosted.org/packages/11/3b/e84581290a9520024a08640b63d07673057aec5ca548177a82026187ba73/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba", size = 1545690, upload-time = "2026-01-03T17:31:40.57Z" }, - { url = "https://files.pythonhosted.org/packages/f5/04/0c3655a566c43fd647c81b895dfe361b9f9ad6d58c19309d45cff52d6c3b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c", size = 1746390, upload-time = "2026-01-03T17:31:42.857Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/71165b26978f719c3419381514c9690bd5980e764a09440a10bb816ea4ab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43", size = 1702188, upload-time = "2026-01-03T17:31:44.984Z" }, - { url = "https://files.pythonhosted.org/packages/29/a7/cbe6c9e8e136314fa1980da388a59d2f35f35395948a08b6747baebb6aa6/aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1", size = 433126, upload-time = "2026-01-03T17:31:47.463Z" }, - { url = "https://files.pythonhosted.org/packages/de/56/982704adea7d3b16614fc5936014e9af85c0e34b58f9046655817f04306e/aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984", size = 459128, upload-time = "2026-01-03T17:31:49.2Z" }, - { url = "https://files.pythonhosted.org/packages/6c/2a/3c79b638a9c3d4658d345339d22070241ea341ed4e07b5ac60fb0f418003/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c", size = 769512, upload-time = "2026-01-03T17:31:51.134Z" }, - { url = "https://files.pythonhosted.org/packages/29/b9/3e5014d46c0ab0db8707e0ac2711ed28c4da0218c358a4e7c17bae0d8722/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592", size = 506444, upload-time = "2026-01-03T17:31:52.85Z" }, - { url = "https://files.pythonhosted.org/packages/90/03/c1d4ef9a054e151cd7839cdc497f2638f00b93cbe8043983986630d7a80c/aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f", size = 510798, upload-time = "2026-01-03T17:31:54.91Z" }, - { url = "https://files.pythonhosted.org/packages/ea/76/8c1e5abbfe8e127c893fe7ead569148a4d5a799f7cf958d8c09f3eedf097/aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29", size = 1868835, upload-time = "2026-01-03T17:31:56.733Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ac/984c5a6f74c363b01ff97adc96a3976d9c98940b8969a1881575b279ac5d/aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc", size = 1720486, upload-time = "2026-01-03T17:31:58.65Z" }, - { url = "https://files.pythonhosted.org/packages/b2/9a/b7039c5f099c4eb632138728828b33428585031a1e658d693d41d07d89d1/aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2", size = 1847951, upload-time = "2026-01-03T17:32:00.989Z" }, - { url = "https://files.pythonhosted.org/packages/3c/02/3bec2b9a1ba3c19ff89a43a19324202b8eb187ca1e928d8bdac9bbdddebd/aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587", size = 1941001, upload-time = "2026-01-03T17:32:03.122Z" }, - { url = "https://files.pythonhosted.org/packages/37/df/d879401cedeef27ac4717f6426c8c36c3091c6e9f08a9178cc87549c537f/aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8", size = 1797246, upload-time = "2026-01-03T17:32:05.255Z" }, - { url = "https://files.pythonhosted.org/packages/8d/15/be122de1f67e6953add23335c8ece6d314ab67c8bebb3f181063010795a7/aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632", size = 1627131, upload-time = "2026-01-03T17:32:07.607Z" }, - { url = "https://files.pythonhosted.org/packages/12/12/70eedcac9134cfa3219ab7af31ea56bc877395b1ac30d65b1bc4b27d0438/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64", size = 1795196, upload-time = "2026-01-03T17:32:09.59Z" }, - { url = "https://files.pythonhosted.org/packages/32/11/b30e1b1cd1f3054af86ebe60df96989c6a414dd87e27ad16950eee420bea/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0", size = 1782841, upload-time = "2026-01-03T17:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/88/0d/d98a9367b38912384a17e287850f5695c528cff0f14f791ce8ee2e4f7796/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56", size = 1795193, upload-time = "2026-01-03T17:32:13.705Z" }, - { url = "https://files.pythonhosted.org/packages/43/a5/a2dfd1f5ff5581632c7f6a30e1744deda03808974f94f6534241ef60c751/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72", size = 1621979, upload-time = "2026-01-03T17:32:15.965Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f0/12973c382ae7c1cccbc4417e129c5bf54c374dfb85af70893646e1f0e749/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df", size = 1822193, upload-time = "2026-01-03T17:32:18.219Z" }, - { url = "https://files.pythonhosted.org/packages/3c/5f/24155e30ba7f8c96918af1350eb0663e2430aad9e001c0489d89cd708ab1/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa", size = 1769801, upload-time = "2026-01-03T17:32:20.25Z" }, - { url = "https://files.pythonhosted.org/packages/eb/f8/7314031ff5c10e6ece114da79b338ec17eeff3a079e53151f7e9f43c4723/aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767", size = 466523, upload-time = "2026-01-03T17:32:22.215Z" }, - { url = "https://files.pythonhosted.org/packages/b4/63/278a98c715ae467624eafe375542d8ba9b4383a016df8fdefe0ae28382a7/aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344", size = 499694, upload-time = "2026-01-03T17:32:24.546Z" }, - { url = "https://files.pythonhosted.org/packages/bf/79/446655656861d3e7e2c32bfcf160c7aa9e9dc63776a691b124dba65cdd77/aiohttp-3.13.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31a83ea4aead760dfcb6962efb1d861db48c34379f2ff72db9ddddd4cda9ea2e", size = 741433, upload-time = "2026-01-03T17:32:26.453Z" }, - { url = "https://files.pythonhosted.org/packages/cb/49/773c4b310b5140d2fb5e79bb0bf40b7b41dad80a288ca1a8759f5f72bda9/aiohttp-3.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:988a8c5e317544fdf0d39871559e67b6341065b87fceac641108c2096d5506b7", size = 497332, upload-time = "2026-01-03T17:32:28.37Z" }, - { url = "https://files.pythonhosted.org/packages/bc/31/1dcbc4b83a4e6f76a0ad883f07f21ffbfe29750c89db97381701508c9f45/aiohttp-3.13.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b174f267b5cfb9a7dba9ee6859cecd234e9a681841eb85068059bc867fb8f02", size = 492365, upload-time = "2026-01-03T17:32:30.234Z" }, - { url = "https://files.pythonhosted.org/packages/5a/b5/b50657496c8754482cd7964e50aaf3aa84b3db61ed45daec4c1aec5b94b4/aiohttp-3.13.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:947c26539750deeaee933b000fb6517cc770bbd064bad6033f1cff4803881e43", size = 1660440, upload-time = "2026-01-03T17:32:32.586Z" }, - { url = "https://files.pythonhosted.org/packages/2a/73/9b69e5139d89d75127569298931444ad78ea86a5befd5599780b1e9a6880/aiohttp-3.13.3-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9ebf57d09e131f5323464bd347135a88622d1c0976e88ce15b670e7ad57e4bd6", size = 1632740, upload-time = "2026-01-03T17:32:34.793Z" }, - { url = "https://files.pythonhosted.org/packages/ef/fe/3ea9b5af694b4e3aec0d0613a806132ca744747146fca68e96bf056f61a7/aiohttp-3.13.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4ae5b5a0e1926e504c81c5b84353e7a5516d8778fbbff00429fe7b05bb25cbce", size = 1719782, upload-time = "2026-01-03T17:32:37.737Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c2/46b3b06e60851cbb71efb0f79a3267279cbef7b12c58e68a1e897f269cca/aiohttp-3.13.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ba0eea45eb5cc3172dbfc497c066f19c41bac70963ea1a67d51fc92e4cf9a80", size = 1813527, upload-time = "2026-01-03T17:32:39.973Z" }, - { url = "https://files.pythonhosted.org/packages/36/23/71ceb78c769ed65fe4c697692de232b63dab399210678d2b00961ccb0619/aiohttp-3.13.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bae5c2ed2eae26cc382020edad80d01f36cb8e746da40b292e68fec40421dc6a", size = 1661268, upload-time = "2026-01-03T17:32:42.082Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8d/86e929523d955e85ebab7c0e2b9e0cb63604cfc27dc3280e10d0063cf682/aiohttp-3.13.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a60e60746623925eab7d25823329941aee7242d559baa119ca2b253c88a7bd6", size = 1552742, upload-time = "2026-01-03T17:32:44.622Z" }, - { url = "https://files.pythonhosted.org/packages/3a/ea/3f5987cba1bab6bd151f0d97aa60f0ce04d3c83316692a6bb6ba2fb69f92/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e50a2e1404f063427c9d027378472316201a2290959a295169bcf25992d04558", size = 1632918, upload-time = "2026-01-03T17:32:46.749Z" }, - { url = "https://files.pythonhosted.org/packages/be/2c/7e1e85121f2e31ee938cb83a8f32dfafd4908530c10fabd6d46761c12ac7/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:9a9dc347e5a3dc7dfdbc1f82da0ef29e388ddb2ed281bfce9dd8248a313e62b7", size = 1644446, upload-time = "2026-01-03T17:32:49.063Z" }, - { url = "https://files.pythonhosted.org/packages/5d/35/ce6133d423ad0e8ca976a7c848f7146bca3520eea4ccf6b95e2d077c9d20/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b46020d11d23fe16551466c77823df9cc2f2c1e63cc965daf67fa5eec6ca1877", size = 1689487, upload-time = "2026-01-03T17:32:51.113Z" }, - { url = "https://files.pythonhosted.org/packages/50/f7/ff7a27c15603d460fd1366b3c22054f7ae4fa9310aca40b43bde35867fcd/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:69c56fbc1993fa17043e24a546959c0178fe2b5782405ad4559e6c13975c15e3", size = 1540715, upload-time = "2026-01-03T17:32:53.38Z" }, - { url = "https://files.pythonhosted.org/packages/17/02/053f11346e5b962e6d8a1c4f8c70c29d5970a1b4b8e7894c68e12c27a57f/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b99281b0704c103d4e11e72a76f1b543d4946fea7dd10767e7e1b5f00d4e5704", size = 1711835, upload-time = "2026-01-03T17:32:56.088Z" }, - { url = "https://files.pythonhosted.org/packages/fb/71/9b9761ddf276fd6708d13720197cbac19b8d67ecfa9116777924056cfcaa/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:40c5e40ecc29ba010656c18052b877a1c28f84344825efa106705e835c28530f", size = 1649593, upload-time = "2026-01-03T17:32:58.181Z" }, - { url = "https://files.pythonhosted.org/packages/ae/72/5d817e9ea218acae12a5e3b9ad1178cf0c12fc3570c0b47eea2daf95f9ea/aiohttp-3.13.3-cp39-cp39-win32.whl", hash = "sha256:56339a36b9f1fc708260c76c87e593e2afb30d26de9ae1eb445b5e051b98a7a1", size = 434831, upload-time = "2026-01-03T17:33:00.577Z" }, - { url = "https://files.pythonhosted.org/packages/39/cb/22659d9bf3149b7a2927bc2769cc9c8f8f5a80eba098398e03c199a43a85/aiohttp-3.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:c6b8568a3bb5819a0ad087f16d40e5a3fb6099f39ea1d5625a3edc1e923fc538", size = 457697, upload-time = "2026-01-03T17:33:03.167Z" }, -] - -[[package]] -name = "aiosignal" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "frozenlist" }, - { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, -] - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, -] - -[[package]] -name = "anyio" -version = "4.12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "idna" }, - { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, -] - -[[package]] -name = "async-timeout" -version = "5.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, -] - -[[package]] -name = "attrs" -version = "25.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, -] - -[[package]] -name = "backports-asyncio-runner" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, -] - -[[package]] -name = "certifi" -version = "2026.1.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "dirty-equals" -version = "0.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/1d/c5913ac9d6615515a00f4bdc71356d302437cb74ff2e9aaccd3c14493b78/dirty_equals-0.11.tar.gz", hash = "sha256:f4ac74ee88f2d11e2fa0f65eb30ee4f07105c5f86f4dc92b09eb1138775027c3", size = 128067, upload-time = "2025-11-17T01:51:24.451Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/8d/dbff05239043271dbeace563a7686212a3dd517864a35623fe4d4a64ca19/dirty_equals-0.11-py3-none-any.whl", hash = "sha256:b1d7093273fc2f9be12f443a8ead954ef6daaf6746fd42ef3a5616433ee85286", size = 28051, upload-time = "2025-11-17T01:51:22.849Z" }, -] - -[[package]] -name = "distro" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, -] - -[[package]] -name = "execnet" -version = "2.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, -] - -[[package]] -name = "frozenlist" -version = "1.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, - { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, - { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, - { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, - { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, - { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, - { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, - { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, - { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, - { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, - { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, - { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, - { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, - { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, - { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, - { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, - { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, - { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, - { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, - { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, - { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, - { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, - { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, - { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, - { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, - { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, - { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, - { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, - { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, - { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, - { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, - { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, - { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, - { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, - { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, - { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, - { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, - { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, - { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, - { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, - { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, - { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, - { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, - { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, - { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, - { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, - { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, - { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, - { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, - { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, - { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, - { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, - { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, - { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, - { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, - { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, - { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, - { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, - { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, - { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, - { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, - { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, - { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, - { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, - { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, - { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, - { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, - { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, - { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, - { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, - { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, - { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, - { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, - { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, - { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, - { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, - { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, - { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, - { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, - { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, - { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, - { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, - { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, - { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, - { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, - { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, - { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, - { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, - { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, - { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, - { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, - { url = "https://files.pythonhosted.org/packages/c2/59/ae5cdac87a00962122ea37bb346d41b66aec05f9ce328fa2b9e216f8967b/frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47", size = 86967, upload-time = "2025-10-06T05:37:55.607Z" }, - { url = "https://files.pythonhosted.org/packages/8a/10/17059b2db5a032fd9323c41c39e9d1f5f9d0c8f04d1e4e3e788573086e61/frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca", size = 49984, upload-time = "2025-10-06T05:37:57.049Z" }, - { url = "https://files.pythonhosted.org/packages/4b/de/ad9d82ca8e5fa8f0c636e64606553c79e2b859ad253030b62a21fe9986f5/frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068", size = 50240, upload-time = "2025-10-06T05:37:58.145Z" }, - { url = "https://files.pythonhosted.org/packages/4e/45/3dfb7767c2a67d123650122b62ce13c731b6c745bc14424eea67678b508c/frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95", size = 219472, upload-time = "2025-10-06T05:37:59.239Z" }, - { url = "https://files.pythonhosted.org/packages/0b/bf/5bf23d913a741b960d5c1dac7c1985d8a2a1d015772b2d18ea168b08e7ff/frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459", size = 221531, upload-time = "2025-10-06T05:38:00.521Z" }, - { url = "https://files.pythonhosted.org/packages/d0/03/27ec393f3b55860859f4b74cdc8c2a4af3dbf3533305e8eacf48a4fd9a54/frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675", size = 219211, upload-time = "2025-10-06T05:38:01.842Z" }, - { url = "https://files.pythonhosted.org/packages/3a/ad/0fd00c404fa73fe9b169429e9a972d5ed807973c40ab6b3cf9365a33d360/frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61", size = 231775, upload-time = "2025-10-06T05:38:03.384Z" }, - { url = "https://files.pythonhosted.org/packages/8a/c3/86962566154cb4d2995358bc8331bfc4ea19d07db1a96f64935a1607f2b6/frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6", size = 236631, upload-time = "2025-10-06T05:38:04.609Z" }, - { url = "https://files.pythonhosted.org/packages/ea/9e/6ffad161dbd83782d2c66dc4d378a9103b31770cb1e67febf43aea42d202/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5", size = 218632, upload-time = "2025-10-06T05:38:05.917Z" }, - { url = "https://files.pythonhosted.org/packages/58/b2/4677eee46e0a97f9b30735e6ad0bf6aba3e497986066eb68807ac85cf60f/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3", size = 235967, upload-time = "2025-10-06T05:38:07.614Z" }, - { url = "https://files.pythonhosted.org/packages/05/f3/86e75f8639c5a93745ca7addbbc9de6af56aebb930d233512b17e46f6493/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1", size = 228799, upload-time = "2025-10-06T05:38:08.845Z" }, - { url = "https://files.pythonhosted.org/packages/30/00/39aad3a7f0d98f5eb1d99a3c311215674ed87061aecee7851974b335c050/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178", size = 230566, upload-time = "2025-10-06T05:38:10.52Z" }, - { url = "https://files.pythonhosted.org/packages/0d/4d/aa144cac44568d137846ddc4d5210fb5d9719eb1d7ec6fa2728a54b5b94a/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda", size = 217715, upload-time = "2025-10-06T05:38:11.832Z" }, - { url = "https://files.pythonhosted.org/packages/64/4c/8f665921667509d25a0dd72540513bc86b356c95541686f6442a3283019f/frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087", size = 39933, upload-time = "2025-10-06T05:38:13.061Z" }, - { url = "https://files.pythonhosted.org/packages/79/bd/bcc926f87027fad5e59926ff12d136e1082a115025d33c032d1cd69ab377/frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a", size = 44121, upload-time = "2025-10-06T05:38:14.572Z" }, - { url = "https://files.pythonhosted.org/packages/4c/07/9c2e4eb7584af4b705237b971b89a4155a8e57599c4483a131a39256a9a0/frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103", size = 40312, upload-time = "2025-10-06T05:38:15.699Z" }, - { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, -] - -[[package]] -name = "httpx-aiohttp" -version = "0.1.12" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "httpx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/2c/b894861cecf030fb45675ea24aa55b5722e97c602a163d872fca66c5a6d8/httpx_aiohttp-0.1.12.tar.gz", hash = "sha256:81feec51fd82c0ecfa0e9aaf1b1a6c2591260d5e2bcbeb7eb0277a78e610df2c", size = 275945, upload-time = "2025-12-12T10:12:15.283Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/8d/85c9701e9af72ca132a1783e2a54364a90c6da832304416a30fc11196ab2/httpx_aiohttp-0.1.12-py3-none-any.whl", hash = "sha256:5b0eac39a7f360fa7867a60bcb46bb1024eada9c01cbfecdb54dc1edb3fb7141", size = 6367, upload-time = "2025-12-12T10:12:14.018Z" }, -] - -[[package]] -name = "idna" -version = "3.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, -] - -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and extra != 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and extra == 'group-14-moderation-api-pydantic-v1' and extra != 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and extra != 'group-14-moderation-api-pydantic-v1' and extra != 'group-14-moderation-api-pydantic-v2'", -] -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, -] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "mdurl", marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, -] - -[[package]] -name = "markdown-it-py" -version = "4.0.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and extra != 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and extra == 'group-14-moderation-api-pydantic-v1' and extra != 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and extra != 'group-14-moderation-api-pydantic-v1' and extra != 'group-14-moderation-api-pydantic-v2'", -] -dependencies = [ - { name = "mdurl", marker = "python_full_version >= '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, -] - -[[package]] -name = "moderation-api" -version = "1.0.2" -source = { editable = "." } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, - { name = "pydantic", version = "1.10.26", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-14-moderation-api-pydantic-v1'" }, - { name = "pydantic", version = "2.12.5", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-14-moderation-api-pydantic-v2' or extra != 'group-14-moderation-api-pydantic-v1'" }, - { name = "sniffio" }, - { name = "typing-extensions" }, -] - -[package.optional-dependencies] -aiohttp = [ - { name = "aiohttp" }, - { name = "httpx-aiohttp" }, -] - -[package.dev-dependencies] -dev = [ - { name = "dirty-equals" }, - { name = "importlib-metadata" }, - { name = "mypy" }, - { name = "pyright" }, - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "pytest-asyncio", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "pytest-asyncio", version = "1.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "pytest-xdist" }, - { name = "respx" }, - { name = "rich" }, - { name = "ruff" }, - { name = "time-machine", version = "2.19.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "time-machine", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, -] -pydantic-v1 = [ - { name = "pydantic", version = "1.10.26", source = { registry = "https://pypi.org/simple" } }, -] -pydantic-v2 = [ - { name = "pydantic", version = "2.12.5", source = { registry = "https://pypi.org/simple" } }, -] - -[package.metadata] -requires-dist = [ - { name = "aiohttp", marker = "extra == 'aiohttp'" }, - { name = "anyio", specifier = ">=3.5.0,<5" }, - { name = "distro", specifier = ">=1.7.0,<2" }, - { name = "httpx", specifier = ">=0.23.0,<1" }, - { name = "httpx-aiohttp", marker = "extra == 'aiohttp'", specifier = ">=0.1.9" }, - { name = "pydantic", specifier = ">=1.9.0,<3" }, - { name = "sniffio" }, - { name = "typing-extensions", specifier = ">=4.14,<5" }, -] -provides-extras = ["aiohttp"] - -[package.metadata.requires-dev] -dev = [ - { name = "dirty-equals", specifier = ">=0.6.0" }, - { name = "importlib-metadata", specifier = ">=6.7.0" }, - { name = "mypy", specifier = "==1.17" }, - { name = "pyright", specifier = "==1.1.399" }, - { name = "pytest" }, - { name = "pytest-asyncio" }, - { name = "pytest-xdist", specifier = ">=3.6.1" }, - { name = "respx" }, - { name = "rich", specifier = ">=13.7.1" }, - { name = "ruff" }, - { name = "time-machine" }, -] -pydantic-v1 = [{ name = "pydantic", specifier = ">=1.9.0,<2" }] -pydantic-v2 = [ - { name = "pydantic", marker = "python_full_version < '3.14'", specifier = "~=2.0" }, - { name = "pydantic", marker = "python_full_version >= '3.14'", specifier = "~=2.12" }, -] - -[[package]] -name = "multidict" -version = "6.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/63/7bdd4adc330abcca54c85728db2327130e49e52e8c3ce685cec44e0f2e9f/multidict-6.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9f474ad5acda359c8758c8accc22032c6abe6dc87a8be2440d097785e27a9349", size = 77153, upload-time = "2025-10-06T14:48:26.409Z" }, - { url = "https://files.pythonhosted.org/packages/3f/bb/b6c35ff175ed1a3142222b78455ee31be71a8396ed3ab5280fbe3ebe4e85/multidict-6.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a9db5a870f780220e931d0002bbfd88fb53aceb6293251e2c839415c1b20e", size = 44993, upload-time = "2025-10-06T14:48:28.4Z" }, - { url = "https://files.pythonhosted.org/packages/e0/1f/064c77877c5fa6df6d346e68075c0f6998547afe952d6471b4c5f6a7345d/multidict-6.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03ca744319864e92721195fa28c7a3b2bc7b686246b35e4078c1e4d0eb5466d3", size = 44607, upload-time = "2025-10-06T14:48:29.581Z" }, - { url = "https://files.pythonhosted.org/packages/04/7a/bf6aa92065dd47f287690000b3d7d332edfccb2277634cadf6a810463c6a/multidict-6.7.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f0e77e3c0008bc9316e662624535b88d360c3a5d3f81e15cf12c139a75250046", size = 241847, upload-time = "2025-10-06T14:48:32.107Z" }, - { url = "https://files.pythonhosted.org/packages/94/39/297a8de920f76eda343e4ce05f3b489f0ab3f9504f2576dfb37b7c08ca08/multidict-6.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08325c9e5367aa379a3496aa9a022fe8837ff22e00b94db256d3a1378c76ab32", size = 242616, upload-time = "2025-10-06T14:48:34.054Z" }, - { url = "https://files.pythonhosted.org/packages/39/3a/d0eee2898cfd9d654aea6cb8c4addc2f9756e9a7e09391cfe55541f917f7/multidict-6.7.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e2862408c99f84aa571ab462d25236ef9cb12a602ea959ba9c9009a54902fc73", size = 222333, upload-time = "2025-10-06T14:48:35.9Z" }, - { url = "https://files.pythonhosted.org/packages/05/48/3b328851193c7a4240815b71eea165b49248867bbb6153a0aee227a0bb47/multidict-6.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d72a9a2d885f5c208b0cb91ff2ed43636bb7e345ec839ff64708e04f69a13cc", size = 253239, upload-time = "2025-10-06T14:48:37.302Z" }, - { url = "https://files.pythonhosted.org/packages/b1/ca/0706a98c8d126a89245413225ca4a3fefc8435014de309cf8b30acb68841/multidict-6.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:478cc36476687bac1514d651cbbaa94b86b0732fb6855c60c673794c7dd2da62", size = 251618, upload-time = "2025-10-06T14:48:38.963Z" }, - { url = "https://files.pythonhosted.org/packages/5e/4f/9c7992f245554d8b173f6f0a048ad24b3e645d883f096857ec2c0822b8bd/multidict-6.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6843b28b0364dc605f21481c90fadb5f60d9123b442eb8a726bb74feef588a84", size = 241655, upload-time = "2025-10-06T14:48:40.312Z" }, - { url = "https://files.pythonhosted.org/packages/31/79/26a85991ae67efd1c0b1fc2e0c275b8a6aceeb155a68861f63f87a798f16/multidict-6.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23bfeee5316266e5ee2d625df2d2c602b829435fc3a235c2ba2131495706e4a0", size = 239245, upload-time = "2025-10-06T14:48:41.848Z" }, - { url = "https://files.pythonhosted.org/packages/14/1e/75fa96394478930b79d0302eaf9a6c69f34005a1a5251ac8b9c336486ec9/multidict-6.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:680878b9f3d45c31e1f730eef731f9b0bc1da456155688c6745ee84eb818e90e", size = 233523, upload-time = "2025-10-06T14:48:43.749Z" }, - { url = "https://files.pythonhosted.org/packages/b2/5e/085544cb9f9c4ad2b5d97467c15f856df8d9bac410cffd5c43991a5d878b/multidict-6.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eb866162ef2f45063acc7a53a88ef6fe8bf121d45c30ea3c9cd87ce7e191a8d4", size = 243129, upload-time = "2025-10-06T14:48:45.225Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c3/e9d9e2f20c9474e7a8fcef28f863c5cbd29bb5adce6b70cebe8bdad0039d/multidict-6.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df0e3bf7993bdbeca5ac25aa859cf40d39019e015c9c91809ba7093967f7a648", size = 248999, upload-time = "2025-10-06T14:48:46.703Z" }, - { url = "https://files.pythonhosted.org/packages/b5/3f/df171b6efa3239ae33b97b887e42671cd1d94d460614bfb2c30ffdab3b95/multidict-6.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:661709cdcd919a2ece2234f9bae7174e5220c80b034585d7d8a755632d3e2111", size = 243711, upload-time = "2025-10-06T14:48:48.146Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2f/9b5564888c4e14b9af64c54acf149263721a283aaf4aa0ae89b091d5d8c1/multidict-6.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:096f52730c3fb8ed419db2d44391932b63891b2c5ed14850a7e215c0ba9ade36", size = 237504, upload-time = "2025-10-06T14:48:49.447Z" }, - { url = "https://files.pythonhosted.org/packages/6c/3a/0bd6ca0f7d96d790542d591c8c3354c1e1b6bfd2024d4d92dc3d87485ec7/multidict-6.7.0-cp310-cp310-win32.whl", hash = "sha256:afa8a2978ec65d2336305550535c9c4ff50ee527914328c8677b3973ade52b85", size = 41422, upload-time = "2025-10-06T14:48:50.789Z" }, - { url = "https://files.pythonhosted.org/packages/00/35/f6a637ea2c75f0d3b7c7d41b1189189acff0d9deeb8b8f35536bb30f5e33/multidict-6.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:b15b3afff74f707b9275d5ba6a91ae8f6429c3ffb29bbfd216b0b375a56f13d7", size = 46050, upload-time = "2025-10-06T14:48:51.938Z" }, - { url = "https://files.pythonhosted.org/packages/e7/b8/f7bf8329b39893d02d9d95cf610c75885d12fc0f402b1c894e1c8e01c916/multidict-6.7.0-cp310-cp310-win_arm64.whl", hash = "sha256:4b73189894398d59131a66ff157837b1fafea9974be486d036bb3d32331fdbf0", size = 43153, upload-time = "2025-10-06T14:48:53.146Z" }, - { url = "https://files.pythonhosted.org/packages/34/9e/5c727587644d67b2ed479041e4b1c58e30afc011e3d45d25bbe35781217c/multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc", size = 76604, upload-time = "2025-10-06T14:48:54.277Z" }, - { url = "https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721", size = 44715, upload-time = "2025-10-06T14:48:55.445Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6", size = 44332, upload-time = "2025-10-06T14:48:56.706Z" }, - { url = "https://files.pythonhosted.org/packages/31/61/0c2d50241ada71ff61a79518db85ada85fdabfcf395d5968dae1cbda04e5/multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c", size = 245212, upload-time = "2025-10-06T14:48:58.042Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e0/919666a4e4b57fff1b57f279be1c9316e6cdc5de8a8b525d76f6598fefc7/multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7", size = 246671, upload-time = "2025-10-06T14:49:00.004Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cc/d027d9c5a520f3321b65adea289b965e7bcbd2c34402663f482648c716ce/multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7", size = 225491, upload-time = "2025-10-06T14:49:01.393Z" }, - { url = "https://files.pythonhosted.org/packages/75/c4/bbd633980ce6155a28ff04e6a6492dd3335858394d7bb752d8b108708558/multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9", size = 257322, upload-time = "2025-10-06T14:49:02.745Z" }, - { url = "https://files.pythonhosted.org/packages/4c/6d/d622322d344f1f053eae47e033b0b3f965af01212de21b10bcf91be991fb/multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8", size = 254694, upload-time = "2025-10-06T14:49:04.15Z" }, - { url = "https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd", size = 246715, upload-time = "2025-10-06T14:49:05.967Z" }, - { url = "https://files.pythonhosted.org/packages/78/59/950818e04f91b9c2b95aab3d923d9eabd01689d0dcd889563988e9ea0fd8/multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb", size = 243189, upload-time = "2025-10-06T14:49:07.37Z" }, - { url = "https://files.pythonhosted.org/packages/7a/3d/77c79e1934cad2ee74991840f8a0110966d9599b3af95964c0cd79bb905b/multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6", size = 237845, upload-time = "2025-10-06T14:49:08.759Z" }, - { url = "https://files.pythonhosted.org/packages/63/1b/834ce32a0a97a3b70f86437f685f880136677ac00d8bce0027e9fd9c2db7/multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2", size = 246374, upload-time = "2025-10-06T14:49:10.574Z" }, - { url = "https://files.pythonhosted.org/packages/23/ef/43d1c3ba205b5dec93dc97f3fba179dfa47910fc73aaaea4f7ceb41cec2a/multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff", size = 253345, upload-time = "2025-10-06T14:49:12.331Z" }, - { url = "https://files.pythonhosted.org/packages/6b/03/eaf95bcc2d19ead522001f6a650ef32811aa9e3624ff0ad37c445c7a588c/multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b", size = 246940, upload-time = "2025-10-06T14:49:13.821Z" }, - { url = "https://files.pythonhosted.org/packages/e8/df/ec8a5fd66ea6cd6f525b1fcbb23511b033c3e9bc42b81384834ffa484a62/multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34", size = 242229, upload-time = "2025-10-06T14:49:15.603Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a2/59b405d59fd39ec86d1142630e9049243015a5f5291ba49cadf3c090c541/multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff", size = 41308, upload-time = "2025-10-06T14:49:16.871Z" }, - { url = "https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81", size = 46037, upload-time = "2025-10-06T14:49:18.457Z" }, - { url = "https://files.pythonhosted.org/packages/84/1f/68588e31b000535a3207fd3c909ebeec4fb36b52c442107499c18a896a2a/multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912", size = 43023, upload-time = "2025-10-06T14:49:19.648Z" }, - { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" }, - { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" }, - { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" }, - { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" }, - { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" }, - { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" }, - { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" }, - { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" }, - { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" }, - { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" }, - { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" }, - { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" }, - { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" }, - { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" }, - { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" }, - { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" }, - { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" }, - { url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload-time = "2025-10-06T14:49:54.26Z" }, - { url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload-time = "2025-10-06T14:49:55.82Z" }, - { url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload-time = "2025-10-06T14:49:57.048Z" }, - { url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload-time = "2025-10-06T14:49:58.368Z" }, - { url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload-time = "2025-10-06T14:49:59.89Z" }, - { url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload-time = "2025-10-06T14:50:01.485Z" }, - { url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload-time = "2025-10-06T14:50:02.955Z" }, - { url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload-time = "2025-10-06T14:50:04.446Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload-time = "2025-10-06T14:50:05.98Z" }, - { url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload-time = "2025-10-06T14:50:07.511Z" }, - { url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload-time = "2025-10-06T14:50:09.074Z" }, - { url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload-time = "2025-10-06T14:50:10.714Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload-time = "2025-10-06T14:50:12.28Z" }, - { url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload-time = "2025-10-06T14:50:14.16Z" }, - { url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload-time = "2025-10-06T14:50:15.639Z" }, - { url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload-time = "2025-10-06T14:50:17.066Z" }, - { url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload-time = "2025-10-06T14:50:18.264Z" }, - { url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload-time = "2025-10-06T14:50:19.853Z" }, - { url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload-time = "2025-10-06T14:50:21.223Z" }, - { url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload-time = "2025-10-06T14:50:22.871Z" }, - { url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload-time = "2025-10-06T14:50:24.258Z" }, - { url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload-time = "2025-10-06T14:50:25.716Z" }, - { url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload-time = "2025-10-06T14:50:28.192Z" }, - { url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload-time = "2025-10-06T14:50:29.82Z" }, - { url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload-time = "2025-10-06T14:50:31.731Z" }, - { url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload-time = "2025-10-06T14:50:33.26Z" }, - { url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload-time = "2025-10-06T14:50:34.808Z" }, - { url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload-time = "2025-10-06T14:50:36.436Z" }, - { url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload-time = "2025-10-06T14:50:37.953Z" }, - { url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload-time = "2025-10-06T14:50:39.574Z" }, - { url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload-time = "2025-10-06T14:50:41.612Z" }, - { url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload-time = "2025-10-06T14:50:43.972Z" }, - { url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload-time = "2025-10-06T14:50:45.648Z" }, - { url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload-time = "2025-10-06T14:50:47.154Z" }, - { url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload-time = "2025-10-06T14:50:48.851Z" }, - { url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload-time = "2025-10-06T14:50:50.16Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload-time = "2025-10-06T14:50:51.92Z" }, - { url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload-time = "2025-10-06T14:50:53.275Z" }, - { url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload-time = "2025-10-06T14:50:54.911Z" }, - { url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload-time = "2025-10-06T14:50:56.369Z" }, - { url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload-time = "2025-10-06T14:50:57.991Z" }, - { url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload-time = "2025-10-06T14:50:59.589Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload-time = "2025-10-06T14:51:01.183Z" }, - { url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload-time = "2025-10-06T14:51:02.794Z" }, - { url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload-time = "2025-10-06T14:51:04.724Z" }, - { url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload-time = "2025-10-06T14:51:06.306Z" }, - { url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload-time = "2025-10-06T14:51:08.091Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload-time = "2025-10-06T14:51:10.365Z" }, - { url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload-time = "2025-10-06T14:51:12.466Z" }, - { url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload-time = "2025-10-06T14:51:14.48Z" }, - { url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload-time = "2025-10-06T14:51:16.072Z" }, - { url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload-time = "2025-10-06T14:51:17.544Z" }, - { url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload-time = "2025-10-06T14:51:18.875Z" }, - { url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload-time = "2025-10-06T14:51:20.225Z" }, - { url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload-time = "2025-10-06T14:51:21.588Z" }, - { url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload-time = "2025-10-06T14:51:22.93Z" }, - { url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload-time = "2025-10-06T14:51:24.352Z" }, - { url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload-time = "2025-10-06T14:51:25.822Z" }, - { url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload-time = "2025-10-06T14:51:27.604Z" }, - { url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload-time = "2025-10-06T14:51:29.664Z" }, - { url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload-time = "2025-10-06T14:51:31.684Z" }, - { url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload-time = "2025-10-06T14:51:33.699Z" }, - { url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload-time = "2025-10-06T14:51:36.189Z" }, - { url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload-time = "2025-10-06T14:51:41.291Z" }, - { url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload-time = "2025-10-06T14:51:43.55Z" }, - { url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload-time = "2025-10-06T14:51:45.265Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload-time = "2025-10-06T14:51:46.836Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload-time = "2025-10-06T14:51:48.541Z" }, - { url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload-time = "2025-10-06T14:51:50.355Z" }, - { url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload-time = "2025-10-06T14:51:51.883Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload-time = "2025-10-06T14:51:53.672Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload-time = "2025-10-06T14:51:55.415Z" }, - { url = "https://files.pythonhosted.org/packages/90/d7/4cf84257902265c4250769ac49f4eaab81c182ee9aff8bf59d2714dbb174/multidict-6.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:363eb68a0a59bd2303216d2346e6c441ba10d36d1f9969fcb6f1ba700de7bb5c", size = 77073, upload-time = "2025-10-06T14:51:57.386Z" }, - { url = "https://files.pythonhosted.org/packages/6d/51/194e999630a656e76c2965a1590d12faa5cd528170f2abaa04423e09fe8d/multidict-6.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d874eb056410ca05fed180b6642e680373688efafc7f077b2a2f61811e873a40", size = 44928, upload-time = "2025-10-06T14:51:58.791Z" }, - { url = "https://files.pythonhosted.org/packages/e5/6b/2a195373c33068c9158e0941d0b46cfcc9c1d894ca2eb137d1128081dff0/multidict-6.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b55d5497b51afdfde55925e04a022f1de14d4f4f25cdfd4f5d9b0aa96166851", size = 44581, upload-time = "2025-10-06T14:52:00.174Z" }, - { url = "https://files.pythonhosted.org/packages/69/7b/7f4f2e644b6978bf011a5fd9a5ebb7c21de3f38523b1f7897d36a1ac1311/multidict-6.7.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f8e5c0031b90ca9ce555e2e8fd5c3b02a25f14989cbc310701823832c99eb687", size = 239901, upload-time = "2025-10-06T14:52:02.416Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b5/952c72786710a031aa204a9adf7db66d7f97a2c6573889d58b9e60fe6702/multidict-6.7.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cf41880c991716f3c7cec48e2f19ae4045fc9db5fc9cff27347ada24d710bb5", size = 240534, upload-time = "2025-10-06T14:52:04.105Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ef/109fe1f2471e4c458c74242c7e4a833f2d9fc8a6813cd7ee345b0bad18f9/multidict-6.7.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cfc12a8630a29d601f48d47787bd7eb730e475e83edb5d6c5084317463373eb", size = 219545, upload-time = "2025-10-06T14:52:06.208Z" }, - { url = "https://files.pythonhosted.org/packages/42/bd/327d91288114967f9fe90dc53de70aa3fec1b9073e46aa32c4828f771a87/multidict-6.7.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3996b50c3237c4aec17459217c1e7bbdead9a22a0fcd3c365564fbd16439dde6", size = 251187, upload-time = "2025-10-06T14:52:08.049Z" }, - { url = "https://files.pythonhosted.org/packages/f4/13/a8b078ebbaceb7819fd28cd004413c33b98f1b70d542a62e6a00b74fb09f/multidict-6.7.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7f5170993a0dd3ab871c74f45c0a21a4e2c37a2f2b01b5f722a2ad9c6650469e", size = 249379, upload-time = "2025-10-06T14:52:09.831Z" }, - { url = "https://files.pythonhosted.org/packages/e3/6d/ab12e1246be4d65d1f55de1e6f6aaa9b8120eddcfdd1d290439c7833d5ce/multidict-6.7.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec81878ddf0e98817def1e77d4f50dae5ef5b0e4fe796fae3bd674304172416e", size = 239241, upload-time = "2025-10-06T14:52:11.561Z" }, - { url = "https://files.pythonhosted.org/packages/bb/d7/079a93625208c173b8fa756396814397c0fd9fee61ef87b75a748820b86e/multidict-6.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9281bf5b34f59afbc6b1e477a372e9526b66ca446f4bf62592839c195a718b32", size = 237418, upload-time = "2025-10-06T14:52:13.671Z" }, - { url = "https://files.pythonhosted.org/packages/c9/29/03777c2212274aa9440918d604dc9d6af0e6b4558c611c32c3dcf1a13870/multidict-6.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:68af405971779d8b37198726f2b6fe3955db846fee42db7a4286fc542203934c", size = 232987, upload-time = "2025-10-06T14:52:15.708Z" }, - { url = "https://files.pythonhosted.org/packages/d9/00/11188b68d85a84e8050ee34724d6ded19ad03975caebe0c8dcb2829b37bf/multidict-6.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3ba3ef510467abb0667421a286dc906e30eb08569365f5cdb131d7aff7c2dd84", size = 240985, upload-time = "2025-10-06T14:52:17.317Z" }, - { url = "https://files.pythonhosted.org/packages/df/0c/12eef6aeda21859c6cdf7d75bd5516d83be3efe3d8cc45fd1a3037f5b9dc/multidict-6.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b61189b29081a20c7e4e0b49b44d5d44bb0dc92be3c6d06a11cc043f81bf9329", size = 246855, upload-time = "2025-10-06T14:52:19.096Z" }, - { url = "https://files.pythonhosted.org/packages/69/f6/076120fd8bb3975f09228e288e08bff6b9f1bfd5166397c7ba284f622ab2/multidict-6.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fb287618b9c7aa3bf8d825f02d9201b2f13078a5ed3b293c8f4d953917d84d5e", size = 241804, upload-time = "2025-10-06T14:52:21.166Z" }, - { url = "https://files.pythonhosted.org/packages/5f/51/41bb950c81437b88a93e6ddfca1d8763569ae861e638442838c4375f7497/multidict-6.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:521f33e377ff64b96c4c556b81c55d0cfffb96a11c194fd0c3f1e56f3d8dd5a4", size = 235321, upload-time = "2025-10-06T14:52:23.208Z" }, - { url = "https://files.pythonhosted.org/packages/5a/cf/5bbd31f055199d56c1f6b04bbadad3ccb24e6d5d4db75db774fc6d6674b8/multidict-6.7.0-cp39-cp39-win32.whl", hash = "sha256:ce8fdc2dca699f8dbf055a61d73eaa10482569ad20ee3c36ef9641f69afa8c91", size = 41435, upload-time = "2025-10-06T14:52:24.735Z" }, - { url = "https://files.pythonhosted.org/packages/af/01/547ffe9c2faec91c26965c152f3fea6cff068b6037401f61d310cc861ff4/multidict-6.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:7e73299c99939f089dd9b2120a04a516b95cdf8c1cd2b18c53ebf0de80b1f18f", size = 46193, upload-time = "2025-10-06T14:52:26.101Z" }, - { url = "https://files.pythonhosted.org/packages/27/77/cfa5461d1d2651d6fc24216c92b4a21d4e385a41c46e0d9f3b070675167b/multidict-6.7.0-cp39-cp39-win_arm64.whl", hash = "sha256:6bdce131e14b04fd34a809b6380dbfd826065c3e2fe8a50dbae659fa0c390546", size = 43118, upload-time = "2025-10-06T14:52:27.876Z" }, - { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, -] - -[[package]] -name = "mypy" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mypy-extensions" }, - { name = "pathspec" }, - { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1e/e3/034322d5a779685218ed69286c32faa505247f1f096251ef66c8fd203b08/mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03", size = 3352114, upload-time = "2025-07-14T20:34:30.181Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/31/e762baa3b73905c856d45ab77b4af850e8159dffffd86a52879539a08c6b/mypy-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8e08de6138043108b3b18f09d3f817a4783912e48828ab397ecf183135d84d6", size = 10998313, upload-time = "2025-07-14T20:33:24.519Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c1/25b2f0d46fb7e0b5e2bee61ec3a47fe13eff9e3c2f2234f144858bbe6485/mypy-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce4a17920ec144647d448fc43725b5873548b1aae6c603225626747ededf582d", size = 10128922, upload-time = "2025-07-14T20:34:06.414Z" }, - { url = "https://files.pythonhosted.org/packages/02/78/6d646603a57aa8a2886df1b8881fe777ea60f28098790c1089230cd9c61d/mypy-1.17.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ff25d151cc057fdddb1cb1881ef36e9c41fa2a5e78d8dd71bee6e4dcd2bc05b", size = 11913524, upload-time = "2025-07-14T20:33:19.109Z" }, - { url = "https://files.pythonhosted.org/packages/4f/19/dae6c55e87ee426fb76980f7e78484450cad1c01c55a1dc4e91c930bea01/mypy-1.17.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93468cf29aa9a132bceb103bd8475f78cacde2b1b9a94fd978d50d4bdf616c9a", size = 12650527, upload-time = "2025-07-14T20:32:44.095Z" }, - { url = "https://files.pythonhosted.org/packages/86/e1/f916845a235235a6c1e4d4d065a3930113767001d491b8b2e1b61ca56647/mypy-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:98189382b310f16343151f65dd7e6867386d3e35f7878c45cfa11383d175d91f", size = 12897284, upload-time = "2025-07-14T20:33:38.168Z" }, - { url = "https://files.pythonhosted.org/packages/ae/dc/414760708a4ea1b096bd214d26a24e30ac5e917ef293bc33cdb6fe22d2da/mypy-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:c004135a300ab06a045c1c0d8e3f10215e71d7b4f5bb9a42ab80236364429937", size = 9506493, upload-time = "2025-07-14T20:34:01.093Z" }, - { url = "https://files.pythonhosted.org/packages/d4/24/82efb502b0b0f661c49aa21cfe3e1999ddf64bf5500fc03b5a1536a39d39/mypy-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d4fe5c72fd262d9c2c91c1117d16aac555e05f5beb2bae6a755274c6eec42be", size = 10914150, upload-time = "2025-07-14T20:31:51.985Z" }, - { url = "https://files.pythonhosted.org/packages/03/96/8ef9a6ff8cedadff4400e2254689ca1dc4b420b92c55255b44573de10c54/mypy-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96b196e5c16f41b4f7736840e8455958e832871990c7ba26bf58175e357ed61", size = 10039845, upload-time = "2025-07-14T20:32:30.527Z" }, - { url = "https://files.pythonhosted.org/packages/df/32/7ce359a56be779d38021d07941cfbb099b41411d72d827230a36203dbb81/mypy-1.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73a0ff2dd10337ceb521c080d4147755ee302dcde6e1a913babd59473904615f", size = 11837246, upload-time = "2025-07-14T20:32:01.28Z" }, - { url = "https://files.pythonhosted.org/packages/82/16/b775047054de4d8dbd668df9137707e54b07fe18c7923839cd1e524bf756/mypy-1.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cfcc1179c4447854e9e406d3af0f77736d631ec87d31c6281ecd5025df625d", size = 12571106, upload-time = "2025-07-14T20:34:26.942Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cf/fa33eaf29a606102c8d9ffa45a386a04c2203d9ad18bf4eef3e20c43ebc8/mypy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56f180ff6430e6373db7a1d569317675b0a451caf5fef6ce4ab365f5f2f6c3", size = 12759960, upload-time = "2025-07-14T20:33:42.882Z" }, - { url = "https://files.pythonhosted.org/packages/94/75/3f5a29209f27e739ca57e6350bc6b783a38c7621bdf9cac3ab8a08665801/mypy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:eafaf8b9252734400f9b77df98b4eee3d2eecab16104680d51341c75702cad70", size = 9503888, upload-time = "2025-07-14T20:32:34.392Z" }, - { url = "https://files.pythonhosted.org/packages/12/e9/e6824ed620bbf51d3bf4d6cbbe4953e83eaf31a448d1b3cfb3620ccb641c/mypy-1.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f986f1cab8dbec39ba6e0eaa42d4d3ac6686516a5d3dccd64be095db05ebc6bb", size = 11086395, upload-time = "2025-07-14T20:34:11.452Z" }, - { url = "https://files.pythonhosted.org/packages/ba/51/a4afd1ae279707953be175d303f04a5a7bd7e28dc62463ad29c1c857927e/mypy-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:51e455a54d199dd6e931cd7ea987d061c2afbaf0960f7f66deef47c90d1b304d", size = 10120052, upload-time = "2025-07-14T20:33:09.897Z" }, - { url = "https://files.pythonhosted.org/packages/8a/71/19adfeac926ba8205f1d1466d0d360d07b46486bf64360c54cb5a2bd86a8/mypy-1.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3204d773bab5ff4ebbd1f8efa11b498027cd57017c003ae970f310e5b96be8d8", size = 11861806, upload-time = "2025-07-14T20:32:16.028Z" }, - { url = "https://files.pythonhosted.org/packages/0b/64/d6120eca3835baf7179e6797a0b61d6c47e0bc2324b1f6819d8428d5b9ba/mypy-1.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1051df7ec0886fa246a530ae917c473491e9a0ba6938cfd0ec2abc1076495c3e", size = 12744371, upload-time = "2025-07-14T20:33:33.503Z" }, - { url = "https://files.pythonhosted.org/packages/1f/dc/56f53b5255a166f5bd0f137eed960e5065f2744509dfe69474ff0ba772a5/mypy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f773c6d14dcc108a5b141b4456b0871df638eb411a89cd1c0c001fc4a9d08fc8", size = 12914558, upload-time = "2025-07-14T20:33:56.961Z" }, - { url = "https://files.pythonhosted.org/packages/69/ac/070bad311171badc9add2910e7f89271695a25c136de24bbafc7eded56d5/mypy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:1619a485fd0e9c959b943c7b519ed26b712de3002d7de43154a489a2d0fd817d", size = 9585447, upload-time = "2025-07-14T20:32:20.594Z" }, - { url = "https://files.pythonhosted.org/packages/be/7b/5f8ab461369b9e62157072156935cec9d272196556bdc7c2ff5f4c7c0f9b/mypy-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c41aa59211e49d717d92b3bb1238c06d387c9325d3122085113c79118bebb06", size = 11070019, upload-time = "2025-07-14T20:32:07.99Z" }, - { url = "https://files.pythonhosted.org/packages/9c/f8/c49c9e5a2ac0badcc54beb24e774d2499748302c9568f7f09e8730e953fa/mypy-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e69db1fb65b3114f98c753e3930a00514f5b68794ba80590eb02090d54a5d4a", size = 10114457, upload-time = "2025-07-14T20:33:47.285Z" }, - { url = "https://files.pythonhosted.org/packages/89/0c/fb3f9c939ad9beed3e328008b3fb90b20fda2cddc0f7e4c20dbefefc3b33/mypy-1.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03ba330b76710f83d6ac500053f7727270b6b8553b0423348ffb3af6f2f7b889", size = 11857838, upload-time = "2025-07-14T20:33:14.462Z" }, - { url = "https://files.pythonhosted.org/packages/4c/66/85607ab5137d65e4f54d9797b77d5a038ef34f714929cf8ad30b03f628df/mypy-1.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037bc0f0b124ce46bfde955c647f3e395c6174476a968c0f22c95a8d2f589bba", size = 12731358, upload-time = "2025-07-14T20:32:25.579Z" }, - { url = "https://files.pythonhosted.org/packages/73/d0/341dbbfb35ce53d01f8f2969facbb66486cee9804048bf6c01b048127501/mypy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38876106cb6132259683632b287238858bd58de267d80defb6f418e9ee50658", size = 12917480, upload-time = "2025-07-14T20:34:21.868Z" }, - { url = "https://files.pythonhosted.org/packages/64/63/70c8b7dbfc520089ac48d01367a97e8acd734f65bd07813081f508a8c94c/mypy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:d30ba01c0f151998f367506fab31c2ac4527e6a7b2690107c7a7f9e3cb419a9c", size = 9589666, upload-time = "2025-07-14T20:34:16.841Z" }, - { url = "https://files.pythonhosted.org/packages/9f/a0/6263dd11941231f688f0a8f2faf90ceac1dc243d148d314a089d2fe25108/mypy-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:63e751f1b5ab51d6f3d219fe3a2fe4523eaa387d854ad06906c63883fde5b1ab", size = 10988185, upload-time = "2025-07-14T20:33:04.797Z" }, - { url = "https://files.pythonhosted.org/packages/02/13/b8f16d6b0dc80277129559c8e7dbc9011241a0da8f60d031edb0e6e9ac8f/mypy-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f7fb09d05e0f1c329a36dcd30e27564a3555717cde87301fae4fb542402ddfad", size = 10120169, upload-time = "2025-07-14T20:32:38.84Z" }, - { url = "https://files.pythonhosted.org/packages/14/ef/978ba79df0d65af680e20d43121363cf643eb79b04bf3880d01fc8afeb6f/mypy-1.17.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b72c34ce05ac3a1361ae2ebb50757fb6e3624032d91488d93544e9f82db0ed6c", size = 11918121, upload-time = "2025-07-14T20:33:52.328Z" }, - { url = "https://files.pythonhosted.org/packages/f4/10/55ef70b104151a0d8280474f05268ff0a2a79be8d788d5e647257d121309/mypy-1.17.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:434ad499ad8dde8b2f6391ddfa982f41cb07ccda8e3c67781b1bfd4e5f9450a8", size = 12648821, upload-time = "2025-07-14T20:32:59.631Z" }, - { url = "https://files.pythonhosted.org/packages/26/8c/7781fcd2e1eef48fbedd3a422c21fe300a8e03ed5be2eb4bd10246a77f4e/mypy-1.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f105f61a5eff52e137fd73bee32958b2add9d9f0a856f17314018646af838e97", size = 12896955, upload-time = "2025-07-14T20:32:49.543Z" }, - { url = "https://files.pythonhosted.org/packages/78/13/03ac759dabe86e98ca7b6681f114f90ee03f3ff8365a57049d311bd4a4e3/mypy-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:ba06254a5a22729853209550d80f94e28690d5530c661f9416a68ac097b13fc4", size = 9512957, upload-time = "2025-07-14T20:33:28.619Z" }, - { url = "https://files.pythonhosted.org/packages/e3/fc/ee058cc4316f219078464555873e99d170bde1d9569abd833300dbeb484a/mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496", size = 2283195, upload-time = "2025-07-14T20:31:54.753Z" }, -] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, -] - -[[package]] -name = "nodeenv" -version = "1.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, -] - -[[package]] -name = "packaging" -version = "25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, -] - -[[package]] -name = "pathspec" -version = "1.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/b2/bb8e495d5262bfec41ab5cb18f522f1012933347fb5d9e62452d446baca2/pathspec-1.0.3.tar.gz", hash = "sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d", size = 130841, upload-time = "2026-01-09T15:46:46.009Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/2b/121e912bd60eebd623f873fd090de0e84f322972ab25a7f9044c056804ed/pathspec-1.0.3-py3-none-any.whl", hash = "sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c", size = 55021, upload-time = "2026-01-09T15:46:44.652Z" }, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, -] - -[[package]] -name = "propcache" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/0e/934b541323035566a9af292dba85a195f7b78179114f2c6ebb24551118a9/propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db", size = 79534, upload-time = "2025-10-08T19:46:02.083Z" }, - { url = "https://files.pythonhosted.org/packages/a1/6b/db0d03d96726d995dc7171286c6ba9d8d14251f37433890f88368951a44e/propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8", size = 45526, upload-time = "2025-10-08T19:46:03.884Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c3/82728404aea669e1600f304f2609cde9e665c18df5a11cdd57ed73c1dceb/propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925", size = 47263, upload-time = "2025-10-08T19:46:05.405Z" }, - { url = "https://files.pythonhosted.org/packages/df/1b/39313ddad2bf9187a1432654c38249bab4562ef535ef07f5eb6eb04d0b1b/propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21", size = 201012, upload-time = "2025-10-08T19:46:07.165Z" }, - { url = "https://files.pythonhosted.org/packages/5b/01/f1d0b57d136f294a142acf97f4ed58c8e5b974c21e543000968357115011/propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5", size = 209491, upload-time = "2025-10-08T19:46:08.909Z" }, - { url = "https://files.pythonhosted.org/packages/a1/c8/038d909c61c5bb039070b3fb02ad5cccdb1dde0d714792e251cdb17c9c05/propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db", size = 215319, upload-time = "2025-10-08T19:46:10.7Z" }, - { url = "https://files.pythonhosted.org/packages/08/57/8c87e93142b2c1fa2408e45695205a7ba05fb5db458c0bf5c06ba0e09ea6/propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7", size = 196856, upload-time = "2025-10-08T19:46:12.003Z" }, - { url = "https://files.pythonhosted.org/packages/42/df/5615fec76aa561987a534759b3686008a288e73107faa49a8ae5795a9f7a/propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4", size = 193241, upload-time = "2025-10-08T19:46:13.495Z" }, - { url = "https://files.pythonhosted.org/packages/d5/21/62949eb3a7a54afe8327011c90aca7e03547787a88fb8bd9726806482fea/propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60", size = 190552, upload-time = "2025-10-08T19:46:14.938Z" }, - { url = "https://files.pythonhosted.org/packages/30/ee/ab4d727dd70806e5b4de96a798ae7ac6e4d42516f030ee60522474b6b332/propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f", size = 200113, upload-time = "2025-10-08T19:46:16.695Z" }, - { url = "https://files.pythonhosted.org/packages/8a/0b/38b46208e6711b016aa8966a3ac793eee0d05c7159d8342aa27fc0bc365e/propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900", size = 200778, upload-time = "2025-10-08T19:46:18.023Z" }, - { url = "https://files.pythonhosted.org/packages/cf/81/5abec54355ed344476bee711e9f04815d4b00a311ab0535599204eecc257/propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c", size = 193047, upload-time = "2025-10-08T19:46:19.449Z" }, - { url = "https://files.pythonhosted.org/packages/ec/b6/1f237c04e32063cb034acd5f6ef34ef3a394f75502e72703545631ab1ef6/propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb", size = 38093, upload-time = "2025-10-08T19:46:20.643Z" }, - { url = "https://files.pythonhosted.org/packages/a6/67/354aac4e0603a15f76439caf0427781bcd6797f370377f75a642133bc954/propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37", size = 41638, upload-time = "2025-10-08T19:46:21.935Z" }, - { url = "https://files.pythonhosted.org/packages/e0/e1/74e55b9fd1a4c209ff1a9a824bf6c8b3d1fc5a1ac3eabe23462637466785/propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581", size = 38229, upload-time = "2025-10-08T19:46:23.368Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, - { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, - { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, - { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, - { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, - { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, - { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, - { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, - { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, - { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, - { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, - { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, - { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, - { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, - { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, - { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, - { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, - { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, - { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, - { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, - { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, - { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, - { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, - { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, - { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, - { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, - { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, - { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, - { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, - { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, - { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, - { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, - { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, - { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, - { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, - { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, - { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, - { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, - { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, - { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, - { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, - { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, - { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, - { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, - { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, - { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, - { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, - { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, - { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, - { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, - { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, - { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, - { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, - { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, - { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, - { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, - { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, - { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, - { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, - { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, - { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, - { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, - { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, - { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, - { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, - { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, - { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, - { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, - { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, - { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, - { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, - { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, - { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, - { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, - { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, - { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, - { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, - { url = "https://files.pythonhosted.org/packages/9b/01/0ebaec9003f5d619a7475165961f8e3083cf8644d704b60395df3601632d/propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff", size = 80277, upload-time = "2025-10-08T19:48:36.647Z" }, - { url = "https://files.pythonhosted.org/packages/34/58/04af97ac586b4ef6b9026c3fd36ee7798b737a832f5d3440a4280dcebd3a/propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb", size = 45865, upload-time = "2025-10-08T19:48:37.859Z" }, - { url = "https://files.pythonhosted.org/packages/7c/19/b65d98ae21384518b291d9939e24a8aeac4fdb5101b732576f8f7540e834/propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac", size = 47636, upload-time = "2025-10-08T19:48:39.038Z" }, - { url = "https://files.pythonhosted.org/packages/b3/0f/317048c6d91c356c7154dca5af019e6effeb7ee15fa6a6db327cc19e12b4/propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888", size = 201126, upload-time = "2025-10-08T19:48:40.774Z" }, - { url = "https://files.pythonhosted.org/packages/71/69/0b2a7a5a6ee83292b4b997dbd80549d8ce7d40b6397c1646c0d9495f5a85/propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc", size = 209837, upload-time = "2025-10-08T19:48:42.167Z" }, - { url = "https://files.pythonhosted.org/packages/a5/92/c699ac495a6698df6e497fc2de27af4b6ace10d8e76528357ce153722e45/propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a", size = 215578, upload-time = "2025-10-08T19:48:43.56Z" }, - { url = "https://files.pythonhosted.org/packages/b3/ee/14de81c5eb02c0ee4f500b4e39c4e1bd0677c06e72379e6ab18923c773fc/propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88", size = 197187, upload-time = "2025-10-08T19:48:45.309Z" }, - { url = "https://files.pythonhosted.org/packages/1d/94/48dce9aaa6d8dd5a0859bad75158ec522546d4ac23f8e2f05fac469477dd/propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00", size = 193478, upload-time = "2025-10-08T19:48:47.743Z" }, - { url = "https://files.pythonhosted.org/packages/60/b5/0516b563e801e1ace212afde869a0596a0d7115eec0b12d296d75633fb29/propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0", size = 190650, upload-time = "2025-10-08T19:48:49.373Z" }, - { url = "https://files.pythonhosted.org/packages/24/89/e0f7d4a5978cd56f8cd67735f74052f257dc471ec901694e430f0d1572fe/propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e", size = 200251, upload-time = "2025-10-08T19:48:51.4Z" }, - { url = "https://files.pythonhosted.org/packages/06/7d/a1fac863d473876ed4406c914f2e14aa82d2f10dd207c9e16fc383cc5a24/propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781", size = 200919, upload-time = "2025-10-08T19:48:53.227Z" }, - { url = "https://files.pythonhosted.org/packages/c3/4e/f86a256ff24944cf5743e4e6c6994e3526f6acfcfb55e21694c2424f758c/propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183", size = 193211, upload-time = "2025-10-08T19:48:55.027Z" }, - { url = "https://files.pythonhosted.org/packages/6e/3f/3fbad5f4356b068f1b047d300a6ff2c66614d7030f078cd50be3fec04228/propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19", size = 38314, upload-time = "2025-10-08T19:48:56.792Z" }, - { url = "https://files.pythonhosted.org/packages/a4/45/d78d136c3a3d215677abb886785aae744da2c3005bcb99e58640c56529b1/propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f", size = 41912, upload-time = "2025-10-08T19:48:57.995Z" }, - { url = "https://files.pythonhosted.org/packages/fc/2a/b0632941f25139f4e58450b307242951f7c2717a5704977c6d5323a800af/propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938", size = 38450, upload-time = "2025-10-08T19:48:59.349Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, -] - -[[package]] -name = "pydantic" -version = "1.10.26" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version < '3.10'", -] -dependencies = [ - { name = "typing-extensions", marker = "extra == 'group-14-moderation-api-pydantic-v1'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7b/da/fd89f987a376c807cd81ea0eff4589aade783bbb702637b4734ef2c743a2/pydantic-1.10.26.tar.gz", hash = "sha256:8c6aa39b494c5af092e690127c283d84f363ac36017106a9e66cb33a22ac412e", size = 357906, upload-time = "2025-12-18T15:47:46.557Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/08/2587a6d4314e7539eec84acd062cb7b037638edb57a0335d20e4c5b8878c/pydantic-1.10.26-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7ae36fa0ecef8d39884120f212e16c06bb096a38f523421278e2f39c1784546", size = 2444588, upload-time = "2025-12-18T15:46:28.882Z" }, - { url = "https://files.pythonhosted.org/packages/47/e6/10df5f08c105bcbb4adbee7d1108ff4b347702b110fed058f6a03f1c6b73/pydantic-1.10.26-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d95a76cf503f0f72ed7812a91de948440b2bf564269975738a4751e4fadeb572", size = 2255972, upload-time = "2025-12-18T15:46:31.72Z" }, - { url = "https://files.pythonhosted.org/packages/ba/7d/fdb961e7adc2c31f394feba6f560ef2c74c446f0285e2c2eb87d2b7206c7/pydantic-1.10.26-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a943ce8e00ad708ed06a1d9df5b4fd28f5635a003b82a4908ece6f24c0b18464", size = 2857175, upload-time = "2025-12-18T15:46:34Z" }, - { url = "https://files.pythonhosted.org/packages/8f/6c/f21e27dda475d4c562bd01b5874284dd3180f336c1e669413b743ca8b278/pydantic-1.10.26-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:465ad8edb29b15c10b779b16431fe8e77c380098badf6db367b7a1d3e572cf53", size = 2947001, upload-time = "2025-12-18T15:46:35.922Z" }, - { url = "https://files.pythonhosted.org/packages/6d/f6/27ea206232cbb6ec24dc4e4e8888a9a734f96a1eaf13504be4b30ef26aa7/pydantic-1.10.26-cp310-cp310-win_amd64.whl", hash = "sha256:80e6be6272839c8a7641d26ad569ab77772809dd78f91d0068dc0fc97f071945", size = 2066217, upload-time = "2025-12-18T15:46:37.614Z" }, - { url = "https://files.pythonhosted.org/packages/1d/c1/d521e64c8130e1ad9d22c270bed3fabcc0940c9539b076b639c88fd32a8d/pydantic-1.10.26-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:116233e53889bcc536f617e38c1b8337d7fa9c280f0fd7a4045947515a785637", size = 2428347, upload-time = "2025-12-18T15:46:39.41Z" }, - { url = "https://files.pythonhosted.org/packages/2c/08/f4b804a00c16e3ea994cb640a7c25c579b4f1fa674cde6a19fa0dfb0ae4f/pydantic-1.10.26-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3cfdd361addb6eb64ccd26ac356ad6514cee06a61ab26b27e16b5ed53108f77", size = 2212605, upload-time = "2025-12-18T15:46:41.006Z" }, - { url = "https://files.pythonhosted.org/packages/5d/78/0df4b9efef29bbc5e39f247fcba99060d15946b4463d82a5589cf7923d71/pydantic-1.10.26-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e4451951a9a93bf9a90576f3e25240b47ee49ab5236adccb8eff6ac943adf0f", size = 2753560, upload-time = "2025-12-18T15:46:43.215Z" }, - { url = "https://files.pythonhosted.org/packages/68/66/6ab6c1d3a116d05d2508fce64f96e35242938fac07544d611e11d0d363a0/pydantic-1.10.26-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9858ed44c6bea5f29ffe95308db9e62060791c877766c67dd5f55d072c8612b5", size = 2859235, upload-time = "2025-12-18T15:46:45.112Z" }, - { url = "https://files.pythonhosted.org/packages/61/4e/f1676bb0fcdf6ed2ce4670d7d1fc1d6c3a06d84497644acfbe02649503f1/pydantic-1.10.26-cp311-cp311-win_amd64.whl", hash = "sha256:ac1089f723e2106ebde434377d31239e00870a7563245072968e5af5cc4d33df", size = 2066646, upload-time = "2025-12-18T15:46:46.816Z" }, - { url = "https://files.pythonhosted.org/packages/02/6c/cd97a5a776c4515e6ee2ae81c2f2c5be51376dda6c31f965d7746ce0019f/pydantic-1.10.26-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:468d5b9cacfcaadc76ed0a4645354ab6f263ec01a63fb6d05630ea1df6ae453f", size = 2433795, upload-time = "2025-12-18T15:46:49.321Z" }, - { url = "https://files.pythonhosted.org/packages/47/12/de20affa30dcef728fcf9cc98e13ff4438c7a630de8d2f90eb38eba0891c/pydantic-1.10.26-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2c1b0b914be31671000ca25cf7ea17fcaaa68cfeadf6924529c5c5aa24b7ab1f", size = 2227387, upload-time = "2025-12-18T15:46:50.877Z" }, - { url = "https://files.pythonhosted.org/packages/7b/1d/9d65dcc5b8c17ba590f1f9f486e9306346831902318b7ee93f63516f4003/pydantic-1.10.26-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15b13b9f8ba8867095769e1156e0d7fbafa1f65b898dd40fd1c02e34430973cb", size = 2629594, upload-time = "2025-12-18T15:46:53.42Z" }, - { url = "https://files.pythonhosted.org/packages/3f/76/acb41409356789e23e1a7ef58f93821410c96409183ce314ddb58d97f23e/pydantic-1.10.26-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad7025ca324ae263d4313998e25078dcaec5f9ed0392c06dedb57e053cc8086b", size = 2745305, upload-time = "2025-12-18T15:46:55.987Z" }, - { url = "https://files.pythonhosted.org/packages/22/72/a98c0c5e527a66057d969fedd61675223c7975ade61acebbca9f1abd6dc0/pydantic-1.10.26-cp312-cp312-win_amd64.whl", hash = "sha256:4482b299874dabb88a6c3759e3d85c6557c407c3b586891f7d808d8a38b66b9c", size = 1937647, upload-time = "2025-12-18T15:46:57.905Z" }, - { url = "https://files.pythonhosted.org/packages/28/b9/17a5a5a421c23ac27486b977724a42c9d5f8b7f0f4aab054251066223900/pydantic-1.10.26-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1ae7913bb40a96c87e3d3f6fe4e918ef53bf181583de4e71824360a9b11aef1c", size = 2494599, upload-time = "2025-12-18T15:47:00.209Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8e/6e3bd4241076cf227b443d7577245dd5d181ecf40b3182fcb908bc8c197d/pydantic-1.10.26-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8154c13f58d4de5d3a856bb6c909c7370f41fb876a5952a503af6b975265f4ba", size = 2254391, upload-time = "2025-12-18T15:47:02.268Z" }, - { url = "https://files.pythonhosted.org/packages/a8/30/a1c4092eda2145ecbead6c92db489b223e101e1ba0da82576d0cf73dd422/pydantic-1.10.26-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8af0507bf6118b054a9765fb2e402f18a8b70c964f420d95b525eb711122d62", size = 2609445, upload-time = "2025-12-18T15:47:04.909Z" }, - { url = "https://files.pythonhosted.org/packages/3a/2a/0491f1729ee4b7b6bc859ec22f69752f0c09bee1b66ac6f5f701136f34c3/pydantic-1.10.26-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dcb5a7318fb43189fde6af6f21ac7149c4bcbcfffc54bc87b5becddc46084847", size = 2732124, upload-time = "2025-12-18T15:47:07.464Z" }, - { url = "https://files.pythonhosted.org/packages/2a/56/b59f3b2f84e1df2b04ae768a1bb04d9f0288ff71b67cdcbb07683757b2c0/pydantic-1.10.26-cp313-cp313-win_amd64.whl", hash = "sha256:71cde228bc0600cf8619f0ee62db050d1880dcc477eba0e90b23011b4ee0f314", size = 1939888, upload-time = "2025-12-18T15:47:09.618Z" }, - { url = "https://files.pythonhosted.org/packages/d2/8b/0c3dc02d4b97790b0f199bf933f677c14e7be4a8d21307c5f2daae06aa41/pydantic-1.10.26-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6b40730cc81d53d515dc0b8bb5c9b43fadb9bed46de4a3c03bd95e8571616dba", size = 2502689, upload-time = "2025-12-18T15:47:12.308Z" }, - { url = "https://files.pythonhosted.org/packages/d4/9d/d31aeea45542b2ae4b09ecba92b88aaba696b801c31919811aa979a1242d/pydantic-1.10.26-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c3bbb9c0eecdf599e4db9b372fa9cc55be12e80a0d9c6d307950a39050cb0e37", size = 2269494, upload-time = "2025-12-18T15:47:14.53Z" }, - { url = "https://files.pythonhosted.org/packages/78/c1/3a4d069593283ca4dd0006039ba33644e21e432cddc09da706ac50441610/pydantic-1.10.26-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc2e3fe7bc4993626ef6b6fa855defafa1d6f8996aa1caef2deb83c5ac4d043a", size = 2620047, upload-time = "2025-12-18T15:47:17.089Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0e/340c3d29197d99c15ab04093d43bb9c9d0fd17c2a34b80cb9d36ed732b09/pydantic-1.10.26-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:36d9e46b588aaeb1dcd2409fa4c467fe0b331f3cc9f227b03a7a00643704e962", size = 2747625, upload-time = "2025-12-18T15:47:19.21Z" }, - { url = "https://files.pythonhosted.org/packages/1e/58/f12ab3727339b172c830b32151919456b67787cdfe8808b2568b322fb15c/pydantic-1.10.26-cp314-cp314-win_amd64.whl", hash = "sha256:81ce3c8616d12a7be31b4aadfd3434f78f6b44b75adbfaec2fe1ad4f7f999b8c", size = 1976436, upload-time = "2025-12-18T15:47:21.384Z" }, - { url = "https://files.pythonhosted.org/packages/e1/8a/3a5a6267d5f03617b5c0f1985aa9fdfbafd33a50ef6dadd866a15ed4d123/pydantic-1.10.26-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:502b9d30d18a2dfaf81b7302f6ba0e5853474b1c96212449eb4db912cb604b7d", size = 2457039, upload-time = "2025-12-18T15:47:34.584Z" }, - { url = "https://files.pythonhosted.org/packages/f3/fa/343ac0db26918a033ac6256c036d72c3b6eb1196b7de622e2e8a94b19079/pydantic-1.10.26-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0d8f6087bf697dec3bf7ffcd7fe8362674f16519f3151789f33cbe8f1d19fc15", size = 2266441, upload-time = "2025-12-18T15:47:36.807Z" }, - { url = "https://files.pythonhosted.org/packages/fc/36/1ab48136578608dba2f2a62e452f3db2083b474d4e49be5749c6ae0c123c/pydantic-1.10.26-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dd40a99c358419910c85e6f5d22f9c56684c25b5e7abc40879b3b4a52f34ae90", size = 2869383, upload-time = "2025-12-18T15:47:38.883Z" }, - { url = "https://files.pythonhosted.org/packages/a2/25/41dbf1bffc31eb242cece8080561a4133eaeb513372dec36a84477a3fb71/pydantic-1.10.26-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ce3293b86ca9f4125df02ff0a70be91bc7946522467cbd98e7f1493f340616ba", size = 2963582, upload-time = "2025-12-18T15:47:40.854Z" }, - { url = "https://files.pythonhosted.org/packages/61/2f/f072ae160a300c85eb9f059915101fd33dacf12d8df08c2b804acb3b95d1/pydantic-1.10.26-cp39-cp39-win_amd64.whl", hash = "sha256:1a4e3062b71ab1d5df339ba12c48f9ed5817c5de6cb92a961dd5c64bb32e7b96", size = 2075530, upload-time = "2025-12-18T15:47:43.181Z" }, - { url = "https://files.pythonhosted.org/packages/1f/98/556e82f00b98486def0b8af85da95e69d2be7e367cf2431408e108bc3095/pydantic-1.10.26-py3-none-any.whl", hash = "sha256:c43ad70dc3ce7787543d563792426a16fd7895e14be4b194b5665e36459dd917", size = 166975, upload-time = "2025-12-18T15:47:44.927Z" }, -] - -[[package]] -name = "pydantic" -version = "2.12.5" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and extra != 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2'", - "python_full_version < '3.10' and extra != 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and extra != 'group-14-moderation-api-pydantic-v1' and extra != 'group-14-moderation-api-pydantic-v2'", - "python_full_version < '3.10' and extra != 'group-14-moderation-api-pydantic-v1' and extra != 'group-14-moderation-api-pydantic-v2'", -] -dependencies = [ - { name = "annotated-types", marker = "extra == 'group-14-moderation-api-pydantic-v2' or extra != 'group-14-moderation-api-pydantic-v1'" }, - { name = "pydantic-core", marker = "extra == 'group-14-moderation-api-pydantic-v2' or extra != 'group-14-moderation-api-pydantic-v1'" }, - { name = "typing-extensions", marker = "extra == 'group-14-moderation-api-pydantic-v2' or extra != 'group-14-moderation-api-pydantic-v1'" }, - { name = "typing-inspection", marker = "extra == 'group-14-moderation-api-pydantic-v2' or extra != 'group-14-moderation-api-pydantic-v1'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, -] - -[[package]] -name = "pydantic-core" -version = "2.41.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "extra == 'group-14-moderation-api-pydantic-v2' or extra != 'group-14-moderation-api-pydantic-v1'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, - { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, - { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, - { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, - { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, - { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, - { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, - { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, - { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, - { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, - { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, - { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, - { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, - { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, - { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, - { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, - { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, - { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/54/db/160dffb57ed9a3705c4cbcbff0ac03bdae45f1ca7d58ab74645550df3fbd/pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf", size = 2107999, upload-time = "2025-11-04T13:42:03.885Z" }, - { url = "https://files.pythonhosted.org/packages/a3/7d/88e7de946f60d9263cc84819f32513520b85c0f8322f9b8f6e4afc938383/pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5", size = 1929745, upload-time = "2025-11-04T13:42:06.075Z" }, - { url = "https://files.pythonhosted.org/packages/d5/c2/aef51e5b283780e85e99ff19db0f05842d2d4a8a8cd15e63b0280029b08f/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d", size = 1920220, upload-time = "2025-11-04T13:42:08.457Z" }, - { url = "https://files.pythonhosted.org/packages/c7/97/492ab10f9ac8695cd76b2fdb24e9e61f394051df71594e9bcc891c9f586e/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60", size = 2067296, upload-time = "2025-11-04T13:42:10.817Z" }, - { url = "https://files.pythonhosted.org/packages/ec/23/984149650e5269c59a2a4c41d234a9570adc68ab29981825cfaf4cfad8f4/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82", size = 2231548, upload-time = "2025-11-04T13:42:13.843Z" }, - { url = "https://files.pythonhosted.org/packages/71/0c/85bcbb885b9732c28bec67a222dbed5ed2d77baee1f8bba2002e8cd00c5c/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5", size = 2362571, upload-time = "2025-11-04T13:42:16.208Z" }, - { url = "https://files.pythonhosted.org/packages/c0/4a/412d2048be12c334003e9b823a3fa3d038e46cc2d64dd8aab50b31b65499/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3", size = 2068175, upload-time = "2025-11-04T13:42:18.911Z" }, - { url = "https://files.pythonhosted.org/packages/73/f4/c58b6a776b502d0a5540ad02e232514285513572060f0d78f7832ca3c98b/pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425", size = 2177203, upload-time = "2025-11-04T13:42:22.578Z" }, - { url = "https://files.pythonhosted.org/packages/ed/ae/f06ea4c7e7a9eead3d165e7623cd2ea0cb788e277e4f935af63fc98fa4e6/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504", size = 2148191, upload-time = "2025-11-04T13:42:24.89Z" }, - { url = "https://files.pythonhosted.org/packages/c1/57/25a11dcdc656bf5f8b05902c3c2934ac3ea296257cc4a3f79a6319e61856/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5", size = 2343907, upload-time = "2025-11-04T13:42:27.683Z" }, - { url = "https://files.pythonhosted.org/packages/96/82/e33d5f4933d7a03327c0c43c65d575e5919d4974ffc026bc917a5f7b9f61/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3", size = 2322174, upload-time = "2025-11-04T13:42:30.776Z" }, - { url = "https://files.pythonhosted.org/packages/81/45/4091be67ce9f469e81656f880f3506f6a5624121ec5eb3eab37d7581897d/pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460", size = 1990353, upload-time = "2025-11-04T13:42:33.111Z" }, - { url = "https://files.pythonhosted.org/packages/44/8a/a98aede18db6e9cd5d66bcacd8a409fcf8134204cdede2e7de35c5a2c5ef/pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b", size = 2015698, upload-time = "2025-11-04T13:42:35.484Z" }, - { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, - { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, - { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, - { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, - { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, - { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, - { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, - { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, - { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, - { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, - { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, - { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, -] - -[[package]] -name = "pygments" -version = "2.19.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, -] - -[[package]] -name = "pyright" -version = "1.1.399" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nodeenv" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/db/9d/d91d5f6d26b2db95476fefc772e2b9a16d54c6bd0ea6bb5c1b6d635ab8b4/pyright-1.1.399.tar.gz", hash = "sha256:439035d707a36c3d1b443aec980bc37053fbda88158eded24b8eedcf1c7b7a1b", size = 3856954, upload-time = "2025-04-10T04:40:25.703Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/b5/380380c9e7a534cb1783c70c3e8ac6d1193c599650a55838d0557586796e/pyright-1.1.399-py3-none-any.whl", hash = "sha256:55f9a875ddf23c9698f24208c764465ffdfd38be6265f7faf9a176e1dc549f3b", size = 5592584, upload-time = "2025-04-10T04:40:23.502Z" }, -] - -[[package]] -name = "pytest" -version = "8.4.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "colorama", marker = "(python_full_version < '3.10' and sys_platform == 'win32') or (python_full_version >= '3.10' and extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2') or (sys_platform != 'win32' and extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "exceptiongroup", marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "packaging", marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "pluggy", marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "pygments", marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "tomli", marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, -] - -[[package]] -name = "pytest" -version = "9.0.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and extra != 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and extra == 'group-14-moderation-api-pydantic-v1' and extra != 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and extra != 'group-14-moderation-api-pydantic-v1' and extra != 'group-14-moderation-api-pydantic-v2'", -] -dependencies = [ - { name = "colorama", marker = "(python_full_version >= '3.10' and sys_platform == 'win32') or (python_full_version < '3.10' and extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2') or (sys_platform != 'win32' and extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "exceptiongroup", marker = "python_full_version == '3.10.*' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "packaging", marker = "python_full_version >= '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "pluggy", marker = "python_full_version >= '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "pygments", marker = "python_full_version >= '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "tomli", marker = "python_full_version == '3.10.*' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, -] - -[[package]] -name = "pytest-asyncio" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "backports-asyncio-runner", marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "typing-extensions", marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, -] - -[[package]] -name = "pytest-asyncio" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and extra != 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and extra == 'group-14-moderation-api-pydantic-v1' and extra != 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and extra != 'group-14-moderation-api-pydantic-v1' and extra != 'group-14-moderation-api-pydantic-v2'", -] -dependencies = [ - { name = "backports-asyncio-runner", marker = "python_full_version == '3.10.*' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "typing-extensions", marker = "(python_full_version >= '3.10' and python_full_version < '3.13') or (python_full_version < '3.10' and extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2') or (python_full_version >= '3.13' and extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, -] - -[[package]] -name = "pytest-xdist" -version = "3.8.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "execnet" }, - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, -] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six", marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, -] - -[[package]] -name = "respx" -version = "0.22.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "httpx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f4/7c/96bd0bc759cf009675ad1ee1f96535edcb11e9666b985717eb8c87192a95/respx-0.22.0.tar.gz", hash = "sha256:3c8924caa2a50bd71aefc07aa812f2466ff489f1848c96e954a5362d17095d91", size = 28439, upload-time = "2024-12-19T22:33:59.374Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/67/afbb0978d5399bc9ea200f1d4489a23c9a1dad4eee6376242b8182389c79/respx-0.22.0-py2.py3-none-any.whl", hash = "sha256:631128d4c9aba15e56903fb5f66fb1eff412ce28dd387ca3a81339e52dbd3ad0", size = 25127, upload-time = "2024-12-19T22:33:57.837Z" }, -] - -[[package]] -name = "rich" -version = "14.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, -] - -[[package]] -name = "ruff" -version = "0.14.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/50/0a/1914efb7903174b381ee2ffeebb4253e729de57f114e63595114c8ca451f/ruff-0.14.13.tar.gz", hash = "sha256:83cd6c0763190784b99650a20fec7633c59f6ebe41c5cc9d45ee42749563ad47", size = 6059504, upload-time = "2026-01-15T20:15:16.918Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/ae/0deefbc65ca74b0ab1fd3917f94dc3b398233346a74b8bbb0a916a1a6bf6/ruff-0.14.13-py3-none-linux_armv6l.whl", hash = "sha256:76f62c62cd37c276cb03a275b198c7c15bd1d60c989f944db08a8c1c2dbec18b", size = 13062418, upload-time = "2026-01-15T20:14:50.779Z" }, - { url = "https://files.pythonhosted.org/packages/47/df/5916604faa530a97a3c154c62a81cb6b735c0cb05d1e26d5ad0f0c8ac48a/ruff-0.14.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:914a8023ece0528d5cc33f5a684f5f38199bbb566a04815c2c211d8f40b5d0ed", size = 13442344, upload-time = "2026-01-15T20:15:07.94Z" }, - { url = "https://files.pythonhosted.org/packages/4c/f3/e0e694dd69163c3a1671e102aa574a50357536f18a33375050334d5cd517/ruff-0.14.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d24899478c35ebfa730597a4a775d430ad0d5631b8647a3ab368c29b7e7bd063", size = 12354720, upload-time = "2026-01-15T20:15:09.854Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e8/67f5fcbbaee25e8fc3b56cc33e9892eca7ffe09f773c8e5907757a7e3bdb/ruff-0.14.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aaf3870f14d925bbaf18b8a2347ee0ae7d95a2e490e4d4aea6813ed15ebc80e", size = 12774493, upload-time = "2026-01-15T20:15:20.908Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ce/d2e9cb510870b52a9565d885c0d7668cc050e30fa2c8ac3fb1fda15c083d/ruff-0.14.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac5b7f63dd3b27cc811850f5ffd8fff845b00ad70e60b043aabf8d6ecc304e09", size = 12815174, upload-time = "2026-01-15T20:15:05.74Z" }, - { url = "https://files.pythonhosted.org/packages/88/00/c38e5da58beebcf4fa32d0ddd993b63dfacefd02ab7922614231330845bf/ruff-0.14.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d2b1097750d90ba82ce4ba676e85230a0ed694178ca5e61aa9b459970b3eb9", size = 13680909, upload-time = "2026-01-15T20:15:14.537Z" }, - { url = "https://files.pythonhosted.org/packages/61/61/cd37c9dd5bd0a3099ba79b2a5899ad417d8f3b04038810b0501a80814fd7/ruff-0.14.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d0bf87705acbbcb8d4c24b2d77fbb73d40210a95c3903b443cd9e30824a5032", size = 15144215, upload-time = "2026-01-15T20:15:22.886Z" }, - { url = "https://files.pythonhosted.org/packages/56/8a/85502d7edbf98c2df7b8876f316c0157359165e16cdf98507c65c8d07d3d/ruff-0.14.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3eb5da8e2c9e9f13431032fdcbe7681de9ceda5835efee3269417c13f1fed5c", size = 14706067, upload-time = "2026-01-15T20:14:48.271Z" }, - { url = "https://files.pythonhosted.org/packages/7e/2f/de0df127feb2ee8c1e54354dc1179b4a23798f0866019528c938ba439aca/ruff-0.14.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:642442b42957093811cd8d2140dfadd19c7417030a7a68cf8d51fcdd5f217427", size = 14133916, upload-time = "2026-01-15T20:14:57.357Z" }, - { url = "https://files.pythonhosted.org/packages/0d/77/9b99686bb9fe07a757c82f6f95e555c7a47801a9305576a9c67e0a31d280/ruff-0.14.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4acdf009f32b46f6e8864af19cbf6841eaaed8638e65c8dac845aea0d703c841", size = 13859207, upload-time = "2026-01-15T20:14:55.111Z" }, - { url = "https://files.pythonhosted.org/packages/7d/46/2bdcb34a87a179a4d23022d818c1c236cb40e477faf0d7c9afb6813e5876/ruff-0.14.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:591a7f68860ea4e003917d19b5c4f5ac39ff558f162dc753a2c5de897fd5502c", size = 14043686, upload-time = "2026-01-15T20:14:52.841Z" }, - { url = "https://files.pythonhosted.org/packages/1a/a9/5c6a4f56a0512c691cf143371bcf60505ed0f0860f24a85da8bd123b2bf1/ruff-0.14.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:774c77e841cc6e046fc3e91623ce0903d1cd07e3a36b1a9fe79b81dab3de506b", size = 12663837, upload-time = "2026-01-15T20:15:18.921Z" }, - { url = "https://files.pythonhosted.org/packages/fe/bb/b920016ece7651fa7fcd335d9d199306665486694d4361547ccb19394c44/ruff-0.14.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:61f4e40077a1248436772bb6512db5fc4457fe4c49e7a94ea7c5088655dd21ae", size = 12805867, upload-time = "2026-01-15T20:14:59.272Z" }, - { url = "https://files.pythonhosted.org/packages/7d/b3/0bd909851e5696cd21e32a8fc25727e5f58f1934b3596975503e6e85415c/ruff-0.14.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6d02f1428357fae9e98ac7aa94b7e966fd24151088510d32cf6f902d6c09235e", size = 13208528, upload-time = "2026-01-15T20:15:03.732Z" }, - { url = "https://files.pythonhosted.org/packages/3b/3b/e2d94cb613f6bbd5155a75cbe072813756363eba46a3f2177a1fcd0cd670/ruff-0.14.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e399341472ce15237be0c0ae5fbceca4b04cd9bebab1a2b2c979e015455d8f0c", size = 13929242, upload-time = "2026-01-15T20:15:11.918Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c5/abd840d4132fd51a12f594934af5eba1d5d27298a6f5b5d6c3be45301caf/ruff-0.14.13-py3-none-win32.whl", hash = "sha256:ef720f529aec113968b45dfdb838ac8934e519711da53a0456038a0efecbd680", size = 12919024, upload-time = "2026-01-15T20:14:43.647Z" }, - { url = "https://files.pythonhosted.org/packages/c2/55/6384b0b8ce731b6e2ade2b5449bf07c0e4c31e8a2e68ea65b3bafadcecc5/ruff-0.14.13-py3-none-win_amd64.whl", hash = "sha256:6070bd026e409734b9257e03e3ef18c6e1a216f0435c6751d7a8ec69cb59abef", size = 14097887, upload-time = "2026-01-15T20:15:01.48Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e1/7348090988095e4e39560cfc2f7555b1b2a7357deba19167b600fdf5215d/ruff-0.14.13-py3-none-win_arm64.whl", hash = "sha256:7ab819e14f1ad9fe39f246cfcc435880ef7a9390d81a2b6ac7e01039083dd247", size = 13080224, upload-time = "2026-01-15T20:14:45.853Z" }, -] - -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, -] - -[[package]] -name = "time-machine" -version = "2.19.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "python-dateutil", marker = "python_full_version < '3.10' or (extra == 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/1b5fdd165f61b67f445fac2a7feb0c655118edef429cd09ff5a8067f7f1d/time_machine-2.19.0.tar.gz", hash = "sha256:7c5065a8b3f2bbb449422c66ef71d114d3f909c276a6469642ecfffb6a0fcd29", size = 14576, upload-time = "2025-08-19T17:22:08.402Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/8f/19125611ebbcb3a14da14cd982b9eb4573e2733db60c9f1fbf6a39534f40/time_machine-2.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b5169018ef47206997b46086ce01881cd3a4666fd2998c9d76a87858ca3e49e9", size = 19659, upload-time = "2025-08-19T17:20:30.062Z" }, - { url = "https://files.pythonhosted.org/packages/74/da/9b0a928321e7822a3ff96dbd1eae089883848e30e9e1b149b85fb96ba56b/time_machine-2.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85bb7ed440fccf6f6d0c8f7d68d849e7c3d1f771d5e0b2cdf871fa6561da569f", size = 15157, upload-time = "2025-08-19T17:20:31.931Z" }, - { url = "https://files.pythonhosted.org/packages/36/ff/d7e943422038f5f2161fe2c2d791e64a45be691ef946020b20f3a6efc4d4/time_machine-2.19.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a3b12028af1cdc09ccd595be2168b7b26f206c1e190090b048598fbe278beb8e", size = 32860, upload-time = "2025-08-19T17:20:33.241Z" }, - { url = "https://files.pythonhosted.org/packages/fc/80/2b0f1070ed9808ee7da7a6da62a4a0b776957cb4d861578348f86446e778/time_machine-2.19.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c261f073086cf081d1443cbf7684148c662659d3d139d06b772bfe3fe7cc71a6", size = 34510, upload-time = "2025-08-19T17:20:34.221Z" }, - { url = "https://files.pythonhosted.org/packages/ef/b4/48038691c8d89924b36c83335a73adeeb68c884f5a1da08a5b17b8a956f3/time_machine-2.19.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:011954d951230a9f1079f22b39ed1a3a9abb50ee297dfb8c557c46351659d94d", size = 36204, upload-time = "2025-08-19T17:20:35.163Z" }, - { url = "https://files.pythonhosted.org/packages/37/2e/60e8adb541df195e83cb74b720b2cfb1f22ed99c5a7f8abf2a9ab3442cb5/time_machine-2.19.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b0f83308b29c7872006803f2e77318874eb84d0654f2afe0e48e3822e7a2e39b", size = 34936, upload-time = "2025-08-19T17:20:36.61Z" }, - { url = "https://files.pythonhosted.org/packages/5e/72/e8cee59c6cd99dd3b25b8001a0253e779a286aa8f44d5b40777cbd66210b/time_machine-2.19.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:39733ef844e2984620ec9382a42d00cccc4757d75a5dd572be8c2572e86e50b9", size = 32932, upload-time = "2025-08-19T17:20:37.901Z" }, - { url = "https://files.pythonhosted.org/packages/2c/eb/83f300d93c1504965d944e03679f1c943a923bce2d0fdfadef0e2e22cc13/time_machine-2.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f8db99f6334432e9ffbf00c215caf2ae9773f17cec08304d77e9e90febc3507b", size = 34010, upload-time = "2025-08-19T17:20:39.202Z" }, - { url = "https://files.pythonhosted.org/packages/e1/77/f35f2500e04daac5033a22fbfd17e68467822b8406ee77966bf222ccaa26/time_machine-2.19.0-cp310-cp310-win32.whl", hash = "sha256:72bf66cd19e27ffd26516b9cbe676d50c2e0b026153289765dfe0cf406708128", size = 17121, upload-time = "2025-08-19T17:20:40.108Z" }, - { url = "https://files.pythonhosted.org/packages/db/df/32d3e0404be1760a64a44caab2af34b07e952bfe00a23134fea9ddba3e8a/time_machine-2.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:46f1c945934ce3d6b4f388b8e581fce7f87ec891ea90d7128e19520e434f96f0", size = 17957, upload-time = "2025-08-19T17:20:41.079Z" }, - { url = "https://files.pythonhosted.org/packages/66/df/598a71a1afb4b509a4587273b76590b16d9110a3e9106f01eedc68d02bb2/time_machine-2.19.0-cp310-cp310-win_arm64.whl", hash = "sha256:fb4897c7a5120a4fd03f0670f332d83b7e55645886cd8864a71944c4c2e5b35b", size = 16821, upload-time = "2025-08-19T17:20:41.967Z" }, - { url = "https://files.pythonhosted.org/packages/1d/ed/4815ebcc9b6c14273f692b9be38a9b09eae52a7e532407cc61a51912b121/time_machine-2.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5ee91664880434d98e41585c3446dac7180ec408c786347451ddfca110d19296", size = 19342, upload-time = "2025-08-19T17:20:43.207Z" }, - { url = "https://files.pythonhosted.org/packages/ee/08/154cce8b11b60d8238b0b751b8901d369999f4e8f7c3a5f917caa5d95b0b/time_machine-2.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed3732b83a893d1c7b8cabde762968b4dc5680ee0d305b3ecca9bb516f4e3862", size = 14978, upload-time = "2025-08-19T17:20:44.134Z" }, - { url = "https://files.pythonhosted.org/packages/c7/b7/b689d8c8eeca7af375cfcd64973e49e83aa817cc00f80f98548d42c0eb50/time_machine-2.19.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6ba0303e9cc9f7f947e344f501e26bedfb68fab521e3c2729d370f4f332d2d55", size = 30964, upload-time = "2025-08-19T17:20:45.366Z" }, - { url = "https://files.pythonhosted.org/packages/80/91/38bf9c79674e95ce32e23c267055f281dff651eec77ed32a677db3dc011a/time_machine-2.19.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2851825b524a988ee459c37c1c26bdfaa7eff78194efb2b562ea497a6f375b0a", size = 32606, upload-time = "2025-08-19T17:20:46.693Z" }, - { url = "https://files.pythonhosted.org/packages/19/4a/e9222d85d4de68975a5e799f539a9d32f3a134a9101fca0a61fa6aa33d68/time_machine-2.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68d32b09ecfd7fef59255c091e8e7c24dd117f882c4880b5c7ab8c5c32a98f89", size = 34405, upload-time = "2025-08-19T17:20:48.032Z" }, - { url = "https://files.pythonhosted.org/packages/14/e2/09480d608d42d6876f9ff74593cfc9197a7eb2c31381a74fb2b145575b65/time_machine-2.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60c46ab527bf2fa144b530f639cc9e12803524c9e1f111dc8c8f493bb6586eeb", size = 33181, upload-time = "2025-08-19T17:20:48.937Z" }, - { url = "https://files.pythonhosted.org/packages/84/64/f9359e000fad32d9066305c48abc527241d608bcdf77c19d67d66e268455/time_machine-2.19.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:56f26ab9f0201c453d18fe76bb7d1cf05fe58c1b9d9cb0c7d243d05132e01292", size = 31036, upload-time = "2025-08-19T17:20:50.276Z" }, - { url = "https://files.pythonhosted.org/packages/71/0d/fab2aacec71e3e482bd7fce0589381f9414a4a97f8766bddad04ad047b7b/time_machine-2.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6c806cf3c1185baa1d807b7f51bed0db7a6506832c961d5d1b4c94c775749bc0", size = 32145, upload-time = "2025-08-19T17:20:51.449Z" }, - { url = "https://files.pythonhosted.org/packages/44/fb/faeba2405fb27553f7b28db441a500e2064ffdb2dcba001ee315fdd2c121/time_machine-2.19.0-cp311-cp311-win32.whl", hash = "sha256:b30039dfd89855c12138095bee39c540b4633cbc3684580d684ef67a99a91587", size = 17004, upload-time = "2025-08-19T17:20:52.38Z" }, - { url = "https://files.pythonhosted.org/packages/2f/84/87e483d660ca669426192969280366635c845c3154a9fe750be546ed3afc/time_machine-2.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:13ed8b34430f1de79905877f5600adffa626793ab4546a70a99fb72c6a3350d8", size = 17822, upload-time = "2025-08-19T17:20:53.348Z" }, - { url = "https://files.pythonhosted.org/packages/41/f4/ebf7bbf5047854a528adaf54a5e8780bc5f7f0104c298ab44566a3053bf8/time_machine-2.19.0-cp311-cp311-win_arm64.whl", hash = "sha256:cc29a50a0257d8750b08056b66d7225daab47606832dea1a69e8b017323bf511", size = 16680, upload-time = "2025-08-19T17:20:54.26Z" }, - { url = "https://files.pythonhosted.org/packages/9b/aa/7e00614d339e4d687f6e96e312a1566022528427d237ec639df66c4547bc/time_machine-2.19.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c85cf437dc3c07429456d8d6670ac90ecbd8241dcd0fbf03e8db2800576f91ff", size = 19308, upload-time = "2025-08-19T17:20:55.25Z" }, - { url = "https://files.pythonhosted.org/packages/ab/3c/bde3c757394f5bca2fbc1528d4117960a26c38f9b160bf471b38d2378d8f/time_machine-2.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d9238897e8ef54acdf59f5dff16f59ca0720e7c02d820c56b4397c11db5d3eb9", size = 15019, upload-time = "2025-08-19T17:20:56.204Z" }, - { url = "https://files.pythonhosted.org/packages/c8/e0/8ca916dd918018352d377f1f5226ee071cfbeb7dbbde2b03d14a411ac2b1/time_machine-2.19.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e312c7d5d6bfffb96c6a7b39ff29e3046de100d7efaa3c01552654cfbd08f14c", size = 33079, upload-time = "2025-08-19T17:20:57.166Z" }, - { url = "https://files.pythonhosted.org/packages/48/69/184a0209f02dd0cb5e01e8d13cd4c97a5f389c4e3d09b95160dd676ad1e7/time_machine-2.19.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:714c40b2c90d1c57cc403382d5a9cf16e504cb525bfe9650095317da3c3d62b5", size = 34925, upload-time = "2025-08-19T17:20:58.117Z" }, - { url = "https://files.pythonhosted.org/packages/43/42/4bbf4309e8e57cea1086eb99052d97ff6ddecc1ab6a3b07aa4512f8bf963/time_machine-2.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2eaa1c675d500dc3ccae19e9fb1feff84458a68c132bbea47a80cc3dd2df7072", size = 36384, upload-time = "2025-08-19T17:20:59.108Z" }, - { url = "https://files.pythonhosted.org/packages/b1/af/9f510dc1719157348c1a2e87423aed406589070b54b503cb237d9bf3a4fe/time_machine-2.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e77a414e9597988af53b2b2e67242c9d2f409769df0d264b6d06fda8ca3360d4", size = 34881, upload-time = "2025-08-19T17:21:00.116Z" }, - { url = "https://files.pythonhosted.org/packages/ca/28/61764a635c70cc76c76ba582dfdc1a84834cddaeb96789023af5214426b2/time_machine-2.19.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cd93996970e11c382b04d4937c3cd0b0167adeef14725ece35aae88d8a01733c", size = 32931, upload-time = "2025-08-19T17:21:01.095Z" }, - { url = "https://files.pythonhosted.org/packages/b6/e0/f028d93b266e6ade8aca5851f76ebbc605b2905cdc29981a2943b43e1a6c/time_machine-2.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8e20a6d8d6e23174bd7e931e134d9610b136db460b249d07e84ecdad029ec352", size = 34241, upload-time = "2025-08-19T17:21:02.052Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a6/36d1950ed1d3f613158024cf1dcc73db1d9ef0b9117cf51ef2e37dc06499/time_machine-2.19.0-cp312-cp312-win32.whl", hash = "sha256:95afc9bc65228b27be80c2756799c20b8eb97c4ef382a9b762b6d7888bc84099", size = 17021, upload-time = "2025-08-19T17:21:03.374Z" }, - { url = "https://files.pythonhosted.org/packages/b1/0d/e2dce93355abda3cac69e77fe96566757e98b8fe7fdcbddce89c9ced3f5f/time_machine-2.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:e84909af950e2448f4e2562ea5759c946248c99ab380d2b47d79b62bd76fa236", size = 17857, upload-time = "2025-08-19T17:21:04.331Z" }, - { url = "https://files.pythonhosted.org/packages/eb/28/50ae6fb83b7feeeca7a461c0dc156cf7ef5e6ef594a600d06634fde6a2cb/time_machine-2.19.0-cp312-cp312-win_arm64.whl", hash = "sha256:0390a1ea9fa7e9d772a39b7c61b34fdcca80eb9ffac339cc0441c6c714c81470", size = 16677, upload-time = "2025-08-19T17:21:05.39Z" }, - { url = "https://files.pythonhosted.org/packages/a9/b8/24ebce67aa531bae2cbe164bb3f4abc6467dc31f3aead35e77f5a075ea3e/time_machine-2.19.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5e172866753e6041d3b29f3037dc47c20525176a494a71bbd0998dfdc4f11f2f", size = 19373, upload-time = "2025-08-19T17:21:06.701Z" }, - { url = "https://files.pythonhosted.org/packages/53/a5/c9a5240fd2f845d3ff9fa26f8c8eaa29f7239af9d65007e61d212250f15b/time_machine-2.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f70f68379bd6f542ae6775cce9a4fa3dcc20bf7959c42eaef871c14469e18097", size = 15056, upload-time = "2025-08-19T17:21:07.667Z" }, - { url = "https://files.pythonhosted.org/packages/b9/92/66cce5d2fb2a5e68459aca85fd18a7e2d216f725988940cd83f96630f2f1/time_machine-2.19.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e69e0b0f694728a00e72891ef8dd00c7542952cb1c87237db594b6b27d504a96", size = 33172, upload-time = "2025-08-19T17:21:08.619Z" }, - { url = "https://files.pythonhosted.org/packages/ae/20/b499e9ab4364cd466016c33dcdf4f56629ca4c20b865bd4196d229f31d92/time_machine-2.19.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3ae0a8b869574301ec5637e32c270c7384cca5cd6e230f07af9d29271a7fa293", size = 35042, upload-time = "2025-08-19T17:21:09.622Z" }, - { url = "https://files.pythonhosted.org/packages/41/32/b252d3d32791eb16c07d553c820dbc33d9c7fa771de3d1c602190bded2b7/time_machine-2.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:554e4317de90e2f7605ff80d153c8bb56b38c0d0c0279feb17e799521e987b8c", size = 36535, upload-time = "2025-08-19T17:21:10.571Z" }, - { url = "https://files.pythonhosted.org/packages/98/cf/4d0470062b9742e1b040ab81bad04d1a5d1de09806507bb6188989cfa1a7/time_machine-2.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6567a5ec5538ed550539ac29be11b3cb36af1f9894e2a72940cba0292cc7c3c9", size = 34945, upload-time = "2025-08-19T17:21:11.538Z" }, - { url = "https://files.pythonhosted.org/packages/24/71/2f741b29d98b1c18f6777a32236497c3d3264b6077e431cea4695684c8a1/time_machine-2.19.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82e9ffe8dfff07b0d810a2ad015a82cd78c6a237f6c7cf185fa7f747a3256f8a", size = 33014, upload-time = "2025-08-19T17:21:12.858Z" }, - { url = "https://files.pythonhosted.org/packages/e8/83/ca8dba6106562843fd99f672e5aaf95badbc10f4f13f7cfe8d8640a7019d/time_machine-2.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7e1c4e578cdd69b3531d8dd3fbcb92a0cd879dadb912ee37af99c3a9e3c0d285", size = 34350, upload-time = "2025-08-19T17:21:13.923Z" }, - { url = "https://files.pythonhosted.org/packages/21/7f/34fe540450e18d0a993240100e4b86e8d03d831b92af8bb6ddb2662dc6fc/time_machine-2.19.0-cp313-cp313-win32.whl", hash = "sha256:72dbd4cbc3d96dec9dd281ddfbb513982102776b63e4e039f83afb244802a9e5", size = 17047, upload-time = "2025-08-19T17:21:14.874Z" }, - { url = "https://files.pythonhosted.org/packages/bf/5d/c8be73df82c7ebe7cd133279670e89b8b110af3ce1412c551caa9d08e625/time_machine-2.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:e17e3e089ac95f9a145ce07ff615e3c85674f7de36f2d92aaf588493a23ffb4b", size = 17868, upload-time = "2025-08-19T17:21:15.819Z" }, - { url = "https://files.pythonhosted.org/packages/92/13/2dfd3b8fb285308f61cd7aa9bfa96f46ddf916e3549a0f0afd094c556599/time_machine-2.19.0-cp313-cp313-win_arm64.whl", hash = "sha256:149072aff8e3690e14f4916103d898ea0d5d9c95531b6aa0995251c299533f7b", size = 16710, upload-time = "2025-08-19T17:21:16.748Z" }, - { url = "https://files.pythonhosted.org/packages/05/c1/deebb361727d2c5790f9d4d874be1b19afd41f4375581df465e6718b46a2/time_machine-2.19.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f3589fee1ed0ab6ee424a55b0ea1ec694c4ba64cc26895bcd7d99f3d1bc6a28a", size = 20053, upload-time = "2025-08-19T17:21:17.704Z" }, - { url = "https://files.pythonhosted.org/packages/45/e8/fe3376951e6118d8ec1d1f94066a169b791424fe4a26c7dfc069b153ee08/time_machine-2.19.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7887e85275c4975fe54df03dcdd5f38bd36be973adc68a8c77e17441c3b443d6", size = 15423, upload-time = "2025-08-19T17:21:18.668Z" }, - { url = "https://files.pythonhosted.org/packages/9c/c7/f88d95cd1a87c650cf3749b4d64afdaf580297aa18ad7f4b44ec9d252dfc/time_machine-2.19.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ce0be294c209928563fcce1c587963e60ec803436cf1e181acd5bc1e425d554b", size = 39630, upload-time = "2025-08-19T17:21:19.645Z" }, - { url = "https://files.pythonhosted.org/packages/cc/5d/65a5c48a65357e56ec6f032972e4abd1c02d4fca4b0717a3aaefd19014d4/time_machine-2.19.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a62fd1ab380012c86f4c042010418ed45eb31604f4bf4453e17c9fa60bc56a29", size = 41242, upload-time = "2025-08-19T17:21:20.979Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f9/fe5209e1615fde0a8cad6c4e857157b150333ed1fe31a7632b08cfe0ebdd/time_machine-2.19.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b25ec853a4530a5800731257f93206b12cbdee85ede964ebf8011b66086a7914", size = 44278, upload-time = "2025-08-19T17:21:21.984Z" }, - { url = "https://files.pythonhosted.org/packages/4a/3a/a5e5fe9c5d614cde0a9387ff35e8dfd12c5ef6384e4c1a21b04e6e0b905d/time_machine-2.19.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a430e4d0e0556f021a9c78e9b9f68e5e8910bdace4aa34ed4d1a73e239ed9384", size = 42321, upload-time = "2025-08-19T17:21:23.755Z" }, - { url = "https://files.pythonhosted.org/packages/a1/c5/56eca774e9162bc1ce59111d2bd69140dc8908c9478c92ec7bd15d547600/time_machine-2.19.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2415b7495ec4364c8067071e964fbadfe746dd4cdb43983f2f0bd6ebed13315c", size = 39270, upload-time = "2025-08-19T17:21:26.009Z" }, - { url = "https://files.pythonhosted.org/packages/9b/69/5dd0c420667578169a12acc8c8fd7452e8cfb181e41c9b4ac7e88fa36686/time_machine-2.19.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbfc6b90c10f288594e1bf89a728a98cc0030791fd73541bbdc6b090aff83143", size = 40193, upload-time = "2025-08-19T17:21:27.054Z" }, - { url = "https://files.pythonhosted.org/packages/75/a7/de974d421bd55c9355583427c2a38fb0237bb5fd6614af492ba89dacb2f9/time_machine-2.19.0-cp313-cp313t-win32.whl", hash = "sha256:16f5d81f650c0a4d117ab08036dc30b5f8b262e11a4a0becc458e7f1c011b228", size = 17542, upload-time = "2025-08-19T17:21:28.674Z" }, - { url = "https://files.pythonhosted.org/packages/76/0a/aa0d05becd5d06ae8d3f16d657dc8cc9400c8d79aef80299de196467ff12/time_machine-2.19.0-cp313-cp313t-win_amd64.whl", hash = "sha256:645699616ec14e147094f601e6ab9553ff6cea37fad9c42720a6d7ed04bcd5dc", size = 18703, upload-time = "2025-08-19T17:21:29.663Z" }, - { url = "https://files.pythonhosted.org/packages/1f/c0/f785a4c7c73aa176510f7c48b84b49c26be84af0d534deb222e0327f750e/time_machine-2.19.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b32daa965d13237536ea3afaa5ad61ade2b2d9314bc3a20196a0d2e1d7b57c6a", size = 17020, upload-time = "2025-08-19T17:21:30.653Z" }, - { url = "https://files.pythonhosted.org/packages/ed/97/c5fb51def06c0b2b6735332ad118ab35b4d9b85368792e5b638e99b1b686/time_machine-2.19.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:31cb43c8fd2d961f31bed0ff4e0026964d2b35e5de9e0fabbfecf756906d3612", size = 19360, upload-time = "2025-08-19T17:21:31.94Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4e/2d795f7d6b7f5205ffe737a05bb1cf19d8038233b797062b2ef412b8512b/time_machine-2.19.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:bdf481a75afc6bff3e520db594501975b652f7def21cd1de6aa971d35ba644e6", size = 15033, upload-time = "2025-08-19T17:21:32.934Z" }, - { url = "https://files.pythonhosted.org/packages/dd/32/9bad501e360b4e758c58fae616ca5f8c7ad974b343f2463a15b2bf77a366/time_machine-2.19.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:00bee4bb950ac6a08d62af78e4da0cf2b4fc2abf0de2320d0431bf610db06e7c", size = 33379, upload-time = "2025-08-19T17:21:33.925Z" }, - { url = "https://files.pythonhosted.org/packages/a3/45/eda0ca4d793dfd162478d6163759b1c6ce7f6e61daa7fd7d62b31f21f87f/time_machine-2.19.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9f02199490906582302ce09edd32394fb393271674c75d7aa76c7a3245f16003", size = 35123, upload-time = "2025-08-19T17:21:34.945Z" }, - { url = "https://files.pythonhosted.org/packages/f0/5a/97e16325442ae5731fcaac794f0a1ef9980eff8a5491e58201d7eb814a34/time_machine-2.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e35726c7ba625f844c13b1fc0d4f81f394eefaee1d3a094a9093251521f2ef15", size = 36588, upload-time = "2025-08-19T17:21:35.975Z" }, - { url = "https://files.pythonhosted.org/packages/e8/9d/bf0b2ccc930cc4a316f26f1c78d3f313cd0fa13bb7480369b730a8f129db/time_machine-2.19.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:304315023999cd401ff02698870932b893369e1cfeb2248d09f6490507a92e97", size = 35013, upload-time = "2025-08-19T17:21:37.017Z" }, - { url = "https://files.pythonhosted.org/packages/f0/5a/39ac6a3078174f9715d88364871348b249631f12e76de1b862433b3f8862/time_machine-2.19.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9765d4f003f263ea8bfd90d2d15447ca4b3dfa181922cf6cf808923b02ac180a", size = 33303, upload-time = "2025-08-19T17:21:38.352Z" }, - { url = "https://files.pythonhosted.org/packages/b3/ac/d8646baf9f95f2e792a6d7a7b35e92fca253c4a992afff801beafae0e5c2/time_machine-2.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7837ef3fd5911eb9b480909bb93d922737b6bdecea99dfcedb0a03807de9b2d3", size = 34440, upload-time = "2025-08-19T17:21:39.382Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8b/8b6568c5ae966d80ead03ab537be3c6acf2af06fb501c2d466a3162c6295/time_machine-2.19.0-cp314-cp314-win32.whl", hash = "sha256:4bb5bd43b1bdfac3007b920b51d8e761f024ed465cfeec63ac4296922a4ec428", size = 17162, upload-time = "2025-08-19T17:21:40.381Z" }, - { url = "https://files.pythonhosted.org/packages/46/a5/211c1ab4566eba5308b2dc001b6349e3a032e3f6afa67ca2f27ea6b27af5/time_machine-2.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:f583bbd0aa8ab4a7c45a684bf636d9e042d466e30bcbae1d13e7541e2cbe7207", size = 18040, upload-time = "2025-08-19T17:21:41.363Z" }, - { url = "https://files.pythonhosted.org/packages/b8/fc/4c2fb705f6371cb83824da45a8b967514a922fc092a0ef53979334d97a70/time_machine-2.19.0-cp314-cp314-win_arm64.whl", hash = "sha256:f379c6f8a6575a8284592179cf528ce89373f060301323edcc44f1fa1d37be12", size = 16752, upload-time = "2025-08-19T17:21:42.336Z" }, - { url = "https://files.pythonhosted.org/packages/79/ab/6437d18f31c666b5116c97572a282ac2590a82a0a9867746a6647eaf4613/time_machine-2.19.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a3b8981f9c663b0906b05ab4d0ca211fae4b63b47c6ec26de5374fe56c836162", size = 20057, upload-time = "2025-08-19T17:21:43.35Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a2/e03639ec2ba7200328bbcad8a2b2b1d5fccca9cceb9481b164a1cabdcb33/time_machine-2.19.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8e9c6363893e7f52c226afbebb23e825259222d100e67dfd24c8a6d35f1a1907", size = 15430, upload-time = "2025-08-19T17:21:44.725Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ff/39e63a48e840f3e36ce24846ee51dd99c6dba635659b1750a2993771e88e/time_machine-2.19.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:206fcd6c9a6f00cac83db446ad1effc530a8cec244d2780af62db3a2d0a9871b", size = 39622, upload-time = "2025-08-19T17:21:45.821Z" }, - { url = "https://files.pythonhosted.org/packages/9a/2e/ee5ac79c4954768705801e54817c7d58e07e25a0bb227e775f501f3e2122/time_machine-2.19.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf33016a1403c123373ffaeff25e26e69d63bf2c63b6163932efed94160db7ef", size = 41235, upload-time = "2025-08-19T17:21:46.783Z" }, - { url = "https://files.pythonhosted.org/packages/3a/3e/9af5f39525e779185c77285b8bbae15340eeeaa0afb33d458bc8b47d459b/time_machine-2.19.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9247c4bb9bbd3ff584ef4efbdec8efd9f37aa08bcfc4728bde1e489c2cb445bd", size = 44276, upload-time = "2025-08-19T17:21:47.759Z" }, - { url = "https://files.pythonhosted.org/packages/59/fe/572c7443cc27140bbeae3947279bbd4a120f9e8622253a20637f260b7813/time_machine-2.19.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:77f9bb0b86758d1f2d9352642c874946ad5815df53ef4ca22eb9d532179fe50d", size = 42330, upload-time = "2025-08-19T17:21:48.881Z" }, - { url = "https://files.pythonhosted.org/packages/cf/24/1a81c2e08ee7dae13ec8ceed27a29afa980c3d63852e42f1e023bf0faa03/time_machine-2.19.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0b529e262df3b9c449f427385f4d98250828c879168c2e00eec844439f40b370", size = 39281, upload-time = "2025-08-19T17:21:49.907Z" }, - { url = "https://files.pythonhosted.org/packages/d2/60/6f0d6e5108978ca1a2a4ffb4d1c7e176d9199bb109fd44efe2680c60b52a/time_machine-2.19.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9199246e31cdc810e5d89cb71d09144c4d745960fdb0824da4994d152aca3303", size = 40201, upload-time = "2025-08-19T17:21:50.953Z" }, - { url = "https://files.pythonhosted.org/packages/73/b9/3ea4951e8293b0643feb98c0b9a176fa822154f1810835db3f282968ab10/time_machine-2.19.0-cp314-cp314t-win32.whl", hash = "sha256:0fe81bae55b7aefc2c2a34eb552aa82e6c61a86b3353a3c70df79b9698cb02ca", size = 17743, upload-time = "2025-08-19T17:21:51.948Z" }, - { url = "https://files.pythonhosted.org/packages/e4/8b/cd802884ca8a98e2b6cdc2397d57dd12ff8a7d1481e06fc3fad3d4e7e5ff/time_machine-2.19.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7253791b8d7e7399fbeed7a8193cb01bc004242864306288797056badbdaf80b", size = 18956, upload-time = "2025-08-19T17:21:52.997Z" }, - { url = "https://files.pythonhosted.org/packages/c6/49/cabb1593896082fd55e34768029b8b0ca23c9be8b2dc127e0fc14796d33e/time_machine-2.19.0-cp314-cp314t-win_arm64.whl", hash = "sha256:536bd1ac31ab06a1522e7bf287602188f502dc19d122b1502c4f60b1e8efac79", size = 17068, upload-time = "2025-08-19T17:21:54.064Z" }, - { url = "https://files.pythonhosted.org/packages/d6/05/0608376c3167afe6cf7cdfd2b05c142ea4c42616eee9ba06d1799965806a/time_machine-2.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8bb00b30ec9fe56d01e9812df1ffe39f331437cef9bfaebcc81c83f7f8f8ee2", size = 19659, upload-time = "2025-08-19T17:21:55.426Z" }, - { url = "https://files.pythonhosted.org/packages/11/c4/72eb8c7b36830cf36c51d7bc2f1ac313d68881c3a58040fb6b42c4523d20/time_machine-2.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d821c60efc08a97cc11e5482798e6fd5eba5c0f22a02db246b50895dbdc0de41", size = 15153, upload-time = "2025-08-19T17:21:56.505Z" }, - { url = "https://files.pythonhosted.org/packages/89/1a/0782e1f5c8ab8809ebd992709e1bb69d67600191baa023af7a5d32023a3c/time_machine-2.19.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb051aec7b3b6e96a200d911c225901e6133ff3da11e470e24111a53bbc13637", size = 32555, upload-time = "2025-08-19T17:21:57.74Z" }, - { url = "https://files.pythonhosted.org/packages/94/b0/8ef58e2f6321851d5900ca3d18044938832c2ed42a2ac7570ca6aa29768a/time_machine-2.19.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe59909d95a2ef5e01ce3354fdea3908404c2932c2069f00f66dff6f27e9363e", size = 34185, upload-time = "2025-08-19T17:21:59.361Z" }, - { url = "https://files.pythonhosted.org/packages/82/74/ce0c9867f788c1fb22c417ec1aae47a24117e53d51f6ff97d7c6ca5392f6/time_machine-2.19.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29e84b8682645b16eb6f9e8ec11c35324ad091841a11cf4fc3fc7f6119094c89", size = 35917, upload-time = "2025-08-19T17:22:00.421Z" }, - { url = "https://files.pythonhosted.org/packages/d2/70/6f97a8f552dbaa66feb10170b5726dab74bc531673d1ed9d6f271547e54c/time_machine-2.19.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a11f1c0e0d06023dc01614c964e256138913551d3ae6dca5148f79081156336", size = 34584, upload-time = "2025-08-19T17:22:01.447Z" }, - { url = "https://files.pythonhosted.org/packages/48/c8/cf139088ce537c15d7f03cf56ec317d3a5cfb520e30aa711ea0248d0ae8a/time_machine-2.19.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:57a235a6307c54df50e69f1906e2f199e47da91bde4b886ee05aff57fe4b6bf6", size = 32608, upload-time = "2025-08-19T17:22:02.548Z" }, - { url = "https://files.pythonhosted.org/packages/b1/17/0ec41ef7a30c6753fb226a28b74162b264b35724905ced4098f2f5076ded/time_machine-2.19.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:426aba552f7af9604adad9ef570c859af7c1081d878db78089fac159cd911b0a", size = 33686, upload-time = "2025-08-19T17:22:03.606Z" }, - { url = "https://files.pythonhosted.org/packages/b0/19/586f15159083ec84f178d494c60758c46603b00c9641b04deb63f1950128/time_machine-2.19.0-cp39-cp39-win32.whl", hash = "sha256:67772c7197a3a712d1b970ed545c6e98db73524bd90e245fd3c8fa7ad7630768", size = 17133, upload-time = "2025-08-19T17:22:04.989Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c2/bfe4b906a9fe0bf2d011534314212ed752d6b8f392c9c82f6ac63dccc5ab/time_machine-2.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:011d7859089263204dc5fdf83dce7388f986fe833c9381d6106b4edfda2ebd3e", size = 17972, upload-time = "2025-08-19T17:22:06.026Z" }, - { url = "https://files.pythonhosted.org/packages/5d/73/182343eba05aa5787732aaa68f3b3feb5e40ddf86b928ae941be45646393/time_machine-2.19.0-cp39-cp39-win_arm64.whl", hash = "sha256:e1af66550fa4685434f00002808a525f176f1f92746646c0019bb86fbff48b27", size = 16820, upload-time = "2025-08-19T17:22:07.227Z" }, -] - -[[package]] -name = "time-machine" -version = "3.2.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and extra != 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-14-moderation-api-pydantic-v1' and extra == 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and extra == 'group-14-moderation-api-pydantic-v1' and extra != 'group-14-moderation-api-pydantic-v2'", - "python_full_version >= '3.10' and extra != 'group-14-moderation-api-pydantic-v1' and extra != 'group-14-moderation-api-pydantic-v2'", -] -sdist = { url = "https://files.pythonhosted.org/packages/02/fc/37b02f6094dbb1f851145330460532176ed2f1dc70511a35828166c41e52/time_machine-3.2.0.tar.gz", hash = "sha256:a4ddd1cea17b8950e462d1805a42b20c81eb9aafc8f66b392dd5ce997e037d79", size = 14804, upload-time = "2025-12-17T23:33:02.599Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/31/6bf41cb4a326230518d9b76c910dfc11d4fc23444d1cbfdf2d7652bd99f4/time_machine-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:68142c070e78b62215d8029ec7394905083a4f9aacb0a2a11514ce70b5951b13", size = 19447, upload-time = "2025-12-17T23:31:30.181Z" }, - { url = "https://files.pythonhosted.org/packages/fa/14/d71ce771712e1cbfa15d8c24452225109262b16cb6caaf967e9f60662b67/time_machine-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:161bbd0648802ffdfcb4bb297ecb26b3009684a47d3a4dedb90bc549df4fa2ad", size = 15432, upload-time = "2025-12-17T23:31:31.381Z" }, - { url = "https://files.pythonhosted.org/packages/8b/d6/dcb43a11f8029561996fad58ff9d3dc5e6d7f32b74f0745a2965d7e4b4f3/time_machine-3.2.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1359ba8c258be695ba69253bc84db882fd616fe69b426cc6056536da2c7bf68e", size = 32956, upload-time = "2025-12-17T23:31:32.469Z" }, - { url = "https://files.pythonhosted.org/packages/77/da/d802cd3c335c414f9b11b479f7459aa72df5de6485c799966cfdf8856d53/time_machine-3.2.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c85b169998ca2c24a78fb214586ec11c4cad56d9c38f55ad8326235cb481c884", size = 34556, upload-time = "2025-12-17T23:31:33.946Z" }, - { url = "https://files.pythonhosted.org/packages/85/ee/51ad553514ab0b940c7c82c6e1519dd10fd06ac07b32039a1d153ef09c88/time_machine-3.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65b9367cb8a10505bc8f67da0da514ba20fa816fc47e11f434f7c60350322b4c", size = 36101, upload-time = "2025-12-17T23:31:35.462Z" }, - { url = "https://files.pythonhosted.org/packages/11/39/938b111b5bb85a2b07502d0f9d8a704fc75bd760d62e76bce23c89ed16c9/time_machine-3.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9faca6a0f1973d7df3233c951fc2a11ff0c54df74087d8aaf41ae3deb19d0893", size = 34905, upload-time = "2025-12-17T23:31:36.543Z" }, - { url = "https://files.pythonhosted.org/packages/dd/50/0951f73b23e76455de0b4a3a58ac5a24bd8d10489624b1c5e03f10c6fc0b/time_machine-3.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:213b1ada7f385d467e598999b642eda4a8e89ae10ad5dc4f5d8f672cbf604261", size = 33012, upload-time = "2025-12-17T23:31:37.967Z" }, - { url = "https://files.pythonhosted.org/packages/4f/95/5304912d3dcecc4e14ed222dbe0396352efdf8497534abc3c9edd67a7528/time_machine-3.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:160b6afd94c39855af04d39c58e4cf602406abd6d79427ab80e830ea71789cfb", size = 34104, upload-time = "2025-12-17T23:31:39.449Z" }, - { url = "https://files.pythonhosted.org/packages/d4/1c/af56518652ec7adac4ced193b7a42c4ff354fef28a412b3b5ffa5763aead/time_machine-3.2.0-cp310-cp310-win32.whl", hash = "sha256:c15d9ac257c78c124d112e4fc91fa9f3dcb004bdda913c19f0e7368d713cf080", size = 17468, upload-time = "2025-12-17T23:31:40.432Z" }, - { url = "https://files.pythonhosted.org/packages/48/15/0213f00ca3cf6fe1c9fdbd7fd467e801052fc85534f30c0e4684bd474190/time_machine-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:3bf0f428487f93b8fe9d27aa01eccc817885da3290b467341b4a4a795e1d1891", size = 18313, upload-time = "2025-12-17T23:31:41.617Z" }, - { url = "https://files.pythonhosted.org/packages/77/e4/811f96aa7a634b2b264d9a476f3400e710744dda503b4ad87a5c76db32c9/time_machine-3.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:347f6be2129fcd35b1c94b9387fcb2cbe7949b1e649228c5f22949a811b78976", size = 17037, upload-time = "2025-12-17T23:31:42.924Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e1/03aae5fbaa53859f665094af696338fc7cae733d926a024af69982712350/time_machine-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c188a9dda9fcf975022f1b325b466651b96a4dfc223c523ed7ed8d979f9bf3e8", size = 19143, upload-time = "2025-12-17T23:31:44.258Z" }, - { url = "https://files.pythonhosted.org/packages/75/8f/98cb17bebb52b22ff4ec26984dd44280f9c71353c3bae0640a470e6683e5/time_machine-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17245f1cc2dd13f9d63a174be59bb2684a9e5e0a112ab707e37be92068cd655f", size = 15273, upload-time = "2025-12-17T23:31:45.246Z" }, - { url = "https://files.pythonhosted.org/packages/dd/2f/ca11e4a7897234bb9331fcc5f4ed4714481ba4012370cc79a0ae8c42ea0a/time_machine-3.2.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d9bd1de1996e76efd36ae15970206c5089fb3728356794455bd5cd8d392b5537", size = 31049, upload-time = "2025-12-17T23:31:46.613Z" }, - { url = "https://files.pythonhosted.org/packages/cf/ad/d17d83a59943094e6b6c6a3743caaf6811b12203c3e07a30cc7bcc2ab7ee/time_machine-3.2.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:98493cd50e8b7f941eab69b9e18e697ad69db1a0ec1959f78f3d7b0387107e5c", size = 32632, upload-time = "2025-12-17T23:31:47.72Z" }, - { url = "https://files.pythonhosted.org/packages/71/50/d60576d047a0dfb5638cdfb335e9c3deb6e8528544fa0b3966a8480f72b7/time_machine-3.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:31f2a33d595d9f91eb9bc7f157f0dc5721f5789f4c4a9e8b852cdedb2a7d9b16", size = 34289, upload-time = "2025-12-17T23:31:48.913Z" }, - { url = "https://files.pythonhosted.org/packages/fa/fe/4afa602dbdebddde6d0ea4a7fe849e49b9bb85dc3fb415725a87ccb4b471/time_machine-3.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9f78ac4213c10fbc44283edd1a29cfb7d3382484f4361783ddc057292aaa1889", size = 33175, upload-time = "2025-12-17T23:31:50.611Z" }, - { url = "https://files.pythonhosted.org/packages/0d/87/c152e23977c1d7d7c94eb3ed3ea45cc55971796205125c6fdff40db2c60f/time_machine-3.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c1326b09e947b360926d529a96d1d9e126ce120359b63b506ecdc6ee20755c23", size = 31170, upload-time = "2025-12-17T23:31:51.645Z" }, - { url = "https://files.pythonhosted.org/packages/80/af/54acf51d0f3ade3b51eab73df6192937c9a938753ef5456dff65eb8630be/time_machine-3.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9f2949f03d15264cc15c38918a2cda8966001f0f4ebe190cbfd9c56d91aed8ac", size = 32292, upload-time = "2025-12-17T23:31:52.803Z" }, - { url = "https://files.pythonhosted.org/packages/cc/bc/3745963f36e75661a807196428639327a366f4332f35f1f775c074d4062f/time_machine-3.2.0-cp311-cp311-win32.whl", hash = "sha256:6dfe48e0499e6e16751476b9799e67be7514e6ef04cdf39571ef95a279645831", size = 17349, upload-time = "2025-12-17T23:31:54.19Z" }, - { url = "https://files.pythonhosted.org/packages/82/a2/057469232a99d1f5a0160ae7c5bae7b095c9168b333dd598fcbcfbc1c87b/time_machine-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:809bdf267a29189c304154873620fe0bcc0c9513295fa46b19e21658231c4915", size = 18191, upload-time = "2025-12-17T23:31:55.472Z" }, - { url = "https://files.pythonhosted.org/packages/79/d8/bf9c8de57262ee7130d92a6ed49ed6a6e40a36317e46979428d373630c12/time_machine-3.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:a3f4c17fa90f54902a3f8692c75caf67be87edc3429eeb71cb4595da58198f8e", size = 16905, upload-time = "2025-12-17T23:31:56.658Z" }, - { url = "https://files.pythonhosted.org/packages/71/8b/080c8eedcd67921a52ba5bd0e075362062509ab63c86fc1a0442fad241a6/time_machine-3.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cc4bee5b0214d7dc4ebc91f4a4c600f1a598e9b5606ac751f42cb6f6740b1dbb", size = 19255, upload-time = "2025-12-17T23:31:58.057Z" }, - { url = "https://files.pythonhosted.org/packages/66/17/0e5291e9eb705bf8a5a1305f826e979af307bbeb79def4ddbf4b3f9a81e0/time_machine-3.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ca036304b4460ae2fdc1b52dd8b1fa7cf1464daa427fc49567413c09aa839c1", size = 15360, upload-time = "2025-12-17T23:31:59.048Z" }, - { url = "https://files.pythonhosted.org/packages/8b/e8/9ab87b71d2e2b62463b9b058b7ae7ac09fb57f8fcd88729dec169d304340/time_machine-3.2.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5442735b41d7a2abc2f04579b4ca6047ed4698a8338a4fec92c7c9423e7938cb", size = 33029, upload-time = "2025-12-17T23:32:00.413Z" }, - { url = "https://files.pythonhosted.org/packages/4b/26/b5ca19da6f25ea905b3e10a0ea95d697c1aeba0404803a43c68f1af253e6/time_machine-3.2.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:97da3e971e505cb637079fb07ab0bcd36e33279f8ecac888ff131f45ef1e4d8d", size = 34579, upload-time = "2025-12-17T23:32:01.431Z" }, - { url = "https://files.pythonhosted.org/packages/79/ca/6ac7ad5f10ea18cc1d9de49716ba38c32132c7b64532430d92ef240c116b/time_machine-3.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3cdda6dee4966e38aeb487309bb414c6cb23a81fc500291c77a8fcd3098832e7", size = 35961, upload-time = "2025-12-17T23:32:02.521Z" }, - { url = "https://files.pythonhosted.org/packages/33/67/390dd958bed395ab32d79a9fe61fe111825c0dd4ded54dbba7e867f171e6/time_machine-3.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:33d9efd302a6998bcc8baa4d84f259f8a4081105bd3d7f7af7f1d0abd3b1c8aa", size = 34668, upload-time = "2025-12-17T23:32:03.585Z" }, - { url = "https://files.pythonhosted.org/packages/da/57/c88fff034a4e9538b3ae7c68c9cfb283670b14d17522c5a8bc17d29f9a4b/time_machine-3.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3a0b0a33971f14145853c9bd95a6ab0353cf7e0019fa2a7aa1ae9fddfe8eab50", size = 32891, upload-time = "2025-12-17T23:32:04.656Z" }, - { url = "https://files.pythonhosted.org/packages/2d/70/ebbb76022dba0fec8f9156540fc647e4beae1680c787c01b1b6200e56d70/time_machine-3.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2d0be9e5f22c38082d247a2cdcd8a936504e9db60b7b3606855fb39f299e9548", size = 34080, upload-time = "2025-12-17T23:32:06.146Z" }, - { url = "https://files.pythonhosted.org/packages/db/9a/2ca9e7af3df540dc1c79e3de588adeddb7dcc2107829248e6969c4f14167/time_machine-3.2.0-cp312-cp312-win32.whl", hash = "sha256:3f74623648b936fdce5f911caf386c0a0b579456410975de8c0dfeaaffece1d8", size = 17371, upload-time = "2025-12-17T23:32:07.164Z" }, - { url = "https://files.pythonhosted.org/packages/d8/ce/21d23efc9c2151939af1b7ee4e60d86d661b74ef32b8eaa148f6fe8c899c/time_machine-3.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:34e26a41d994b5e4b205136a90e9578470386749cc9a2ecf51ca18f83ce25e23", size = 18132, upload-time = "2025-12-17T23:32:08.447Z" }, - { url = "https://files.pythonhosted.org/packages/2f/34/c2b70be483accf6db9e5d6c3139bce3c38fe51f898ccf64e8d3fe14fbf4d/time_machine-3.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:0615d3d82c418d6293f271c348945c5091a71f37e37173653d5c26d0e74b13a8", size = 16930, upload-time = "2025-12-17T23:32:09.477Z" }, - { url = "https://files.pythonhosted.org/packages/ee/cd/43ad5efc88298af3c59b66769cea7f055567a85071579ed40536188530c1/time_machine-3.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c421a8eb85a4418a7675a41bf8660224318c46cc62e4751c8f1ceca752059090", size = 19318, upload-time = "2025-12-17T23:32:10.518Z" }, - { url = "https://files.pythonhosted.org/packages/b0/f6/084010ef7f4a3f38b5a4900923d7c85b29e797655c4f6ee4ce54d903cca8/time_machine-3.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f4e758f7727d0058c4950c66b58200c187072122d6f7a98b610530a4233ea7b", size = 15390, upload-time = "2025-12-17T23:32:11.625Z" }, - { url = "https://files.pythonhosted.org/packages/25/aa/1cabb74134f492270dc6860cb7865859bf40ecf828be65972827646e91ad/time_machine-3.2.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:154bd3f75c81f70218b2585cc12b60762fb2665c507eec5ec5037d8756d9b4e0", size = 33115, upload-time = "2025-12-17T23:32:13.219Z" }, - { url = "https://files.pythonhosted.org/packages/5e/03/78c5d7dfa366924eb4dbfcc3fc917c39a4280ca234b12819cc1f16c03d88/time_machine-3.2.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d50cfe5ebea422c896ad8d278af9648412b7533b8ea6adeeee698a3fd9b1d3b7", size = 34705, upload-time = "2025-12-17T23:32:14.29Z" }, - { url = "https://files.pythonhosted.org/packages/86/93/d5e877c24541f674c6869ff6e9c56833369796010190252e92c9d7ae5f0f/time_machine-3.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:636576501724bd6a9124e69d86e5aef263479e89ef739c5db361469f0463a0a1", size = 36104, upload-time = "2025-12-17T23:32:15.354Z" }, - { url = "https://files.pythonhosted.org/packages/22/1c/d4bae72f388f67efc9609f89b012e434bb19d9549c7a7b47d6c7d9e5c55d/time_machine-3.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40e6f40c57197fcf7ec32d2c563f4df0a82c42cdcc3cab27f688e98f6060df10", size = 34765, upload-time = "2025-12-17T23:32:16.434Z" }, - { url = "https://files.pythonhosted.org/packages/1d/c3/ac378cf301d527d8dfad2f0db6bad0dfb1ab73212eaa56d6b96ee5d9d20b/time_machine-3.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a1bcf0b846bbfc19a79bc19e3fa04d8c7b1e8101c1b70340ffdb689cd801ea53", size = 33010, upload-time = "2025-12-17T23:32:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/06/35/7ce897319accda7a6970b288a9a8c52d25227342a7508505a2b3d235b649/time_machine-3.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae55a56c179f4fe7a62575ad5148b6ed82f6c7e5cf2f9a9ec65f2f5b067db5f5", size = 34185, upload-time = "2025-12-17T23:32:18.566Z" }, - { url = "https://files.pythonhosted.org/packages/bf/28/f922022269749cb02eee2b62919671153c4088994fa955a6b0e50327ff81/time_machine-3.2.0-cp313-cp313-win32.whl", hash = "sha256:a66fe55a107e46916007a391d4030479df8864ec6ad6f6a6528221befc5c886e", size = 17397, upload-time = "2025-12-17T23:32:19.605Z" }, - { url = "https://files.pythonhosted.org/packages/ee/dc/fd87cde397f4a7bea493152f0aca8fd569ec709cad9e0f2ca7011eb8c7f7/time_machine-3.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:30c9ce57165df913e4f74e285a8ab829ff9b7aa3e5ec0973f88f642b9a7b3d15", size = 18139, upload-time = "2025-12-17T23:32:20.991Z" }, - { url = "https://files.pythonhosted.org/packages/75/81/b8ce58233addc5d7d54d2fabc49dcbc02d79e3f079d150aa1bec3d5275ef/time_machine-3.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:89cad7e179e9bdcc84dcf09efe52af232c4cc7a01b3de868356bbd59d95bd9b8", size = 16964, upload-time = "2025-12-17T23:32:22.075Z" }, - { url = "https://files.pythonhosted.org/packages/67/e7/487f0ba5fe6c58186a5e1af2a118dfa2c160fedb37ef53a7e972d410408e/time_machine-3.2.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:59d71545e62525a4b85b6de9ab5c02ee3c61110fd7f636139914a2335dcbfc9c", size = 20000, upload-time = "2025-12-17T23:32:23.058Z" }, - { url = "https://files.pythonhosted.org/packages/e1/17/eb2c0054c8d44dd42df84ccd434539249a9c7d0b8eb53f799be2102500ab/time_machine-3.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:999672c621c35362bc28e03ca0c7df21500195540773c25993421fd8d6cc5003", size = 15657, upload-time = "2025-12-17T23:32:24.125Z" }, - { url = "https://files.pythonhosted.org/packages/43/21/93443b5d1dd850f8bb9442e90d817a9033dcce6bfbdd3aabbb9786251c80/time_machine-3.2.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5faf7397f0580c7b9d67288522c8d7863e85f0cffadc0f1fccdb2c3dfce5783e", size = 39216, upload-time = "2025-12-17T23:32:25.542Z" }, - { url = "https://files.pythonhosted.org/packages/9f/9e/18544cf8acc72bb1dc03762231c82ecc259733f4bb6770a7bbe5cd138603/time_machine-3.2.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3dd886ec49f1fa5a00e844f5947e5c0f98ce574750c24b7424c6f77fc1c3e87", size = 40764, upload-time = "2025-12-17T23:32:26.643Z" }, - { url = "https://files.pythonhosted.org/packages/27/f7/9fe9ce2795636a3a7467307af6bdf38bb613ddb701a8a5cd50ec713beb5e/time_machine-3.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da0ecd96bc7bbe450acaaabe569d84e81688f1be8ad58d1470e42371d145fb53", size = 43526, upload-time = "2025-12-17T23:32:27.693Z" }, - { url = "https://files.pythonhosted.org/packages/03/c1/a93e975ba9dec22e87ec92d18c28e67d36bd536f9119ffa439b2892b0c9c/time_machine-3.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:158220e946c1c4fb8265773a0282c88c35a7e3bb5d78e3561214e3b3231166f3", size = 41727, upload-time = "2025-12-17T23:32:28.985Z" }, - { url = "https://files.pythonhosted.org/packages/5f/fb/e3633e5a6bbed1c76bb2e9810dabc2f8467532ffcd29b9aed404b473061a/time_machine-3.2.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c1aee29bc54356f248d5d7dfdd131e12ca825e850a08c0ebdb022266d073013", size = 38952, upload-time = "2025-12-17T23:32:30.031Z" }, - { url = "https://files.pythonhosted.org/packages/82/3d/02e9fb2526b3d6b1b45bc8e4d912d95d1cd699d1a3f6df985817d37a0600/time_machine-3.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c8ed2224f09d25b1c2fc98683613aca12f90f682a427eabb68fc824d27014e4a", size = 39829, upload-time = "2025-12-17T23:32:31.075Z" }, - { url = "https://files.pythonhosted.org/packages/85/c8/c14265212436da8e0814c45463987b3f57de3eca4de023cc2eabb0c62ef3/time_machine-3.2.0-cp313-cp313t-win32.whl", hash = "sha256:3498719f8dab51da76d29a20c1b5e52ee7db083dddf3056af7fa69c1b94e1fe6", size = 17852, upload-time = "2025-12-17T23:32:32.079Z" }, - { url = "https://files.pythonhosted.org/packages/1d/bc/8acb13cf6149f47508097b158a9a8bec9ec4530a70cb406124e8023581f5/time_machine-3.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e0d90bee170b219e1d15e6a58164aa808f5170090e4f090bd0670303e34181b1", size = 18918, upload-time = "2025-12-17T23:32:33.106Z" }, - { url = "https://files.pythonhosted.org/packages/24/87/c443ee508c2708fd2514ccce9052f5e48888783ce690506919629ebc8eb0/time_machine-3.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:051de220fdb6e20d648111bbad423d9506fdbb2e44d4429cef3dc0382abf1fc2", size = 17261, upload-time = "2025-12-17T23:32:34.446Z" }, - { url = "https://files.pythonhosted.org/packages/61/70/b4b980d126ed155c78d1879c50d60c8dcbd47bd11cb14ee7be50e0dfc07f/time_machine-3.2.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:1398980c017fe5744d66f419e0115ee48a53b00b146d738e1416c225eb610b82", size = 19303, upload-time = "2025-12-17T23:32:35.796Z" }, - { url = "https://files.pythonhosted.org/packages/73/73/eaa33603c69a68fe2b6f54f9dd75481693d62f1d29676531002be06e2d1c/time_machine-3.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4f8f4e35f4191ef70c2ab8ff490761ee9051b891afce2bf86dde3918eb7b537b", size = 15431, upload-time = "2025-12-17T23:32:37.244Z" }, - { url = "https://files.pythonhosted.org/packages/76/10/b81e138e86cc7bab40cdb59d294b341e172201f4a6c84bb0ec080407977a/time_machine-3.2.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6db498686ecf6163c5aa8cf0bcd57bbe0f4081184f247edf3ee49a2612b584f9", size = 33206, upload-time = "2025-12-17T23:32:38.713Z" }, - { url = "https://files.pythonhosted.org/packages/d3/72/4deab446b579e8bd5dca91de98595c5d6bd6a17ce162abf5c5f2ce40d3d8/time_machine-3.2.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:027c1807efb74d0cd58ad16524dec94212fbe900115d70b0123399883657ac0f", size = 34792, upload-time = "2025-12-17T23:32:40.223Z" }, - { url = "https://files.pythonhosted.org/packages/2c/39/439c6b587ddee76d533fe972289d0646e0a5520e14dc83d0a30aeb5565f7/time_machine-3.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92432610c05676edd5e6946a073c6f0c926923123ce7caee1018dc10782c713d", size = 36187, upload-time = "2025-12-17T23:32:41.705Z" }, - { url = "https://files.pythonhosted.org/packages/4b/db/2da4368db15180989bab83746a857bde05ad16e78f326801c142bb747a06/time_machine-3.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c25586b62480eb77ef3d953fba273209478e1ef49654592cd6a52a68dfe56a67", size = 34855, upload-time = "2025-12-17T23:32:42.817Z" }, - { url = "https://files.pythonhosted.org/packages/88/84/120a431fee50bc4c241425bee4d3a4910df4923b7ab5f7dff1bf0c772f08/time_machine-3.2.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6bf3a2fa738d15e0b95d14469a0b8ea42635467408d8b490e263d5d45c9a177f", size = 33222, upload-time = "2025-12-17T23:32:43.94Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ea/89cfda82bb8c57ff91bb9a26751aa234d6d90e9b4d5ab0ad9dce0f9f0329/time_machine-3.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ce76b82276d7ad2a66cdc85dad4df19d1422b69183170a34e8fbc4c3f35502f7", size = 34270, upload-time = "2025-12-17T23:32:45.037Z" }, - { url = "https://files.pythonhosted.org/packages/8a/aa/235357da4f69a51a8d35fcbfcfa77cdc7dc24f62ae54025006570bda7e2d/time_machine-3.2.0-cp314-cp314-win32.whl", hash = "sha256:14d6778273c543441863dff712cd1d7803dee946b18de35921eb8df10714539d", size = 17544, upload-time = "2025-12-17T23:32:46.099Z" }, - { url = "https://files.pythonhosted.org/packages/7b/51/6c8405a7276be79693b792cff22ce41067ec05db26a7d02f2d5b06324434/time_machine-3.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:cbf821da96dbc80d349fa9e7c36e670b41d68a878d28c8850057992fed430eef", size = 18423, upload-time = "2025-12-17T23:32:47.468Z" }, - { url = "https://files.pythonhosted.org/packages/d9/03/a3cf419e20c35fc203c6e4fed48b5b667c1a2b4da456d9971e605f73ecef/time_machine-3.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:71c75d71f8e68abc8b669bca26ed2ddd558430a6c171e32b8620288565f18c0e", size = 17050, upload-time = "2025-12-17T23:32:48.91Z" }, - { url = "https://files.pythonhosted.org/packages/86/a1/142de946dc4393f910bf4564b5c3ba819906e1f49b06c9cb557519c849e4/time_machine-3.2.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4e374779021446fc2b5c29d80457ec9a3b1a5df043dc2aae07d7c1415d52323c", size = 19991, upload-time = "2025-12-17T23:32:49.933Z" }, - { url = "https://files.pythonhosted.org/packages/ee/62/7f17def6289901f94726921811a16b9adce46e666362c75d45730c60274f/time_machine-3.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:122310a6af9c36e9a636da32830e591e7923e8a07bdd0a43276c3a36c6821c90", size = 15707, upload-time = "2025-12-17T23:32:50.969Z" }, - { url = "https://files.pythonhosted.org/packages/5d/d3/3502fb9bd3acb159c18844b26c43220201a0d4a622c0c853785d07699a92/time_machine-3.2.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ba3eeb0f018cc362dd8128befa3426696a2e16dd223c3fb695fde184892d4d8c", size = 39207, upload-time = "2025-12-17T23:32:52.033Z" }, - { url = "https://files.pythonhosted.org/packages/5a/be/8b27f4aa296fda14a5a2ad7f588ddd450603c33415ab3f8e85b2f1a44678/time_machine-3.2.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:77d38ba664b381a7793f8786efc13b5004f0d5f672dae814430445b8202a67a6", size = 40764, upload-time = "2025-12-17T23:32:53.167Z" }, - { url = "https://files.pythonhosted.org/packages/42/cd/fe4c4e5c8ab6d48fab3624c32be9116fb120173a35fe67e482e5cf68b3d2/time_machine-3.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f09abeb8f03f044d72712207e0489a62098ad3ad16dac38927fcf80baca4d6a7", size = 43508, upload-time = "2025-12-17T23:32:54.597Z" }, - { url = "https://files.pythonhosted.org/packages/b4/28/5a3ba2fce85b97655a425d6bb20a441550acd2b304c96b2c19d3839f721a/time_machine-3.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6b28367ce4f73987a55e230e1d30a57a3af85da8eb1a140074eb6e8c7e6ef19f", size = 41712, upload-time = "2025-12-17T23:32:55.781Z" }, - { url = "https://files.pythonhosted.org/packages/81/58/e38084be7fdabb4835db68a3a47e58c34182d79fc35df1ecbe0db2c5359f/time_machine-3.2.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:903c7751c904581da9f7861c3015bed7cdc40047321291d3694a3cdc783bbca3", size = 38939, upload-time = "2025-12-17T23:32:56.867Z" }, - { url = "https://files.pythonhosted.org/packages/40/d0/ad3feb0a392ef4e0c08bc32024950373ddc0669002cbdcbb9f3bf0c2d114/time_machine-3.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:528217cad85ede5f85c8bc78b0341868d3c3cfefc6ecb5b622e1cacb6c73247b", size = 39837, upload-time = "2025-12-17T23:32:58.283Z" }, - { url = "https://files.pythonhosted.org/packages/5b/9e/5f4b2ea63b267bd78f3245e76f5528836611b5f2d30b5e7300a722fe4428/time_machine-3.2.0-cp314-cp314t-win32.whl", hash = "sha256:75724762ffd517e7e80aaec1fad1ff5a7414bd84e2b3ee7a0bacfeb67c14926e", size = 18091, upload-time = "2025-12-17T23:32:59.403Z" }, - { url = "https://files.pythonhosted.org/packages/39/6f/456b1f4d2700ae02b19eba830f870596a4b89b74bac3b6c80666f1b108c5/time_machine-3.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2526abbd053c5bca898d1b3e7898eec34626b12206718d8c7ce88fd12c1c9c5c", size = 19208, upload-time = "2025-12-17T23:33:00.488Z" }, - { url = "https://files.pythonhosted.org/packages/2f/22/8063101427ecd3d2652aada4d21d0876b07a3dc789125bca2ee858fec3ed/time_machine-3.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:7f2fb6784b414edbe2c0b558bfaab0c251955ba27edd62946cce4a01675a992c", size = 17359, upload-time = "2025-12-17T23:33:01.54Z" }, -] - -[[package]] -name = "tomli" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, - { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, - { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, - { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, - { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, - { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, - { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, - { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, - { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, - { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, - { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, - { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, - { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, - { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, - { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, - { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, - { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, - { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, - { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, - { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, - { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, - { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, - { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, - { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, - { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, - { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, - { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, - { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, - { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, - { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, - { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, - { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, - { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, - { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, - { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "extra == 'group-14-moderation-api-pydantic-v2' or extra != 'group-14-moderation-api-pydantic-v1'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, -] - -[[package]] -name = "yarl" -version = "1.22.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "multidict" }, - { name = "propcache" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/43/a2204825342f37c337f5edb6637040fa14e365b2fcc2346960201d457579/yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e", size = 140517, upload-time = "2025-10-06T14:08:42.494Z" }, - { url = "https://files.pythonhosted.org/packages/44/6f/674f3e6f02266428c56f704cd2501c22f78e8b2eeb23f153117cc86fb28a/yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f", size = 93495, upload-time = "2025-10-06T14:08:46.2Z" }, - { url = "https://files.pythonhosted.org/packages/b8/12/5b274d8a0f30c07b91b2f02cba69152600b47830fcfb465c108880fcee9c/yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf", size = 94400, upload-time = "2025-10-06T14:08:47.855Z" }, - { url = "https://files.pythonhosted.org/packages/e2/7f/df1b6949b1fa1aa9ff6de6e2631876ad4b73c4437822026e85d8acb56bb1/yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a", size = 347545, upload-time = "2025-10-06T14:08:49.683Z" }, - { url = "https://files.pythonhosted.org/packages/84/09/f92ed93bd6cd77872ab6c3462df45ca45cd058d8f1d0c9b4f54c1704429f/yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c", size = 319598, upload-time = "2025-10-06T14:08:51.215Z" }, - { url = "https://files.pythonhosted.org/packages/c3/97/ac3f3feae7d522cf7ccec3d340bb0b2b61c56cb9767923df62a135092c6b/yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147", size = 363893, upload-time = "2025-10-06T14:08:53.144Z" }, - { url = "https://files.pythonhosted.org/packages/06/49/f3219097403b9c84a4d079b1d7bda62dd9b86d0d6e4428c02d46ab2c77fc/yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb", size = 371240, upload-time = "2025-10-06T14:08:55.036Z" }, - { url = "https://files.pythonhosted.org/packages/35/9f/06b765d45c0e44e8ecf0fe15c9eacbbde342bb5b7561c46944f107bfb6c3/yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6", size = 346965, upload-time = "2025-10-06T14:08:56.722Z" }, - { url = "https://files.pythonhosted.org/packages/c5/69/599e7cea8d0fcb1694323b0db0dda317fa3162f7b90166faddecf532166f/yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0", size = 342026, upload-time = "2025-10-06T14:08:58.563Z" }, - { url = "https://files.pythonhosted.org/packages/95/6f/9dfd12c8bc90fea9eab39832ee32ea48f8e53d1256252a77b710c065c89f/yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda", size = 335637, upload-time = "2025-10-06T14:09:00.506Z" }, - { url = "https://files.pythonhosted.org/packages/57/2e/34c5b4eb9b07e16e873db5b182c71e5f06f9b5af388cdaa97736d79dd9a6/yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc", size = 359082, upload-time = "2025-10-06T14:09:01.936Z" }, - { url = "https://files.pythonhosted.org/packages/31/71/fa7e10fb772d273aa1f096ecb8ab8594117822f683bab7d2c5a89914c92a/yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737", size = 357811, upload-time = "2025-10-06T14:09:03.445Z" }, - { url = "https://files.pythonhosted.org/packages/26/da/11374c04e8e1184a6a03cf9c8f5688d3e5cec83ed6f31ad3481b3207f709/yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467", size = 351223, upload-time = "2025-10-06T14:09:05.401Z" }, - { url = "https://files.pythonhosted.org/packages/82/8f/e2d01f161b0c034a30410e375e191a5d27608c1f8693bab1a08b089ca096/yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea", size = 82118, upload-time = "2025-10-06T14:09:11.148Z" }, - { url = "https://files.pythonhosted.org/packages/62/46/94c76196642dbeae634c7a61ba3da88cd77bed875bf6e4a8bed037505aa6/yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca", size = 86852, upload-time = "2025-10-06T14:09:12.958Z" }, - { url = "https://files.pythonhosted.org/packages/af/af/7df4f179d3b1a6dcb9a4bd2ffbc67642746fcafdb62580e66876ce83fff4/yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b", size = 82012, upload-time = "2025-10-06T14:09:14.664Z" }, - { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, - { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, - { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, - { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, - { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, - { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, - { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, - { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, - { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, - { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, - { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, - { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, - { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, - { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, - { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, - { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, - { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, - { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, - { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, - { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, - { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, - { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, - { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, - { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, - { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, - { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, - { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, - { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" }, - { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" }, - { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" }, - { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" }, - { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" }, - { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" }, - { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" }, - { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" }, - { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" }, - { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" }, - { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" }, - { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" }, - { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" }, - { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" }, - { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" }, - { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" }, - { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" }, - { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" }, - { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" }, - { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" }, - { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" }, - { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" }, - { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" }, - { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" }, - { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" }, - { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" }, - { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" }, - { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, - { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" }, - { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" }, - { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" }, - { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" }, - { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" }, - { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" }, - { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" }, - { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" }, - { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" }, - { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" }, - { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" }, - { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" }, - { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" }, - { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" }, - { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" }, - { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" }, - { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" }, - { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" }, - { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" }, - { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" }, - { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" }, - { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" }, - { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" }, - { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" }, - { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" }, - { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" }, - { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" }, - { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" }, - { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" }, - { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, - { url = "https://files.pythonhosted.org/packages/94/fd/6480106702a79bcceda5fd9c63cb19a04a6506bd5ce7fd8d9b63742f0021/yarl-1.22.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3aa27acb6de7a23785d81557577491f6c38a5209a254d1191519d07d8fe51748", size = 141301, upload-time = "2025-10-06T14:12:19.01Z" }, - { url = "https://files.pythonhosted.org/packages/42/e1/6d95d21b17a93e793e4ec420a925fe1f6a9342338ca7a563ed21129c0990/yarl-1.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:af74f05666a5e531289cb1cc9c883d1de2088b8e5b4de48004e5ca8a830ac859", size = 93864, upload-time = "2025-10-06T14:12:21.05Z" }, - { url = "https://files.pythonhosted.org/packages/32/58/b8055273c203968e89808413ea4c984988b6649baabf10f4522e67c22d2f/yarl-1.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62441e55958977b8167b2709c164c91a6363e25da322d87ae6dd9c6019ceecf9", size = 94706, upload-time = "2025-10-06T14:12:23.287Z" }, - { url = "https://files.pythonhosted.org/packages/18/91/d7bfbc28a88c2895ecd0da6a874def0c147de78afc52c773c28e1aa233a3/yarl-1.22.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b580e71cac3f8113d3135888770903eaf2f507e9421e5697d6ee6d8cd1c7f054", size = 347100, upload-time = "2025-10-06T14:12:28.527Z" }, - { url = "https://files.pythonhosted.org/packages/bd/e8/37a1e7b99721c0564b1fc7b0a4d1f595ef6fb8060d82ca61775b644185f7/yarl-1.22.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e81fda2fb4a07eda1a2252b216aa0df23ebcd4d584894e9612e80999a78fd95b", size = 318902, upload-time = "2025-10-06T14:12:30.528Z" }, - { url = "https://files.pythonhosted.org/packages/1c/ef/34724449d7ef2db4f22df644f2dac0b8a275d20f585e526937b3ae47b02d/yarl-1.22.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99b6fc1d55782461b78221e95fc357b47ad98b041e8e20f47c1411d0aacddc60", size = 363302, upload-time = "2025-10-06T14:12:32.295Z" }, - { url = "https://files.pythonhosted.org/packages/8a/04/88a39a5dad39889f192cce8d66cc4c58dbeca983e83f9b6bf23822a7ed91/yarl-1.22.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:088e4e08f033db4be2ccd1f34cf29fe994772fb54cfe004bbf54db320af56890", size = 370816, upload-time = "2025-10-06T14:12:34.01Z" }, - { url = "https://files.pythonhosted.org/packages/6b/1f/5e895e547129413f56c76be2c3ce4b96c797d2d0ff3e16a817d9269b12e6/yarl-1.22.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4e1f6f0b4da23e61188676e3ed027ef0baa833a2e633c29ff8530800edccba", size = 346465, upload-time = "2025-10-06T14:12:35.977Z" }, - { url = "https://files.pythonhosted.org/packages/11/13/a750e9fd6f9cc9ed3a52a70fe58ffe505322f0efe0d48e1fd9ffe53281f5/yarl-1.22.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:84fc3ec96fce86ce5aa305eb4aa9358279d1aa644b71fab7b8ed33fe3ba1a7ca", size = 341506, upload-time = "2025-10-06T14:12:37.788Z" }, - { url = "https://files.pythonhosted.org/packages/3c/67/bb6024de76e7186611ebe626aec5b71a2d2ecf9453e795f2dbd80614784c/yarl-1.22.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5dbeefd6ca588b33576a01b0ad58aa934bc1b41ef89dee505bf2932b22ddffba", size = 335030, upload-time = "2025-10-06T14:12:39.775Z" }, - { url = "https://files.pythonhosted.org/packages/a2/be/50b38447fd94a7992996a62b8b463d0579323fcfc08c61bdba949eef8a5d/yarl-1.22.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14291620375b1060613f4aab9ebf21850058b6b1b438f386cc814813d901c60b", size = 358560, upload-time = "2025-10-06T14:12:41.547Z" }, - { url = "https://files.pythonhosted.org/packages/e2/89/c020b6f547578c4e3dbb6335bf918f26e2f34ad0d1e515d72fd33ac0c635/yarl-1.22.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a4fcfc8eb2c34148c118dfa02e6427ca278bfd0f3df7c5f99e33d2c0e81eae3e", size = 357290, upload-time = "2025-10-06T14:12:43.861Z" }, - { url = "https://files.pythonhosted.org/packages/8c/52/c49a619ee35a402fa3a7019a4fa8d26878fec0d1243f6968bbf516789578/yarl-1.22.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:029866bde8d7b0878b9c160e72305bbf0a7342bcd20b9999381704ae03308dc8", size = 350700, upload-time = "2025-10-06T14:12:46.868Z" }, - { url = "https://files.pythonhosted.org/packages/ab/c9/f5042d87777bf6968435f04a2bbb15466b2f142e6e47fa4f34d1a3f32f0c/yarl-1.22.0-cp39-cp39-win32.whl", hash = "sha256:4dcc74149ccc8bba31ce1944acee24813e93cfdee2acda3c172df844948ddf7b", size = 82323, upload-time = "2025-10-06T14:12:48.633Z" }, - { url = "https://files.pythonhosted.org/packages/fd/58/d00f7cad9eba20c4eefac2682f34661d1d1b3a942fc0092eb60e78cfb733/yarl-1.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:10619d9fdee46d20edc49d3479e2f8269d0779f1b031e6f7c2aa1c76be04b7ed", size = 87145, upload-time = "2025-10-06T14:12:50.241Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a3/70904f365080780d38b919edd42d224b8c4ce224a86950d2eaa2a24366ad/yarl-1.22.0-cp39-cp39-win_arm64.whl", hash = "sha256:dd7afd3f8b0bfb4e0d9fc3c31bfe8a4ec7debe124cfd90619305def3c8ca8cd2", size = 82173, upload-time = "2025-10-06T14:12:51.869Z" }, - { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, -] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -]