diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a79632d..bffa656 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - name: Checkout repository diff --git a/HISTORY.md b/HISTORY.md index a34d285..88404b3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,12 +1,33 @@ Release History =============== -dev ---- +0.1.5 (2023-11-01) +------------------ -* Initial release +- Python 3.12 support. -1.0.0 (planned) ---------------- +0.1.4 (2023-10-11) +------------------ -- First planned release \ No newline at end of file +- Add README documentation for various engines. + +0.1.3 (2023-10-06) +------------------ + +- Replace deprecated serpapi_pagination.next_link with 'next'. +- Improve documentation: how to use the client directly for pagination searches. + +0.1.2 (2023-10-03) +------------------ + +- Update project status to Production/Stable. + +0.1.1 (2023-10-03) +------------------ + +- Update documentation link to point to Read the Docs. + +0.1.0 (2023-10-03) +------------------ + +- First release on PyPI. diff --git a/Pipfile b/Pipfile index a9520dd..a5c014f 100644 --- a/Pipfile +++ b/Pipfile @@ -5,6 +5,7 @@ name = "pypi" [packages] serpapi = {editable = true, path = "."} +pytest = "*" [dev-packages] alabaster = "*" diff --git a/Pipfile.lock b/Pipfile.lock index e0db2c3..b648257 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "af8f25e5f407e8511d4991ef1bea16153306344c1b3f417c2b005dce48c36352" + "sha256": "89b1875c3363b3d6f5d499c666e43404b5260933fb44f45d06e40277eceb34cb" }, "pipfile-spec": 6, "requires": {}, @@ -16,92 +16,107 @@ "default": { "certifi": { "hashes": [ - "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", - "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" + "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", + "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" ], "markers": "python_version >= '3.6'", - "version": "==2023.7.22" + "version": "==2023.11.17" }, "charset-normalizer": { "hashes": [ - "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96", - "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c", - "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710", - "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706", - "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020", - "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252", - "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad", - "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329", - "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a", - "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f", - "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6", - "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4", - "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a", - "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46", - "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2", - "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23", - "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace", - "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd", - "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982", - "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10", - "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2", - "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea", - "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09", - "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5", - "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149", - "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489", - "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9", - "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80", - "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592", - "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3", - "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6", - "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed", - "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c", - "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200", - "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a", - "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e", - "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d", - "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6", - "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623", - "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669", - "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3", - "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa", - "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9", - "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2", - "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f", - "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1", - "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4", - "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a", - "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8", - "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3", - "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029", - "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f", - "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959", - "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22", - "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7", - "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952", - "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346", - "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e", - "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d", - "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299", - "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd", - "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a", - "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3", - "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037", - "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94", - "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c", - "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858", - "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a", - "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449", - "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c", - "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918", - "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1", - "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c", - "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac", - "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa" + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.2.0" + "version": "==3.3.2" }, "idna": { "hashes": [ @@ -111,13 +126,38 @@ "markers": "python_version >= '3.5'", "version": "==3.4" }, - "pygments": { + "iniconfig": { "hashes": [ - "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c", - "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1" + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" ], "markers": "python_version >= '3.7'", - "version": "==2.15.1" + "version": "==2.0.0" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "pluggy": { + "hashes": [ + "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12", + "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7" + ], + "markers": "python_version >= '3.8'", + "version": "==1.3.0" + }, + "pytest": { + "hashes": [ + "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac", + "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==7.4.3" }, "requests": { "hashes": [ @@ -129,15 +169,16 @@ }, "serpapi": { "editable": true, + "markers": "python_full_version >= '3.6.0'", "path": "." }, "urllib3": { "hashes": [ - "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11", - "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4" + "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3", + "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54" ], - "markers": "python_version >= '3.7'", - "version": "==2.0.4" + "markers": "python_version >= '3.8'", + "version": "==2.1.0" } }, "develop": { @@ -147,140 +188,153 @@ "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2" ], "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==0.7.13" }, "babel": { "hashes": [ - "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610", - "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455" + "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900", + "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed" ], "markers": "python_version >= '3.7'", - "version": "==2.12.1" + "version": "==2.13.1" }, "black": { "hashes": [ - "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3", - "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb", - "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087", - "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320", - "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6", - "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3", - "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc", - "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f", - "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587", - "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91", - "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a", - "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad", - "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926", - "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9", - "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be", - "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd", - "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96", - "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491", - "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2", - "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a", - "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f", - "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995" + "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4", + "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b", + "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f", + "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07", + "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187", + "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6", + "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05", + "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06", + "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e", + "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5", + "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244", + "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f", + "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221", + "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055", + "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479", + "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394", + "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911", + "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142" ], "index": "pypi", - "version": "==23.7.0" + "markers": "python_version >= '3.8'", + "version": "==23.11.0" }, "certifi": { "hashes": [ - "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", - "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" + "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", + "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" ], "markers": "python_version >= '3.6'", - "version": "==2023.7.22" + "version": "==2023.11.17" }, "charset-normalizer": { "hashes": [ - "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96", - "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c", - "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710", - "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706", - "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020", - "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252", - "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad", - "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329", - "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a", - "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f", - "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6", - "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4", - "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a", - "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46", - "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2", - "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23", - "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace", - "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd", - "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982", - "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10", - "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2", - "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea", - "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09", - "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5", - "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149", - "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489", - "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9", - "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80", - "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592", - "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3", - "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6", - "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed", - "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c", - "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200", - "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a", - "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e", - "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d", - "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6", - "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623", - "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669", - "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3", - "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa", - "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9", - "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2", - "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f", - "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1", - "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4", - "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a", - "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8", - "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3", - "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029", - "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f", - "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959", - "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22", - "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7", - "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952", - "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346", - "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e", - "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d", - "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299", - "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd", - "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a", - "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3", - "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037", - "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94", - "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c", - "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858", - "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a", - "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449", - "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c", - "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918", - "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1", - "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c", - "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac", - "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa" + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.2.0" + "version": "==3.3.2" }, "click": { "hashes": [ - "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd", - "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5" + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" ], "markers": "python_version >= '3.7'", - "version": "==8.1.6" + "version": "==8.1.7" }, "docutils": { "hashes": [ @@ -328,8 +382,11 @@ "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e", "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", + "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc", + "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", + "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c", "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0", "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4", @@ -337,6 +394,7 @@ "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba", "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d", + "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3", "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155", @@ -345,6 +403,7 @@ "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8", "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b", + "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24", "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea", "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198", @@ -352,9 +411,12 @@ "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee", "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be", "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2", + "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707", "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", + "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58", + "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636", "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", @@ -373,7 +435,9 @@ "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", - "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2" + "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", + "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2", + "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11" ], "markers": "python_version >= '3.7'", "version": "==2.1.3" @@ -388,51 +452,52 @@ }, "packaging": { "hashes": [ - "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", - "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" ], "markers": "python_version >= '3.7'", - "version": "==23.1" + "version": "==23.2" }, "pathspec": { "hashes": [ - "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687", - "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293" + "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20", + "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3" ], "markers": "python_version >= '3.7'", - "version": "==0.11.1" + "version": "==0.11.2" }, "platformdirs": { "hashes": [ - "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421", - "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f" + "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b", + "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731" ], "markers": "python_version >= '3.7'", - "version": "==3.9.1" + "version": "==4.0.0" }, "pluggy": { "hashes": [ - "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849", - "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3" + "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12", + "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7" ], - "markers": "python_version >= '3.7'", - "version": "==1.2.0" + "markers": "python_version >= '3.8'", + "version": "==1.3.0" }, "pygments": { "hashes": [ - "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c", - "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1" + "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", + "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367" ], "markers": "python_version >= '3.7'", - "version": "==2.15.1" + "version": "==2.17.2" }, "pytest": { "hashes": [ - "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32", - "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a" + "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac", + "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5" ], "index": "pypi", - "version": "==7.4.0" + "markers": "python_version >= '3.7'", + "version": "==7.4.3" }, "requests": { "hashes": [ @@ -442,6 +507,14 @@ "markers": "python_version >= '3.7'", "version": "==2.31.0" }, + "setuptools": { + "hashes": [ + "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2", + "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6" + ], + "markers": "python_version >= '3.12'", + "version": "==69.0.2" + }, "snowballstemmer": { "hashes": [ "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", @@ -451,35 +524,36 @@ }, "sphinx": { "hashes": [ - "sha256:8f336d0221c3beb23006b3164ed1d46db9cebcce9cb41cdb9c5ecd4bcc509be0", - "sha256:9bdfb5a2b28351d4fdf40a63cd006dbad727f793b243e669fc950d7116c634af" + "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560", + "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5" ], "index": "pypi", - "version": "==7.1.0" + "markers": "python_version >= '3.9'", + "version": "==7.2.6" }, "sphinxcontrib-applehelp": { "hashes": [ - "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228", - "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e" + "sha256:094c4d56209d1734e7d252f6e0b3ccc090bd52ee56807a5d9315b19c122ab15d", + "sha256:39fdc8d762d33b01a7d8f026a3b7d71563ea3b72787d5f00ad8465bd9d6dfbfa" ], - "markers": "python_version >= '3.8'", - "version": "==1.0.4" + "markers": "python_version >= '3.9'", + "version": "==1.0.7" }, "sphinxcontrib-devhelp": { "hashes": [ - "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", - "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" + "sha256:63b41e0d38207ca40ebbeabcf4d8e51f76c03e78cd61abe118cf4435c73d4212", + "sha256:fe8009aed765188f08fcaadbb3ea0d90ce8ae2d76710b7e29ea7d047177dae2f" ], - "markers": "python_version >= '3.5'", - "version": "==1.0.2" + "markers": "python_version >= '3.9'", + "version": "==1.0.5" }, "sphinxcontrib-htmlhelp": { "hashes": [ - "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff", - "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903" + "sha256:6c26a118a05b76000738429b724a0568dbde5b72391a688577da08f11891092a", + "sha256:8001661c077a73c29beaf4a79968d0726103c5605e27db92b9ebed8bab1359e9" ], - "markers": "python_version >= '3.8'", - "version": "==2.0.1" + "markers": "python_version >= '3.9'", + "version": "==2.0.4" }, "sphinxcontrib-jsmath": { "hashes": [ @@ -491,27 +565,27 @@ }, "sphinxcontrib-qthelp": { "hashes": [ - "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", - "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" + "sha256:62b9d1a186ab7f5ee3356d906f648cacb7a6bdb94d201ee7adf26db55092982d", + "sha256:bf76886ee7470b934e363da7a954ea2825650013d367728588732c7350f49ea4" ], - "markers": "python_version >= '3.5'", - "version": "==1.0.3" + "markers": "python_version >= '3.9'", + "version": "==1.0.6" }, "sphinxcontrib-serializinghtml": { "hashes": [ - "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd", - "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952" + "sha256:0c64ff898339e1fac29abd2bf5f11078f3ec413cfe9c046d3120d7ca65530b54", + "sha256:9b36e503703ff04f20e9675771df105e58aa029cfcbc23b8ed716019b7416ae1" ], - "markers": "python_version >= '3.5'", - "version": "==1.1.5" + "markers": "python_version >= '3.9'", + "version": "==1.1.9" }, "urllib3": { "hashes": [ - "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11", - "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4" + "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3", + "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54" ], - "markers": "python_version >= '3.7'", - "version": "==2.0.4" + "markers": "python_version >= '3.8'", + "version": "==2.1.0" } } } diff --git a/README.md b/README.md index 5d5dde9..668055b 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,17 @@ +

SerpApi Python Library & Package

serpapi python library logo - + ![Package](https://badge.fury.io/py/serpapi.svg) + [![serpapi-python](https://github.com/serpapi/serpapi-python/actions/workflows/ci.yml/badge.svg)](https://github.com/serpapi/serpapi-python/actions/workflows/ci.yml)
-This repository is the home of the *soon–to–be* official Python API wrapper for SerpApi (https://serpapi.com). This `serpapi` module allows you to access search data in your Python application. - -SerpApi supports Google, Google Maps, Google Shopping, Bing, Baidu, Yandex, Yahoo, eBay, App Stores, and more. Check out the [documentation](https://serpapi.com/search-api) for a full list. +This repository is the home of the *soon–to–be* official Python API wrapper for [SerpApi](https://serpapi.com). This `serpapi` module allows you to access search data in your Python application. -## Current Status +[SerpApi](https://serpapi.com) supports Google, Google Maps, Google Shopping, Bing, Baidu, Yandex, Yahoo, eBay, App Stores, and more. Check out the [documentation](https://serpapi.com/search-api) for a full list. -This project is under development, and will be released to the public on PyPi soon. ## Installation @@ -23,11 +21,11 @@ To install the `serpapi` package, simply run the following command: $ pip install serpapi ``` -Please note that this package is separate from the *soon–to–be* legacy `serpapi` module, which is currently available on PyPi as `google-search-results`. +Please note that this package is separate from the legacy `serpapi` module, which is available on PyPi as `google-search-results`. This package is maintained by SerpApi, and is the recommended way to access the SerpApi service from Python. ## Usage -Let’s start by searching for Coffee on Google: +Let's start by searching for Coffee on Google: ```pycon >>> import serpapi @@ -36,14 +34,14 @@ Let’s start by searching for Coffee on Google: The `s` variable now contains a `SerpResults` object, which acts just like a standard dictionary, with some convenient functions added on top. -Let’s print the first result: +Let's print the first result: ```pycon >>> s["organic_results"][0]["link"] 'https://en.wikipedia.org/wiki/Coffee' ``` -Let’s print the title of the first result, but in a more Pythonic way: +Let's print the title of the first result, but in a more Pythonic way: ```pycon >>> s["organic_results"][0].get("title") @@ -56,6 +54,292 @@ The [SerpApi.com API Documentation](https://serpapi.com/search-api) contains a l Documentation is [available on Read the Docs](https://serpapi-python.readthedocs.io/en/latest/). +## Basic Examples in Python + +### Search Bing +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'bing', + 'q': 'coffee' +}) +``` +- API Documentation: [serpapi.com/bing-search-api](https://serpapi.com/bing-search-api) + +### Search Baidu +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'baidu', + 'q': 'coffee', +}) +``` +- API Documentation: [serpapi.com/baidu-search-api](https://serpapi.com/baidu-search-api) + +### Search Yahoo +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'yahoo', + 'p': 'coffee', +}) +``` +- API Documentation: [serpapi.com/yahoo-search-api](https://serpapi.com/yahoo-search-api) + +### Search YouTube +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'youtube', + 'search_query': 'coffee', +}) +``` +- API Documentation: [serpapi.com/youtube-search-api](https://serpapi.com/youtube-search-api) + +### Search Walmart +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'walmart', + 'query': 'coffee', +}) +``` +- API Documentation: [serpapi.com/walmart-search-api](https://serpapi.com/walmart-search-api) + +### Search eBay +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'ebay', + '_nkw': 'coffee', +}) +``` +- API Documentation: [serpapi.com/ebay-search-api](https://serpapi.com/ebay-search-api) + +### Search Naver +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'naver', + 'query': 'coffee', +}) +``` +- API Documentation: [serpapi.com/naver-search-api](https://serpapi.com/naver-search-api) + +### Search Home Depot +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'home_depot', + 'q': 'table', +}) +``` +- API Documentation: [serpapi.com/home-depot-search-api](https://serpapi.com/home-depot-search-api) + +### Search Apple App Store +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'apple_app_store', + 'term': 'coffee', +}) +``` +- API Documentation: [serpapi.com/apple-app-store](https://serpapi.com/apple-app-store) + +### Search DuckDuckGo +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'duckduckgo', + 'q': 'coffee', +}) +``` +- API Documentation: [serpapi.com/duckduckgo-search-api](https://serpapi.com/duckduckgo-search-api) + +### Search Google +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'google', + 'q': 'coffee' +}) +``` +- API Documentation: [serpapi.com/search-api](https://serpapi.com/search-api) + +### Search Google Scholar +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'google_scholar', + 'q': 'coffee', +}) +``` +- API Documentation: [serpapi.com/google-scholar-api](https://serpapi.com/google-scholar-api) + +### Search Google Autocomplete +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'google_autocomplete', + 'q': 'coffee', +}) +``` +- API Documentation: [serpapi.com/google-autocomplete-api](https://serpapi.com/google-autocomplete-api) + +### Search Google Product +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'google_product', + 'q': 'coffee', + 'product_id': '4887235756540435899', +}) +``` +- API Documentation: [serpapi.com/google-product-api](https://serpapi.com/google-product-api) + +### Search Google Reverse Image +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'google_reverse_image', + 'image_url': 'https://i.imgur.com/5bGzZi7.jpg', + 'max_results': '1', +}) +``` +- API Documentation: [serpapi.com/google-reverse-image](https://serpapi.com/google-reverse-image) + +### Search Google Events +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'google_events', + 'q': 'coffee', +}) +``` +- API Documentation: [serpapi.com/google-events-api](https://serpapi.com/google-events-api) + +### Search Google Local Services +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'google_local_services', + 'q': 'electrician', + 'data_cid': '6745062158417646970', +}) +``` +- API Documentation: [serpapi.com/google-local-services-api](https://serpapi.com/google-local-services-api) + +### Search Google Maps +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'google_maps', + 'q': 'pizza', + 'll': '@40.7455096,-74.0083012,15.1z', + 'type': 'search', +}) +``` +- API Documentation: [serpapi.com/google-maps-api](https://serpapi.com/google-maps-api) + +### Search Google Jobs +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'google_jobs', + 'q': 'coffee', +}) +``` +- API Documentation: [serpapi.com/google-jobs-api](https://serpapi.com/google-jobs-api) + +### Search Google Play +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'google_play', + 'q': 'kite', + 'store': 'apps', + 'max_results': '2', +}) +``` +- API Documentation: [serpapi.com/google-play-api](https://serpapi.com/google-play-api) + +### Search Google Images +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) +results = client.search({ + 'engine': 'google_images', + 'tbm': 'isch', + 'q': 'coffee', +}) +``` +- API Documentation: [serpapi.com/images-results](https://serpapi.com/images-results) + + ## License MIT License. diff --git a/README.md.erb b/README.md.erb new file mode 100644 index 0000000..0bed824 --- /dev/null +++ b/README.md.erb @@ -0,0 +1,165 @@ +<%- +def snippet(format, path) + lines = File.new(path).readlines + stop = lines.size - 1 + slice = lines[7..stop] + slice.reject! { |l| l.match?(/(^# |assert )/) } + buf = slice.map { |l| l.gsub(/(^\s{2})/, '').gsub(/^\s*$/, '') }.join + url = 'https://github.com/serpapi/serpapi-python/blob/master/' + path + %Q(```#{format}\nimport serpapi\nimport pprint\nimport os\n\n#{buf}```\ntest: [#{path}](#{url})) +end +-%> + +
+

SerpApi Python Library & Package

+ serpapi python library logo + + + [![serpapi-python](https://github.com/serpapi/serpapi-python/actions/workflows/ci.yml/badge.svg)](https://github.com/serpapi/serpapi-python/actions/workflows/ci.yml) +
+ +This repository is the home of the *soon–to–be* official Python API wrapper for [SerpApi](https://serpapi.com). This `serpapi` module allows you to access search data in your Python application. + +[SerpApi](https://serpapi.com) supports Google, Google Maps, Google Shopping, Bing, Baidu, Yandex, Yahoo, eBay, App Stores, and more. Check out the [documentation](https://serpapi.com/search-api) for a full list. + +## Current Status + +This project is under development, and will be released to the public on PyPi soon. + +## Installation + +To install the `serpapi` package, simply run the following command: + +```bash +$ pip install serpapi +``` + +Please note that this package is separate from the *soon–to–be* legacy `serpapi` module, which is currently available on PyPi as `google-search-results`. + +## Usage + +Let's start by searching for Coffee on Google: + +```pycon +>>> import serpapi +>>> s = serpapi.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us") +``` + +The `s` variable now contains a `SerpResults` object, which acts just like a standard dictionary, with some convenient functions added on top. + +Let's print the first result: + +```pycon +>>> s["organic_results"][0]["link"] +'https://en.wikipedia.org/wiki/Coffee' +``` + +Let's print the title of the first result, but in a more Pythonic way: + +```pycon +>>> s["organic_results"][0].get("title") +'Coffee - Wikipedia' +``` + +The [SerpApi.com API Documentation](https://serpapi.com/search-api) contains a list of all the possible parameters that can be passed to the API. + +## Documentation + +Documentation is [available on Read the Docs](https://serpapi-python.readthedocs.io/en/latest/). + +## Examples in python +Here is how to calls the APIs. + +### Search bing +<%= snippet('python', 'tests/example_search_bing_test.py') %> +see: [serpapi.com/bing-search-api](https://serpapi.com/bing-search-api) + +### Search baidu +<%= snippet('python', 'tests/example_search_baidu_test.py') %> +see: [serpapi.com/baidu-search-api](https://serpapi.com/baidu-search-api) + +### Search yahoo +<%= snippet('python', 'tests/example_search_yahoo_test.py') %> +see: [serpapi.com/yahoo-search-api](https://serpapi.com/yahoo-search-api) + +### Search youtube +<%= snippet('python', 'tests/example_search_youtube_test.py') %> +see: [serpapi.com/youtube-search-api](https://serpapi.com/youtube-search-api) + +### Search walmart +<%= snippet('python', 'tests/example_search_walmart_test.py') %> +see: [serpapi.com/walmart-search-api](https://serpapi.com/walmart-search-api) + +### Search ebay +<%= snippet('python', 'tests/example_search_ebay_test.py') %> +see: [serpapi.com/ebay-search-api](https://serpapi.com/ebay-search-api) + +### Search naver +<%= snippet('python', 'tests/example_search_naver_test.py') %> +see: [serpapi.com/naver-search-api](https://serpapi.com/naver-search-api) + +### Search home depot +<%= snippet('python', 'tests/example_search_home_depot_test.py') %> +see: [serpapi.com/home-depot-search-api](https://serpapi.com/home-depot-search-api) + +### Search apple app store +<%= snippet('python', 'tests/example_search_apple_app_store_test.py') %> +see: [serpapi.com/apple-app-store](https://serpapi.com/apple-app-store) + +### Search duckduckgo +<%= snippet('python', 'tests/example_search_duckduckgo_test.py') %> +see: [serpapi.com/duckduckgo-search-api](https://serpapi.com/duckduckgo-search-api) + +### Search google +<%= snippet('python', 'tests/example_search_google_test.py') %> +see: [serpapi.com/search-api](https://serpapi.com/search-api) + +### Search google scholar +<%= snippet('python', 'tests/example_search_google_scholar_test.py') %> +see: [serpapi.com/google-scholar-api](https://serpapi.com/google-scholar-api) + +### Search google autocomplete +<%= snippet('python', 'tests/example_search_google_autocomplete_test.py') %> +see: [serpapi.com/google-autocomplete-api](https://serpapi.com/google-autocomplete-api) + +### Search google product +<%= snippet('python', 'tests/example_search_google_product_test.py') %> +see: [serpapi.com/google-product-api](https://serpapi.com/google-product-api) + +### Search google reverse image +<%= snippet('python', 'tests/example_search_google_reverse_image_test.py') %> +see: [serpapi.com/google-reverse-image](https://serpapi.com/google-reverse-image) + +### Search google events +<%= snippet('python', 'tests/example_search_google_events_test.py') %> +see: [serpapi.com/google-events-api](https://serpapi.com/google-events-api) + +### Search google local services +<%= snippet('python', 'tests/example_search_google_local_services_test.py') %> +see: [serpapi.com/google-local-services-api](https://serpapi.com/google-local-services-api) + +### Search google maps +<%= snippet('python', 'tests/example_search_google_maps_test.py') %> +see: [serpapi.com/google-maps-api](https://serpapi.com/google-maps-api) + +### Search google jobs +<%= snippet('python', 'tests/example_search_google_jobs_test.py') %> +see: [serpapi.com/google-jobs-api](https://serpapi.com/google-jobs-api) + +### Search google play +<%= snippet('python', 'tests/example_search_google_play_test.py') %> +see: [serpapi.com/google-play-api](https://serpapi.com/google-play-api) + +### Search google images +<%= snippet('python', 'tests/example_search_google_images_test.py') %> +see: [serpapi.com/images-results](https://serpapi.com/images-results) + + +## License + +MIT License. + +## Contributing + +Bug reports and pull requests are welcome on GitHub. Once dependencies are installed, you can run the tests with `pytest`. diff --git a/docs/index.rst b/docs/index.rst index b0a9a1a..717c69a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,4 +1,4 @@ -.. serapi-python documentation master file, created by +.. serpapi-python documentation master file, created by sphinx-quickstart on Sun Apr 3 21:09:40 2022. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. @@ -37,6 +37,11 @@ For example, the API endpoint ``https://serpapi.com/search.json`` is represented Any parameters that you pass to ``search()`` will be passed to the API. This includes the ``api_key`` parameter, which is required for all requests. +.. _using-api-client-directly: + +Using the API Client directly +^^^^^^^^^ + To make this less repetitive, and gain the benefit of connection pooling, let's start using the API Client directly:: >>> client = serpapi.Client(api_key="secret_api_key") @@ -78,8 +83,6 @@ This part of the documentation covers all the interfaces of :class:`serpapi` Pyt .. module:: serpapi :platform: Unix, Windows :synopsis: SerpApi Python Library - :members: - :undoc-members: .. autofunction:: serpapi.search .. autofunction:: serpapi.search_archive @@ -118,9 +121,11 @@ You can get the next page of results:: >>> type(s.next_page()) -Or iterate over all pages of results:: +To iterate over all pages of results, it's recommended to :ref:`use the API Client directly `:: - >>> for page in s.yield_pages(): + >>> client = serpapi.Client(api_key="secret_api_key") + >>> search = client.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us") + >>> for page in search.yield_pages(): ... print(page["search_metadata"]["page_number"]) 1 2 @@ -162,7 +167,7 @@ This class also alleviates the need to pass an ``api_key``` along with every se Exceptions ---------- -.. autoexception:: serpapi.SerpAPIError +.. autoexception:: serpapi.SerpApiError :members: .. autoexception:: serpapi.SearchIDNotProvided diff --git a/pylint.rc b/pylint.rc new file mode 100644 index 0000000..8cdc70e --- /dev/null +++ b/pylint.rc @@ -0,0 +1,637 @@ +[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 +# (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.11 + +# 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= + +# 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= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + victor + kenneth + bart + +# 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, + _ + +# 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=20 + +# 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-symbolic-message-instead + unnecessary-pass + invalid-name + + +# 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=c-extension-no-member + + +[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, json +# 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= + +# 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 diff --git a/serpapi/__version__.py b/serpapi/__version__.py index f102a9c..1276d02 100644 --- a/serpapi/__version__.py +++ b/serpapi/__version__.py @@ -1 +1 @@ -__version__ = "0.0.1" +__version__ = "0.1.5" diff --git a/serpapi/core.py b/serpapi/core.py index b6b5beb..a627d21 100644 --- a/serpapi/core.py +++ b/serpapi/core.py @@ -28,10 +28,10 @@ class Client(HTTPClient): def __repr__(self): return "" - def search(self, **params): + def search(self, params: dict = None, **kwargs): """Fetch a page of results from SerpApi. Returns a :class:`SerpResults ` object, or unicode text (*e.g.* if ``'output': 'html'`` was passed). - The following two calls are equivalent: + The following three calls are equivalent: .. code-block:: python @@ -42,6 +42,11 @@ def search(self, **params): >>> params = {"q": "Coffee", "location": "Austin, Texas, United States"} >>> s = serpapi.search(**params) + .. code-block:: python + + >>> params = {"q": "Coffee", "location": "Austin, Texas, United States"} + >>> s = serpapi.search(params) + :param q: typically, this is the parameter for the search engine query. :param engine: the search engine to use. Defaults to ``google``. @@ -52,12 +57,17 @@ def search(self, **params): **Learn more**: https://serpapi.com/search-api """ + if params is None: + params = {} + + if kwargs: + params.update(kwargs) r = self.request("GET", "/search", params=params) return SerpResults.from_http_response(r, client=self) - def search_archive(self, **params): + def search_archive(self, params: dict = None, **kwargs): """Get a result from the SerpApi Search Archive API. :param search_id: the Search ID of the search to retrieve from the archive. @@ -67,6 +77,11 @@ def search_archive(self, **params): **Learn more**: https://serpapi.com/search-archive-api """ + if params is None: + params = {} + + if kwargs: + params.update(kwargs) try: search_id = params["search_id"] @@ -78,7 +93,7 @@ def search_archive(self, **params): r = self.request("GET", f"/searches/{ search_id }", params=params) return SerpResults.from_http_response(r, client=self) - def locations(self, **params): + def locations(self, params: dict = None, **kwargs): """Get a list of supported Google locations. @@ -88,6 +103,11 @@ def locations(self, **params): **Learn more**: https://serpapi.com/locations-api """ + if params is None: + params = {} + + if kwargs: + params.update(kwargs) r = self.request( "GET", @@ -97,10 +117,7 @@ def locations(self, **params): ) return r.json() - def account( - self, - **params, - ): + def account(self, params: dict = None, **kwargs): """Get SerpApi account information. :param api_key: the API Key to use for SerpApi.com. @@ -109,10 +126,17 @@ def account( **Learn more**: https://serpapi.com/account-api """ + if params is None: + params = {} + + if kwargs: + params.update(kwargs) + r = self.request("GET", "/account.json", params=params, assert_200=True) return r.json() +# An un-authenticated client instance. _client = Client() search = _client.search search_archive = _client.search_archive diff --git a/serpapi/exceptions.py b/serpapi/exceptions.py index f1e31be..f501189 100644 --- a/serpapi/exceptions.py +++ b/serpapi/exceptions.py @@ -1,31 +1,31 @@ import requests -class SerpAPIError(Exception): +class SerpApiError(Exception): """Base class for exceptions in this module.""" pass -class APIKeyNotProvided(ValueError, SerpAPIError): +class APIKeyNotProvided(ValueError, SerpApiError): """API key is not provided.""" pass -class SearchIDNotProvided(ValueError, SerpAPIError): +class SearchIDNotProvided(ValueError, SerpApiError): """Search ID is not provided.""" pass -class HTTPError(requests.exceptions.HTTPError, SerpAPIError): +class HTTPError(requests.exceptions.HTTPError, SerpApiError): """HTTP Error.""" pass -class HTTPConnectionError(HTTPError, requests.exceptions.ConnectionError, SerpAPIError): +class HTTPConnectionError(HTTPError, requests.exceptions.ConnectionError, SerpApiError): """Connection Error.""" pass diff --git a/serpapi/models.py b/serpapi/models.py index 07f280f..0aa7720 100644 --- a/serpapi/models.py +++ b/serpapi/models.py @@ -3,14 +3,12 @@ from pprint import pformat from collections import UserDict -import requests - from .textui import prettify_json from .exceptions import HTTPError class SerpResults(UserDict): - """A dictionary-like object that represents the results of a SerpAPI request. + """A dictionary-like object that represents the results of a SerpApi request. .. code-block:: python @@ -53,7 +51,7 @@ def next_page_url(self): serpapi_pagination = self.data.get("serpapi_pagination") if serpapi_pagination: - return serpapi_pagination.get("next_link") + return serpapi_pagination.get("next") def next_page(self): """Return the next page of results, if any.""" @@ -72,12 +70,16 @@ def yield_pages(self, max_pages=1_000): """ current_page_count = 0 - + current_page = self - while current_page.next_page_url and current_page_count < max_pages: - current_page = current_page.next_page() - current_page_count += 1 + while current_page and current_page_count < max_pages: yield current_page + current_page_count += 1 + if current_page.next_page_url: + current_page = current_page.next_page() + else: + break + @classmethod def from_http_response(cls, r, *, client=None): diff --git a/setup.py b/setup.py index 52236a6..085e79a 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,27 @@ about["__version__"] = VERSION +class TestCommand(Command): + """Support setup.py test.""" + + description = "Test the package." + user_options = [] + + @staticmethod + def status(s): + """Prints things in bold.""" + print("\033[1m{0}\033[0m".format(s)) + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + os.system("{0} -m pytest".format(sys.executable)) + sys.exit() + class UploadCommand(Command): """Support setup.py upload.""" @@ -104,7 +125,7 @@ def run(self): extras_require=EXTRAS, include_package_data=True, license="MIT", - project_urls={"Documentation": "https://serpapi.com/search-api"}, + project_urls={"Documentation": "https://serpapi-python.readthedocs.io/en/latest/"}, keywords="scrape,serp,api,serpapi,scraping,json,search,localized,rank,google,bing,baidu,yandex,yahoo,ebay,scale,datamining,training,machine,ml,youtube,naver,walmart,apple,store,app,serpapi", classifiers=[ # Trove classifiers @@ -119,6 +140,7 @@ def run(self): "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Natural Language :: English", "Topic :: Utilities", "Topic :: Internet :: WWW/HTTP", @@ -129,5 +151,6 @@ def run(self): # $ setup.py publish support. cmdclass={ "upload": UploadCommand, + "test": TestCommand }, ) diff --git a/tests/example_search_apple_app_store_test.py b/tests/example_search_apple_app_store_test.py new file mode 100644 index 0000000..70bb30b --- /dev/null +++ b/tests/example_search_apple_app_store_test.py @@ -0,0 +1,12 @@ +# Example: apple_app_store search engine +import pytest +import os +import serpapi + +def test_search_apple_app_store(client): + data = client.search({ + 'engine': 'apple_app_store', + 'term': 'coffee', + }) + assert data.get('error') is None + assert data['organic_results'] diff --git a/tests/example_search_baidu_test.py b/tests/example_search_baidu_test.py new file mode 100644 index 0000000..d5a0d52 --- /dev/null +++ b/tests/example_search_baidu_test.py @@ -0,0 +1,13 @@ +# Example: baidu search engine +import pytest +import os +import serpapi + + +def test_search_baidu(client): + data = client.search({ + 'engine': 'baidu', + 'q': 'coffee', + }) + assert data.get('error') is None + assert data['organic_results'] diff --git a/tests/example_search_bing_test.py b/tests/example_search_bing_test.py new file mode 100644 index 0000000..36a1c79 --- /dev/null +++ b/tests/example_search_bing_test.py @@ -0,0 +1,12 @@ +# Example: bing search engine +import pytest +import os +import serpapi + +def test_search_bing(client): + data = client.search({ + 'engine': 'bing', + 'q': 'coffee', + }) + assert data.get('error') is None + assert data['organic_results'] diff --git a/tests/example_search_duckduckgo_test.py b/tests/example_search_duckduckgo_test.py new file mode 100644 index 0000000..4a198ab --- /dev/null +++ b/tests/example_search_duckduckgo_test.py @@ -0,0 +1,12 @@ +# Example: duckduckgo search engine +import pytest +import os +import serpapi + +def test_search_duckduckgo(client): + data = client.search({ + 'engine': 'duckduckgo', + 'q': 'coffee', + }) + assert data.get('error') is None + assert data['organic_results'] diff --git a/tests/example_search_ebay_test.py b/tests/example_search_ebay_test.py new file mode 100644 index 0000000..fd41037 --- /dev/null +++ b/tests/example_search_ebay_test.py @@ -0,0 +1,12 @@ +# Example: ebay search engine +import pytest +import os +import serpapi + +def test_search_ebay(client): + data = client.search({ + 'engine': 'ebay', + '_nkw': 'coffee', + }) + assert data.get('error') is None + assert data['organic_results'] diff --git a/tests/example_search_google_autocomplete_test.py b/tests/example_search_google_autocomplete_test.py new file mode 100644 index 0000000..3de2012 --- /dev/null +++ b/tests/example_search_google_autocomplete_test.py @@ -0,0 +1,12 @@ +# Example: google_autocomplete search engine +import pytest +import os +import serpapi + +def test_search_google_autocomplete(client): + data = client.search({ + 'engine': 'google_autocomplete', + 'q': 'coffee', + }) + assert data.get('error') is None + assert data['suggestions'] diff --git a/tests/example_search_google_events_test.py b/tests/example_search_google_events_test.py new file mode 100644 index 0000000..a46f5bf --- /dev/null +++ b/tests/example_search_google_events_test.py @@ -0,0 +1,12 @@ +# Example: google_events search engine +import pytest +import os +import serpapi + +def test_search_google_events(client): + data = client.search({ + 'engine': 'google_events', + 'q': 'coffee', + }) + assert data.get('error') is None + assert data['events_results'] diff --git a/tests/example_search_google_images_test.py b/tests/example_search_google_images_test.py new file mode 100644 index 0000000..338e69b --- /dev/null +++ b/tests/example_search_google_images_test.py @@ -0,0 +1,14 @@ +# Example: google_images search engine +import pytest +import os +import serpapi + +def test_search_google_images(client): + data = client.search({ + 'engine': 'google_images', + 'engine': 'google_images', + 'tbm': 'isch', + 'q': 'coffee', + }) + assert data.get('error') is None + assert data['images_results'] diff --git a/tests/example_search_google_jobs_test.py b/tests/example_search_google_jobs_test.py new file mode 100644 index 0000000..c465589 --- /dev/null +++ b/tests/example_search_google_jobs_test.py @@ -0,0 +1,12 @@ +# Example: google_jobs search engine +import pytest +import os +import serpapi + +def test_search_google_jobs(client): + data = client.search({ + 'engine': 'google_jobs', + 'q': 'coffee', + }) + assert data.get('error') is None + assert data['jobs_results'] diff --git a/tests/example_search_google_local_services_test.py b/tests/example_search_google_local_services_test.py new file mode 100644 index 0000000..964b013 --- /dev/null +++ b/tests/example_search_google_local_services_test.py @@ -0,0 +1,14 @@ +# Example: google_local_services search engine +import pytest +import os +import serpapi + +def test_search_google_local_services(client): + + data = client.search({ + 'engine': 'google_local_services', + 'q': 'electrician', + 'data_cid': '6745062158417646970', + }) + assert data.get('error') is None + assert data['local_ads'] diff --git a/tests/example_search_google_maps_test.py b/tests/example_search_google_maps_test.py new file mode 100644 index 0000000..453b5dc --- /dev/null +++ b/tests/example_search_google_maps_test.py @@ -0,0 +1,14 @@ +# Example: google_maps search engine +import pytest +import os +import serpapi + +def test_search_google_maps(client): + data = client.search({ + 'engine': 'google_maps', + 'q': 'pizza', + 'll': '@40.7455096,-74.0083012,15.1z', + 'type': 'search', + }) + assert data.get('error') is None + assert data['local_results'] diff --git a/tests/example_search_google_play_test.py b/tests/example_search_google_play_test.py new file mode 100644 index 0000000..bd44bef --- /dev/null +++ b/tests/example_search_google_play_test.py @@ -0,0 +1,14 @@ +# Example: google_play search engine +import pytest +import os +import serpapi + +def test_search_google_play(client): + data = client.search({ + 'engine': 'google_play', + 'q': 'kite', + 'store': 'apps', + 'max_results': '2', + }) + assert data.get('error') is None + assert data['organic_results'] diff --git a/tests/example_search_google_product_test.py b/tests/example_search_google_product_test.py new file mode 100644 index 0000000..9229a07 --- /dev/null +++ b/tests/example_search_google_product_test.py @@ -0,0 +1,13 @@ +# Example: google_product search engine +import pytest +import os +import serpapi + +def test_search_google_product(client): + data = client.search({ + 'engine': 'google_product', + 'q': 'coffee', + 'product_id': '4887235756540435899', + }) + assert data.get('error') is None + assert data['product_results'] diff --git a/tests/example_search_google_reverse_image_test.py b/tests/example_search_google_reverse_image_test.py new file mode 100644 index 0000000..cbe9bac --- /dev/null +++ b/tests/example_search_google_reverse_image_test.py @@ -0,0 +1,13 @@ +# Example: google_reverse_image search engine +import pytest +import os +import serpapi + +def test_search_google_reverse_image(client): + data = client.search({ + 'engine': 'google_reverse_image', + 'image_url': 'https://i.imgur.com/5bGzZi7.jpg', + 'max_results': '1', + }) + assert data.get('error') is None + assert data['image_sizes'] diff --git a/tests/example_search_google_scholar_test.py b/tests/example_search_google_scholar_test.py new file mode 100644 index 0000000..61ff7e0 --- /dev/null +++ b/tests/example_search_google_scholar_test.py @@ -0,0 +1,13 @@ +# Example: google_scholar search engine +import pytest +import os +import serpapi + +def test_search_google_scholar(client): + + data = client.search({ + 'engine': 'google_scholar', + 'q': 'coffee', + }) + assert data.get('error') is None + assert data['organic_results'] diff --git a/tests/example_search_google_test.py b/tests/example_search_google_test.py new file mode 100644 index 0000000..650bcff --- /dev/null +++ b/tests/example_search_google_test.py @@ -0,0 +1,14 @@ +# Example: google search engine +import pytest +import os +import serpapi + +def test_search_google(client): + + data = client.search({ + 'engine': 'google', + 'q': 'coffee', + 'engine': 'google', + }) + assert data.get('error') is None + assert data['organic_results'] diff --git a/tests/example_search_home_depot_test.py b/tests/example_search_home_depot_test.py new file mode 100644 index 0000000..a09f4fd --- /dev/null +++ b/tests/example_search_home_depot_test.py @@ -0,0 +1,13 @@ +# Example: home_depot search engine +import pytest +import os +import serpapi + +def test_search_home_depot(client): + + data = client.search({ + 'engine': 'home_depot', + 'q': 'table', + }) + assert data.get('error') is None + assert data['products'] diff --git a/tests/example_search_naver_test.py b/tests/example_search_naver_test.py new file mode 100644 index 0000000..fa2bb26 --- /dev/null +++ b/tests/example_search_naver_test.py @@ -0,0 +1,12 @@ +# Example: naver search engine +import pytest +import os +import serpapi + +def test_search_naver(client): + data = client.search({ + 'engine': 'naver', + 'query': 'coffee', + }) + assert data.get('error') is None + assert data['ads_results'] diff --git a/tests/example_search_walmart_test.py b/tests/example_search_walmart_test.py new file mode 100644 index 0000000..24248fa --- /dev/null +++ b/tests/example_search_walmart_test.py @@ -0,0 +1,13 @@ +# Example: walmart search engine +import pytest +import os +import serpapi + +def test_search_walmart(client): + data = client.search({ + 'engine': 'walmart', + 'query': 'coffee', + }) + assert data.get('error') is None + assert data['organic_results'] + diff --git a/tests/example_search_yahoo_test.py b/tests/example_search_yahoo_test.py new file mode 100644 index 0000000..0a5af69 --- /dev/null +++ b/tests/example_search_yahoo_test.py @@ -0,0 +1,12 @@ +# Example: yahoo search engine +import pytest +import os +import serpapi + +def test_search_yahoo(client): + data = client.search({ + 'engine': 'yahoo', + 'p': 'coffee', + }) + assert data.get('error') is None + assert data['organic_results'] diff --git a/tests/example_search_youtube_test.py b/tests/example_search_youtube_test.py new file mode 100644 index 0000000..00c3da6 --- /dev/null +++ b/tests/example_search_youtube_test.py @@ -0,0 +1,13 @@ +# Example: youtube search engine +import pytest +import os +import serpapi + +def test_search_youtube(client): + + data = client.search({ + 'engine': 'youtube', + 'search_query': 'coffee', + }) + assert data.get('error') is None + assert data['video_results'] diff --git a/tests/test_integration.py b/tests/test_integration.py index 2902b23..644ef50 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -58,6 +58,9 @@ def test_coffee_search_n_pages(coffee_search): max_pages = 3 for page in coffee_search.yield_pages(max_pages=max_pages): + if page_count == 0: + assert 'start' not in page['search_parameters'], "The 'start' parameter should not be in the first page" + page_count += 1 assert page_count == max_pages @@ -68,3 +71,14 @@ def test_coffee_search_next_page(coffee_search): assert isinstance(next_page, serpapi.SerpResults) assert coffee_search["search_metadata"]["id"] != next_page["search_metadata"]["id"] + + +def test_search_function_signature(coffee_params, client): + s = client.search(coffee_params) + assert s["search_metadata"]["id"] + + s = client.search(**coffee_params) + assert s["search_metadata"]["id"] + + s = client.search(q='coffee') + assert s["search_metadata"]["id"]