From 157732cff18988f1b304d6745525ad54fcd15fcb Mon Sep 17 00:00:00 2001 From: askpatricw <4002194+askpatrickw@users.noreply.github.com> Date: Wed, 16 Dec 2020 17:28:06 -0800 Subject: [PATCH 001/305] One more case for _buffer_split0 --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index c09b17f..323ec44 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -274,7 +274,7 @@ def close(self): self._throw_away(self._remaining) elif self._chunked: while True: - chunk_header = self._readto(b"\r\n").split(b";", 1)[0] + chunk_header = _buffer_split0(self._readto(b"\r\n"), b";") chunk_size = int(bytes(chunk_header), 16) if chunk_size == 0: break From 88eebf25c238b62b5976e566ed752fda0de0620b Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 30 Dec 2020 15:32:59 -0500 Subject: [PATCH 002/305] fix RuntimeError: Cannot access content after getting text or json --- examples/requests_advanced.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/requests_advanced.py b/examples/requests_advanced.py index 6fb81d4..c0ba1aa 100644 --- a/examples/requests_advanced.py +++ b/examples/requests_advanced.py @@ -39,7 +39,7 @@ # Initialize a requests object with a socket and esp32spi interface socket.set_interface(esp) -requests.set_socket(socket) +requests.set_socket(socket, esp) JSON_GET_URL = "http://httpbin.org/get" @@ -59,8 +59,5 @@ print("Response HTTP Status Code: ", response.status_code) print("-" * 60) -# Read Response, as raw bytes instead of pretty text -print("Raw Response: ", response.content) - # Close, delete and collect the response data response.close() From 6c18a02381ab78e0b2c103585dc55cc327623838 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 30 Dec 2020 15:34:40 -0500 Subject: [PATCH 003/305] set socket correctly --- examples/requests_simpletest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/requests_simpletest.py b/examples/requests_simpletest.py index 4d4070e..449bfdd 100755 --- a/examples/requests_simpletest.py +++ b/examples/requests_simpletest.py @@ -40,7 +40,7 @@ # Initialize a requests object with a socket and esp32spi interface socket.set_interface(esp) -requests.set_socket(socket) +requests.set_socket(socket, esp) TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" JSON_GET_URL = "http://httpbin.org/get" From 91249504343ed7ffc0a3e0fada80b7b9e2a6f99a Mon Sep 17 00:00:00 2001 From: dherrada Date: Mon, 11 Jan 2021 15:06:44 -0500 Subject: [PATCH 004/305] Added pre-commit and SPDX copyright Signed-off-by: dherrada --- .github/workflows/build.yml | 28 ++++++++++++++++++++++++---- .github/workflows/release.yml | 4 ++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b6977a9..59baa53 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT + name: Build CI on: [pull_request, push] @@ -38,20 +42,36 @@ jobs: # (e.g. - apt-get: gettext, etc; pip: circuitpython-build-tools, requirements.txt; etc.) run: | source actions-ci/install.sh - - name: Pip install pylint, black, & Sphinx + - name: Pip install pylint, Sphinx, pre-commit run: | - pip install --force-reinstall pylint black==19.10b0 Sphinx sphinx-rtd-theme + pip install --force-reinstall pylint Sphinx sphinx-rtd-theme pre-commit - name: Library version run: git describe --dirty --always --tags - - name: Check formatting + - name: Pre-commit hooks run: | - black --check --target-version=py35 . + pre-commit run --all-files - name: PyLint run: | pylint $( find . -path './adafruit*.py' ) ([[ ! -d "examples" ]] || pylint --disable=missing-docstring,invalid-name,bad-whitespace $( find . -path "./examples/*.py" )) - name: Build assets run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . + - name: Archive bundles + uses: actions/upload-artifact@v2 + with: + name: bundles + path: ${{ github.workspace }}/bundles/ - name: Build docs working-directory: docs run: sphinx-build -E -W -b html . _build/html + - name: Check For setup.py + id: need-pypi + run: | + echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) + - name: Build Python package + if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + run: | + pip install --upgrade setuptools wheel twine readme_renderer testresources + python setup.py sdist + python setup.py bdist_wheel --universal + twine check dist/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 18efb9c..6d0015a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT + name: Release Actions on: From 4b53423a2efce96b82ccc220d38178fd6341d407 Mon Sep 17 00:00:00 2001 From: dherrada Date: Mon, 11 Jan 2021 16:06:47 -0500 Subject: [PATCH 005/305] Added pre-commit-config file Signed-off-by: dherrada --- .pre-commit-config.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..aab5f1c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2020 Diego Elio Pettenò +# +# SPDX-License-Identifier: Unlicense + +repos: +- repo: https://github.com/python/black + rev: stable + hooks: + - id: black +- repo: https://github.com/fsfe/reuse-tool + rev: latest + hooks: + - id: reuse +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace From 1f40218f0ab355c20d23f73e380175c3bf78673e Mon Sep 17 00:00:00 2001 From: dherrada Date: Tue, 12 Jan 2021 11:43:22 -0500 Subject: [PATCH 006/305] Ran pre-commit, added licenses --- .github/workflows/test.yml | 25 -- .gitignore | 6 +- .pylintrc | 4 + .readthedocs.yml | 4 + CODE_OF_CONDUCT.md | 14 +- LICENSES/CC-BY-4.0.txt | 324 +++++++++++++++++++++++ LICENSES/MIT.txt | 19 ++ LICENSES/Unlicense.txt | 20 ++ README.rst.license | 3 + adafruit_requests.py | 25 +- docs/_static/favicon.ico.license | 3 + docs/api.rst.license | 3 + docs/conf.py | 4 + docs/examples.rst.license | 3 + docs/index.rst.license | 3 + examples/requests_advanced.py | 3 + examples/requests_advanced_cellular.py | 3 + examples/requests_advanced_cpython.py | 3 + examples/requests_advanced_ethernet.py | 3 + examples/requests_github_cpython.py | 3 + examples/requests_https_cpython.py | 3 + examples/requests_simpletest.py | 3 + examples/requests_simpletest_cellular.py | 3 + examples/requests_simpletest_cpython.py | 3 + examples/requests_simpletest_ethernet.py | 3 + requirements.txt | 4 + setup.py | 4 + tests/chunk_test.py | 14 +- tests/concurrent_test.py | 24 +- tests/header_test.py | 4 + tests/legacy_mocket.py | 4 + tests/legacy_test.py | 50 +++- tests/mocket.py | 4 + tests/parse_test.py | 4 + tests/post_test.py | 9 +- tests/protocol_test.py | 19 +- tests/reuse_test.py | 54 +++- 37 files changed, 604 insertions(+), 80 deletions(-) delete mode 100644 .github/workflows/test.yml create mode 100644 LICENSES/CC-BY-4.0.txt create mode 100644 LICENSES/MIT.txt create mode 100644 LICENSES/Unlicense.txt create mode 100644 README.rst.license create mode 100644 docs/_static/favicon.ico.license create mode 100644 docs/api.rst.license create mode 100644 docs/examples.rst.license create mode 100644 docs/index.rst.license diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 2f9163b..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Run Tests - -on: [pull_request, push] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Set up Python 3.6 - uses: actions/setup-python@v1 - with: - python-version: 3.6 - - name: Versions - run: | - python3 --version - - name: Checkout Current Repo - uses: actions/checkout@v1 - with: - submodules: true - - name: Install pytest - run: pip install pytest - - name: Install locally - run: pip install . - - name: Run tests - run: pytest diff --git a/.gitignore b/.gitignore index c83f8b7..9647e71 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + *.mpy .idea __pycache__ @@ -8,4 +12,4 @@ bundles *.DS_Store .eggs dist -**/*.egg-info \ No newline at end of file +**/*.egg-info diff --git a/.pylintrc b/.pylintrc index d8f0ee8..5c31f66 100755 --- a/.pylintrc +++ b/.pylintrc @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + [MASTER] # A comma-separated list of package or module names from where C extensions may diff --git a/.readthedocs.yml b/.readthedocs.yml index f4243ad..ffa84c4 100755 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + python: version: 3 requirements_file: requirements.txt diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 134d510..8a55c07 100755 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,3 +1,9 @@ + + # Adafruit Community Code of Conduct ## Our Pledge @@ -43,7 +49,7 @@ Examples of unacceptable behavior by participants include: The goal of the standards and moderation guidelines outlined here is to build and maintain a respectful community. We ask that you don’t just aim to be -"technically unimpeachable", but rather try to be your best self. +"technically unimpeachable", but rather try to be your best self. We value many things beyond technical expertise, including collaboration and supporting others within our community. Providing a positive experience for @@ -74,9 +80,9 @@ You may report in the following ways: In any situation, you may send an email to . On the Adafruit Discord, you may send an open message from any channel -to all Community Moderators by tagging @community moderators. You may -also send an open message from any channel, or a direct message to -@kattni#1507, @tannewt#4653, @Dan Halbert#1614, @cater#2442, +to all Community Moderators by tagging @community moderators. You may +also send an open message from any channel, or a direct message to +@kattni#1507, @tannewt#4653, @Dan Halbert#1614, @cater#2442, @sommersoft#0222, @Mr. Certainly#0472 or @Andon#8175. Email and direct message reports will be kept confidential. diff --git a/LICENSES/CC-BY-4.0.txt b/LICENSES/CC-BY-4.0.txt new file mode 100644 index 0000000..3f92dfc --- /dev/null +++ b/LICENSES/CC-BY-4.0.txt @@ -0,0 +1,324 @@ +Creative Commons Attribution 4.0 International Creative Commons Corporation +("Creative Commons") is not a law firm and does not provide legal services +or legal advice. Distribution of Creative Commons public licenses does not +create a lawyer-client or other relationship. Creative Commons makes its licenses +and related information available on an "as-is" basis. Creative Commons gives +no warranties regarding its licenses, any material licensed under their terms +and conditions, or any related information. Creative Commons disclaims all +liability for damages resulting from their use to the fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and conditions +that creators and other rights holders may use to share original works of +authorship and other material subject to copyright and certain other rights +specified in the public license below. The following considerations are for +informational purposes only, are not exhaustive, and do not form part of our +licenses. + +Considerations for licensors: Our public licenses are intended for use by +those authorized to give the public permission to use material in ways otherwise +restricted by copyright and certain other rights. Our licenses are irrevocable. +Licensors should read and understand the terms and conditions of the license +they choose before applying it. Licensors should also secure all rights necessary +before applying our licenses so that the public can reuse the material as +expected. Licensors should clearly mark any material not subject to the license. +This includes other CC-licensed material, or material used under an exception +or limitation to copyright. More considerations for licensors : wiki.creativecommons.org/Considerations_for_licensors + +Considerations for the public: By using one of our public licenses, a licensor +grants the public permission to use the licensed material under specified +terms and conditions. If the licensor's permission is not necessary for any +reason–for example, because of any applicable exception or limitation to copyright–then +that use is not regulated by the license. Our licenses grant only permissions +under copyright and certain other rights that a licensor has authority to +grant. Use of the licensed material may still be restricted for other reasons, +including because others have copyright or other rights in the material. A +licensor may make special requests, such as asking that all changes be marked +or described. Although not required by our licenses, you are encouraged to +respect those requests where reasonable. More considerations for the public +: wiki.creativecommons.org/Considerations_for_licensees Creative Commons Attribution +4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree to +be bound by the terms and conditions of this Creative Commons Attribution +4.0 International Public License ("Public License"). To the extent this Public +License may be interpreted as a contract, You are granted the Licensed Rights +in consideration of Your acceptance of these terms and conditions, and the +Licensor grants You such rights in consideration of benefits the Licensor +receives from making the Licensed Material available under these terms and +conditions. + +Section 1 – Definitions. + +a. Adapted Material means material subject to Copyright and Similar Rights +that is derived from or based upon the Licensed Material and in which the +Licensed Material is translated, altered, arranged, transformed, or otherwise +modified in a manner requiring permission under the Copyright and Similar +Rights held by the Licensor. For purposes of this Public License, where the +Licensed Material is a musical work, performance, or sound recording, Adapted +Material is always produced where the Licensed Material is synched in timed +relation with a moving image. + +b. Adapter's License means the license You apply to Your Copyright and Similar +Rights in Your contributions to Adapted Material in accordance with the terms +and conditions of this Public License. + +c. Copyright and Similar Rights means copyright and/or similar rights closely +related to copyright including, without limitation, performance, broadcast, +sound recording, and Sui Generis Database Rights, without regard to how the +rights are labeled or categorized. For purposes of this Public License, the +rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. + +d. Effective Technological Measures means those measures that, in the absence +of proper authority, may not be circumvented under laws fulfilling obligations +under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, +and/or similar international agreements. + +e. Exceptions and Limitations means fair use, fair dealing, and/or any other +exception or limitation to Copyright and Similar Rights that applies to Your +use of the Licensed Material. + +f. Licensed Material means the artistic or literary work, database, or other +material to which the Licensor applied this Public License. + +g. Licensed Rights means the rights granted to You subject to the terms and +conditions of this Public License, which are limited to all Copyright and +Similar Rights that apply to Your use of the Licensed Material and that the +Licensor has authority to license. + +h. Licensor means the individual(s) or entity(ies) granting rights under this +Public License. + +i. Share means to provide material to the public by any means or process that +requires permission under the Licensed Rights, such as reproduction, public +display, public performance, distribution, dissemination, communication, or +importation, and to make material available to the public including in ways +that members of the public may access the material from a place and at a time +individually chosen by them. + +j. Sui Generis Database Rights means rights other than copyright resulting +from Directive 96/9/EC of the European Parliament and of the Council of 11 +March 1996 on the legal protection of databases, as amended and/or succeeded, +as well as other essentially equivalent rights anywhere in the world. + +k. You means the individual or entity exercising the Licensed Rights under +this Public License. Your has a corresponding meaning. + +Section 2 – Scope. + + a. License grant. + +1. Subject to the terms and conditions of this Public License, the Licensor +hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, +irrevocable license to exercise the Licensed Rights in the Licensed Material +to: + + A. reproduce and Share the Licensed Material, in whole or in part; and + + B. produce, reproduce, and Share Adapted Material. + +2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions +and Limitations apply to Your use, this Public License does not apply, and +You do not need to comply with its terms and conditions. + + 3. Term. The term of this Public License is specified in Section 6(a). + +4. Media and formats; technical modifications allowed. The Licensor authorizes +You to exercise the Licensed Rights in all media and formats whether now known +or hereafter created, and to make technical modifications necessary to do +so. The Licensor waives and/or agrees not to assert any right or authority +to forbid You from making technical modifications necessary to exercise the +Licensed Rights, including technical modifications necessary to circumvent +Effective Technological Measures. For purposes of this Public License, simply +making modifications authorized by this Section 2(a)(4) never produces Adapted +Material. + + 5. Downstream recipients. + +A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed +Material automatically receives an offer from the Licensor to exercise the +Licensed Rights under the terms and conditions of this Public License. + +B. No downstream restrictions. You may not offer or impose any additional +or different terms or conditions on, or apply any Effective Technological +Measures to, the Licensed Material if doing so restricts exercise of the Licensed +Rights by any recipient of the Licensed Material. + +6. No endorsement. Nothing in this Public License constitutes or may be construed +as permission to assert or imply that You are, or that Your use of the Licensed +Material is, connected with, or sponsored, endorsed, or granted official status +by, the Licensor or others designated to receive attribution as provided in +Section 3(a)(1)(A)(i). + + b. Other rights. + +1. Moral rights, such as the right of integrity, are not licensed under this +Public License, nor are publicity, privacy, and/or other similar personality +rights; however, to the extent possible, the Licensor waives and/or agrees +not to assert any such rights held by the Licensor to the limited extent necessary +to allow You to exercise the Licensed Rights, but not otherwise. + +2. Patent and trademark rights are not licensed under this Public License. + +3. To the extent possible, the Licensor waives any right to collect royalties +from You for the exercise of the Licensed Rights, whether directly or through +a collecting society under any voluntary or waivable statutory or compulsory +licensing scheme. In all other cases the Licensor expressly reserves any right +to collect such royalties. + +Section 3 – License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the following +conditions. + + a. Attribution. + +1. If You Share the Licensed Material (including in modified form), You must: + +A. retain the following if it is supplied by the Licensor with the Licensed +Material: + +i. identification of the creator(s) of the Licensed Material and any others +designated to receive attribution, in any reasonable manner requested by the +Licensor (including by pseudonym if designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of warranties; + +v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; + +B. indicate if You modified the Licensed Material and retain an indication +of any previous modifications; and + +C. indicate the Licensed Material is licensed under this Public License, and +include the text of, or the URI or hyperlink to, this Public License. + +2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner +based on the medium, means, and context in which You Share the Licensed Material. +For example, it may be reasonable to satisfy the conditions by providing a +URI or hyperlink to a resource that includes the required information. + +3. If requested by the Licensor, You must remove any of the information required +by Section 3(a)(1)(A) to the extent reasonably practicable. + +4. If You Share Adapted Material You produce, the Adapter's License You apply +must not prevent recipients of the Adapted Material from complying with this +Public License. + +Section 4 – Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that apply to +Your use of the Licensed Material: + +a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, +reuse, reproduce, and Share all or a substantial portion of the contents of +the database; + +b. if You include all or a substantial portion of the database contents in +a database in which You have Sui Generis Database Rights, then the database +in which You have Sui Generis Database Rights (but not its individual contents) +is Adapted Material; and + +c. You must comply with the conditions in Section 3(a) if You Share all or +a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not replace +Your obligations under this Public License where the Licensed Rights include +other Copyright and Similar Rights. + +Section 5 – Disclaimer of Warranties and Limitation of Liability. + +a. Unless otherwise separately undertaken by the Licensor, to the extent possible, +the Licensor offers the Licensed Material as-is and as-available, and makes +no representations or warranties of any kind concerning the Licensed Material, +whether express, implied, statutory, or other. This includes, without limitation, +warranties of title, merchantability, fitness for a particular purpose, non-infringement, +absence of latent or other defects, accuracy, or the presence or absence of +errors, whether or not known or discoverable. Where disclaimers of warranties +are not allowed in full or in part, this disclaimer may not apply to You. + +b. To the extent possible, in no event will the Licensor be liable to You +on any legal theory (including, without limitation, negligence) or otherwise +for any direct, special, indirect, incidental, consequential, punitive, exemplary, +or other losses, costs, expenses, or damages arising out of this Public License +or use of the Licensed Material, even if the Licensor has been advised of +the possibility of such losses, costs, expenses, or damages. Where a limitation +of liability is not allowed in full or in part, this limitation may not apply +to You. + +c. The disclaimer of warranties and limitation of liability provided above +shall be interpreted in a manner that, to the extent possible, most closely +approximates an absolute disclaimer and waiver of all liability. + +Section 6 – Term and Termination. + +a. This Public License applies for the term of the Copyright and Similar Rights +licensed here. However, if You fail to comply with this Public License, then +Your rights under this Public License terminate automatically. + +b. Where Your right to use the Licensed Material has terminated under Section +6(a), it reinstates: + +1. automatically as of the date the violation is cured, provided it is cured +within 30 days of Your discovery of the violation; or + + 2. upon express reinstatement by the Licensor. + +c. For the avoidance of doubt, this Section 6(b) does not affect any right +the Licensor may have to seek remedies for Your violations of this Public +License. + +d. For the avoidance of doubt, the Licensor may also offer the Licensed Material +under separate terms or conditions or stop distributing the Licensed Material +at any time; however, doing so will not terminate this Public License. + + e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. + +Section 7 – Other Terms and Conditions. + +a. The Licensor shall not be bound by any additional or different terms or +conditions communicated by You unless expressly agreed. + +b. Any arrangements, understandings, or agreements regarding the Licensed +Material not stated herein are separate from and independent of the terms +and conditions of this Public License. + +Section 8 – Interpretation. + +a. For the avoidance of doubt, this Public License does not, and shall not +be interpreted to, reduce, limit, restrict, or impose conditions on any use +of the Licensed Material that could lawfully be made without permission under +this Public License. + +b. To the extent possible, if any provision of this Public License is deemed +unenforceable, it shall be automatically reformed to the minimum extent necessary +to make it enforceable. If the provision cannot be reformed, it shall be severed +from this Public License without affecting the enforceability of the remaining +terms and conditions. + +c. No term or condition of this Public License will be waived and no failure +to comply consented to unless expressly agreed to by the Licensor. + +d. Nothing in this Public License constitutes or may be interpreted as a limitation +upon, or waiver of, any privileges and immunities that apply to the Licensor +or You, including from the legal processes of any jurisdiction or authority. + +Creative Commons is not a party to its public licenses. Notwithstanding, Creative +Commons may elect to apply one of its public licenses to material it publishes +and in those instances will be considered the "Licensor." The text of the +Creative Commons public licenses is dedicated to the public domain under the +CC0 Public Domain Dedication. Except for the limited purpose of indicating +that material is shared under a Creative Commons public license or as otherwise +permitted by the Creative Commons policies published at creativecommons.org/policies, +Creative Commons does not authorize the use of the trademark "Creative Commons" +or any other trademark or logo of Creative Commons without its prior written +consent including, without limitation, in connection with any unauthorized +modifications to any of its public licenses or any other arrangements, understandings, +or agreements concerning use of licensed material. For the avoidance of doubt, +this paragraph does not form part of the public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 0000000..204b93d --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,19 @@ +MIT License Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/LICENSES/Unlicense.txt b/LICENSES/Unlicense.txt new file mode 100644 index 0000000..24a8f90 --- /dev/null +++ b/LICENSES/Unlicense.txt @@ -0,0 +1,20 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute +this software, either in source code form or as a compiled binary, for any +purpose, commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and +to the detriment of our heirs and successors. We intend this dedication to +be an overt act of relinquishment in perpetuity of all present and future +rights to this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH +THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, +please refer to diff --git a/README.rst.license b/README.rst.license new file mode 100644 index 0000000..11cd75d --- /dev/null +++ b/README.rst.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries + +SPDX-License-Identifier: MIT diff --git a/adafruit_requests.py b/adafruit_requests.py index 323ec44..5aea7e8 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -1,25 +1,8 @@ -# The MIT License (MIT) +# SPDX-FileCopyrightText: 2019 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2020 Scott Shawcroft for Adafruit Industries # -# Copyright (c) 2019 ladyada for Adafruit Industries -# Copyright (c) 2020 Scott Shawcroft for Adafruit Industries -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# SPDX-License-Identifier: MIT + """ `adafruit_requests` ================================================================================ diff --git a/docs/_static/favicon.ico.license b/docs/_static/favicon.ico.license new file mode 100644 index 0000000..86a3fbf --- /dev/null +++ b/docs/_static/favicon.ico.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2018 Phillip Torrone for Adafruit Industries + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/docs/api.rst.license b/docs/api.rst.license new file mode 100644 index 0000000..9aae48d --- /dev/null +++ b/docs/api.rst.license @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2020 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: MIT diff --git a/docs/conf.py b/docs/conf.py index 5bbe8de..c0e0389 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,9 @@ # -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: MIT + import os import sys diff --git a/docs/examples.rst.license b/docs/examples.rst.license new file mode 100644 index 0000000..9aae48d --- /dev/null +++ b/docs/examples.rst.license @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2020 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: MIT diff --git a/docs/index.rst.license b/docs/index.rst.license new file mode 100644 index 0000000..9aae48d --- /dev/null +++ b/docs/index.rst.license @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2020 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: MIT diff --git a/examples/requests_advanced.py b/examples/requests_advanced.py index c0ba1aa..65ec126 100644 --- a/examples/requests_advanced.py +++ b/examples/requests_advanced.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + import board import busio from digitalio import DigitalInOut diff --git a/examples/requests_advanced_cellular.py b/examples/requests_advanced_cellular.py index 18cc600..ea8d512 100755 --- a/examples/requests_advanced_cellular.py +++ b/examples/requests_advanced_cellular.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + # pylint: disable=unused-import import time import board diff --git a/examples/requests_advanced_cpython.py b/examples/requests_advanced_cpython.py index 379620e..6a9e109 100644 --- a/examples/requests_advanced_cpython.py +++ b/examples/requests_advanced_cpython.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + import socket import adafruit_requests diff --git a/examples/requests_advanced_ethernet.py b/examples/requests_advanced_ethernet.py index 2a0e7df..e9ecf57 100644 --- a/examples/requests_advanced_ethernet.py +++ b/examples/requests_advanced_ethernet.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + import board import busio from digitalio import DigitalInOut diff --git a/examples/requests_github_cpython.py b/examples/requests_github_cpython.py index e258d1f..88d7135 100755 --- a/examples/requests_github_cpython.py +++ b/examples/requests_github_cpython.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + # adafruit_requests usage with a CPython socket import socket import ssl diff --git a/examples/requests_https_cpython.py b/examples/requests_https_cpython.py index 8013ffc..39bd6dc 100755 --- a/examples/requests_https_cpython.py +++ b/examples/requests_https_cpython.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + # adafruit_requests usage with a CPython socket import socket import ssl diff --git a/examples/requests_simpletest.py b/examples/requests_simpletest.py index 449bfdd..519f347 100755 --- a/examples/requests_simpletest.py +++ b/examples/requests_simpletest.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + # adafruit_requests usage with an esp32spi_socket import board import busio diff --git a/examples/requests_simpletest_cellular.py b/examples/requests_simpletest_cellular.py index 6727815..6b4d1a7 100755 --- a/examples/requests_simpletest_cellular.py +++ b/examples/requests_simpletest_cellular.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + # pylint: disable=unused-import import time import board diff --git a/examples/requests_simpletest_cpython.py b/examples/requests_simpletest_cpython.py index db9fca2..923ed03 100755 --- a/examples/requests_simpletest_cpython.py +++ b/examples/requests_simpletest_cpython.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + # adafruit_requests usage with a CPython socket import socket import adafruit_requests diff --git a/examples/requests_simpletest_ethernet.py b/examples/requests_simpletest_ethernet.py index 865d777..a432bea 100644 --- a/examples/requests_simpletest_ethernet.py +++ b/examples/requests_simpletest_ethernet.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + import board import busio from digitalio import DigitalInOut diff --git a/requirements.txt b/requirements.txt index edf9394..17a850d 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,5 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + Adafruit-Blinka diff --git a/setup.py b/setup.py index cff2828..d92f376 100755 --- a/setup.py +++ b/setup.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: MIT + """A setuptools based setup module. See: diff --git a/tests/chunk_test.py b/tests/chunk_test.py index 0982652..e55a922 100644 --- a/tests/chunk_test.py +++ b/tests/chunk_test.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + from unittest import mock import mocket import adafruit_requests @@ -53,7 +57,10 @@ def do_test_get_text(extra=b""): ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(b"wifitest.adafruit.com"),] + [ + mock.call(b"Host: "), + mock.call(b"wifitest.adafruit.com"), + ] ) assert r.text == str(text, "utf-8") @@ -89,7 +96,10 @@ def do_test_close_flush(extra=b""): ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(b"wifitest.adafruit.com"),] + [ + mock.call(b"Host: "), + mock.call(b"wifitest.adafruit.com"), + ] ) r.close() diff --git a/tests/concurrent_test.py b/tests/concurrent_test.py index b857418..9b2c894 100644 --- a/tests/concurrent_test.py +++ b/tests/concurrent_test.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + from unittest import mock import mocket import pytest @@ -28,11 +32,17 @@ def test_second_connect_fails_memoryerror(): r = s.get("https://" + host + path) sock.send.assert_has_calls( - [mock.call(b"testwifi/index.html"),] + [ + mock.call(b"testwifi/index.html"), + ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(b"wifitest.adafruit.com"), mock.call(b"\r\n"),] + [ + mock.call(b"Host: "), + mock.call(b"wifitest.adafruit.com"), + mock.call(b"\r\n"), + ] ) assert r.text == str(text, "utf-8") @@ -65,11 +75,17 @@ def test_second_connect_fails_oserror(): r = s.get("https://" + host + path) sock.send.assert_has_calls( - [mock.call(b"testwifi/index.html"),] + [ + mock.call(b"testwifi/index.html"), + ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(b"wifitest.adafruit.com"), mock.call(b"\r\n"),] + [ + mock.call(b"Host: "), + mock.call(b"wifitest.adafruit.com"), + mock.call(b"\r\n"), + ] ) assert r.text == str(text, "utf-8") diff --git a/tests/header_test.py b/tests/header_test.py index 46dcc76..346bbf0 100644 --- a/tests/header_test.py +++ b/tests/header_test.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + from unittest import mock import mocket import json diff --git a/tests/legacy_mocket.py b/tests/legacy_mocket.py index 40d1045..4506a6f 100644 --- a/tests/legacy_mocket.py +++ b/tests/legacy_mocket.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + from unittest import mock SOCK_STREAM = 0 diff --git a/tests/legacy_test.py b/tests/legacy_test.py index bacdc0f..7743f3f 100644 --- a/tests/legacy_test.py +++ b/tests/legacy_test.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + from unittest import mock import legacy_mocket as mocket import json @@ -63,11 +67,17 @@ def test_second_tls_send_fails(): r = adafruit_requests.get("https://" + host + "/testwifi/index.html") sock.send.assert_has_calls( - [mock.call(b"testwifi/index.html"),] + [ + mock.call(b"testwifi/index.html"), + ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(host.encode("utf-8")), mock.call(b"\r\n"),] + [ + mock.call(b"Host: "), + mock.call(host.encode("utf-8")), + mock.call(b"\r\n"), + ] ) assert r.text == str(encoded, "utf-8") @@ -93,11 +103,17 @@ def test_second_send_fails(): r = adafruit_requests.get("http://" + host + "/testwifi/index.html") sock.send.assert_has_calls( - [mock.call(b"testwifi/index.html"),] + [ + mock.call(b"testwifi/index.html"), + ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(host.encode("utf-8")), mock.call(b"\r\n"),] + [ + mock.call(b"Host: "), + mock.call(host.encode("utf-8")), + mock.call(b"\r\n"), + ] ) assert r.text == str(encoded, "utf-8") @@ -124,15 +140,25 @@ def test_first_read_fails(): r = adafruit_requests.get("http://" + host + "/testwifi/index.html") sock.send.assert_has_calls( - [mock.call(b"testwifi/index.html"),] + [ + mock.call(b"testwifi/index.html"), + ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(host.encode("utf-8")), mock.call(b"\r\n"),] + [ + mock.call(b"Host: "), + mock.call(host.encode("utf-8")), + mock.call(b"\r\n"), + ] ) sock2.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(host.encode("utf-8")), mock.call(b"\r\n"),] + [ + mock.call(b"Host: "), + mock.call(host.encode("utf-8")), + mock.call(b"\r\n"), + ] ) sock.connect.assert_called_once_with((ip, 80)) @@ -155,11 +181,17 @@ def test_second_tls_connect_fails(): r = adafruit_requests.get("https://" + host + "/testwifi/index.html") sock.send.assert_has_calls( - [mock.call(b"testwifi/index.html"),] + [ + mock.call(b"testwifi/index.html"), + ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(host.encode("utf-8")), mock.call(b"\r\n"),] + [ + mock.call(b"Host: "), + mock.call(host.encode("utf-8")), + mock.call(b"\r\n"), + ] ) assert r.text == str(encoded, "utf-8") diff --git a/tests/mocket.py b/tests/mocket.py index bc24daf..2a44ed2 100644 --- a/tests/mocket.py +++ b/tests/mocket.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + from unittest import mock diff --git a/tests/parse_test.py b/tests/parse_test.py index bef739e..551b264 100644 --- a/tests/parse_test.py +++ b/tests/parse_test.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + from unittest import mock import mocket import json diff --git a/tests/post_test.py b/tests/post_test.py index c2a9b7e..0438c94 100644 --- a/tests/post_test.py +++ b/tests/post_test.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + from unittest import mock import mocket import json @@ -31,7 +35,10 @@ def test_method(): ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(b"httpbin.org"),] + [ + mock.call(b"Host: "), + mock.call(b"httpbin.org"), + ] ) diff --git a/tests/protocol_test.py b/tests/protocol_test.py index 5857ad3..35b3a95 100644 --- a/tests/protocol_test.py +++ b/tests/protocol_test.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + from unittest import mock import mocket import pytest @@ -42,7 +46,10 @@ def test_get_https_text(): ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(b"wifitest.adafruit.com"),] + [ + mock.call(b"Host: "), + mock.call(b"wifitest.adafruit.com"), + ] ) assert r.text == str(text, "utf-8") @@ -70,7 +77,10 @@ def test_get_http_text(): ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(b"wifitest.adafruit.com"),] + [ + mock.call(b"Host: "), + mock.call(b"wifitest.adafruit.com"), + ] ) assert r.text == str(text, "utf-8") @@ -96,6 +106,9 @@ def test_get_close(): ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(b"wifitest.adafruit.com"),] + [ + mock.call(b"Host: "), + mock.call(b"wifitest.adafruit.com"), + ] ) r.close() diff --git a/tests/reuse_test.py b/tests/reuse_test.py index 4e10b2d..def1ca8 100644 --- a/tests/reuse_test.py +++ b/tests/reuse_test.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + from unittest import mock import mocket import pytest @@ -30,7 +34,10 @@ def test_get_twice(): ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(b"wifitest.adafruit.com"),] + [ + mock.call(b"Host: "), + mock.call(b"wifitest.adafruit.com"), + ] ) assert r.text == str(text, "utf-8") @@ -45,7 +52,10 @@ def test_get_twice(): ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(b"wifitest.adafruit.com"),] + [ + mock.call(b"Host: "), + mock.call(b"wifitest.adafruit.com"), + ] ) assert r.text == str(text, "utf-8") @@ -72,7 +82,10 @@ def test_get_twice_after_second(): ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(b"wifitest.adafruit.com"),] + [ + mock.call(b"Host: "), + mock.call(b"wifitest.adafruit.com"), + ] ) r2 = s.get("https://" + host + path + "2") @@ -86,7 +99,10 @@ def test_get_twice_after_second(): ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(b"wifitest.adafruit.com"),] + [ + mock.call(b"Host: "), + mock.call(b"wifitest.adafruit.com"), + ] ) sock.connect.assert_called_once_with((host, 443)) pool.socket.assert_called_once() @@ -117,7 +133,10 @@ def test_connect_out_of_memory(): ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(b"wifitest.adafruit.com"),] + [ + mock.call(b"Host: "), + mock.call(b"wifitest.adafruit.com"), + ] ) assert r.text == str(text, "utf-8") @@ -131,7 +150,10 @@ def test_connect_out_of_memory(): ] ) sock3.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(b"wifitest2.adafruit.com"),] + [ + mock.call(b"Host: "), + mock.call(b"wifitest2.adafruit.com"), + ] ) assert r.text == str(text, "utf-8") @@ -153,11 +175,17 @@ def test_second_send_fails(): r = s.get("https://" + host + path) sock.send.assert_has_calls( - [mock.call(b"testwifi/index.html"),] + [ + mock.call(b"testwifi/index.html"), + ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(b"wifitest.adafruit.com"), mock.call(b"\r\n"),] + [ + mock.call(b"Host: "), + mock.call(b"wifitest.adafruit.com"), + mock.call(b"\r\n"), + ] ) assert r.text == str(text, "utf-8") @@ -185,11 +213,17 @@ def test_second_send_lies_recv_fails(): r = s.get("https://" + host + path) sock.send.assert_has_calls( - [mock.call(b"testwifi/index.html"),] + [ + mock.call(b"testwifi/index.html"), + ] ) sock.send.assert_has_calls( - [mock.call(b"Host: "), mock.call(b"wifitest.adafruit.com"), mock.call(b"\r\n"),] + [ + mock.call(b"Host: "), + mock.call(b"wifitest.adafruit.com"), + mock.call(b"\r\n"), + ] ) assert r.text == str(text, "utf-8") From 714608c17d3aad68f5dc1300b30cda6a990b1bdc Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Wed, 27 Jan 2021 19:19:10 -0800 Subject: [PATCH 007/305] Fix chunked request detection The response to http://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/scoreboard has an extra space between Transfer-Encoding: and chunked so the comparison fails. Fixes #64 --- adafruit_requests.py | 2 +- tests/chunk_test.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 5aea7e8..8abd58b 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -293,7 +293,7 @@ def _parse_headers(self): len(title) == len("transfer-encoding") and title.lower() == "transfer-encoding" ): - self._chunked = content.lower() == "chunked" + self._chunked = content.strip().lower() == "chunked" self._headers[title] = content @property diff --git a/tests/chunk_test.py b/tests/chunk_test.py index e55a922..d4feebf 100644 --- a/tests/chunk_test.py +++ b/tests/chunk_test.py @@ -11,6 +11,7 @@ path = "/testwifi/index.html" text = b"This is a test of Adafruit WiFi!\r\nIf you can read this, its working :)" headers = b"HTTP/1.0 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n" +headers_extra_space = b"HTTP/1.0 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n" def _chunk(response, split, extra=b""): @@ -111,3 +112,33 @@ def test_close_flush(): def test_close_flush_extra(): do_test_close_flush(b";blahblah; blah") + + +def do_test_get_text_extra_space(extra=b""): + pool = mocket.MocketPool() + pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) + c = _chunk(text, 33, extra) + print(c) + sock = mocket.Mocket(headers_extra_space + c) + pool.socket.return_value = sock + + s = adafruit_requests.Session(pool) + r = s.get("http://" + host + path) + + sock.connect.assert_called_once_with((ip, 80)) + + sock.send.assert_has_calls( + [ + mock.call(b"GET"), + mock.call(b" /"), + mock.call(b"testwifi/index.html"), + mock.call(b" HTTP/1.1\r\n"), + ] + ) + sock.send.assert_has_calls( + [ + mock.call(b"Host: "), + mock.call(b"wifitest.adafruit.com"), + ] + ) + assert r.text == str(text, "utf-8") \ No newline at end of file From c0c41c2ae6bdc00c934169f820643db27616c846 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Thu, 28 Jan 2021 10:16:17 -0800 Subject: [PATCH 008/305] Black --- tests/chunk_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/chunk_test.py b/tests/chunk_test.py index d4feebf..166f67c 100644 --- a/tests/chunk_test.py +++ b/tests/chunk_test.py @@ -141,4 +141,4 @@ def do_test_get_text_extra_space(extra=b""): mock.call(b"wifitest.adafruit.com"), ] ) - assert r.text == str(text, "utf-8") \ No newline at end of file + assert r.text == str(text, "utf-8") From 90782c8ebe5608393ac7d5ddc7d08e680b9c43c0 Mon Sep 17 00:00:00 2001 From: Jim Abernathy Date: Mon, 1 Feb 2021 08:12:30 -0500 Subject: [PATCH 009/305] Adding new example for circuitpython requests_https --- examples/requests_https_circuitpython.py | 61 ++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100755 examples/requests_https_circuitpython.py diff --git a/examples/requests_https_circuitpython.py b/examples/requests_https_circuitpython.py new file mode 100755 index 0000000..f08ec33 --- /dev/null +++ b/examples/requests_https_circuitpython.py @@ -0,0 +1,61 @@ +# adafruit_requests usage with a CircuitPython socket +# this has been tested with Adafruit Metro ESP32-S2 Express + +import socketpool +import ssl +import adafruit_requests as requests +import wifi +import secrets + +# Get wifi details and more from a secrets.py file +try: + from secrets import secrets +except ImportError: + print("WiFi secrets are kept in secrets.py, please add them there!") + raise + +print("Connecting to %s"%secrets["ssid"]) +wifi.radio.connect(secrets["ssid"], secrets["password"]) +print("Connected to %s!"%secrets["ssid"]) +print("My IP address is", wifi.radio.ipv4_address) + +socket = socketpool.SocketPool(wifi.radio) +https = requests.Session(socket, ssl.create_default_context()) + +TEXT_URL = "https://httpbin.org/get" +JSON_GET_URL = "https://httpbin.org/get" +JSON_POST_URL = "https://httpbin.org/post" + +print("Fetching text from %s" % TEXT_URL) +response = https.get(TEXT_URL) +print("-" * 40) +print("Text Response: ", response.text) +print("-" * 40) +response.close() + +print("Fetching JSON data from %s" % JSON_GET_URL) +response = https.get(JSON_GET_URL) +print("-" * 40) + +print("JSON Response: ", response.json()) +print("-" * 40) + +data = "31F" +print("POSTing data to {0}: {1}".format(JSON_POST_URL, data)) +response = https.post(JSON_POST_URL, data=data) +print("-" * 40) + +json_resp = response.json() +# Parse out the 'data' key from json_resp dict. +print("Data received from server:", json_resp["data"]) +print("-" * 40) + +json_data = {"Date": "July 25, 2019"} +print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data)) +response = https.post(JSON_POST_URL, json=json_data) +print("-" * 40) + +json_resp = response.json() +# Parse out the 'json' key from json_resp dict. +print("JSON Data received from server:", json_resp["json"]) +print("-" * 40) \ No newline at end of file From 7de8f29b1dc0f0ca2a4c136c7c392907894213ba Mon Sep 17 00:00:00 2001 From: Jim Abernathy Date: Tue, 2 Feb 2021 17:29:16 -0500 Subject: [PATCH 010/305] fixed some formating --- examples/requests_https_circuitpython.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/requests_https_circuitpython.py b/examples/requests_https_circuitpython.py index f08ec33..3cb269b 100755 --- a/examples/requests_https_circuitpython.py +++ b/examples/requests_https_circuitpython.py @@ -14,9 +14,9 @@ print("WiFi secrets are kept in secrets.py, please add them there!") raise -print("Connecting to %s"%secrets["ssid"]) +print("Connecting to %s" % secrets["ssid"]) wifi.radio.connect(secrets["ssid"], secrets["password"]) -print("Connected to %s!"%secrets["ssid"]) +print("Connected to %s!" % secrets["ssid"]) print("My IP address is", wifi.radio.ipv4_address) socket = socketpool.SocketPool(wifi.radio) @@ -58,4 +58,4 @@ json_resp = response.json() # Parse out the 'json' key from json_resp dict. print("JSON Data received from server:", json_resp["json"]) -print("-" * 40) \ No newline at end of file +print("-" * 40) From c6b60d9c48530a2a28ef18c271658b1be27a28f1 Mon Sep 17 00:00:00 2001 From: Jim Abernathy Date: Wed, 3 Feb 2021 16:41:18 -0500 Subject: [PATCH 011/305] added license statement --- examples/requests_https_circuitpython.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/requests_https_circuitpython.py b/examples/requests_https_circuitpython.py index 3cb269b..e63790f 100755 --- a/examples/requests_https_circuitpython.py +++ b/examples/requests_https_circuitpython.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2021 jfabernathy for Adafruit Industries +# SPDX-License-Identifier: MIT + # adafruit_requests usage with a CircuitPython socket # this has been tested with Adafruit Metro ESP32-S2 Express From d672c7a8d1d7c9b284c9bff1857ff30af2f9448a Mon Sep 17 00:00:00 2001 From: Jim Abernathy Date: Wed, 3 Feb 2021 17:30:35 -0500 Subject: [PATCH 012/305] fix misc formatting --- examples/requests_https_circuitpython.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/requests_https_circuitpython.py b/examples/requests_https_circuitpython.py index e63790f..31b7e12 100755 --- a/examples/requests_https_circuitpython.py +++ b/examples/requests_https_circuitpython.py @@ -4,11 +4,12 @@ # adafruit_requests usage with a CircuitPython socket # this has been tested with Adafruit Metro ESP32-S2 Express -import socketpool import ssl -import adafruit_requests as requests import wifi -import secrets +import socketpool + +import adafruit_requests as requests + # Get wifi details and more from a secrets.py file try: @@ -61,4 +62,4 @@ json_resp = response.json() # Parse out the 'json' key from json_resp dict. print("JSON Data received from server:", json_resp["json"]) -print("-" * 40) +print("-" * 40) \ No newline at end of file From 5bc5ddd8cf29f82f190ee6ab9f2513459cfb6813 Mon Sep 17 00:00:00 2001 From: Jim Abernathy Date: Thu, 4 Feb 2021 06:03:39 -0500 Subject: [PATCH 013/305] formating change --- examples/requests_https_circuitpython.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/requests_https_circuitpython.py b/examples/requests_https_circuitpython.py index 31b7e12..026aa2d 100755 --- a/examples/requests_https_circuitpython.py +++ b/examples/requests_https_circuitpython.py @@ -62,4 +62,4 @@ json_resp = response.json() # Parse out the 'json' key from json_resp dict. print("JSON Data received from server:", json_resp["json"]) -print("-" * 40) \ No newline at end of file +print("-" * 40) From a1e8bbbb09d9a038bac0f96e186028997bc34a89 Mon Sep 17 00:00:00 2001 From: anecdata <16617689+anecdata@users.noreply.github.com> Date: Thu, 4 Feb 2021 14:39:04 -0600 Subject: [PATCH 014/305] Catch OSError to avoid socket leak --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 8abd58b..f10a342 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -560,7 +560,7 @@ def request( ok = True try: self._send_request(socket, host, method, path, headers, data, json) - except _SendFailed: + except (_SendFailed, OSError): ok = False if ok: # Read the H of "HTTP/1.1" to make sure the socket is alive. send can appear to work From 6567d208f47812f8ba2e8413bd18fb1c56697f01 Mon Sep 17 00:00:00 2001 From: anecdata <16617689+anecdata@users.noreply.github.com> Date: Sat, 6 Feb 2021 08:40:10 -0600 Subject: [PATCH 015/305] Catch recv-Into exception fix socket leak --- adafruit_requests.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index f10a342..3b56ea5 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -569,7 +569,10 @@ def request( result = socket.recv(1) else: result = bytearray(1) - socket.recv_into(result) + try: + socket.recv_into(result) + except OSError: + pass if result == b"H": # Things seem to be ok so break with socket set. break From 10eebbd41458cf2c40f351eae4c4d09efc62d9f9 Mon Sep 17 00:00:00 2001 From: anecdata <16617689+anecdata@users.noreply.github.com> Date: Mon, 8 Feb 2021 13:43:44 -0600 Subject: [PATCH 016/305] Add string to OutOfRetries exception --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 3b56ea5..a94db30 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -580,7 +580,7 @@ def request( socket = None if not socket: - raise OutOfRetries() + raise OutOfRetries("Repeated socket failures") resp = Response(socket, self) # our response if "location" in resp.headers and 300 <= resp.status_code <= 399: From 421f755405de800402a35f26b1ae99f209505045 Mon Sep 17 00:00:00 2001 From: dherrada Date: Wed, 3 Feb 2021 16:38:51 -0500 Subject: [PATCH 017/305] Hardcoded Black and REUSE versions Signed-off-by: dherrada --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aab5f1c..07f886c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,11 +4,11 @@ repos: - repo: https://github.com/python/black - rev: stable + rev: 20.8b1 hooks: - id: black - repo: https://github.com/fsfe/reuse-tool - rev: latest + rev: v0.12.1 hooks: - id: reuse - repo: https://github.com/pre-commit/pre-commit-hooks From 9bd3af88f987bafd46684028bcf1d0e4c0f91d40 Mon Sep 17 00:00:00 2001 From: John Furcean Date: Tue, 16 Feb 2021 23:08:38 -0500 Subject: [PATCH 018/305] proper fix thanks to @Neradoc --- adafruit_requests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/adafruit_requests.py b/adafruit_requests.py index a94db30..f85465e 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -405,6 +405,7 @@ def _free_sockets(self): self._close_socket(sock) def _get_socket(self, host, port, proto, *, timeout=1): + # pylint: disable=too-many-branches key = (host, port, proto) if key in self._open_sockets: sock = self._open_sockets[key] @@ -434,6 +435,8 @@ def _get_socket(self, host, port, proto, *, timeout=1): ) except OSError: continue + except RuntimeError: + continue connect_host = addr_info[-1][0] if proto == "https:": From 86a6362edf9b4c2c1fadf2b700ce645db556519b Mon Sep 17 00:00:00 2001 From: dherrada Date: Tue, 2 Mar 2021 16:46:17 -0500 Subject: [PATCH 019/305] Removed pylint process from github workflow Signed-off-by: dherrada --- .github/workflows/build.yml | 8 ++------ .pre-commit-config.yaml | 15 +++++++++++++++ .pylintrc | 2 +- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 59baa53..621d5ef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,18 +42,14 @@ jobs: # (e.g. - apt-get: gettext, etc; pip: circuitpython-build-tools, requirements.txt; etc.) run: | source actions-ci/install.sh - - name: Pip install pylint, Sphinx, pre-commit + - name: Pip install Sphinx, pre-commit run: | - pip install --force-reinstall pylint Sphinx sphinx-rtd-theme pre-commit + pip install --force-reinstall Sphinx sphinx-rtd-theme pre-commit - name: Library version run: git describe --dirty --always --tags - name: Pre-commit hooks run: | pre-commit run --all-files - - name: PyLint - run: | - pylint $( find . -path './adafruit*.py' ) - ([[ ! -d "examples" ]] || pylint --disable=missing-docstring,invalid-name,bad-whitespace $( find . -path "./examples/*.py" )) - name: Build assets run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . - name: Archive bundles diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 07f886c..354c761 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,3 +17,18 @@ repos: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace +- repo: https://github.com/pycqa/pylint + rev: pylint-2.7.1 + hooks: + - id: pylint + name: pylint (library code) + types: [python] + exclude: "^(docs/|examples/|setup.py$)" +- repo: local + hooks: + - id: pylint_examples + name: pylint (examples code) + description: Run pylint rules on "examples/*.py" files + entry: /usr/bin/env bash -c + args: ['([[ ! -d "examples" ]] || for example in $(find . -path "./examples/*.py"); do pylint --disable=missing-docstring,invalid-name $example; done)'] + language: system diff --git a/.pylintrc b/.pylintrc index 5c31f66..9ed669e 100755 --- a/.pylintrc +++ b/.pylintrc @@ -250,7 +250,7 @@ ignore-comments=yes ignore-docstrings=yes # Ignore imports when computing similarities. -ignore-imports=no +ignore-imports=yes # Minimum lines number of a similarity. min-similarity-lines=4 From 27c90c8b2fe44122c30d4492b78d170d5b97fdc2 Mon Sep 17 00:00:00 2001 From: dherrada Date: Tue, 2 Mar 2021 17:17:50 -0500 Subject: [PATCH 020/305] Re-added pylint install to build.yml Signed-off-by: dherrada --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 621d5ef..3baf502 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,9 +42,9 @@ jobs: # (e.g. - apt-get: gettext, etc; pip: circuitpython-build-tools, requirements.txt; etc.) run: | source actions-ci/install.sh - - name: Pip install Sphinx, pre-commit + - name: Pip install pylint, Sphinx, pre-commit run: | - pip install --force-reinstall Sphinx sphinx-rtd-theme pre-commit + pip install --force-reinstall pylint Sphinx sphinx-rtd-theme pre-commit - name: Library version run: git describe --dirty --always --tags - name: Pre-commit hooks From eb116da8b785e54243c55bac044bdb2c2985b552 Mon Sep 17 00:00:00 2001 From: dherrada Date: Fri, 19 Mar 2021 13:47:11 -0400 Subject: [PATCH 021/305] "Increase duplicate code check threshold " --- .pylintrc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.pylintrc b/.pylintrc index 9ed669e..0238b90 100755 --- a/.pylintrc +++ b/.pylintrc @@ -22,8 +22,7 @@ ignore-patterns= #init-hook= # Use multiple processes to speed up Pylint. -# jobs=1 -jobs=2 +jobs=1 # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. @@ -253,7 +252,7 @@ ignore-docstrings=yes ignore-imports=yes # Minimum lines number of a similarity. -min-similarity-lines=4 +min-similarity-lines=12 [BASIC] From a15369641682147eafce8a0df2d9e7ad82e60036 Mon Sep 17 00:00:00 2001 From: Patrick <4002194+askpatrickw@users.noreply.github.com> Date: Wed, 24 Mar 2021 22:45:17 -0700 Subject: [PATCH 022/305] Fixes #48 Adds airlift to example --- adafruit_requests.py | 8 +++++--- examples/requests_simpletest.py | 9 +++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index f85465e..fcae649 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -652,9 +652,11 @@ def wrap_socket(self, socket, server_hostname=None): def set_socket(sock, iface=None): """Legacy API for setting the socket and network interface. Use a `Session` instead.""" global _default_session # pylint: disable=global-statement,invalid-name - _default_session = Session(sock, _FakeSSLContext(iface)) - if iface: - sock.set_interface(iface) + if not iface: + _default_session = Session(sock, _FakeSSLContext(sock._the_interface)) + else: + _default_session = Session(sock, _FakeSSLContext(iface)) + sock.set_interface(iface) def request(method, url, data=None, json=None, headers=None, stream=False, timeout=1): diff --git a/examples/requests_simpletest.py b/examples/requests_simpletest.py index 519f347..14cf66c 100755 --- a/examples/requests_simpletest.py +++ b/examples/requests_simpletest.py @@ -29,6 +29,11 @@ # esp32_ready = DigitalInOut(board.D10) # esp32_reset = DigitalInOut(board.D5) +# If you have an AirLift Featherwing or ItsyBitsy Airlift: +# esp32_cs = DigitalInOut(board.D13) +# esp32_ready = DigitalInOut(board.D11) +# esp32_reset = DigitalInOut(board.D12) + spi = busio.SPI(board.SCK, board.MOSI, board.MISO) esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) @@ -46,8 +51,8 @@ requests.set_socket(socket, esp) TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" -JSON_GET_URL = "http://httpbin.org/get" -JSON_POST_URL = "http://httpbin.org/post" +JSON_GET_URL = "https://httpbin.org/get" +JSON_POST_URL = "https://httpbin.org/post" print("Fetching text from %s" % TEXT_URL) response = requests.get(TEXT_URL) From 8154d7356649b9d5c4d4a7d9d07d4cbd9cd89fab Mon Sep 17 00:00:00 2001 From: Patrick <4002194+askpatrickw@users.noreply.github.com> Date: Thu, 25 Mar 2021 01:15:22 -0700 Subject: [PATCH 023/305] make tests happy for pylint and precommit --- .pre-commit-config.yaml | 10 +++- tests/chunk_test.py | 77 ++++++++++++++----------- tests/concurrent_test.py | 67 +++++++++++----------- tests/header_test.py | 22 ++++---- tests/legacy_mocket.py | 19 ++++--- tests/legacy_test.py | 118 +++++++++++++++++++-------------------- tests/mocket.py | 26 ++++++--- tests/parse_test.py | 36 ++++++------ tests/post_test.py | 54 +++++++++--------- tests/protocol_test.py | 58 +++++++++---------- tests/reuse_test.py | 104 +++++++++++++++++----------------- 11 files changed, 315 insertions(+), 276 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 354c761..6f11a4c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: - id: pylint name: pylint (library code) types: [python] - exclude: "^(docs/|examples/|setup.py$)" + exclude: "^(docs/|examples/|tests/|setup.py$)" - repo: local hooks: - id: pylint_examples @@ -32,3 +32,11 @@ repos: entry: /usr/bin/env bash -c args: ['([[ ! -d "examples" ]] || for example in $(find . -path "./examples/*.py"); do pylint --disable=missing-docstring,invalid-name $example; done)'] language: system +- repo: local + hooks: + - id: pylint_tests + name: pylint (tests code) + description: Run pylint rules on "tests/*.py" files + entry: /usr/bin/env bash -c + args: ['([[ ! -d "tests" ]] || for example in $(find . -path "./tests/*.py"); do pylint --disable=missing-docstring,invalid-name $example; done)'] + language: system diff --git a/tests/chunk_test.py b/tests/chunk_test.py index 166f67c..a03eec8 100644 --- a/tests/chunk_test.py +++ b/tests/chunk_test.py @@ -2,16 +2,18 @@ # # SPDX-License-Identifier: Unlicense +""" Chunk Tests """ + from unittest import mock import mocket import adafruit_requests -ip = "1.2.3.4" -host = "wifitest.adafruit.com" -path = "/testwifi/index.html" -text = b"This is a test of Adafruit WiFi!\r\nIf you can read this, its working :)" -headers = b"HTTP/1.0 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n" -headers_extra_space = b"HTTP/1.0 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n" +IP = "1.2.3.4" +HOST = "wifitest.adafruit.com" +PATH = "/testwifi/index.html" +TEXT = b"This is a test of Adafruit WiFi!\r\nIf you can read this, its working :)" +HEADERS = b"HTTP/1.0 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n" +HEADERS_EXTRA_SPACE = b"HTTP/1.0 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n" def _chunk(response, split, extra=b""): @@ -36,18 +38,20 @@ def _chunk(response, split, extra=b""): return chunked -def do_test_get_text(extra=b""): +def do_test_get_text( + extra=b"", +): pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - c = _chunk(text, 33, extra) - print(c) - sock = mocket.Mocket(headers + c) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + chunk = _chunk(TEXT, 33, extra) + print(chunk) + sock = mocket.Mocket(HEADERS + chunk) pool.socket.return_value = sock - s = adafruit_requests.Session(pool) - r = s.get("http://" + host + path) + requests_session = adafruit_requests.Session(pool) + response = requests_session.get("http://" + HOST + PATH) - sock.connect.assert_called_once_with((ip, 80)) + sock.connect.assert_called_once_with((IP, 80)) sock.send.assert_has_calls( [ @@ -63,7 +67,7 @@ def do_test_get_text(extra=b""): mock.call(b"wifitest.adafruit.com"), ] ) - assert r.text == str(text, "utf-8") + assert response.text == str(TEXT, "utf-8") def test_get_text(): @@ -74,19 +78,22 @@ def test_get_text_extra(): do_test_get_text(b";blahblah; blah") -def do_test_close_flush(extra=b""): - """Test that a chunked response can be closed even when the request contents were not accessed.""" +def do_test_close_flush( + extra=b"", +): + """Test that a chunked response can be closed even when the + request contents were not accessed.""" pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - c = _chunk(text, 33, extra) - print(c) - sock = mocket.Mocket(headers + c) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + chunk = _chunk(TEXT, 33, extra) + print(chunk) + sock = mocket.Mocket(HEADERS + chunk) pool.socket.return_value = sock - s = adafruit_requests.Session(pool) - r = s.get("http://" + host + path) + requests_session = adafruit_requests.Session(pool) + response = requests_session.get("http://" + HOST + PATH) - sock.connect.assert_called_once_with((ip, 80)) + sock.connect.assert_called_once_with((IP, 80)) sock.send.assert_has_calls( [ @@ -103,7 +110,7 @@ def do_test_close_flush(extra=b""): ] ) - r.close() + response.close() def test_close_flush(): @@ -114,18 +121,20 @@ def test_close_flush_extra(): do_test_close_flush(b";blahblah; blah") -def do_test_get_text_extra_space(extra=b""): +def do_test_get_text_extra_space( + extra=b"", +): pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - c = _chunk(text, 33, extra) - print(c) - sock = mocket.Mocket(headers_extra_space + c) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + chunk = _chunk(TEXT, 33, extra) + print(chunk) + sock = mocket.Mocket(HEADERS_EXTRA_SPACE + chunk) pool.socket.return_value = sock - s = adafruit_requests.Session(pool) - r = s.get("http://" + host + path) + requests_session = adafruit_requests.Session(pool) + response = requests_session.get("http://" + HOST + PATH) - sock.connect.assert_called_once_with((ip, 80)) + sock.connect.assert_called_once_with((IP, 80)) sock.send.assert_has_calls( [ @@ -141,4 +150,4 @@ def do_test_get_text_extra_space(extra=b""): mock.call(b"wifitest.adafruit.com"), ] ) - assert r.text == str(text, "utf-8") + assert response.text == str(TEXT, "utf-8") diff --git a/tests/concurrent_test.py b/tests/concurrent_test.py index 9b2c894..42df46c 100644 --- a/tests/concurrent_test.py +++ b/tests/concurrent_test.py @@ -2,34 +2,35 @@ # # SPDX-License-Identifier: Unlicense +""" Concurrent Tests """ + +import errno from unittest import mock import mocket -import pytest -import errno import adafruit_requests -ip = "1.2.3.4" -host = "wifitest.adafruit.com" -host2 = "wifitest2.adafruit.com" -path = "/testwifi/index.html" -text = b"This is a test of Adafruit WiFi!\r\nIf you can read this, its working :)" -response = b"HTTP/1.0 200 OK\r\nContent-Length: 70\r\n\r\n" + text +IP = "1.2.3.4" +HOST = "wifitest.adafruit.com" +HOST2 = "test.adafruit.com" +PATH = "/testwifi/index.html" +TEXT = b"This is a test of Adafruit WiFi!\r\nIf you can read this, its working :)" +RESPONSE = b"HTTP/1.0 200 OK\r\nContent-Length: 70\r\n\r\n" + TEXT -def test_second_connect_fails_memoryerror(): +def test_second_connect_fails_memoryerror(): # pylint: disable=invalid-name pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(response) - sock2 = mocket.Mocket(response) - sock3 = mocket.Mocket(response) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE) + sock2 = mocket.Mocket(RESPONSE) + sock3 = mocket.Mocket(RESPONSE) pool.socket.call_count = 0 # Reset call count pool.socket.side_effect = [sock, sock2, sock3] sock2.connect.side_effect = MemoryError() ssl = mocket.SSLContext() - s = adafruit_requests.Session(pool, ssl) - r = s.get("https://" + host + path) + requests_session = adafruit_requests.Session(pool, ssl) + response = requests_session.get("https://" + HOST + PATH) sock.send.assert_has_calls( [ @@ -44,14 +45,13 @@ def test_second_connect_fails_memoryerror(): mock.call(b"\r\n"), ] ) - assert r.text == str(text, "utf-8") + assert response.text == str(TEXT, "utf-8") - host2 = "test.adafruit.com" - s.get("https://" + host2 + path) + requests_session.get("https://" + HOST2 + PATH) - sock.connect.assert_called_once_with((host, 443)) - sock2.connect.assert_called_once_with((host2, 443)) - sock3.connect.assert_called_once_with((host2, 443)) + sock.connect.assert_called_once_with((HOST, 443)) + sock2.connect.assert_called_once_with((HOST2, 443)) + sock3.connect.assert_called_once_with((HOST2, 443)) # Make sure that the socket is closed after send fails. sock.close.assert_called_once() sock2.close.assert_called_once() @@ -59,20 +59,20 @@ def test_second_connect_fails_memoryerror(): assert pool.socket.call_count == 3 -def test_second_connect_fails_oserror(): +def test_second_connect_fails_oserror(): # pylint: disable=invalid-name pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(response) - sock2 = mocket.Mocket(response) - sock3 = mocket.Mocket(response) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE) + sock2 = mocket.Mocket(RESPONSE) + sock3 = mocket.Mocket(RESPONSE) pool.socket.call_count = 0 # Reset call count pool.socket.side_effect = [sock, sock2, sock3] sock2.connect.side_effect = OSError(errno.ENOMEM) ssl = mocket.SSLContext() - s = adafruit_requests.Session(pool, ssl) - r = s.get("https://" + host + path) + requests_session = adafruit_requests.Session(pool, ssl) + response = requests_session.get("https://" + HOST + PATH) sock.send.assert_has_calls( [ @@ -87,14 +87,13 @@ def test_second_connect_fails_oserror(): mock.call(b"\r\n"), ] ) - assert r.text == str(text, "utf-8") + assert response.text == str(TEXT, "utf-8") - host2 = "test.adafruit.com" - s.get("https://" + host2 + path) + requests_session.get("https://" + HOST2 + PATH) - sock.connect.assert_called_once_with((host, 443)) - sock2.connect.assert_called_once_with((host2, 443)) - sock3.connect.assert_called_once_with((host2, 443)) + sock.connect.assert_called_once_with((HOST, 443)) + sock2.connect.assert_called_once_with((HOST2, 443)) + sock3.connect.assert_called_once_with((HOST2, 443)) # Make sure that the socket is closed after send fails. sock.close.assert_called_once() sock2.close.assert_called_once() diff --git a/tests/header_test.py b/tests/header_test.py index 346bbf0..05bf88f 100644 --- a/tests/header_test.py +++ b/tests/header_test.py @@ -2,34 +2,34 @@ # # SPDX-License-Identifier: Unlicense -from unittest import mock +""" Header Tests """ + import mocket -import json import adafruit_requests -ip = "1.2.3.4" -host = "httpbin.org" -response_headers = b"HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n" +IP = "1.2.3.4" +HOST = "httpbin.org" +RESPONSE_HEADERS = b"HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n" def test_json(): pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(response_headers) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE_HEADERS) pool.socket.return_value = sock sent = [] def _send(data): - sent.append(data) + sent.append(data) # pylint: disable=no-member return len(data) sock.send.side_effect = _send - s = adafruit_requests.Session(pool) + requests_session = adafruit_requests.Session(pool) headers = {"user-agent": "blinka/1.0.0"} - r = s.get("http://" + host + "/get", headers=headers) + requests_session.get("http://" + HOST + "/get", headers=headers) - sock.connect.assert_called_once_with((ip, 80)) + sock.connect.assert_called_once_with((IP, 80)) sent = b"".join(sent).lower() assert b"user-agent: blinka/1.0.0\r\n" in sent # The current implementation sends two user agents. Fix it, and uncomment below. diff --git a/tests/legacy_mocket.py b/tests/legacy_mocket.py index 4506a6f..8e18c4f 100644 --- a/tests/legacy_mocket.py +++ b/tests/legacy_mocket.py @@ -2,6 +2,8 @@ # # SPDX-License-Identifier: Unlicense +""" Mock for Legacy Socket """ + from unittest import mock SOCK_STREAM = 0 @@ -12,7 +14,9 @@ socket = mock.Mock() -class Mocket: +class Mocket: # pylint: disable=too-few-public-methods + """ Mock Socket """ + def __init__(self, response): self.settimeout = mock.Mock() self.close = mock.Mock() @@ -24,21 +28,20 @@ def __init__(self, response): self._response = response self._position = 0 - def _send(self, data): + def _send(self, data): # pylint: disable=unused-argument if self.fail_next_send: self.fail_next_send = False raise RuntimeError("Send failed") - return None def _readline(self): i = self._response.find(b"\r\n", self._position) - r = self._response[self._position : i + 2] + response = self._response[self._position : i + 2] self._position = i + 2 - return r + return response def _recv(self, count): end = self._position + count - r = self._response[self._position : end] + response = self._response[self._position : end] self._position = end - print(r) - return r + print(response) + return response diff --git a/tests/legacy_test.py b/tests/legacy_test.py index 7743f3f..cba604d 100644 --- a/tests/legacy_test.py +++ b/tests/legacy_test.py @@ -2,69 +2,70 @@ # # SPDX-License-Identifier: Unlicense +""" Legacy Tests """ + from unittest import mock -import legacy_mocket as mocket import json -import pytest +import legacy_mocket as mocket import adafruit_requests -ip = "1.2.3.4" -host = "httpbin.org" -response = {"Date": "July 25, 2019"} -encoded = json.dumps(response).encode("utf-8") -headers = "HTTP/1.0 200 OK\r\nContent-Length: {}\r\n\r\n".format(len(encoded)).encode( +IP = "1.2.3.4" +HOST = "httpbin.org" +RESPONSE = {"Date": "July 25, 2019"} +ENCODED = json.dumps(RESPONSE).encode("utf-8") +HEADERS = "HTTP/1.0 200 OK\r\nContent-Length: {}\r\n\r\n".format(len(ENCODED)).encode( "utf-8" ) def test_get_json(): - mocket.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(headers + encoded) + mocket.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(HEADERS + ENCODED) mocket.socket.return_value = sock adafruit_requests.set_socket(mocket, mocket.interface) - r = adafruit_requests.get("http://" + host + "/get") + response = adafruit_requests.get("http://" + HOST + "/get") - sock.connect.assert_called_once_with((ip, 80)) - assert r.json() == response - r.close() + sock.connect.assert_called_once_with((IP, 80)) + assert response.json() == RESPONSE + response.close() def test_tls_mode(): - mocket.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(headers + encoded) + mocket.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(HEADERS + ENCODED) mocket.socket.return_value = sock adafruit_requests.set_socket(mocket, mocket.interface) - r = adafruit_requests.get("https://" + host + "/get") + response = adafruit_requests.get("https://" + HOST + "/get") - sock.connect.assert_called_once_with((host, 443), mocket.interface.TLS_MODE) - assert r.json() == response - r.close() + sock.connect.assert_called_once_with((HOST, 443), mocket.interface.TLS_MODE) + assert response.json() == RESPONSE + response.close() def test_post_string(): - mocket.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(headers + encoded) + mocket.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(HEADERS + ENCODED) mocket.socket.return_value = sock adafruit_requests.set_socket(mocket, mocket.interface) data = "31F" - r = adafruit_requests.post("http://" + host + "/post", data=data) - sock.connect.assert_called_once_with((ip, 80)) + response = adafruit_requests.post("http://" + HOST + "/post", data=data) + sock.connect.assert_called_once_with((IP, 80)) sock.send.assert_called_with(b"31F") - r.close() + response.close() def test_second_tls_send_fails(): - mocket.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(headers + encoded) - sock2 = mocket.Mocket(headers + encoded) + mocket.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(HEADERS + ENCODED) + sock2 = mocket.Mocket(HEADERS + ENCODED) mocket.socket.call_count = 0 # Reset call count mocket.socket.side_effect = [sock, sock2] adafruit_requests.set_socket(mocket, mocket.interface) - r = adafruit_requests.get("https://" + host + "/testwifi/index.html") + response = adafruit_requests.get("https://" + HOST + "/testwifi/index.html") sock.send.assert_has_calls( [ @@ -75,17 +76,17 @@ def test_second_tls_send_fails(): sock.send.assert_has_calls( [ mock.call(b"Host: "), - mock.call(host.encode("utf-8")), + mock.call(HOST.encode("utf-8")), mock.call(b"\r\n"), ] ) - assert r.text == str(encoded, "utf-8") + assert response.text == str(ENCODED, "utf-8") sock.fail_next_send = True - adafruit_requests.get("https://" + host + "/get2") + adafruit_requests.get("https://" + HOST + "/get2") - sock.connect.assert_called_once_with((host, 443), mocket.interface.TLS_MODE) - sock2.connect.assert_called_once_with((host, 443), mocket.interface.TLS_MODE) + sock.connect.assert_called_once_with((HOST, 443), mocket.interface.TLS_MODE) + sock2.connect.assert_called_once_with((HOST, 443), mocket.interface.TLS_MODE) # Make sure that the socket is closed after send fails. sock.close.assert_called_once() assert sock2.close.call_count == 0 @@ -93,14 +94,14 @@ def test_second_tls_send_fails(): def test_second_send_fails(): - mocket.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(headers + encoded) - sock2 = mocket.Mocket(headers + encoded) + mocket.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(HEADERS + ENCODED) + sock2 = mocket.Mocket(HEADERS + ENCODED) mocket.socket.call_count = 0 # Reset call count mocket.socket.side_effect = [sock, sock2] adafruit_requests.set_socket(mocket, mocket.interface) - r = adafruit_requests.get("http://" + host + "/testwifi/index.html") + response = adafruit_requests.get("http://" + HOST + "/testwifi/index.html") sock.send.assert_has_calls( [ @@ -111,17 +112,17 @@ def test_second_send_fails(): sock.send.assert_has_calls( [ mock.call(b"Host: "), - mock.call(host.encode("utf-8")), + mock.call(HOST.encode("utf-8")), mock.call(b"\r\n"), ] ) - assert r.text == str(encoded, "utf-8") + assert response.text == str(ENCODED, "utf-8") sock.fail_next_send = True - adafruit_requests.get("http://" + host + "/get2") + adafruit_requests.get("http://" + HOST + "/get2") - sock.connect.assert_called_once_with((ip, 80)) - sock2.connect.assert_called_once_with((ip, 80)) + sock.connect.assert_called_once_with((IP, 80)) + sock2.connect.assert_called_once_with((IP, 80)) # Make sure that the socket is closed after send fails. sock.close.assert_called_once() assert sock2.close.call_count == 0 @@ -129,15 +130,14 @@ def test_second_send_fails(): def test_first_read_fails(): - mocket.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) + mocket.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) sock = mocket.Mocket(b"") - sock2 = mocket.Mocket(headers + encoded) + sock2 = mocket.Mocket(HEADERS + ENCODED) mocket.socket.call_count = 0 # Reset call count mocket.socket.side_effect = [sock, sock2] adafruit_requests.set_socket(mocket, mocket.interface) - - r = adafruit_requests.get("http://" + host + "/testwifi/index.html") + adafruit_requests.get("http://" + HOST + "/testwifi/index.html") sock.send.assert_has_calls( [ @@ -148,7 +148,7 @@ def test_first_read_fails(): sock.send.assert_has_calls( [ mock.call(b"Host: "), - mock.call(host.encode("utf-8")), + mock.call(HOST.encode("utf-8")), mock.call(b"\r\n"), ] ) @@ -156,29 +156,29 @@ def test_first_read_fails(): sock2.send.assert_has_calls( [ mock.call(b"Host: "), - mock.call(host.encode("utf-8")), + mock.call(HOST.encode("utf-8")), mock.call(b"\r\n"), ] ) - sock.connect.assert_called_once_with((ip, 80)) - sock2.connect.assert_called_once_with((ip, 80)) + sock.connect.assert_called_once_with((IP, 80)) + sock2.connect.assert_called_once_with((IP, 80)) # Make sure that the socket is closed after the first receive fails. sock.close.assert_called_once() assert mocket.socket.call_count == 2 def test_second_tls_connect_fails(): - mocket.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(headers + encoded) - sock2 = mocket.Mocket(headers + encoded) - sock3 = mocket.Mocket(headers + encoded) + mocket.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(HEADERS + ENCODED) + sock2 = mocket.Mocket(HEADERS + ENCODED) + sock3 = mocket.Mocket(HEADERS + ENCODED) mocket.socket.call_count = 0 # Reset call count mocket.socket.side_effect = [sock, sock2, sock3] sock2.connect.side_effect = RuntimeError("error connecting") adafruit_requests.set_socket(mocket, mocket.interface) - r = adafruit_requests.get("https://" + host + "/testwifi/index.html") + response = adafruit_requests.get("https://" + HOST + "/testwifi/index.html") sock.send.assert_has_calls( [ @@ -189,16 +189,16 @@ def test_second_tls_connect_fails(): sock.send.assert_has_calls( [ mock.call(b"Host: "), - mock.call(host.encode("utf-8")), + mock.call(HOST.encode("utf-8")), mock.call(b"\r\n"), ] ) - assert r.text == str(encoded, "utf-8") + assert response.text == str(ENCODED, "utf-8") host2 = "test.adafruit.com" - r = adafruit_requests.get("https://" + host2 + "/get2") + response = adafruit_requests.get("https://" + host2 + "/get2") - sock.connect.assert_called_once_with((host, 443), mocket.interface.TLS_MODE) + sock.connect.assert_called_once_with((HOST, 443), mocket.interface.TLS_MODE) sock2.connect.assert_called_once_with((host2, 443), mocket.interface.TLS_MODE) sock3.connect.assert_called_once_with((host2, 443), mocket.interface.TLS_MODE) # Make sure that the socket is closed after send fails. diff --git a/tests/mocket.py b/tests/mocket.py index 2a44ed2..578a808 100644 --- a/tests/mocket.py +++ b/tests/mocket.py @@ -2,10 +2,14 @@ # # SPDX-License-Identifier: Unlicense +""" Mock Socket """ + from unittest import mock -class MocketPool: +class MocketPool: # pylint: disable=too-few-public-methods + """ Mock SocketPool """ + SOCK_STREAM = 0 def __init__(self): @@ -13,7 +17,9 @@ def __init__(self): self.socket = mock.Mock() -class Mocket: +class Mocket: # pylint: disable=too-few-public-methods + """ Mock Socket """ + def __init__(self, response): self.settimeout = mock.Mock() self.close = mock.Mock() @@ -34,15 +40,15 @@ def _send(self, data): def _readline(self): i = self._response.find(b"\r\n", self._position) - r = self._response[self._position : i + 2] + response = self._response[self._position : i + 2] self._position = i + 2 - return r + return response def _recv(self, count): end = self._position + count - r = self._response[self._position : end] + response = self._response[self._position : end] self._position = end - return r + return response def _recv_into(self, buf, nbytes=0): assert isinstance(nbytes, int) and nbytes >= 0 @@ -56,9 +62,13 @@ def _recv_into(self, buf, nbytes=0): return read -class SSLContext: +class SSLContext: # pylint: disable=too-few-public-methods + """ Mock SSL Context """ + def __init__(self): self.wrap_socket = mock.Mock(side_effect=self._wrap_socket) - def _wrap_socket(self, sock, server_hostname=None): + def _wrap_socket( + self, sock, server_hostname=None + ): # pylint: disable=no-self-use,unused-argument return sock diff --git a/tests/parse_test.py b/tests/parse_test.py index 551b264..36ccd10 100644 --- a/tests/parse_test.py +++ b/tests/parse_test.py @@ -2,31 +2,35 @@ # # SPDX-License-Identifier: Unlicense -from unittest import mock -import mocket +""" Parse Tests """ + import json +import mocket import adafruit_requests -ip = "1.2.3.4" -host = "httpbin.org" -response = {"Date": "July 25, 2019"} -encoded = json.dumps(response).encode("utf-8") +IP = "1.2.3.4" +HOST = "httpbin.org" +RESPONSE = {"Date": "July 25, 2019"} +ENCODED = json.dumps(RESPONSE).encode("utf-8") # Padding here tests the case where a header line is exactly 32 bytes buffered by # aligning the Content-Type header after it. -headers = "HTTP/1.0 200 OK\r\npadding: 000\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n".format( - len(encoded) -).encode( - "utf-8" +HEADERS = ( + ( + "HTTP/1.0 200 OK\r\npadding: 000\r\n" + "Content-Type: application/json\r\nContent-Length: {}\r\n\r\n" + ) + .format(len(ENCODED)) + .encode("utf-8") ) def test_json(): pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(headers + encoded) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(HEADERS + ENCODED) pool.socket.return_value = sock - s = adafruit_requests.Session(pool) - r = s.get("http://" + host + "/get") - sock.connect.assert_called_once_with((ip, 80)) - assert r.json() == response + requests_session = adafruit_requests.Session(pool) + response = requests_session.get("http://" + HOST + "/get") + sock.connect.assert_called_once_with((IP, 80)) + assert response.json() == RESPONSE diff --git a/tests/post_test.py b/tests/post_test.py index 0438c94..1d0364f 100644 --- a/tests/post_test.py +++ b/tests/post_test.py @@ -2,29 +2,31 @@ # # SPDX-License-Identifier: Unlicense +""" Post Tests """ + from unittest import mock -import mocket import json +import mocket import adafruit_requests -ip = "1.2.3.4" -host = "httpbin.org" -response = {} -encoded = json.dumps(response).encode("utf-8") -headers = "HTTP/1.0 200 OK\r\nContent-Length: {}\r\n\r\n".format(len(encoded)).encode( +IP = "1.2.3.4" +HOST = "httpbin.org" +RESPONSE = {} +ENCODED = json.dumps(RESPONSE).encode("utf-8") +HEADERS = "HTTP/1.0 200 OK\r\nContent-Length: {}\r\n\r\n".format(len(ENCODED)).encode( "utf-8" ) def test_method(): pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(headers + encoded) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(HEADERS + ENCODED) pool.socket.return_value = sock - s = adafruit_requests.Session(pool) - r = s.post("http://" + host + "/post") - sock.connect.assert_called_once_with((ip, 80)) + requests_session = adafruit_requests.Session(pool) + requests_session.post("http://" + HOST + "/post") + sock.connect.assert_called_once_with((IP, 80)) sock.send.assert_has_calls( [ @@ -44,38 +46,38 @@ def test_method(): def test_string(): pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(headers + encoded) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(HEADERS + ENCODED) pool.socket.return_value = sock - s = adafruit_requests.Session(pool) + requests_session = adafruit_requests.Session(pool) data = "31F" - r = s.post("http://" + host + "/post", data=data) - sock.connect.assert_called_once_with((ip, 80)) + requests_session.post("http://" + HOST + "/post", data=data) + sock.connect.assert_called_once_with((IP, 80)) sock.send.assert_called_with(b"31F") def test_form(): pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(headers + encoded) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(HEADERS + ENCODED) pool.socket.return_value = sock - s = adafruit_requests.Session(pool) + requests_session = adafruit_requests.Session(pool) data = {"Date": "July 25, 2019"} - r = s.post("http://" + host + "/post", data=data) - sock.connect.assert_called_once_with((ip, 80)) + requests_session.post("http://" + HOST + "/post", data=data) + sock.connect.assert_called_once_with((IP, 80)) sock.send.assert_called_with(b"Date=July 25, 2019") def test_json(): pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(headers + encoded) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(HEADERS + ENCODED) pool.socket.return_value = sock - s = adafruit_requests.Session(pool) + requests_session = adafruit_requests.Session(pool) json_data = {"Date": "July 25, 2019"} - r = s.post("http://" + host + "/post", json=json_data) - sock.connect.assert_called_once_with((ip, 80)) + requests_session.post("http://" + HOST + "/post", json=json_data) + sock.connect.assert_called_once_with((IP, 80)) sock.send.assert_called_with(b'{"Date": "July 25, 2019"}') diff --git a/tests/protocol_test.py b/tests/protocol_test.py index 35b3a95..fb0471b 100644 --- a/tests/protocol_test.py +++ b/tests/protocol_test.py @@ -2,40 +2,42 @@ # # SPDX-License-Identifier: Unlicense +""" Protocol Tests """ + from unittest import mock import mocket import pytest import adafruit_requests -ip = "1.2.3.4" -host = "wifitest.adafruit.com" -path = "/testwifi/index.html" -text = b"This is a test of Adafruit WiFi!\r\nIf you can read this, its working :)" -response = b"HTTP/1.0 200 OK\r\nContent-Length: 70\r\n\r\n" + text +IP = "1.2.3.4" +HOST = "wifitest.adafruit.com" +PATH = "/testwifi/index.html" +TEXT = b"This is a test of Adafruit WiFi!\r\nIf you can read this, its working :)" +RESPONSE = b"HTTP/1.0 200 OK\r\nContent-Length: 70\r\n\r\n" + TEXT def test_get_https_no_ssl(): pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(response) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE) pool.socket.return_value = sock - s = adafruit_requests.Session(pool) + requests_session = adafruit_requests.Session(pool) with pytest.raises(RuntimeError): - r = s.get("https://" + host + path) + requests_session.get("https://" + HOST + PATH) def test_get_https_text(): pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(response) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE) pool.socket.return_value = sock ssl = mocket.SSLContext() - s = adafruit_requests.Session(pool, ssl) - r = s.get("https://" + host + path) + requests_session = adafruit_requests.Session(pool, ssl) + response = requests_session.get("https://" + HOST + PATH) - sock.connect.assert_called_once_with((host, 443)) + sock.connect.assert_called_once_with((HOST, 443)) sock.send.assert_has_calls( [ @@ -51,22 +53,22 @@ def test_get_https_text(): mock.call(b"wifitest.adafruit.com"), ] ) - assert r.text == str(text, "utf-8") + assert response.text == str(TEXT, "utf-8") # Close isn't needed but can be called to release the socket early. - r.close() + response.close() def test_get_http_text(): pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(response) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE) pool.socket.return_value = sock - s = adafruit_requests.Session(pool) - r = s.get("http://" + host + path) + requests_session = adafruit_requests.Session(pool) + response = requests_session.get("http://" + HOST + PATH) - sock.connect.assert_called_once_with((ip, 80)) + sock.connect.assert_called_once_with((IP, 80)) sock.send.assert_has_calls( [ @@ -82,20 +84,20 @@ def test_get_http_text(): mock.call(b"wifitest.adafruit.com"), ] ) - assert r.text == str(text, "utf-8") + assert response.text == str(TEXT, "utf-8") def test_get_close(): """Test that a response can be closed without the contents being accessed.""" pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(response) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE) pool.socket.return_value = sock - s = adafruit_requests.Session(pool) - r = s.get("http://" + host + path) + requests_session = adafruit_requests.Session(pool) + response = requests_session.get("http://" + HOST + PATH) - sock.connect.assert_called_once_with((ip, 80)) + sock.connect.assert_called_once_with((IP, 80)) sock.send.assert_has_calls( [ @@ -111,4 +113,4 @@ def test_get_close(): mock.call(b"wifitest.adafruit.com"), ] ) - r.close() + response.close() diff --git a/tests/reuse_test.py b/tests/reuse_test.py index def1ca8..0f9cc55 100644 --- a/tests/reuse_test.py +++ b/tests/reuse_test.py @@ -2,28 +2,30 @@ # # SPDX-License-Identifier: Unlicense +""" Reuse Tests """ + from unittest import mock import mocket import pytest import adafruit_requests -ip = "1.2.3.4" -host = "wifitest.adafruit.com" -host2 = "wifitest2.adafruit.com" -path = "/testwifi/index.html" -text = b"This is a test of Adafruit WiFi!\r\nIf you can read this, its working :)" -response = b"HTTP/1.0 200 OK\r\nContent-Length: 70\r\n\r\n" + text +IP = "1.2.3.4" +HOST = "wifitest.adafruit.com" +HOST2 = "wifitest2.adafruit.com" +PATH = "/testwifi/index.html" +TEXT = b"This is a test of Adafruit WiFi!\r\nIf you can read this, its working :)" +RESPONSE = b"HTTP/1.0 200 OK\r\nContent-Length: 70\r\n\r\n" + TEXT def test_get_twice(): pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(response + response) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE + RESPONSE) pool.socket.return_value = sock ssl = mocket.SSLContext() - s = adafruit_requests.Session(pool, ssl) - r = s.get("https://" + host + path) + requests_session = adafruit_requests.Session(pool, ssl) + response = requests_session.get("https://" + HOST + PATH) sock.send.assert_has_calls( [ @@ -39,9 +41,9 @@ def test_get_twice(): mock.call(b"wifitest.adafruit.com"), ] ) - assert r.text == str(text, "utf-8") + assert response.text == str(TEXT, "utf-8") - r = s.get("https://" + host + path + "2") + response = requests_session.get("https://" + HOST + PATH + "2") sock.send.assert_has_calls( [ @@ -58,20 +60,20 @@ def test_get_twice(): ] ) - assert r.text == str(text, "utf-8") - sock.connect.assert_called_once_with((host, 443)) + assert response.text == str(TEXT, "utf-8") + sock.connect.assert_called_once_with((HOST, 443)) pool.socket.assert_called_once() def test_get_twice_after_second(): pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(response + response) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE + RESPONSE) pool.socket.return_value = sock ssl = mocket.SSLContext() - s = adafruit_requests.Session(pool, ssl) - r = s.get("https://" + host + path) + requests_session = adafruit_requests.Session(pool, ssl) + response = requests_session.get("https://" + HOST + PATH) sock.send.assert_has_calls( [ @@ -88,7 +90,7 @@ def test_get_twice_after_second(): ] ) - r2 = s.get("https://" + host + path + "2") + requests_session.get("https://" + HOST + PATH + "2") sock.send.assert_has_calls( [ @@ -104,25 +106,25 @@ def test_get_twice_after_second(): mock.call(b"wifitest.adafruit.com"), ] ) - sock.connect.assert_called_once_with((host, 443)) + sock.connect.assert_called_once_with((HOST, 443)) pool.socket.assert_called_once() with pytest.raises(RuntimeError): - r.text == str(text, "utf-8") + response.text == str(TEXT, "utf-8") # pylint: disable=expression-not-assigned def test_connect_out_of_memory(): pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(response) - sock2 = mocket.Mocket(response) - sock3 = mocket.Mocket(response) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE) + sock2 = mocket.Mocket(RESPONSE) + sock3 = mocket.Mocket(RESPONSE) pool.socket.side_effect = [sock, sock2, sock3] sock2.connect.side_effect = MemoryError() ssl = mocket.SSLContext() - s = adafruit_requests.Session(pool, ssl) - r = s.get("https://" + host + path) + requests_session = adafruit_requests.Session(pool, ssl) + response = requests_session.get("https://" + HOST + PATH) sock.send.assert_has_calls( [ @@ -138,9 +140,9 @@ def test_connect_out_of_memory(): mock.call(b"wifitest.adafruit.com"), ] ) - assert r.text == str(text, "utf-8") + assert response.text == str(TEXT, "utf-8") - r = s.get("https://" + host2 + path) + response = requests_session.get("https://" + HOST2 + PATH) sock3.send.assert_has_calls( [ mock.call(b"GET"), @@ -156,23 +158,23 @@ def test_connect_out_of_memory(): ] ) - assert r.text == str(text, "utf-8") + assert response.text == str(TEXT, "utf-8") sock.close.assert_called_once() - sock.connect.assert_called_once_with((host, 443)) - sock3.connect.assert_called_once_with((host2, 443)) + sock.connect.assert_called_once_with((HOST, 443)) + sock3.connect.assert_called_once_with((HOST2, 443)) def test_second_send_fails(): pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(response) - sock2 = mocket.Mocket(response) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE) + sock2 = mocket.Mocket(RESPONSE) pool.socket.side_effect = [sock, sock2] ssl = mocket.SSLContext() - s = adafruit_requests.Session(pool, ssl) - r = s.get("https://" + host + path) + requests_session = adafruit_requests.Session(pool, ssl) + response = requests_session.get("https://" + HOST + PATH) sock.send.assert_has_calls( [ @@ -187,30 +189,30 @@ def test_second_send_fails(): mock.call(b"\r\n"), ] ) - assert r.text == str(text, "utf-8") + assert response.text == str(TEXT, "utf-8") sock.fail_next_send = True - s.get("https://" + host + path + "2") + requests_session.get("https://" + HOST + PATH + "2") - sock.connect.assert_called_once_with((host, 443)) - sock2.connect.assert_called_once_with((host, 443)) + sock.connect.assert_called_once_with((HOST, 443)) + sock2.connect.assert_called_once_with((HOST, 443)) # Make sure that the socket is closed after send fails. sock.close.assert_called_once() assert sock2.close.call_count == 0 assert pool.socket.call_count == 2 -def test_second_send_lies_recv_fails(): +def test_second_send_lies_recv_fails(): # pylint: disable=invalid-name pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (ip, 80)),) - sock = mocket.Mocket(response) - sock2 = mocket.Mocket(response) + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE) + sock2 = mocket.Mocket(RESPONSE) pool.socket.side_effect = [sock, sock2] ssl = mocket.SSLContext() - s = adafruit_requests.Session(pool, ssl) - r = s.get("https://" + host + path) + requests_session = adafruit_requests.Session(pool, ssl) + response = requests_session.get("https://" + HOST + PATH) sock.send.assert_has_calls( [ @@ -225,12 +227,12 @@ def test_second_send_lies_recv_fails(): mock.call(b"\r\n"), ] ) - assert r.text == str(text, "utf-8") + assert response.text == str(TEXT, "utf-8") - s.get("https://" + host + path + "2") + requests_session.get("https://" + HOST + PATH + "2") - sock.connect.assert_called_once_with((host, 443)) - sock2.connect.assert_called_once_with((host, 443)) + sock.connect.assert_called_once_with((HOST, 443)) + sock2.connect.assert_called_once_with((HOST, 443)) # Make sure that the socket is closed after send fails. sock.close.assert_called_once() assert sock2.close.call_count == 0 From c0509bf30131148fde562fc2be234597bdd45e26 Mon Sep 17 00:00:00 2001 From: Patrick <4002194+askpatrickw@users.noreply.github.com> Date: Thu, 25 Mar 2021 11:49:42 -0700 Subject: [PATCH 024/305] allow use of protected member --- adafruit_requests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/adafruit_requests.py b/adafruit_requests.py index fcae649..3e681ac 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -653,6 +653,7 @@ def set_socket(sock, iface=None): """Legacy API for setting the socket and network interface. Use a `Session` instead.""" global _default_session # pylint: disable=global-statement,invalid-name if not iface: + # pylint: disable=protected-access _default_session = Session(sock, _FakeSSLContext(sock._the_interface)) else: _default_session = Session(sock, _FakeSSLContext(iface)) From 338545dbe20bc9825a193a2fae9a469a5c88a57a Mon Sep 17 00:00:00 2001 From: dherrada Date: Wed, 19 May 2021 13:32:42 -0400 Subject: [PATCH 025/305] Added pull request template Signed-off-by: dherrada --- .../adafruit_circuitpython_pr.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md diff --git a/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md b/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md new file mode 100644 index 0000000..71ef8f8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2021 Adafruit Industries +# +# SPDX-License-Identifier: MIT + +Thank you for contributing! Before you submit a pull request, please read the following. + +Make sure any changes you're submitting are in line with the CircuitPython Design Guide, available here: https://circuitpython.readthedocs.io/en/latest/docs/design_guide.html + +If your changes are to documentation, please verify that the documentation builds locally by following the steps found here: https://adafru.it/build-docs + +Before submitting the pull request, make sure you've run Pylint and Black locally on your code. You can do this manually or using pre-commit. Instructions are available here: https://adafru.it/check-your-code + +Please remove all of this text before submitting. Include an explanation or list of changes included in your PR, as well as, if applicable, a link to any related issues. From 84ba275d11651bc25baca2d24926f9bc4123b560 Mon Sep 17 00:00:00 2001 From: dherrada Date: Wed, 19 May 2021 13:35:18 -0400 Subject: [PATCH 026/305] Added help text and problem matcher Signed-off-by: dherrada --- .github/workflows/build.yml | 2 ++ .github/workflows/failure-help-text.yml | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 .github/workflows/failure-help-text.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3baf502..0ab7182 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,3 +71,5 @@ jobs: python setup.py sdist python setup.py bdist_wheel --universal twine check dist/* + - name: Setup problem matchers + uses: adafruit/circuitpython-action-library-ci-problem-matchers@v1 diff --git a/.github/workflows/failure-help-text.yml b/.github/workflows/failure-help-text.yml new file mode 100644 index 0000000..0b1194f --- /dev/null +++ b/.github/workflows/failure-help-text.yml @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2021 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +name: Failure help text + +on: + workflow_run: + workflows: ["Build CI"] + types: + - completed + +jobs: + post-help: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }} + steps: + - name: Post comment to help + uses: adafruit/circuitpython-action-library-ci-failed@v1 From 2e85ec08508019475a49225aa429b5db9e20e311 Mon Sep 17 00:00:00 2001 From: dherrada Date: Mon, 24 May 2021 09:54:31 -0400 Subject: [PATCH 027/305] Moved CI to Python 3.7 Signed-off-by: dherrada --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0ab7182..c4c975d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,10 +22,10 @@ jobs: awk -F '\/' '{ print tolower($2) }' | tr '_' '-' ) - - name: Set up Python 3.6 + - name: Set up Python 3.7 uses: actions/setup-python@v1 with: - python-version: 3.6 + python-version: 3.7 - name: Versions run: | python3 --version From 74b0a5a0590923aec6bc17c8783ae9b35b234aad Mon Sep 17 00:00:00 2001 From: dherrada Date: Thu, 3 Jun 2021 11:13:13 -0400 Subject: [PATCH 028/305] Moved default branch to main --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 51a5ec0..8d4b18a 100755 --- a/README.rst +++ b/README.rst @@ -59,7 +59,7 @@ Contributing ============ Contributions are welcome! Please read our `Code of Conduct -`_ +`_ before contributing to help this project stay welcoming. Documentation From 522a61ac44fc43a30a7522a31d4b7632a3a59a12 Mon Sep 17 00:00:00 2001 From: Michael Mogenson Date: Wed, 7 Jul 2021 20:59:46 -0400 Subject: [PATCH 029/305] Add simple support for redirects Follow 3XX status redirects with a new request. Parse the `location` header for an absolute url, absolute path, or relative path. Replace or amend the url from the original request and place a new request. Convert all keys in the headers dictionary to lowercase to perform a case-insensitive search. There's no limit on the number of consecutive redirects. Since the `requets()` method is now recursive, multiple redirects may crash the stack. Especially on a CircuitPython microcontroller. Expand the requests_simpletest_cpython.py example with new requests utilizing the three supported types of redirects. Note: We're using the httpbingo.org domain for redirects until the following issue on httpbin.org is resolved: https://github.com/postmanlabs/httpbin/issues/617 This has been tested on CPython 3.8 and an ESP32-S2 based MagTag board running CircuitPython 6.0. --- adafruit_requests.py | 36 +++++++++++++++++-------- examples/requests_simpletest_cpython.py | 26 ++++++++++++++++++ 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 3e681ac..a00670e 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -281,18 +281,12 @@ def _parse_headers(self): content = self._readto(b"\r\n") if title and content: - title = str(title, "utf-8") + # enforce that all headers are lowercase + title = str(title, "utf-8").lower() content = str(content, "utf-8") - # Check len first so we can skip the .lower allocation most of the time. - if ( - len(title) == len("content-length") - and title.lower() == "content-length" - ): + if title == "content-length": self._remaining = int(content) - if ( - len(title) == len("transfer-encoding") - and title.lower() == "transfer-encoding" - ): + if title == "transfer-encoding": self._chunked = content.strip().lower() == "chunked" self._headers[title] = content @@ -587,7 +581,27 @@ def request( resp = Response(socket, self) # our response if "location" in resp.headers and 300 <= resp.status_code <= 399: - raise NotImplementedError("Redirects not yet supported") + # a naive handler for redirects + redirect = resp.headers["location"] + + if redirect.startswith("http"): + # absolute URL + url = redirect + elif redirect[0] == "/": + # relative URL, absolute path + url = "/".join([proto, dummy, host, redirect[1:]]) + else: + # relative URL, relative path + path = path.rsplit("/", 1)[0] + + while redirect.startswith("../"): + path = path.rsplit("/", 1)[0] + redirect = redirect.split("../", 1)[1] + + url = "/".join([proto, dummy, host, path, redirect]) + + self._last_response = resp + resp = self.request(method, url, data, json, headers, stream, timeout) self._last_response = resp return resp diff --git a/examples/requests_simpletest_cpython.py b/examples/requests_simpletest_cpython.py index 923ed03..be51d57 100755 --- a/examples/requests_simpletest_cpython.py +++ b/examples/requests_simpletest_cpython.py @@ -10,6 +10,9 @@ TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" JSON_GET_URL = "http://httpbin.org/get" JSON_POST_URL = "http://httpbin.org/post" +REDIRECT_URL = "http://httpbingo.org/redirect/1" +RELATIVE_REDIRECT_URL = "http://httpbingo.org/relative-redirect/1" +ABSOLUTE_REDIRECT_URL = "http://httpbingo.org/absolute-redirect/1" print("Fetching text from %s" % TEXT_URL) response = http.get(TEXT_URL) @@ -45,3 +48,26 @@ # Parse out the 'json' key from json_resp dict. print("JSON Data received from server:", json_resp["json"]) print("-" * 40) + +print("Fetching JSON data from redirect url %s" % REDIRECT_URL) +response = http.get(REDIRECT_URL) +print("-" * 40) + +print("JSON Response: ", response.json()) +print("-" * 40) + +print("Fetching JSON data from relative redirect url %s" % RELATIVE_REDIRECT_URL) +response = http.get(RELATIVE_REDIRECT_URL) +print("-" * 40) + +print("JSON Response: ", response.json()) +print("-" * 40) + +print("Fetching JSON data from aboslute redirect url %s" % ABSOLUTE_REDIRECT_URL) +response = http.get(ABSOLUTE_REDIRECT_URL) +print("-" * 40) + +print("JSON Response: ", response.json()) +print("-" * 40) + +response.close() From 36821bda2c8f8bfc3834f573def621ce91afb38c Mon Sep 17 00:00:00 2001 From: dherrada Date: Thu, 23 Sep 2021 17:52:55 -0400 Subject: [PATCH 030/305] Globally disabled consider-using-f-string pylint check Signed-off-by: dherrada --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6f11a4c..49d9415 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: name: pylint (examples code) description: Run pylint rules on "examples/*.py" files entry: /usr/bin/env bash -c - args: ['([[ ! -d "examples" ]] || for example in $(find . -path "./examples/*.py"); do pylint --disable=missing-docstring,invalid-name $example; done)'] + args: ['([[ ! -d "examples" ]] || for example in $(find . -path "./examples/*.py"); do pylint --disable=missing-docstring,invalid-name,consider-using-f-string $example; done)'] language: system - repo: local hooks: From edbfeb00ec217e511d3c45e7e415344c352bbed9 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Sun, 24 Oct 2021 22:21:00 -0400 Subject: [PATCH 031/305] Remove last arg from .socket() call --- adafruit_requests.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index a00670e..ed2f97c 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -424,9 +424,7 @@ def _get_socket(self, host, port, proto, *, timeout=1): retry_count += 1 try: - sock = self._socket_pool.socket( - addr_info[0], addr_info[1], addr_info[2] - ) + sock = self._socket_pool.socket(addr_info[0], addr_info[1]) except OSError: continue except RuntimeError: From 42c58cc81c8582b4deb8c1768dd1c2a5e62e8a73 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 25 Oct 2021 11:30:27 -0500 Subject: [PATCH 032/305] add docs link to readme --- README.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.rst b/README.rst index 8d4b18a..3696db9 100755 --- a/README.rst +++ b/README.rst @@ -55,6 +55,11 @@ Usage Example Usage examples are within the `examples` subfolder of this library. +Documentation +============= + +API documentation for this library can be found on `Read the Docs `_. + Contributing ============ From 9e83bda8498ab9626831fadb4a1164bd5942b5f1 Mon Sep 17 00:00:00 2001 From: dherrada Date: Fri, 5 Nov 2021 14:49:30 -0400 Subject: [PATCH 033/305] Disabled unspecified-encoding pylint check Signed-off-by: dherrada --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 0238b90..f006a4a 100755 --- a/.pylintrc +++ b/.pylintrc @@ -55,7 +55,7 @@ confidence= # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" # disable=import-error,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call -disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error,bad-continuation +disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error,bad-continuation,unspecified-encoding # 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 From da722dfbc0e6153f86f6f88da4249c08de30a924 Mon Sep 17 00:00:00 2001 From: Dylan Herrada <33632497+dherrada@users.noreply.github.com> Date: Mon, 8 Nov 2021 16:27:03 -0500 Subject: [PATCH 034/305] Updated readthedocs requirements path --- .readthedocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index ffa84c4..49dcab3 100755 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -4,4 +4,4 @@ python: version: 3 -requirements_file: requirements.txt +requirements_file: docs/requirements.txt From 8f41838815aeff837f3a92401c7d800564fa30db Mon Sep 17 00:00:00 2001 From: dherrada Date: Tue, 9 Nov 2021 13:31:14 -0500 Subject: [PATCH 035/305] Updated readthedocs file Signed-off-by: dherrada --- .readthedocs.yaml | 15 +++++++++++++++ .readthedocs.yml | 7 ------- 2 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 .readthedocs.yaml delete mode 100755 .readthedocs.yml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..95ec218 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +python: + version: "3.6" + install: + - requirements: docs/requirements.txt + - requirements: requirements.txt diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100755 index 49dcab3..0000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,7 +0,0 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -python: - version: 3 -requirements_file: docs/requirements.txt From a1d99b41be5bb303cd1ba65dfbd755e9d57f95ff Mon Sep 17 00:00:00 2001 From: dherrada Date: Thu, 11 Nov 2021 15:29:17 -0500 Subject: [PATCH 036/305] Pylint and RTD update patch, and other fixes --- .github/workflows/build.yml | 4 ++-- .pre-commit-config.yaml | 30 +++++++++++++++--------------- .pylintrc | 2 +- adafruit_requests.py | 6 +++--- docs/requirements.txt | 5 +++++ tests/mocket.py | 3 +-- 6 files changed, 27 insertions(+), 23 deletions(-) create mode 100644 docs/requirements.txt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c4c975d..ca35544 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,9 +42,9 @@ jobs: # (e.g. - apt-get: gettext, etc; pip: circuitpython-build-tools, requirements.txt; etc.) run: | source actions-ci/install.sh - - name: Pip install pylint, Sphinx, pre-commit + - name: Pip install Sphinx, pre-commit run: | - pip install --force-reinstall pylint Sphinx sphinx-rtd-theme pre-commit + pip install --force-reinstall Sphinx sphinx-rtd-theme pre-commit - name: Library version run: git describe --dirty --always --tags - name: Pre-commit hooks diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 49d9415..1b9fadc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,25 +18,25 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/pylint - rev: pylint-2.7.1 + rev: v2.11.1 hooks: - id: pylint name: pylint (library code) types: [python] + args: + - --disable=consider-using-f-string exclude: "^(docs/|examples/|tests/|setup.py$)" -- repo: local - hooks: - - id: pylint_examples - name: pylint (examples code) + - id: pylint + name: pylint (example code) description: Run pylint rules on "examples/*.py" files - entry: /usr/bin/env bash -c - args: ['([[ ! -d "examples" ]] || for example in $(find . -path "./examples/*.py"); do pylint --disable=missing-docstring,invalid-name,consider-using-f-string $example; done)'] - language: system -- repo: local - hooks: - - id: pylint_tests - name: pylint (tests code) + types: [python] + files: "^examples/" + args: + - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code + - id: pylint + name: pylint (test code) description: Run pylint rules on "tests/*.py" files - entry: /usr/bin/env bash -c - args: ['([[ ! -d "tests" ]] || for example in $(find . -path "./tests/*.py"); do pylint --disable=missing-docstring,invalid-name $example; done)'] - language: system + types: [python] + files: "^tests/" + args: + - --disable=missing-docstring,consider-using-f-string,duplicate-code diff --git a/.pylintrc b/.pylintrc index f006a4a..cfd1c41 100755 --- a/.pylintrc +++ b/.pylintrc @@ -252,7 +252,7 @@ ignore-docstrings=yes ignore-imports=yes # Minimum lines number of a similarity. -min-similarity-lines=12 +min-similarity-lines=4 [BASIC] diff --git a/adafruit_requests.py b/adafruit_requests.py index ed2f97c..aaa56cb 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -383,7 +383,7 @@ def _close_socket(self, sock): sock.close() del self._socket_free[sock] key = None - for k in self._open_sockets: + for k in self._open_sockets: # pylint: disable=consider-using-dict-items if self._open_sockets[k] == sock: key = k break @@ -392,8 +392,8 @@ def _close_socket(self, sock): def _free_sockets(self): free_sockets = [] - for sock in self._socket_free: - if self._socket_free[sock]: + for sock, val in self._socket_free.items(): + if val: free_sockets.append(sock) for sock in free_sockets: self._close_socket(sock) diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..88e6733 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +sphinx>=4.0.0 diff --git a/tests/mocket.py b/tests/mocket.py index 578a808..fe11cc9 100644 --- a/tests/mocket.py +++ b/tests/mocket.py @@ -54,8 +54,7 @@ def _recv_into(self, buf, nbytes=0): assert isinstance(nbytes, int) and nbytes >= 0 read = nbytes if nbytes > 0 else len(buf) remaining = len(self._response) - self._position - if read > remaining: - read = remaining + read = min(read, remaining) end = self._position + read buf[:read] = self._response[self._position : end] self._position = end From 38ae3b87f8e13ca8112aa44c261fbe1308f9788f Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 23 Nov 2021 13:16:12 -0600 Subject: [PATCH 037/305] update rtd py version --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 95ec218..1335112 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,7 +9,7 @@ version: 2 python: - version: "3.6" + version: "3.7" install: - requirements: docs/requirements.txt - requirements: requirements.txt From dd4c4a041b3e549c922ddc7ad019e301cb11dc67 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 12:43:10 -0500 Subject: [PATCH 038/305] Add type hints for _RawResponse and Response classes --- adafruit_requests.py | 51 +++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index aaa56cb..57cae84 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -38,10 +38,21 @@ import errno +try: + from typing import Union, Optional, Dict, List, Any + import types + import adafruit_esp32spi.adafruit_esp32spi_socket as esp32_socket + import adafruit_wiznet5k.adafruit_wiznet5k_socket as wiznet_socket + import adafruit_fona.adafruit_fona_socket as cellular_socket + SocketType = Union[esp32_socket.socket, wiznet_socket.socket, cellular_socket.socket] + SocketPoolType = types.MethodType +except ImportError: + pass + # CircuitPython 6.0 does not have the bytearray.split method. # This function emulates buf.split(needle)[0], which is the functionality # required. -def _buffer_split0(buf, needle): +def _buffer_split0(buf, needle): # TODO: add typing index = buf.find(needle) if index == -1: return buf @@ -49,10 +60,10 @@ def _buffer_split0(buf, needle): class _RawResponse: - def __init__(self, response): + def __init__(self, response: 'Response') -> None: self._response = response - def read(self, size=-1): + def read(self, size: int = -1) -> bytes: """Read as much as available or up to size and return it in a byte string. Do NOT use this unless you really need to. Reusing memory with `readinto` is much better. @@ -61,7 +72,7 @@ def read(self, size=-1): return self._response.content return self._response.socket.recv(size) - def readinto(self, buf): + def readinto(self, buf: bytearray) -> int: """Read as much as available into buf or until it is full. Returns the number of bytes read into buf.""" return self._response._readinto(buf) # pylint: disable=protected-access @@ -82,7 +93,7 @@ class Response: encoding = None - def __init__(self, sock, session=None): + def __init__(self, sock: SocketType, session: Optional['Session'] = None) -> None: self.socket = sock self.encoding = "utf-8" self._cached = None @@ -110,13 +121,13 @@ def __init__(self, sock, session=None): self._raw = None self._session = session - def __enter__(self): + def __enter__(self) -> 'Response': return self - def __exit__(self, exc_type, exc_value, traceback): + def __exit__(self, exc_type, exc_value, traceback) -> None: # TODO: Add type hints for arguments self.close() - def _recv_into(self, buf, size=0): + def _recv_into(self, buf: bytearray, size: int = 0) -> int: if self._backwards_compatible: size = len(buf) if size == 0 else size b = self.socket.recv(size) @@ -126,7 +137,7 @@ def _recv_into(self, buf, size=0): return self.socket.recv_into(buf, size) @staticmethod - def _find(buf, needle, start, end): + def _find(buf: bytes, needle: bytes, start: int, end: int) -> int: if hasattr(buf, "find"): return buf.find(needle, start, end) result = -1 @@ -142,7 +153,7 @@ def _find(buf, needle, start, end): return result - def _readto(self, first, second=b""): + def _readto(self, first: bytes, second: bytes = b"") -> bytes: buf = self._receive_buffer end = self._received_length while True: @@ -187,7 +198,7 @@ def _readto(self, first, second=b""): return b"" - def _read_from_buffer(self, buf=None, nbytes=None): + def _read_from_buffer(self, buf: Optional[bytearray] = None, nbytes: Optional[int] = None) -> int: if self._received_length == 0: return 0 read = self._received_length @@ -204,7 +215,7 @@ def _read_from_buffer(self, buf=None, nbytes=None): self._received_length = 0 return read - def _readinto(self, buf): + def _readinto(self, buf: bytearray) -> int: if not self.socket: raise RuntimeError( "Newer Response closed this one. Use Responses immediately." @@ -237,7 +248,7 @@ def _readinto(self, buf): return read - def _throw_away(self, nbytes): + def _throw_away(self, nbytes: int) -> None: nbytes -= self._read_from_buffer(nbytes=nbytes) buf = self._receive_buffer @@ -247,7 +258,7 @@ def _throw_away(self, nbytes): if remaining: self._recv_into(buf, remaining) - def close(self): + def close(self) -> None: """Drain the remaining ESP socket buffers. We assume we already got what we wanted.""" if not self.socket: return @@ -269,7 +280,7 @@ def close(self): self.socket.close() self.socket = None - def _parse_headers(self): + def _parse_headers(self) -> None: """ Parses the header portion of an HTTP request/response from the socket. Expects first line of HTTP request/response to have been read already. @@ -291,7 +302,7 @@ def _parse_headers(self): self._headers[title] = content @property - def headers(self): + def headers(self) -> Dict[str, str]: """ The response headers. Does not include headers from the trailer until the content has been read. @@ -299,7 +310,7 @@ def headers(self): return self._headers @property - def content(self): + def content(self) -> bytes: """The HTTP content direct from the socket, as bytes""" if self._cached is not None: if isinstance(self._cached, bytes): @@ -310,7 +321,7 @@ def content(self): return self._cached @property - def text(self): + def text(self) -> str: """The HTTP content, encoded into a string according to the HTTP header encoding""" if self._cached is not None: @@ -320,7 +331,7 @@ def text(self): self._cached = str(self.content, self.encoding) return self._cached - def json(self): + def json(self) -> Union[List[Dict[str, Any]], Dict[str, Any]]: """The HTTP content, parsed into a json dictionary""" # pylint: disable=import-outside-toplevel import json @@ -344,7 +355,7 @@ def json(self): self.close() return obj - def iter_content(self, chunk_size=1, decode_unicode=False): + def iter_content(self, chunk_size: int = 1, decode_unicode: bool = False) -> bytes: """An iterator that will stream data by only reading 'chunk_size' bytes and yielding them, when we can't buffer the whole datastream""" if decode_unicode: From 2f81228a42dfa9742142eae9f613529ea9cb07fa Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 13:00:48 -0500 Subject: [PATCH 039/305] Add TODO for fixing josn() return type hint --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 57cae84..09ff2b6 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -331,7 +331,7 @@ def text(self) -> str: self._cached = str(self.content, self.encoding) return self._cached - def json(self) -> Union[List[Dict[str, Any]], Dict[str, Any]]: + def json(self) -> Union[List[Dict[str, Any]], Dict[str, Any]]: # TODO: Fix typing to match all possible JSON returns """The HTTP content, parsed into a json dictionary""" # pylint: disable=import-outside-toplevel import json From d200444d7db151e493d302a5f116b776cf2b9b8c Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 14:09:34 -0500 Subject: [PATCH 040/305] Type json() return as Any --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 09ff2b6..d0ed289 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -331,7 +331,7 @@ def text(self) -> str: self._cached = str(self.content, self.encoding) return self._cached - def json(self) -> Union[List[Dict[str, Any]], Dict[str, Any]]: # TODO: Fix typing to match all possible JSON returns + def json(self) -> Any: """The HTTP content, parsed into a json dictionary""" # pylint: disable=import-outside-toplevel import json From c14ab4f666d2f7e7abb4c79bb2917d7b199a5b46 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 15:07:50 -0500 Subject: [PATCH 041/305] Add types for Session class --- adafruit_requests.py | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index d0ed289..d9745b7 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -39,13 +39,15 @@ import errno try: - from typing import Union, Optional, Dict, List, Any + from typing import Union, TypeVar, Optional, Dict, Any, List import types + import ssl import adafruit_esp32spi.adafruit_esp32spi_socket as esp32_socket import adafruit_wiznet5k.adafruit_wiznet5k_socket as wiznet_socket import adafruit_fona.adafruit_fona_socket as cellular_socket - SocketType = Union[esp32_socket.socket, wiznet_socket.socket, cellular_socket.socket] - SocketPoolType = types.MethodType + SocketType = TypeVar("SocketType", esp32_socket.socket, wiznet_socket.socket, cellular_socket.socket) + SocketpoolModuleType = TypeVar("SocketpoolModuleType", types.ModuleType("socket"), types.ModuleType("socketpool")) + SSLContextType = TypeVar("SSLContextType", ssl.SSLContext) # Can use either CircuitPython or CPython ssl module except ImportError: pass @@ -377,7 +379,7 @@ def iter_content(self, chunk_size: int = 1, decode_unicode: bool = False) -> byt class Session: """HTTP session that shares sockets and ssl context.""" - def __init__(self, socket_pool, ssl_context=None): + def __init__(self, socket_pool: SocketpoolModuleType, ssl_context: Optional[SSLContextType] = None) -> None: self._socket_pool = socket_pool self._ssl_context = ssl_context # Hang onto open sockets so that we can reuse them. @@ -385,12 +387,12 @@ def __init__(self, socket_pool, ssl_context=None): self._socket_free = {} self._last_response = None - def _free_socket(self, socket): + def _free_socket(self, socket: SocketType) -> None: if socket not in self._open_sockets.values(): raise RuntimeError("Socket not from session") self._socket_free[socket] = True - def _close_socket(self, sock): + def _close_socket(self, sock: SocketType) -> None: sock.close() del self._socket_free[sock] key = None @@ -401,7 +403,7 @@ def _close_socket(self, sock): if key: del self._open_sockets[key] - def _free_sockets(self): + def _free_sockets(self) -> None: free_sockets = [] for sock, val in self._socket_free.items(): if val: @@ -409,7 +411,7 @@ def _free_sockets(self): for sock in free_sockets: self._close_socket(sock) - def _get_socket(self, host, port, proto, *, timeout=1): + def _get_socket(self, host:str, port: int, proto: str, *, timeout: float = 1) -> SocketType: # pylint: disable=too-many-branches key = (host, port, proto) if key in self._open_sockets: @@ -464,7 +466,7 @@ def _get_socket(self, host, port, proto, *, timeout=1): return sock @staticmethod - def _send(socket, data): + def _send(socket: SocketType, data: bytes): total_sent = 0 while total_sent < len(data): # ESP32SPI sockets raise a RuntimeError when unable to send. @@ -478,7 +480,7 @@ def _send(socket, data): raise _SendFailed() total_sent += sent - def _send_request(self, socket, host, method, path, headers, data, json): + def _send_request(self, socket: SocketType, host: str, method: str, path: str, headers: List[Dict[str, str]], data: Any, json: Dict[Any, Any]): # pylint: disable=too-many-arguments self._send(socket, bytes(method, "utf-8")) self._send(socket, b" /") @@ -524,8 +526,8 @@ def _send_request(self, socket, host, method, path, headers, data, json): # pylint: disable=too-many-branches, too-many-statements, unused-argument, too-many-arguments, too-many-locals def request( - self, method, url, data=None, json=None, headers=None, stream=False, timeout=60 - ): + self, method: str, url: str, data: Optional[Any] = None, json: Optional[Dict[Any, Any]] = None, headers: Optional[List[Dict[str, str]]] = None, stream: bool = False, timeout: float = 60 + ) -> Response: """Perform an HTTP request to the given url which we will parse to determine whether to use SSL ('https://') or not. We can also send some provided 'data' or a json dictionary which we will stringify. 'headers' is optional HTTP headers @@ -615,27 +617,27 @@ def request( self._last_response = resp return resp - def head(self, url, **kw): + def head(self, url: str, **kw) -> Response: """Send HTTP HEAD request""" return self.request("HEAD", url, **kw) - def get(self, url, **kw): + def get(self, url: str, **kw) -> Response: """Send HTTP GET request""" return self.request("GET", url, **kw) - def post(self, url, **kw): + def post(self, url: str, **kw) -> Response: """Send HTTP POST request""" return self.request("POST", url, **kw) - def put(self, url, **kw): + def put(self, url: str, **kw) -> Response: """Send HTTP PUT request""" return self.request("PUT", url, **kw) - def patch(self, url, **kw): + def patch(self, url: str, **kw) -> Response: """Send HTTP PATCH request""" return self.request("PATCH", url, **kw) - def delete(self, url, **kw): + def delete(self, url: str, **kw) -> Response: """Send HTTP DELETE request""" return self.request("DELETE", url, **kw) From cd04925922081ea08f60c6ff287a592d4cc10018 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 15:38:33 -0500 Subject: [PATCH 042/305] Finalize prototype of type hints --- adafruit_requests.py | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index d9745b7..11f106d 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -37,6 +37,10 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Requests.git" import errno +from ssl import SSLSocket +from types import ModuleType + +from tests.mocket import SSLContext try: from typing import Union, TypeVar, Optional, Dict, Any, List @@ -45,9 +49,14 @@ import adafruit_esp32spi.adafruit_esp32spi_socket as esp32_socket import adafruit_wiznet5k.adafruit_wiznet5k_socket as wiznet_socket import adafruit_fona.adafruit_fona_socket as cellular_socket - SocketType = TypeVar("SocketType", esp32_socket.socket, wiznet_socket.socket, cellular_socket.socket) + from adafruit_esp32spi.adafruit_esp32spi import ESP_SPIcontrol + from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K + from adafruit_fona.adafruit_fona import FONA + import socket + SocketType = TypeVar("SocketType", esp32_socket.socket, wiznet_socket.socket, cellular_socket.socket, socket.socket) SocketpoolModuleType = TypeVar("SocketpoolModuleType", types.ModuleType("socket"), types.ModuleType("socketpool")) SSLContextType = TypeVar("SSLContextType", ssl.SSLContext) # Can use either CircuitPython or CPython ssl module + InterfaceType = TypeVar("InterfaceType", ESP_SPIcontrol, WIZNET5K, FONA) except ImportError: pass @@ -480,7 +489,7 @@ def _send(socket: SocketType, data: bytes): raise _SendFailed() total_sent += sent - def _send_request(self, socket: SocketType, host: str, method: str, path: str, headers: List[Dict[str, str]], data: Any, json: Dict[Any, Any]): + def _send_request(self, socket: SocketType, host: str, method: str, path: str, headers: List[Dict[str, str]], data: Any, json: Any): # pylint: disable=too-many-arguments self._send(socket, bytes(method, "utf-8")) self._send(socket, b" /") @@ -526,7 +535,7 @@ def _send_request(self, socket: SocketType, host: str, method: str, path: str, h # pylint: disable=too-many-branches, too-many-statements, unused-argument, too-many-arguments, too-many-locals def request( - self, method: str, url: str, data: Optional[Any] = None, json: Optional[Dict[Any, Any]] = None, headers: Optional[List[Dict[str, str]]] = None, stream: bool = False, timeout: float = 60 + self, method: str, url: str, data: Optional[Any] = None, json: Optional[Any] = None, headers: Optional[List[Dict[str, str]]] = None, stream: bool = False, timeout: float = 60 ) -> Response: """Perform an HTTP request to the given url which we will parse to determine whether to use SSL ('https://') or not. We can also send some provided 'data' @@ -648,7 +657,7 @@ def delete(self, url: str, **kw) -> Response: class _FakeSSLSocket: - def __init__(self, socket, tls_mode): + def __init__(self, socket: SocketType, tls_mode: int) -> None: self._socket = socket self._mode = tls_mode self.settimeout = socket.settimeout @@ -656,25 +665,23 @@ def __init__(self, socket, tls_mode): self.recv = socket.recv self.close = socket.close - def connect(self, address): + def connect(self, address: Union[bytes, str]) -> None: """connect wrapper to add non-standard mode parameter""" try: return self._socket.connect(address, self._mode) except RuntimeError as error: raise OSError(errno.ENOMEM) from error - class _FakeSSLContext: - def __init__(self, iface): + def __init__(self, iface: InterfaceType) -> None: self._iface = iface - def wrap_socket(self, socket, server_hostname=None): + def wrap_socket(self, socket: SocketType, server_hostname: Optional[str] = None) -> _FakeSSLSocket: """Return the same socket""" # pylint: disable=unused-argument return _FakeSSLSocket(socket, self._iface.TLS_MODE) - -def set_socket(sock, iface=None): +def set_socket(sock, iface: Optional[InterfaceType] = None) -> None: """Legacy API for setting the socket and network interface. Use a `Session` instead.""" global _default_session # pylint: disable=global-statement,invalid-name if not iface: @@ -685,7 +692,7 @@ def set_socket(sock, iface=None): sock.set_interface(iface) -def request(method, url, data=None, json=None, headers=None, stream=False, timeout=1): +def request(method, url: str, data: Optional[Any] = None, json: Optional[Any] = None, headers: Optional[List[Dict[str, str]]] = None, stream: bool = False, timeout: float = 1) -> None: """Send HTTP request""" # pylint: disable=too-many-arguments _default_session.request( @@ -699,31 +706,31 @@ def request(method, url, data=None, json=None, headers=None, stream=False, timeo ) -def head(url, **kw): +def head(url: str, **kw): """Send HTTP HEAD request""" return _default_session.request("HEAD", url, **kw) -def get(url, **kw): +def get(url: str, **kw): """Send HTTP GET request""" return _default_session.request("GET", url, **kw) -def post(url, **kw): +def post(url: str, **kw): """Send HTTP POST request""" return _default_session.request("POST", url, **kw) -def put(url, **kw): +def put(url: str, **kw): """Send HTTP PUT request""" return _default_session.request("PUT", url, **kw) -def patch(url, **kw): +def patch(url: str, **kw): """Send HTTP PATCH request""" return _default_session.request("PATCH", url, **kw) -def delete(url, **kw): +def delete(url: str, **kw): """Send HTTP DELETE request""" return _default_session.request("DELETE", url, **kw) From 0fe980f0e3e5ffbb8cea70dc9b0509e2bbddca49 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 16:12:48 -0500 Subject: [PATCH 043/305] Reformatted per pre-commit --- adafruit_requests.py | 81 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 11f106d..e1f12ba 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -53,9 +53,22 @@ from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K from adafruit_fona.adafruit_fona import FONA import socket - SocketType = TypeVar("SocketType", esp32_socket.socket, wiznet_socket.socket, cellular_socket.socket, socket.socket) - SocketpoolModuleType = TypeVar("SocketpoolModuleType", types.ModuleType("socket"), types.ModuleType("socketpool")) - SSLContextType = TypeVar("SSLContextType", ssl.SSLContext) # Can use either CircuitPython or CPython ssl module + + SocketType = TypeVar( + "SocketType", + esp32_socket.socket, + wiznet_socket.socket, + cellular_socket.socket, + socket.socket, + ) + SocketpoolModuleType = TypeVar( + "SocketpoolModuleType", + types.ModuleType("socket"), + types.ModuleType("socketpool"), + ) + SSLContextType = TypeVar( + "SSLContextType", ssl.SSLContext + ) # Can use either CircuitPython or CPython ssl module InterfaceType = TypeVar("InterfaceType", ESP_SPIcontrol, WIZNET5K, FONA) except ImportError: pass @@ -63,7 +76,7 @@ # CircuitPython 6.0 does not have the bytearray.split method. # This function emulates buf.split(needle)[0], which is the functionality # required. -def _buffer_split0(buf, needle): # TODO: add typing +def _buffer_split0(buf, needle): # TODO: add typing index = buf.find(needle) if index == -1: return buf @@ -71,7 +84,7 @@ def _buffer_split0(buf, needle): # TODO: add typing class _RawResponse: - def __init__(self, response: 'Response') -> None: + def __init__(self, response: "Response") -> None: self._response = response def read(self, size: int = -1) -> bytes: @@ -104,7 +117,7 @@ class Response: encoding = None - def __init__(self, sock: SocketType, session: Optional['Session'] = None) -> None: + def __init__(self, sock: SocketType, session: Optional["Session"] = None) -> None: self.socket = sock self.encoding = "utf-8" self._cached = None @@ -132,10 +145,12 @@ def __init__(self, sock: SocketType, session: Optional['Session'] = None) -> Non self._raw = None self._session = session - def __enter__(self) -> 'Response': + def __enter__(self) -> "Response": return self - def __exit__(self, exc_type, exc_value, traceback) -> None: # TODO: Add type hints for arguments + def __exit__( + self, exc_type, exc_value, traceback + ) -> None: # TODO: Add type hints for arguments self.close() def _recv_into(self, buf: bytearray, size: int = 0) -> int: @@ -209,7 +224,9 @@ def _readto(self, first: bytes, second: bytes = b"") -> bytes: return b"" - def _read_from_buffer(self, buf: Optional[bytearray] = None, nbytes: Optional[int] = None) -> int: + def _read_from_buffer( + self, buf: Optional[bytearray] = None, nbytes: Optional[int] = None + ) -> int: if self._received_length == 0: return 0 read = self._received_length @@ -388,7 +405,11 @@ def iter_content(self, chunk_size: int = 1, decode_unicode: bool = False) -> byt class Session: """HTTP session that shares sockets and ssl context.""" - def __init__(self, socket_pool: SocketpoolModuleType, ssl_context: Optional[SSLContextType] = None) -> None: + def __init__( + self, + socket_pool: SocketpoolModuleType, + ssl_context: Optional[SSLContextType] = None, + ) -> None: self._socket_pool = socket_pool self._ssl_context = ssl_context # Hang onto open sockets so that we can reuse them. @@ -420,7 +441,9 @@ def _free_sockets(self) -> None: for sock in free_sockets: self._close_socket(sock) - def _get_socket(self, host:str, port: int, proto: str, *, timeout: float = 1) -> SocketType: + def _get_socket( + self, host: str, port: int, proto: str, *, timeout: float = 1 + ) -> SocketType: # pylint: disable=too-many-branches key = (host, port, proto) if key in self._open_sockets: @@ -489,7 +512,16 @@ def _send(socket: SocketType, data: bytes): raise _SendFailed() total_sent += sent - def _send_request(self, socket: SocketType, host: str, method: str, path: str, headers: List[Dict[str, str]], data: Any, json: Any): + def _send_request( + self, + socket: SocketType, + host: str, + method: str, + path: str, + headers: List[Dict[str, str]], + data: Any, + json: Any, + ): # pylint: disable=too-many-arguments self._send(socket, bytes(method, "utf-8")) self._send(socket, b" /") @@ -535,7 +567,14 @@ def _send_request(self, socket: SocketType, host: str, method: str, path: str, h # pylint: disable=too-many-branches, too-many-statements, unused-argument, too-many-arguments, too-many-locals def request( - self, method: str, url: str, data: Optional[Any] = None, json: Optional[Any] = None, headers: Optional[List[Dict[str, str]]] = None, stream: bool = False, timeout: float = 60 + self, + method: str, + url: str, + data: Optional[Any] = None, + json: Optional[Any] = None, + headers: Optional[List[Dict[str, str]]] = None, + stream: bool = False, + timeout: float = 60, ) -> Response: """Perform an HTTP request to the given url which we will parse to determine whether to use SSL ('https://') or not. We can also send some provided 'data' @@ -672,15 +711,19 @@ def connect(self, address: Union[bytes, str]) -> None: except RuntimeError as error: raise OSError(errno.ENOMEM) from error + class _FakeSSLContext: def __init__(self, iface: InterfaceType) -> None: self._iface = iface - def wrap_socket(self, socket: SocketType, server_hostname: Optional[str] = None) -> _FakeSSLSocket: + def wrap_socket( + self, socket: SocketType, server_hostname: Optional[str] = None + ) -> _FakeSSLSocket: """Return the same socket""" # pylint: disable=unused-argument return _FakeSSLSocket(socket, self._iface.TLS_MODE) + def set_socket(sock, iface: Optional[InterfaceType] = None) -> None: """Legacy API for setting the socket and network interface. Use a `Session` instead.""" global _default_session # pylint: disable=global-statement,invalid-name @@ -692,7 +735,15 @@ def set_socket(sock, iface: Optional[InterfaceType] = None) -> None: sock.set_interface(iface) -def request(method, url: str, data: Optional[Any] = None, json: Optional[Any] = None, headers: Optional[List[Dict[str, str]]] = None, stream: bool = False, timeout: float = 1) -> None: +def request( + method, + url: str, + data: Optional[Any] = None, + json: Optional[Any] = None, + headers: Optional[List[Dict[str, str]]] = None, + stream: bool = False, + timeout: float = 1, +) -> None: """Send HTTP request""" # pylint: disable=too-many-arguments _default_session.request( From cac592f8e2baa30e40e6f396e9857c9a19494328 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 16:30:28 -0500 Subject: [PATCH 044/305] Fix imports and typing types --- adafruit_requests.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index e1f12ba..0601971 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -37,13 +37,10 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Requests.git" import errno -from ssl import SSLSocket -from types import ModuleType - -from tests.mocket import SSLContext +from types import TracebackType try: - from typing import Union, TypeVar, Optional, Dict, Any, List + from typing import Union, TypeVar, Optional, Dict, Any, List, Type import types import ssl import adafruit_esp32spi.adafruit_esp32spi_socket as esp32_socket @@ -52,14 +49,14 @@ from adafruit_esp32spi.adafruit_esp32spi import ESP_SPIcontrol from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K from adafruit_fona.adafruit_fona import FONA - import socket + import socket as cpython_socket SocketType = TypeVar( "SocketType", esp32_socket.socket, wiznet_socket.socket, cellular_socket.socket, - socket.socket, + cpython_socket.socket, ) SocketpoolModuleType = TypeVar( "SocketpoolModuleType", From 205afed9d45c906e9dd429e753c6a387381945b1 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 16:30:49 -0500 Subject: [PATCH 045/305] Add type hints for _buffer_split0() --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 0601971..0156462 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -73,7 +73,7 @@ # CircuitPython 6.0 does not have the bytearray.split method. # This function emulates buf.split(needle)[0], which is the functionality # required. -def _buffer_split0(buf, needle): # TODO: add typing +def _buffer_split0(buf, needle: bytes): index = buf.find(needle) if index == -1: return buf From 97a45b0eab4e99b7dc195d4e108143b8a9325845 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 16:31:03 -0500 Subject: [PATCH 046/305] Add type hints for Response.__exit__() --- adafruit_requests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 0156462..2b0d24b 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -146,8 +146,8 @@ def __enter__(self) -> "Response": return self def __exit__( - self, exc_type, exc_value, traceback - ) -> None: # TODO: Add type hints for arguments + self, exc_type: Optional[Type[type]], exc_value: Optional[BaseException], traceback: Optional[TracebackType] + ) -> None: self.close() def _recv_into(self, buf: bytearray, size: int = 0) -> int: From 3c3ff9393a9d28b17abed97573641311b834924d Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 16:32:00 -0500 Subject: [PATCH 047/305] Reformatted per pre-commit --- adafruit_requests.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 2b0d24b..0d87066 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -146,7 +146,10 @@ def __enter__(self) -> "Response": return self def __exit__( - self, exc_type: Optional[Type[type]], exc_value: Optional[BaseException], traceback: Optional[TracebackType] + self, + exc_type: Optional[Type[type]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], ) -> None: self.close() From fd82f9418d18911610f8ca50cd6d37e05d923305 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 16:47:16 -0500 Subject: [PATCH 048/305] Added hardware drivers to mock imports --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index c0e0389..4a485bc 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,7 @@ # Uncomment the below if you use native CircuitPython modules such as # digitalio, micropython and busio. List the modules you use. Without it, the # autodoc module docs will fail to generate with a warning. -# autodoc_mock_imports = ["digitalio", "busio"] +autodoc_mock_imports = ["adafruit_esp32spi", "adafruit_wiznet5k", "adafruit_fona"] intersphinx_mapping = { From 89e8aaecb29f7cdb2035e4cee8687cc7e38cedbf Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 16:47:26 -0500 Subject: [PATCH 049/305] Reformatted per pre-commit --- adafruit_requests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/adafruit_requests.py b/adafruit_requests.py index 0d87066..75e2abd 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -67,6 +67,7 @@ "SSLContextType", ssl.SSLContext ) # Can use either CircuitPython or CPython ssl module InterfaceType = TypeVar("InterfaceType", ESP_SPIcontrol, WIZNET5K, FONA) + except ImportError: pass From 0d4396cb4e06b5d89152e0ce6942d7712e2c2026 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 16:59:25 -0500 Subject: [PATCH 050/305] Change SocketpoolModuleType to Union --- adafruit_requests.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 75e2abd..d67b1ce 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -58,11 +58,10 @@ cellular_socket.socket, cpython_socket.socket, ) - SocketpoolModuleType = TypeVar( - "SocketpoolModuleType", + SocketpoolModuleType = Union[ types.ModuleType("socket"), types.ModuleType("socketpool"), - ) + ] SSLContextType = TypeVar( "SSLContextType", ssl.SSLContext ) # Can use either CircuitPython or CPython ssl module From 080fecd8cc9c09ecc58629643598b33400cb9984 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 17:24:40 -0500 Subject: [PATCH 051/305] Fix SocketpoolModuleType to use types.ModuleType --- adafruit_requests.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index d67b1ce..7a9f0f2 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -58,10 +58,7 @@ cellular_socket.socket, cpython_socket.socket, ) - SocketpoolModuleType = Union[ - types.ModuleType("socket"), - types.ModuleType("socketpool"), - ] + SocketpoolModuleType = types.ModuleType SSLContextType = TypeVar( "SSLContextType", ssl.SSLContext ) # Can use either CircuitPython or CPython ssl module From 72a300f9e339f4c9cd2e2364517bbf9aa997474d Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 9 Dec 2021 17:28:39 -0500 Subject: [PATCH 052/305] Removed TypeVar use for SSLContextType assignment --- adafruit_requests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 7a9f0f2..c0632c0 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -59,8 +59,8 @@ cpython_socket.socket, ) SocketpoolModuleType = types.ModuleType - SSLContextType = TypeVar( - "SSLContextType", ssl.SSLContext + SSLContextType = ( + ssl.SSLContext ) # Can use either CircuitPython or CPython ssl module InterfaceType = TypeVar("InterfaceType", ESP_SPIcontrol, WIZNET5K, FONA) From 73a9428b53cf68fb40e4ebb811f8aa57e868d20e Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 14 Dec 2021 13:58:31 -0500 Subject: [PATCH 053/305] Added SocketpoolModule type to sock param of set_socket() --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index c0632c0..81c08a0 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -721,7 +721,7 @@ def wrap_socket( return _FakeSSLSocket(socket, self._iface.TLS_MODE) -def set_socket(sock, iface: Optional[InterfaceType] = None) -> None: +def set_socket(sock: SocketpoolModuleType, iface: Optional[InterfaceType] = None) -> None: """Legacy API for setting the socket and network interface. Use a `Session` instead.""" global _default_session # pylint: disable=global-statement,invalid-name if not iface: From 531c18ce72fbf83b5092270efb741634b010117c Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 14 Dec 2021 13:58:46 -0500 Subject: [PATCH 054/305] Add str type to method param of request() --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 81c08a0..835a115 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -733,7 +733,7 @@ def set_socket(sock: SocketpoolModuleType, iface: Optional[InterfaceType] = None def request( - method, + method: str, url: str, data: Optional[Any] = None, json: Optional[Any] = None, From 36fb70b037ca5248a181e86456964cf574fdab91 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 14 Dec 2021 13:59:57 -0500 Subject: [PATCH 055/305] Reformatted per pre-commit --- adafruit_requests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 835a115..b80faa7 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -721,7 +721,9 @@ def wrap_socket( return _FakeSSLSocket(socket, self._iface.TLS_MODE) -def set_socket(sock: SocketpoolModuleType, iface: Optional[InterfaceType] = None) -> None: +def set_socket( + sock: SocketpoolModuleType, iface: Optional[InterfaceType] = None +) -> None: """Legacy API for setting the socket and network interface. Use a `Session` instead.""" global _default_session # pylint: disable=global-statement,invalid-name if not iface: From 9dc3304c1cabe936a0e993e0f7625d33c520f77c Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 14 Dec 2021 14:02:15 -0500 Subject: [PATCH 056/305] Added/corrected type hints for _buffer_split0() --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index b80faa7..f59b2f4 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -70,7 +70,7 @@ # CircuitPython 6.0 does not have the bytearray.split method. # This function emulates buf.split(needle)[0], which is the functionality # required. -def _buffer_split0(buf, needle: bytes): +def _buffer_split0(buf: Union[bytes, bytearray], needle: Union[bytes, bytearray]): index = buf.find(needle) if index == -1: return buf From 769e8e3167c50047d501b6d3a2325decb66bca30 Mon Sep 17 00:00:00 2001 From: Neradoc Date: Tue, 28 Dec 2021 07:48:02 +0100 Subject: [PATCH 057/305] From types in the try/catch clause Fixes: ```py Traceback (most recent call last): File "code.py", line 1, in File "adafruit_requests.py", line 40, in ImportError: no module named 'types' ``` --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index f59b2f4..ee857b8 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -37,11 +37,11 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Requests.git" import errno -from types import TracebackType try: from typing import Union, TypeVar, Optional, Dict, Any, List, Type import types + from types import TracebackType import ssl import adafruit_esp32spi.adafruit_esp32spi_socket as esp32_socket import adafruit_wiznet5k.adafruit_wiznet5k_socket as wiznet_socket From 82898b965560f459e9a8682d3991dc1c27548860 Mon Sep 17 00:00:00 2001 From: Kevin Conley Date: Mon, 3 Jan 2022 21:47:51 +0000 Subject: [PATCH 058/305] Make git ignore VS Code folder --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9647e71..39dd71b 100755 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.mpy .idea +.vscode __pycache__ _build *.pyc From 9035f7b1cdda43fed48a175f966247594745a82b Mon Sep 17 00:00:00 2001 From: Kevin Conley Date: Tue, 4 Jan 2022 06:33:01 +0000 Subject: [PATCH 059/305] Use protocols to fix importing in CPython --- adafruit_requests.py | 146 +++++++++++++++++++++++++++++++++---------- docs/conf.py | 2 +- requirements.txt | 1 + setup.py | 2 +- 4 files changed, 117 insertions(+), 34 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index ee857b8..f7bfb94 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -37,35 +37,117 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Requests.git" import errno +import sys + +if sys.implementation.name == "circuitpython": + + def cast(_t, value): + """No-op shim for the typing.cast() function which is not available in CircuitPython.""" + return value + + +else: + from ssl import SSLContext + from types import ModuleType, TracebackType + from typing import Any, Dict, List, Optional, Tuple, Type, Union, cast + from typing_extensions import Protocol + + # Based on https://github.com/python/typeshed/blob/master/stdlib/_socket.pyi + class CommonSocketType(Protocol): + """Describes the common structure every socket type must have.""" + + def send(self, data: bytes, flags: int = ...) -> None: + """Send data to the socket. The meaning of the optional flags kwarg is + implementation-specific.""" + ... + + def settimeout(self, value: Optional[float]) -> None: + """Set a timeout on blocking socket operations.""" + ... + + def close(self) -> None: + """Close the socket.""" + ... + + class CommonCircuitPythonSocketType(CommonSocketType, Protocol): + """Describes the common structure every CircuitPython socket type must have.""" + + def connect( + self, + address: Tuple[str, int], + conntype: Optional[int] = ..., + ) -> None: + """Connect to a remote socket at the provided (host, port) address. The conntype + kwarg optionally may indicate SSL or not, depending on the underlying interface.""" + ... + + class LegacyCircuitPythonSocketType(CommonCircuitPythonSocketType, Protocol): + """Describes the structure a legacy CircuitPython socket type must have.""" + + def recv(self, bufsize: int = ...) -> bytes: + """Receive data from the socket. The return value is a bytes object representing + the data received. The maximum amount of data to be received at once is specified + by bufsize.""" + ... + + class SupportsRecvWithFlags(Protocol): + """Describes a type that posseses a socket recv() method supporting the flags kwarg.""" + + def recv(self, bufsize: int = ..., flags: int = ...) -> bytes: + """Receive data from the socket. The return value is a bytes object representing + the data received. The maximum amount of data to be received at once is specified + by bufsize. The meaning of the optional flags kwarg is implementation-specific.""" + ... + + class SupportsRecvInto(Protocol): + """Describes a type that possesses a socket recv_into() method.""" + + def recv_into( + self, buffer: bytearray, nbytes: int = ..., flags: int = ... + ) -> int: + """Receive up to nbytes bytes from the socket, storing the data into the provided + buffer. If nbytes is not specified (or 0), receive up to the size available in the + given buffer. The meaning of the optional flags kwarg is implementation-specific. + Returns the number of bytes received.""" + ... + + class CircuitPythonSocketType( + CommonCircuitPythonSocketType, + SupportsRecvInto, + SupportsRecvWithFlags, + Protocol, + ): # pylint: disable=too-many-ancestors + """Describes the structure every modern CircuitPython socket type must have.""" + + ... + + class StandardPythonSocketType( + CommonSocketType, SupportsRecvInto, SupportsRecvWithFlags, Protocol + ): + """Describes the structure every standard Python socket type must have.""" -try: - from typing import Union, TypeVar, Optional, Dict, Any, List, Type - import types - from types import TracebackType - import ssl - import adafruit_esp32spi.adafruit_esp32spi_socket as esp32_socket - import adafruit_wiznet5k.adafruit_wiznet5k_socket as wiznet_socket - import adafruit_fona.adafruit_fona_socket as cellular_socket - from adafruit_esp32spi.adafruit_esp32spi import ESP_SPIcontrol - from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K - from adafruit_fona.adafruit_fona import FONA - import socket as cpython_socket - - SocketType = TypeVar( - "SocketType", - esp32_socket.socket, - wiznet_socket.socket, - cellular_socket.socket, - cpython_socket.socket, - ) - SocketpoolModuleType = types.ModuleType - SSLContextType = ( - ssl.SSLContext - ) # Can use either CircuitPython or CPython ssl module - InterfaceType = TypeVar("InterfaceType", ESP_SPIcontrol, WIZNET5K, FONA) + def connect(self, address: Union[Tuple[Any, ...], str, bytes]) -> None: + """Connect to a remote socket at the provided address.""" + ... + + SocketType = Union[ + LegacyCircuitPythonSocketType, + CircuitPythonSocketType, + StandardPythonSocketType, + ] + + SocketpoolModuleType = ModuleType + + class InterfaceType(Protocol): + """Describes the structure every interface type must have.""" + + @property + def TLS_MODE(self) -> int: # pylint: disable=invalid-name + """Constant representing that a socket's connection mode is TLS.""" + ... + + SSLContextType = Union[SSLContext, "_FakeSSLContext"] -except ImportError: - pass # CircuitPython 6.0 does not have the bytearray.split method. # This function emulates buf.split(needle)[0], which is the functionality @@ -157,7 +239,7 @@ def _recv_into(self, buf: bytearray, size: int = 0) -> int: read_size = len(b) buf[:read_size] = b return read_size - return self.socket.recv_into(buf, size) + return cast("SupportsRecvInto", self.socket).recv_into(buf, size) @staticmethod def _find(buf: bytes, needle: bytes, start: int, end: int) -> int: @@ -440,7 +522,7 @@ def _free_sockets(self) -> None: def _get_socket( self, host: str, port: int, proto: str, *, timeout: float = 1 - ) -> SocketType: + ) -> CircuitPythonSocketType: # pylint: disable=too-many-branches key = (host, port, proto) if key in self._open_sockets: @@ -693,7 +775,7 @@ def delete(self, url: str, **kw) -> Response: class _FakeSSLSocket: - def __init__(self, socket: SocketType, tls_mode: int) -> None: + def __init__(self, socket: CircuitPythonSocketType, tls_mode: int) -> None: self._socket = socket self._mode = tls_mode self.settimeout = socket.settimeout @@ -701,7 +783,7 @@ def __init__(self, socket: SocketType, tls_mode: int) -> None: self.recv = socket.recv self.close = socket.close - def connect(self, address: Union[bytes, str]) -> None: + def connect(self, address: Tuple[str, int]) -> None: """connect wrapper to add non-standard mode parameter""" try: return self._socket.connect(address, self._mode) @@ -714,7 +796,7 @@ def __init__(self, iface: InterfaceType) -> None: self._iface = iface def wrap_socket( - self, socket: SocketType, server_hostname: Optional[str] = None + self, socket: CircuitPythonSocketType, server_hostname: Optional[str] = None ) -> _FakeSSLSocket: """Return the same socket""" # pylint: disable=unused-argument diff --git a/docs/conf.py b/docs/conf.py index 4a485bc..c0e0389 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,7 @@ # Uncomment the below if you use native CircuitPython modules such as # digitalio, micropython and busio. List the modules you use. Without it, the # autodoc module docs will fail to generate with a warning. -autodoc_mock_imports = ["adafruit_esp32spi", "adafruit_wiznet5k", "adafruit_fona"] +# autodoc_mock_imports = ["digitalio", "busio"] intersphinx_mapping = { diff --git a/requirements.txt b/requirements.txt index 17a850d..aab6f10 100755 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ # SPDX-License-Identifier: Unlicense Adafruit-Blinka +typing-extensions diff --git a/setup.py b/setup.py index d92f376..a947d99 100755 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ # Author details author="Adafruit Industries", author_email="circuitpython@adafruit.com", - install_requires=["Adafruit-Blinka"], + install_requires=["Adafruit-Blinka", "typing-extensions"], # Choose your license license="MIT", # See https://pypi.python.org/pypi?%3Aaction=list_classifiers From 9741e141bdfbfa1ff430d79eeef9f67620a05ca0 Mon Sep 17 00:00:00 2001 From: Kevin Conley Date: Wed, 5 Jan 2022 05:39:30 +0000 Subject: [PATCH 060/305] Run tests in CI --- .github/workflows/build.yml | 130 +++++++++++++++++++----------------- .gitignore | 1 + tox.ini | 11 +++ 3 files changed, 79 insertions(+), 63 deletions(-) create mode 100644 tox.ini diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca35544..4c2db77 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,66 +10,70 @@ jobs: test: runs-on: ubuntu-latest steps: - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Translate Repo Name For Build Tools filename_prefix - id: repo-name - run: | - echo ::set-output name=repo-name::$( - echo ${{ github.repository }} | - awk -F '\/' '{ print tolower($2) }' | - tr '_' '-' - ) - - name: Set up Python 3.7 - uses: actions/setup-python@v1 - with: - python-version: 3.7 - - name: Versions - run: | - python3 --version - - name: Checkout Current Repo - uses: actions/checkout@v1 - with: - submodules: true - - name: Checkout tools repo - uses: actions/checkout@v2 - with: - repository: adafruit/actions-ci-circuitpython-libs - path: actions-ci - - name: Install dependencies - # (e.g. - apt-get: gettext, etc; pip: circuitpython-build-tools, requirements.txt; etc.) - run: | - source actions-ci/install.sh - - name: Pip install Sphinx, pre-commit - run: | - pip install --force-reinstall Sphinx sphinx-rtd-theme pre-commit - - name: Library version - run: git describe --dirty --always --tags - - name: Pre-commit hooks - run: | - pre-commit run --all-files - - name: Build assets - run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . - - name: Archive bundles - uses: actions/upload-artifact@v2 - with: - name: bundles - path: ${{ github.workspace }}/bundles/ - - name: Build docs - working-directory: docs - run: sphinx-build -E -W -b html . _build/html - - name: Check For setup.py - id: need-pypi - run: | - echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) - - name: Build Python package - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') - run: | - pip install --upgrade setuptools wheel twine readme_renderer testresources - python setup.py sdist - python setup.py bdist_wheel --universal - twine check dist/* - - name: Setup problem matchers - uses: adafruit/circuitpython-action-library-ci-problem-matchers@v1 + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - name: Translate Repo Name For Build Tools filename_prefix + id: repo-name + run: | + echo ::set-output name=repo-name::$( + echo ${{ github.repository }} | + awk -F '\/' '{ print tolower($2) }' | + tr '_' '-' + ) + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + - name: Versions + run: | + python3 --version + - name: Checkout Current Repo + uses: actions/checkout@v1 + with: + submodules: true + - name: Checkout tools repo + uses: actions/checkout@v2 + with: + repository: adafruit/actions-ci-circuitpython-libs + path: actions-ci + - name: Install dependencies + # (e.g. - apt-get: gettext, etc; pip: circuitpython-build-tools, requirements.txt; etc.) + run: | + source actions-ci/install.sh + - name: Pip install Sphinx, pre-commit + run: | + pip install --force-reinstall Sphinx sphinx-rtd-theme pre-commit + - name: Library version + run: git describe --dirty --always --tags + - name: Pre-commit hooks + run: | + pre-commit run --all-files + - name: Build assets + run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . + - name: Archive bundles + uses: actions/upload-artifact@v2 + with: + name: bundles + path: ${{ github.workspace }}/bundles/ + - name: Build docs + working-directory: docs + run: sphinx-build -E -W -b html . _build/html + - name: Check For setup.py + id: need-pypi + run: | + echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) + - name: Build Python package + if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + run: | + pip install --upgrade setuptools wheel twine readme_renderer testresources + python setup.py sdist + python setup.py bdist_wheel --universal + twine check dist/* + - name: Test Python package + run: | + pip install tox==3.24.5 + tox + - name: Setup problem matchers + uses: adafruit/circuitpython-action-library-ci-problem-matchers@v1 diff --git a/.gitignore b/.gitignore index 39dd71b..bbe230d 100755 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ __pycache__ _build *.pyc .env +.tox bundles *.DS_Store .eggs diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..132d27e --- /dev/null +++ b/tox.ini @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2022 Kevin Conley +# +# SPDX-License-Identifier: MIT + +[tox] +envlist = py37 + +[testenv] +changedir = {toxinidir}/tests +deps = pytest==6.2.5 +commands = pytest From 91ce3262ac63c5530c51afcd55a4e0203e34c625 Mon Sep 17 00:00:00 2001 From: Kevin Conley Date: Thu, 6 Jan 2022 05:49:37 +0000 Subject: [PATCH 061/305] Revert whitespace changes to build.yml --- .github/workflows/build.yml | 134 ++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c2db77..1e7f254 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,70 +10,70 @@ jobs: test: runs-on: ubuntu-latest steps: - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Translate Repo Name For Build Tools filename_prefix - id: repo-name - run: | - echo ::set-output name=repo-name::$( - echo ${{ github.repository }} | - awk -F '\/' '{ print tolower($2) }' | - tr '_' '-' - ) - - name: Set up Python 3.7 - uses: actions/setup-python@v1 - with: - python-version: 3.7 - - name: Versions - run: | - python3 --version - - name: Checkout Current Repo - uses: actions/checkout@v1 - with: - submodules: true - - name: Checkout tools repo - uses: actions/checkout@v2 - with: - repository: adafruit/actions-ci-circuitpython-libs - path: actions-ci - - name: Install dependencies - # (e.g. - apt-get: gettext, etc; pip: circuitpython-build-tools, requirements.txt; etc.) - run: | - source actions-ci/install.sh - - name: Pip install Sphinx, pre-commit - run: | - pip install --force-reinstall Sphinx sphinx-rtd-theme pre-commit - - name: Library version - run: git describe --dirty --always --tags - - name: Pre-commit hooks - run: | - pre-commit run --all-files - - name: Build assets - run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . - - name: Archive bundles - uses: actions/upload-artifact@v2 - with: - name: bundles - path: ${{ github.workspace }}/bundles/ - - name: Build docs - working-directory: docs - run: sphinx-build -E -W -b html . _build/html - - name: Check For setup.py - id: need-pypi - run: | - echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) - - name: Build Python package - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') - run: | - pip install --upgrade setuptools wheel twine readme_renderer testresources - python setup.py sdist - python setup.py bdist_wheel --universal - twine check dist/* - - name: Test Python package - run: | - pip install tox==3.24.5 - tox - - name: Setup problem matchers - uses: adafruit/circuitpython-action-library-ci-problem-matchers@v1 + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - name: Translate Repo Name For Build Tools filename_prefix + id: repo-name + run: | + echo ::set-output name=repo-name::$( + echo ${{ github.repository }} | + awk -F '\/' '{ print tolower($2) }' | + tr '_' '-' + ) + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + - name: Versions + run: | + python3 --version + - name: Checkout Current Repo + uses: actions/checkout@v1 + with: + submodules: true + - name: Checkout tools repo + uses: actions/checkout@v2 + with: + repository: adafruit/actions-ci-circuitpython-libs + path: actions-ci + - name: Install dependencies + # (e.g. - apt-get: gettext, etc; pip: circuitpython-build-tools, requirements.txt; etc.) + run: | + source actions-ci/install.sh + - name: Pip install Sphinx, pre-commit + run: | + pip install --force-reinstall Sphinx sphinx-rtd-theme pre-commit + - name: Library version + run: git describe --dirty --always --tags + - name: Pre-commit hooks + run: | + pre-commit run --all-files + - name: Build assets + run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . + - name: Archive bundles + uses: actions/upload-artifact@v2 + with: + name: bundles + path: ${{ github.workspace }}/bundles/ + - name: Build docs + working-directory: docs + run: sphinx-build -E -W -b html . _build/html + - name: Check For setup.py + id: need-pypi + run: | + echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) + - name: Build Python package + if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + run: | + pip install --upgrade setuptools wheel twine readme_renderer testresources + python setup.py sdist + python setup.py bdist_wheel --universal + twine check dist/* + - name: Test Python package + run: | + pip install tox==3.24.5 + tox + - name: Setup problem matchers + uses: adafruit/circuitpython-action-library-ci-problem-matchers@v1 From c63a0bcf96cc79978ac73a358bf287b17489eab5 Mon Sep 17 00:00:00 2001 From: Kevin Conley Date: Tue, 11 Jan 2022 05:12:08 +0000 Subject: [PATCH 062/305] Update repo to require Python >=3.8 --- .github/workflows/build.yml | 4 ++-- .github/workflows/release.yml | 2 +- .readthedocs.yaml | 2 +- setup.py | 4 ++-- tox.ini | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1e7f254..67ecf47 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,10 +22,10 @@ jobs: awk -F '\/' '{ print tolower($2) }' | tr '_' '-' ) - - name: Set up Python 3.7 + - name: Set up Python 3.8 uses: actions/setup-python@v1 with: - python-version: 3.7 + python-version: 3.8 - name: Versions run: | python3 --version diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6d0015a..1421ff9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -69,7 +69,7 @@ jobs: if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') uses: actions/setup-python@v1 with: - python-version: '3.x' + python-version: '3.8' - name: Install dependencies if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') run: | diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 1335112..a2f6d2d 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,7 +9,7 @@ version: 2 python: - version: "3.7" + version: "3.8" install: - requirements: docs/requirements.txt - requirements: requirements.txt diff --git a/setup.py b/setup.py index a947d99..29b3cc0 100755 --- a/setup.py +++ b/setup.py @@ -33,6 +33,7 @@ # Author details author="Adafruit Industries", author_email="circuitpython@adafruit.com", + python_requires=">=3.8", install_requires=["Adafruit-Blinka", "typing-extensions"], # Choose your license license="MIT", @@ -44,8 +45,7 @@ "Topic :: System :: Hardware", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.8", ], # What does your project relate to? keywords="adafruit blinka circuitpython micropython requests requests, networking", diff --git a/tox.ini b/tox.ini index 132d27e..ab2df5e 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT [tox] -envlist = py37 +envlist = py38 [testenv] changedir = {toxinidir}/tests From c93388f85e9d41343b3d932989815aa1c4c30973 Mon Sep 17 00:00:00 2001 From: Kevin Conley Date: Tue, 11 Jan 2022 05:14:14 +0000 Subject: [PATCH 063/305] Remove dependency on typing-extensions --- adafruit_requests.py | 3 +-- requirements.txt | 1 - setup.py | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index f7bfb94..6ec2542 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -49,8 +49,7 @@ def cast(_t, value): else: from ssl import SSLContext from types import ModuleType, TracebackType - from typing import Any, Dict, List, Optional, Tuple, Type, Union, cast - from typing_extensions import Protocol + from typing import Any, Dict, List, Optional, Protocol, Tuple, Type, Union, cast # Based on https://github.com/python/typeshed/blob/master/stdlib/_socket.pyi class CommonSocketType(Protocol): diff --git a/requirements.txt b/requirements.txt index aab6f10..17a850d 100755 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,3 @@ # SPDX-License-Identifier: Unlicense Adafruit-Blinka -typing-extensions diff --git a/setup.py b/setup.py index 29b3cc0..c18e1ce 100755 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ author="Adafruit Industries", author_email="circuitpython@adafruit.com", python_requires=">=3.8", - install_requires=["Adafruit-Blinka", "typing-extensions"], + install_requires=["Adafruit-Blinka"], # Choose your license license="MIT", # See https://pypi.python.org/pypi?%3Aaction=list_classifiers From 82cdd3abb9d9822f942efd511e29bf502d387cf3 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 21 Jan 2022 17:22:26 -0500 Subject: [PATCH 064/305] Post-patch cleanup fixes --- .../PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md | 2 +- .github/workflows/build.yml | 6 +++--- .github/workflows/release.yml | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md b/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md index 71ef8f8..8de294e 100644 --- a/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md +++ b/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md @@ -4,7 +4,7 @@ Thank you for contributing! Before you submit a pull request, please read the following. -Make sure any changes you're submitting are in line with the CircuitPython Design Guide, available here: https://circuitpython.readthedocs.io/en/latest/docs/design_guide.html +Make sure any changes you're submitting are in line with the CircuitPython Design Guide, available here: https://docs.circuitpython.org/en/latest/docs/design_guide.html If your changes are to documentation, please verify that the documentation builds locally by following the steps found here: https://adafru.it/build-docs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 67ecf47..ede40dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,10 +22,10 @@ jobs: awk -F '\/' '{ print tolower($2) }' | tr '_' '-' ) - - name: Set up Python 3.8 - uses: actions/setup-python@v1 + - name: Set up Python 3.x + uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: "3.x" - name: Versions run: | python3 --version diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1421ff9..c10e2a8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,10 +24,10 @@ jobs: awk -F '\/' '{ print tolower($2) }' | tr '_' '-' ) - - name: Set up Python 3.6 - uses: actions/setup-python@v1 + - name: Set up Python 3.x + uses: actions/setup-python@v2 with: - python-version: 3.6 + python-version: "3.x" - name: Versions run: | python3 --version @@ -67,7 +67,7 @@ jobs: echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) - name: Set up Python if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: '3.8' - name: Install dependencies From 4807e30ff67db6300e18e25cb07566e7e8123e68 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 21 Jan 2022 17:47:32 -0500 Subject: [PATCH 065/305] Patch .readthedocs.yaml --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index a2f6d2d..f8b2891 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,7 +9,7 @@ version: 2 python: - version: "3.8" + version: "3.x" install: - requirements: docs/requirements.txt - requirements: requirements.txt From 7c2c91b802aaad05b417cad6296a5bd35b3ae517 Mon Sep 17 00:00:00 2001 From: dherrada Date: Mon, 24 Jan 2022 16:46:17 -0500 Subject: [PATCH 066/305] Updated docs link, updated python docs link, updated setup.py --- README.rst | 4 ++-- docs/conf.py | 4 ++-- docs/index.rst | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 3696db9..1600547 100755 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ Introduction ============ .. image:: https://readthedocs.org/projects/adafruit-circuitpython-requests/badge/?version=latest - :target: https://circuitpython.readthedocs.io/projects/requests/en/latest/ + :target: https://docs.circuitpython.org/projects/requests/en/latest/ :alt: Documentation Status .. image:: https://img.shields.io/discord/327254708534116352.svg @@ -58,7 +58,7 @@ Usage examples are within the `examples` subfolder of this library. Documentation ============= -API documentation for this library can be found on `Read the Docs `_. +API documentation for this library can be found on `Read the Docs `_. Contributing ============ diff --git a/docs/conf.py b/docs/conf.py index c0e0389..5d6e087 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,8 +29,8 @@ intersphinx_mapping = { - "python": ("https://docs.python.org/3.4", None), - "CircuitPython": ("https://circuitpython.readthedocs.io/en/latest/", None), + "python": ("https://docs.python.org/3", None), + "CircuitPython": ("https://docs.circuitpython.org/en/latest/", None), } # Add any paths that contain templates here, relative to this directory. diff --git a/docs/index.rst b/docs/index.rst index 2985f95..ce3fec8 100755 --- a/docs/index.rst +++ b/docs/index.rst @@ -30,7 +30,7 @@ Table of Contents :caption: Other Links Download - CircuitPython Reference Documentation + CircuitPython Reference Documentation CircuitPython Support Forum Discord Chat Adafruit Learning System From 9e28b6b82e048cab10b363a74b94c5da72da9f5d Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 10 Feb 2022 10:16:15 -0500 Subject: [PATCH 067/305] Consolidate Documentation sections of README --- README.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 1600547..4214b37 100755 --- a/README.rst +++ b/README.rst @@ -60,14 +60,11 @@ Documentation API documentation for this library can be found on `Read the Docs `_. +For information on building library documentation, please check out `this guide `_. + Contributing ============ Contributions are welcome! Please read our `Code of Conduct `_ before contributing to help this project stay welcoming. - -Documentation -============= - -For information on building library documentation, please check out `this guide `_. From 7eadff3b7c2f211e07efbd17b4933b8ffe8e103c Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 14 Feb 2022 16:19:07 -0600 Subject: [PATCH 068/305] raise error indicating gzipped data --- adafruit_requests.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/adafruit_requests.py b/adafruit_requests.py index 6ec2542..ddc899c 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -434,6 +434,12 @@ def text(self) -> str: if isinstance(self._cached, str): return self._cached raise RuntimeError("Cannot access text after getting content or json") + + if self.headers["content-encoding"] == "gzip": + raise ValueError( + "Content-encoding is gzip, data cannot be accessed as json or text. " + "Use content property to access raw bytes." + ) self._cached = str(self.content, self.encoding) return self._cached @@ -450,6 +456,11 @@ def json(self) -> Any: if not self._raw: self._raw = _RawResponse(self) + if self.headers["content-encoding"] == "gzip": + raise ValueError( + "Content-encoding is gzip, data cannot be accessed as json or text. " + "Use content property to access raw bytes." + ) try: obj = json.load(self._raw) except OSError: From 83356eb617ea437ab5997e8d372d3191f12a25ea Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 14 Feb 2022 16:49:23 -0600 Subject: [PATCH 069/305] check for 'content-encoding' to exist before accessing --- adafruit_requests.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index ddc899c..4c046ad 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -435,7 +435,10 @@ def text(self) -> str: return self._cached raise RuntimeError("Cannot access text after getting content or json") - if self.headers["content-encoding"] == "gzip": + if ( + "content-encoding" in self.headers + and self.headers["content-encoding"] == "gzip" + ): raise ValueError( "Content-encoding is gzip, data cannot be accessed as json or text. " "Use content property to access raw bytes." @@ -456,7 +459,10 @@ def json(self) -> Any: if not self._raw: self._raw = _RawResponse(self) - if self.headers["content-encoding"] == "gzip": + if ( + "content-encoding" in self.headers + and self.headers["content-encoding"] == "gzip" + ): raise ValueError( "Content-encoding is gzip, data cannot be accessed as json or text. " "Use content property to access raw bytes." From 270565665ada26fe8d7a99a3cb5941b452444471 Mon Sep 17 00:00:00 2001 From: dherrada Date: Mon, 14 Feb 2022 15:35:02 -0500 Subject: [PATCH 070/305] Fixed readthedocs build Signed-off-by: dherrada --- .readthedocs.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index f8b2891..33c2a61 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,8 +8,12 @@ # Required version: 2 +build: + os: ubuntu-20.04 + tools: + python: "3" + python: - version: "3.x" install: - requirements: docs/requirements.txt - requirements: requirements.txt From 2fc8dcb4c60963a8b3469ceaeb9685269f1c5550 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Mon, 21 Mar 2022 13:14:32 -0400 Subject: [PATCH 071/305] cleanup; update to CircuitPython 7 --- .pre-commit-config.yaml | 2 +- adafruit_requests.py | 140 +++++++++++++--------------------------- tests/legacy_mocket.py | 2 +- tests/mocket.py | 6 +- 4 files changed, 50 insertions(+), 100 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1b9fadc..0f46bd4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: - repo: https://github.com/python/black - rev: 20.8b1 + rev: 22.1.0 hooks: - id: black - repo: https://github.com/fsfe/reuse-tool diff --git a/adafruit_requests.py b/adafruit_requests.py index 4c046ad..626df11 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -45,7 +45,6 @@ def cast(_t, value): """No-op shim for the typing.cast() function which is not available in CircuitPython.""" return value - else: from ssl import SSLContext from types import ModuleType, TracebackType @@ -148,16 +147,6 @@ def TLS_MODE(self) -> int: # pylint: disable=invalid-name SSLContextType = Union[SSLContext, "_FakeSSLContext"] -# CircuitPython 6.0 does not have the bytearray.split method. -# This function emulates buf.split(needle)[0], which is the functionality -# required. -def _buffer_split0(buf: Union[bytes, bytearray], needle: Union[bytes, bytearray]): - index = buf.find(needle) - if index == -1: - return buf - return buf[:index] - - class _RawResponse: def __init__(self, response: "Response") -> None: self._response = response @@ -177,10 +166,6 @@ def readinto(self, buf: bytearray) -> int: return self._response._readinto(buf) # pylint: disable=protected-access -class _SendFailed(Exception): - """Custom exception to abort sending a request.""" - - class OutOfRetries(Exception): """Raised when requests has retried to make a request unsuccessfully.""" @@ -240,56 +225,25 @@ def _recv_into(self, buf: bytearray, size: int = 0) -> int: return read_size return cast("SupportsRecvInto", self.socket).recv_into(buf, size) - @staticmethod - def _find(buf: bytes, needle: bytes, start: int, end: int) -> int: - if hasattr(buf, "find"): - return buf.find(needle, start, end) - result = -1 - i = start - while i < end: - j = 0 - while j < len(needle) and i + j < end and buf[i + j] == needle[j]: - j += 1 - if j == len(needle): - result = i - break - i += 1 - - return result - - def _readto(self, first: bytes, second: bytes = b"") -> bytes: + def _readto(self, stop: bytes) -> bytearray: buf = self._receive_buffer end = self._received_length while True: - firsti = self._find(buf, first, 0, end) - secondi = -1 - if second: - secondi = self._find(buf, second, 0, end) - - i = -1 - needle_len = 0 - if firsti >= 0: - i = firsti - needle_len = len(first) - if secondi >= 0 and (firsti < 0 or secondi < firsti): - i = secondi - needle_len = len(second) + i = buf.find(stop, 0, end) if i >= 0: + # Stop was found. Return everything up to but not including stop. result = buf[:i] - new_start = i + needle_len - - if i + needle_len <= end: - new_end = end - new_start - buf[:new_end] = buf[new_start:end] - self._received_length = new_end + new_start = i + len(stop) + # Remove everything up to and including stop from the buffer. + new_end = end - new_start + buf[:new_end] = buf[new_start:end] + self._received_length = new_end return result - # Not found so load more. - + # Not found so load more bytes. # If our buffer is full, then make it bigger to load more. if end == len(buf): - new_size = len(buf) + 32 - new_buf = bytearray(new_size) + new_buf = bytearray(len(buf) + 32) new_buf[: len(buf)] = buf buf = new_buf self._receive_buffer = buf @@ -300,8 +254,6 @@ def _readto(self, first: bytes, second: bytes = b"") -> bytes: return buf[:end] end += read - return b"" - def _read_from_buffer( self, buf: Optional[bytearray] = None, nbytes: Optional[int] = None ) -> int: @@ -333,7 +285,7 @@ def _readinto(self, buf: bytearray) -> int: # Consume trailing \r\n for chunks 2+ if self._remaining == 0: self._throw_away(2) - chunk_header = _buffer_split0(self._readto(b"\r\n"), b";") + chunk_header = bytes(self._readto(b"\r\n")).split(b";", 1)[0] http_chunk_size = int(bytes(chunk_header), 16) if http_chunk_size == 0: self._chunked = False @@ -374,7 +326,7 @@ def close(self) -> None: self._throw_away(self._remaining) elif self._chunked: while True: - chunk_header = _buffer_split0(self._readto(b"\r\n"), b";") + chunk_header = bytes(self._readto(b"\r\n")).split(b";", 1)[0] chunk_size = int(bytes(chunk_header), 16) if chunk_size == 0: break @@ -392,11 +344,10 @@ def _parse_headers(self) -> None: Expects first line of HTTP request/response to have been read already. """ while True: - title = self._readto(b": ", b"\r\n") - if not title: + header = self._readto(b"\r\n") + if not header: break - - content = self._readto(b"\r\n") + title, content = bytes(header).split(b": ", 1) if title and content: # enforce that all headers are lowercase title = str(title, "utf-8").lower() @@ -407,6 +358,17 @@ def _parse_headers(self) -> None: self._chunked = content.strip().lower() == "chunked" self._headers[title] = content + def _validate_not_gzip(self) -> None: + """gzip encoding is not supported. Raise an exception if found.""" + if ( + "content-encoding" in self.headers + and self.headers["content-encoding"] == "gzip" + ): + raise ValueError( + "Content-encoding is gzip, data cannot be accessed as json or text. " + "Use content property to access raw bytes." + ) + @property def headers(self) -> Dict[str, str]: """ @@ -435,14 +397,8 @@ def text(self) -> str: return self._cached raise RuntimeError("Cannot access text after getting content or json") - if ( - "content-encoding" in self.headers - and self.headers["content-encoding"] == "gzip" - ): - raise ValueError( - "Content-encoding is gzip, data cannot be accessed as json or text. " - "Use content property to access raw bytes." - ) + self._validate_not_gzip() + self._cached = str(self.content, self.encoding) return self._cached @@ -459,20 +415,9 @@ def json(self) -> Any: if not self._raw: self._raw = _RawResponse(self) - if ( - "content-encoding" in self.headers - and self.headers["content-encoding"] == "gzip" - ): - raise ValueError( - "Content-encoding is gzip, data cannot be accessed as json or text. " - "Use content property to access raw bytes." - ) - try: - obj = json.load(self._raw) - except OSError: - # <5.3.1 doesn't piecemeal load json from any object with readinto so load the whole - # string. - obj = json.loads(self._raw.read()) + self._validate_not_gzip() + + obj = json.load(self._raw) if not self._cached: self._cached = obj self.close() @@ -599,12 +544,19 @@ def _send(socket: SocketType, data: bytes): # ESP32SPI sockets raise a RuntimeError when unable to send. try: sent = socket.send(data[total_sent:]) - except RuntimeError: - sent = 0 + except OSError as exc: + if exc.errno == errno.EAGAIN: + # Can't send right now (e.g., no buffer space), try again. + continue + # Some worse error. + raise + except RuntimeError as exc: + raise OSError(errno.EIO) from exc if sent is None: sent = len(data) if sent == 0: - raise _SendFailed() + # Not EAGAIN; that was already handled. + raise OSError(errno.EIO) total_sent += sent def _send_request( @@ -637,11 +589,9 @@ def _send_request( if json is not None: assert data is None # pylint: disable=import-outside-toplevel - try: - import json as json_module - except ImportError: - import ujson as json_module - data = json_module.dumps(json) + import json + + data = json.dumps(json) self._send(socket, b"Content-Type: application/json\r\n") if data: if isinstance(data, dict): @@ -711,7 +661,7 @@ def request( ok = True try: self._send_request(socket, host, method, path, headers, data, json) - except (_SendFailed, OSError): + except OSError: ok = False if ok: # Read the H of "HTTP/1.1" to make sure the socket is alive. send can appear to work diff --git a/tests/legacy_mocket.py b/tests/legacy_mocket.py index 8e18c4f..17bf209 100644 --- a/tests/legacy_mocket.py +++ b/tests/legacy_mocket.py @@ -15,7 +15,7 @@ class Mocket: # pylint: disable=too-few-public-methods - """ Mock Socket """ + """Mock Socket""" def __init__(self, response): self.settimeout = mock.Mock() diff --git a/tests/mocket.py b/tests/mocket.py index fe11cc9..3603800 100644 --- a/tests/mocket.py +++ b/tests/mocket.py @@ -8,7 +8,7 @@ class MocketPool: # pylint: disable=too-few-public-methods - """ Mock SocketPool """ + """Mock SocketPool""" SOCK_STREAM = 0 @@ -18,7 +18,7 @@ def __init__(self): class Mocket: # pylint: disable=too-few-public-methods - """ Mock Socket """ + """Mock Socket""" def __init__(self, response): self.settimeout = mock.Mock() @@ -62,7 +62,7 @@ def _recv_into(self, buf, nbytes=0): class SSLContext: # pylint: disable=too-few-public-methods - """ Mock SSL Context """ + """Mock SSL Context""" def __init__(self): self.wrap_socket = mock.Mock(side_effect=self._wrap_socket) From b260fc69c9b79d332a19d66bb88b99a7ae72eba2 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Tue, 22 Mar 2022 17:37:50 -0400 Subject: [PATCH 072/305] fix json name clashes --- adafruit_requests.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 626df11..c8ae170 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -39,6 +39,8 @@ import errno import sys +import json as json_module + if sys.implementation.name == "circuitpython": def cast(_t, value): @@ -404,9 +406,6 @@ def text(self) -> str: def json(self) -> Any: """The HTTP content, parsed into a json dictionary""" - # pylint: disable=import-outside-toplevel - import json - # The cached JSON will be a list or dictionary. if self._cached: if isinstance(self._cached, (list, dict)): @@ -417,7 +416,7 @@ def json(self) -> Any: self._validate_not_gzip() - obj = json.load(self._raw) + obj = json_module.load(self._raw) if not self._cached: self._cached = obj self.close() @@ -588,10 +587,7 @@ def _send_request( self._send(socket, b"\r\n") if json is not None: assert data is None - # pylint: disable=import-outside-toplevel - import json - - data = json.dumps(json) + data = json_module.dumps(json) self._send(socket, b"Content-Type: application/json\r\n") if data: if isinstance(data, dict): From 947bf5cdf1cd78b9d37b1af42d48cf04e33bffd5 Mon Sep 17 00:00:00 2001 From: Eva Herrada <33632497+evaherrada@users.noreply.github.com> Date: Thu, 24 Mar 2022 15:48:56 -0400 Subject: [PATCH 073/305] Changed black version to match cookiecutter --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0f46bd4..1b9fadc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: - repo: https://github.com/python/black - rev: 22.1.0 + rev: 20.8b1 hooks: - id: black - repo: https://github.com/fsfe/reuse-tool From 7feb62e366ed2e11c8162653a499b961e3edf6ef Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sun, 27 Mar 2022 10:03:22 -0400 Subject: [PATCH 074/305] Reformatted per pre-commit --- adafruit_requests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/adafruit_requests.py b/adafruit_requests.py index c8ae170..20535d4 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -47,6 +47,7 @@ def cast(_t, value): """No-op shim for the typing.cast() function which is not available in CircuitPython.""" return value + else: from ssl import SSLContext from types import ModuleType, TracebackType From 3dc3becd323b04a45658837eacb3bcf70cc24999 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Mon, 28 Mar 2022 15:52:04 -0400 Subject: [PATCH 075/305] Update Black to latest. Signed-off-by: Kattni Rembor --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1b9fadc..7467c1d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: - repo: https://github.com/python/black - rev: 20.8b1 + rev: 22.3.0 hooks: - id: black - repo: https://github.com/fsfe/reuse-tool From 5fda7e7b07675462c09f481db9ba25b05aec8d7f Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 29 Mar 2022 13:04:01 -0400 Subject: [PATCH 076/305] Rerun new black version --- adafruit_requests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 20535d4..c8ae170 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -47,7 +47,6 @@ def cast(_t, value): """No-op shim for the typing.cast() function which is not available in CircuitPython.""" return value - else: from ssl import SSLContext from types import ModuleType, TracebackType From 08e31ca9cd3e9a808625a67a4574cc0962cc33b1 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 29 Mar 2022 13:07:01 -0400 Subject: [PATCH 077/305] Add backup import of Protocol from typing_extensions --- adafruit_requests.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index c8ae170..2f3e252 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -50,7 +50,12 @@ def cast(_t, value): else: from ssl import SSLContext from types import ModuleType, TracebackType - from typing import Any, Dict, List, Optional, Protocol, Tuple, Type, Union, cast + from typing import Any, Dict, List, Optional, Tuple, Type, Union, cast + +try: + from typing import Protocol +except ImportError: + from typing_extensions import Protocol # Based on https://github.com/python/typeshed/blob/master/stdlib/_socket.pyi class CommonSocketType(Protocol): From cf17c20921b5f973f4120a99bf0d4b7c03fdf14a Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 29 Mar 2022 13:14:03 -0400 Subject: [PATCH 078/305] Place Protocol import in else block --- adafruit_requests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 2f3e252..9b5bc00 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -52,10 +52,10 @@ def cast(_t, value): from types import ModuleType, TracebackType from typing import Any, Dict, List, Optional, Tuple, Type, Union, cast -try: - from typing import Protocol -except ImportError: - from typing_extensions import Protocol + try: + from typing import Protocol + except ImportError: + from typing_extensions import Protocol # Based on https://github.com/python/typeshed/blob/master/stdlib/_socket.pyi class CommonSocketType(Protocol): From fbf6ea569df876c69b254f91ac2f8ed4f0e1e946 Mon Sep 17 00:00:00 2001 From: Mark Tseng <96064882+MarkTsengTW@users.noreply.github.com> Date: Wed, 6 Apr 2022 22:46:20 +0800 Subject: [PATCH 079/305] Proposed solution for Issue #104: multiple cookies Patch so that the _parse_headers function handles multiple cookies by appending them, separated by a comma and space. See lines 361-364. This mimics the behaviour of Python requests when it comes to response.headers['set-cookie'] and response.headers.items(). Currently this function just writes over any previous cookie, leaving only the last one. Thrilled to be submitting my first public pull request. Thanks to @askpatrickw and @tekktrik for their encouragement. --- adafruit_requests.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index c8ae170..266cc5f 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -358,7 +358,10 @@ def _parse_headers(self) -> None: self._remaining = int(content) if title == "transfer-encoding": self._chunked = content.strip().lower() == "chunked" - self._headers[title] = content + if title == "set-cookie" and title in self._headers: + self._headers[title] = self._headers[title] + ", " + content + else: + self._headers[title] = content def _validate_not_gzip(self) -> None: """gzip encoding is not supported. Raise an exception if found.""" From bacfa8d6b69f6da1899a81da76d0551fba2f644d Mon Sep 17 00:00:00 2001 From: Mark Tseng <96064882+MarkTsengTW@users.noreply.github.com> Date: Fri, 8 Apr 2022 09:40:49 +0800 Subject: [PATCH 080/305] Update adafruit_requests.py Change to be pythonic, per @Tekktrik. Hope it's in the right place this time! --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 266cc5f..b46c348 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -359,7 +359,7 @@ def _parse_headers(self) -> None: if title == "transfer-encoding": self._chunked = content.strip().lower() == "chunked" if title == "set-cookie" and title in self._headers: - self._headers[title] = self._headers[title] + ", " + content + self._headers[title] += ", " + content else: self._headers[title] = content From 1f45f5b4e8fdd0538ac9fcaf2f09f84ae399d4d8 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 16 Apr 2022 11:10:36 -0400 Subject: [PATCH 081/305] Add example for dealing with multiple cookies --- examples/requests_multiple_cookies.py | 58 +++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 examples/requests_multiple_cookies.py diff --git a/examples/requests_multiple_cookies.py b/examples/requests_multiple_cookies.py new file mode 100644 index 0000000..6e59f94 --- /dev/null +++ b/examples/requests_multiple_cookies.py @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: 2022 Alec Delaney +# SPDX-License-Identifier: MIT + +""" +This example was written for the MagTag; changes may be needed +for connecting to the internet depending on your device. +""" + +import ssl +import wifi +import socketpool +import adafruit_requests + +COOKIE_TEST_URL = "https://www.adafruit.com" + +# Get wifi details and more from a secrets.py file +try: + from secrets import secrets +except ImportError: + print("WiFi secrets are kept in secrets.py, please add them there!") + raise + +# Connect to the Wi-Fi network +print("Connecting to %s"%secrets["ssid"]) +wifi.radio.connect(secrets["ssid"], secrets["password"]) + +# Set up the requests library +pool = socketpool.SocketPool(wifi.radio) +requests = adafruit_requests.Session(pool, ssl.create_default_context()) + +# GET from the URL +print("Fetching multiple cookies from", COOKIE_TEST_URL) +response = requests.get(COOKIE_TEST_URL) + +# Spilt up the cookies by ", " +elements = response.headers["set-cookie"].split(", ") + +# NOTE: Some cookies use ", " when describing dates. This code will iterate through +# the previously split up 'set-cookie' header value and piece back together cookies +# that were accidentally split up for this reason +days_of_week = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat") +elements_iter = iter(elements) +cookie_list = [] +for element in elements_iter: + captured_day = [day for day in days_of_week if element.endswith(day)] + if captured_day: + cookie_list.append(element + ", " + next(elements_iter)) + else: + cookie_list.append(element) + +# Pring the information about the cookies +print("Number of cookies:", len(cookie_list)) +print("") +print("Cookies received:") +print("-" * 40) +for cookie in cookie_list: + print(cookie) + print("-" * 40) From 08a21c61d182cac852deb394562af072203e5416 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 16 Apr 2022 11:23:08 -0400 Subject: [PATCH 082/305] Reformatted pre pre-commit --- adafruit_requests.py | 1 + examples/requests_multiple_cookies.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index b46c348..9941f94 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -47,6 +47,7 @@ def cast(_t, value): """No-op shim for the typing.cast() function which is not available in CircuitPython.""" return value + else: from ssl import SSLContext from types import ModuleType, TracebackType diff --git a/examples/requests_multiple_cookies.py b/examples/requests_multiple_cookies.py index 6e59f94..45ae81f 100644 --- a/examples/requests_multiple_cookies.py +++ b/examples/requests_multiple_cookies.py @@ -21,7 +21,7 @@ raise # Connect to the Wi-Fi network -print("Connecting to %s"%secrets["ssid"]) +print("Connecting to %s" % secrets["ssid"]) wifi.radio.connect(secrets["ssid"], secrets["password"]) # Set up the requests library From b726db56e88142964c0159e4accd6d3acb7c5d0c Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 16 Apr 2022 11:27:13 -0400 Subject: [PATCH 083/305] Reformatted per black --- adafruit_requests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 9941f94..b46c348 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -47,7 +47,6 @@ def cast(_t, value): """No-op shim for the typing.cast() function which is not available in CircuitPython.""" return value - else: from ssl import SSLContext from types import ModuleType, TracebackType From 105fdf28ebae5a662a82fd56ff459ebf60e28e5f Mon Sep 17 00:00:00 2001 From: Eva Herrada <33632497+evaherrada@users.noreply.github.com> Date: Thu, 21 Apr 2022 18:53:46 -0400 Subject: [PATCH 084/305] Update .gitignore --- .gitignore | 50 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index bbe230d..544ec4a 100755 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,47 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2022 Kattni Rembor, written for Adafruit Industries # -# SPDX-License-Identifier: Unlicense +# SPDX-License-Identifier: MIT +# Do not include files and directories created by your personal work environment, such as the IDE +# you use, except for those already listed here. Pull requests including changes to this file will +# not be accepted. + +# This .gitignore file contains rules for files generated by working with CircuitPython libraries, +# including building Sphinx, testing with pip, and creating a virual environment, as well as the +# MacOS and IDE-specific files generated by using MacOS in general, or the PyCharm or VSCode IDEs. + +# If you find that there are files being generated on your machine that should not be included in +# your git commit, you should create a .gitignore_global file on your computer to include the +# files created by your personal setup. To do so, follow the two steps below. + +# First, create a file called .gitignore_global somewhere convenient for you, and add rules for +# the files you want to exclude from git commits. + +# Second, configure Git to use the exclude file for all Git repositories by running the +# following via commandline, replacing "path/to/your/" with the actual path to your newly created +# .gitignore_global file: +# git config --global core.excludesfile path/to/your/.gitignore_global + +# CircuitPython-specific files *.mpy -.idea -.vscode + +# Python-specific files __pycache__ -_build *.pyc + +# Sphinx build-specific files +_build + +# This file results from running `pip -e install .` in a local repository +*.egg-info + +# Virtual environment-specific files .env -.tox -bundles + +# MacOS-specific files *.DS_Store -.eggs -dist -**/*.egg-info + +# IDE-specific files +.idea +.vscode +*~ From 3ffcf38e6bd3c7bad3f07f0633b92f7759f8881c Mon Sep 17 00:00:00 2001 From: evaherrada Date: Fri, 22 Apr 2022 15:59:12 -0400 Subject: [PATCH 085/305] Patch: Replaced discord badge image --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 4214b37..b4b376f 100755 --- a/README.rst +++ b/README.rst @@ -5,7 +5,7 @@ Introduction :target: https://docs.circuitpython.org/projects/requests/en/latest/ :alt: Documentation Status -.. image:: https://img.shields.io/discord/327254708534116352.svg +.. image:: https://github.com/adafruit/Adafruit_CircuitPython_Bundle/blob/main/badges/adafruit_discord.svg :target: https://adafru.it/discord :alt: Discord From e1f15e212b985e9d18fb4b5b816c6e992bfdd103 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Sun, 24 Apr 2022 14:05:59 -0500 Subject: [PATCH 086/305] change discord badge --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index b4b376f..e8ae691 100755 --- a/README.rst +++ b/README.rst @@ -5,7 +5,7 @@ Introduction :target: https://docs.circuitpython.org/projects/requests/en/latest/ :alt: Documentation Status -.. image:: https://github.com/adafruit/Adafruit_CircuitPython_Bundle/blob/main/badges/adafruit_discord.svg +.. image:: https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_Bundle/main/badges/adafruit_discord.svg :target: https://adafru.it/discord :alt: Discord From 7bbde260bfe664c0ff1957de354f52dfd4b0c665 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sun, 15 May 2022 12:48:57 -0400 Subject: [PATCH 087/305] Patch .pre-commit-config.yaml --- .pre-commit-config.yaml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7467c1d..3343606 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,40 +3,40 @@ # SPDX-License-Identifier: Unlicense repos: -- repo: https://github.com/python/black + - repo: https://github.com/python/black rev: 22.3.0 hooks: - - id: black -- repo: https://github.com/fsfe/reuse-tool - rev: v0.12.1 + - id: black + - repo: https://github.com/fsfe/reuse-tool + rev: v0.14.0 hooks: - - id: reuse -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 + - id: reuse + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.2.0 hooks: - - id: check-yaml - - id: end-of-file-fixer - - id: trailing-whitespace -- repo: https://github.com/pycqa/pylint + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/pycqa/pylint rev: v2.11.1 hooks: - - id: pylint + - id: pylint name: pylint (library code) types: [python] args: - --disable=consider-using-f-string exclude: "^(docs/|examples/|tests/|setup.py$)" - - id: pylint + - id: pylint name: pylint (example code) description: Run pylint rules on "examples/*.py" files types: [python] files: "^examples/" args: - - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code - - id: pylint + - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code + - id: pylint name: pylint (test code) description: Run pylint rules on "tests/*.py" files types: [python] files: "^tests/" args: - - --disable=missing-docstring,consider-using-f-string,duplicate-code + - --disable=missing-docstring,consider-using-f-string,duplicate-code From f32854eee3c3cabfb4564340dea05d514a648183 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sun, 22 May 2022 00:18:55 -0400 Subject: [PATCH 088/305] Increase min lines similarity Signed-off-by: Alec Delaney --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index cfd1c41..f006a4a 100755 --- a/.pylintrc +++ b/.pylintrc @@ -252,7 +252,7 @@ ignore-docstrings=yes ignore-imports=yes # Minimum lines number of a similarity. -min-similarity-lines=4 +min-similarity-lines=12 [BASIC] From 3b2c6ce799e8f9e41af53c95f29d86af0ec02939 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sun, 22 May 2022 00:18:23 -0400 Subject: [PATCH 089/305] Switch to inclusive terminology Signed-off-by: Alec Delaney --- .pylintrc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pylintrc b/.pylintrc index f006a4a..f772971 100755 --- a/.pylintrc +++ b/.pylintrc @@ -9,11 +9,11 @@ # run arbitrary code extension-pkg-whitelist= -# Add files or directories to the blacklist. They should be base names, not +# Add files or directories to the ignore-list. They should be base names, not # paths. ignore=CVS -# Add files or directories matching the regex patterns to the blacklist. The +# Add files or directories matching the regex patterns to the ignore-list. The # regex matches against base names, not paths. ignore-patterns= From aed5333ef6b2fa702a90c3f01169dc483b17b374 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Mon, 30 May 2022 14:25:04 -0400 Subject: [PATCH 090/305] Set language to "en" for documentation Signed-off-by: Alec Delaney --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 5d6e087..60616a8 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,7 +60,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. From 6e03832b1170d75d7a7e0b8a578160298905b726 Mon Sep 17 00:00:00 2001 From: evaherrada Date: Tue, 7 Jun 2022 15:34:52 -0400 Subject: [PATCH 091/305] Added cp.org link to index.rst --- docs/index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index ce3fec8..006b766 100755 --- a/docs/index.rst +++ b/docs/index.rst @@ -29,7 +29,8 @@ Table of Contents .. toctree:: :caption: Other Links - Download + Download from GitHub + Download Library Bundle CircuitPython Reference Documentation CircuitPython Support Forum Discord Chat From c2b41af9c7495057df2d43b984e8019a09d9044a Mon Sep 17 00:00:00 2001 From: klocs <7843190+klocs@users.noreply.github.com> Date: Sun, 3 Jul 2022 11:24:05 -0700 Subject: [PATCH 092/305] ensure _throw_away works if _recv_into returns partial data --- adafruit_requests.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 2225ca9..574ce00 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -317,11 +317,16 @@ def _throw_away(self, nbytes: int) -> None: nbytes -= self._read_from_buffer(nbytes=nbytes) buf = self._receive_buffer - for _ in range(nbytes // len(buf)): - self._recv_into(buf) - remaining = nbytes % len(buf) + len_buf = len(buf) + for _ in range(nbytes // len_buf): + read = 0 + while read < len_buf: + read += self._recv_into(buf, len_buf-read) + remaining = nbytes % len_buf if remaining: - self._recv_into(buf, remaining) + read = 0 + while read < remaining: + read += self._recv_into(buf, remaining-read) def close(self) -> None: """Drain the remaining ESP socket buffers. We assume we already got what we wanted.""" From 1fee60e74c5186737402ccfc50c9762cf6340881 Mon Sep 17 00:00:00 2001 From: klocs <7843190+klocs@users.noreply.github.com> Date: Sun, 3 Jul 2022 11:25:09 -0700 Subject: [PATCH 093/305] add unit test for chunked redirect testing --- tests/chunked_redirect_test.py | 124 +++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 tests/chunked_redirect_test.py diff --git a/tests/chunked_redirect_test.py b/tests/chunked_redirect_test.py new file mode 100644 index 0000000..949c903 --- /dev/null +++ b/tests/chunked_redirect_test.py @@ -0,0 +1,124 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +""" Redirection Tests """ + +from unittest import mock +import mocket +import adafruit_requests +from chunk_test import _chunk + +IP = "1.2.3.4" +HOST = "docs.google.com" +PATH = "/spreadsheets/d/e/2PACX-1vR1WjUKz35-ek6SiR5droDfvPp51MTds4wUs57vEZNh2uDfihSTPhTaiiRovLbNe1mkeRgurppRJ_Zy/pub?output=tsv" + +# response headers returned from the initial request +HEADERS_REDIRECT = ( + b"HTTP/1.1 307 Temporary Redirect\r\n" + b"Content-Type: text/html; charset=UTF-8\r\n" + b"Cache-Control: no-cache, no-store, max-age=0, must-revalidate\r\n" + b"Pragma: no-cache\r\n" + b"Expires: Mon, 01 Jan 1990 00:00:00 GMT\r\n" + b"Date: Sat, 25 Jun 2022 21:08:48 GMT\r\n" + b"Location: https://doc-14-2g-sheets.googleusercontent.com/pub/70cmver1f290kjsnpar5ku2h9g/3llvt5u8njbvat22m9l19db1h4/1656191325000" + b"/109226138307867586192/*/e@2PACX-1vR1WjUKz35-ek6SiR5droDfvPp51MTds4wUs57vEZNh2uDfihSTPhTaiiRovLbNe1mkeRgurppRJ_Zy?output=tsv\r\n" + b'P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."\r\n' + b"X-Content-Type-Options: nosniff\r\n" + b"X-XSS-Protection: 1; mode=block\r\n" + b"Server: GSE\r\n" + b"Set-Cookie: NID=511=EcnO010Porg0NIrxM8tSG6MhfQiVtWrQS42CjhKEpzwIvzBj2PFYH0-H_N--EAXaPBkR2jFjAWEAxIJNqhvKb0vswOWp9hqcCrO51S8kO5I4C3" + b"Is2ctWe1b_ysRU-6hjnJyLHzqjXotAWzEmr_qA3bpqWDwlRaQIiC6SvxM8L0M; expires=Sun, 25-Dec-2022 21:08:48 GMT; path=/; " + b"domain=.google.com; HttpOnly\r\n" + b'Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443";' + b' ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"\r\n' + b"Accept-Ranges: none\r\n" + b"Vary: Accept-Encoding\r\n" + b"Transfer-Encoding: chunked\r\n\r\n" +) + +# response body returned from the initial request (needs to be chunked.) +BODY_REDIRECT = ( + b'\n\nTemporary Redirect\n\n\n' + b'

Temporary Redirect

\nThe document has moved here.\n\n\n' +) + +# response headers from the request to the redirected location +HEADERS = ( + b"HTTP/1.1 200 OK\r\n" + b"Content-Type: text/tab-separated-values\r\n" + b"X-Frame-Options: ALLOW-FROM https://docs.google.com\r\n" + b"X-Robots-Tag: noindex, nofollow, nosnippet\r\n" + b"Expires: Sat, 25 Jun 2022 21:08:49 GMT\r\n" + b"Date: Sat, 25 Jun 2022 21:08:49 GMT\r\n" + b"Cache-Control: private, max-age=300\r\n" + b"Content-Disposition: attachment; filename=\"WeeklyPlanner-Sheet1.tsv\"; filename*=UTF-8''Weekly%20Planner%20-%20Sheet1.tsv\r\n" + b"Access-Control-Allow-Origin: *\r\n" + b"Access-Control-Expose-Headers: Cache-Control,Content-Disposition,Content-Encoding,Content-Length,Content-Type,Date,Expires,Server,Transfer-Encoding\r\n" + b"Content-Security-Policy: frame-ancestors 'self' https://docs.google.com\r\n" + b"Content-Security-Policy: base-uri 'self';object-src 'self';report-uri https://doc-14-2g-sheets.googleusercontent.com/spreadsheets/cspreport;" + b"script-src 'report-sample' 'nonce-6V57medLoq3hw2BWeyGu_A' 'unsafe-inline' 'strict-dynamic' https: http: 'unsafe-eval';worker-src 'self' blob:\r\n" + b"X-Content-Type-Options: nosniff\r\n" + b"X-XSS-Protection: 1; mode=block\r\n" + b"Server: GSE\r\n" + b'Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443";' + b' ma=2592000,quic=":443"; ma=2592000; v="46,43"\r\n' + b"Accept-Ranges: none\r\n" + b"Vary: Accept-Encoding\r\n" + b"Transfer-Encoding: chunked\r\n\r\n" +) + +# response body from the request to the redirected location (needs to be chunked.) +BODY = ( + b"Sunday\tMonday\tTuesday\tWednesday\tThursday\tFriday\tSaturday\r\n" + b"Rest\tSpin class\tRowing\tWerewolf Twitter\tWeights\tLaundry\tPoke bowl\r\n" + b"\t\tZoom call\tShow & Tell\t\tMow lawn\tSynth Riders\r\n" + b"\t\tTacos\tAsk an Engineer\t\tTrash pickup\t\r\n" + b"\t\t\t\t\tLeg day\t\r\n" + b"\t\t\t\t\tPizza night\t" +) + + +class MocketRecvInto(mocket.Mocket): + """A special Mocket to cap the number of bytes returned from recv_into()""" + + def __init__(self, response): + super().__init__(response) + self.recv_into = mock.Mock(side_effect=self._recv_into) + + def _recv_into(self, buf, nbytes=0): + assert isinstance(nbytes, int) and nbytes >= 0 + read = nbytes if nbytes > 0 else len(buf) + remaining = len(self._response) - self._position + read = min(read, remaining, 205) + end = self._position + read + buf[:read] = self._response[self._position : end] + self._position = end + return read + + +def do_test_chunked_redirect(): + pool = mocket.MocketPool() + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 443)),) + chunk = _chunk(BODY_REDIRECT, len(BODY_REDIRECT)) + chunk2 = _chunk(BODY, len(BODY)) + + sock1 = MocketRecvInto(HEADERS_REDIRECT + chunk) + sock2 = mocket.Mocket(HEADERS + chunk2) + pool.socket.side_effect = (sock1, sock2) + + requests_session = adafruit_requests.Session(pool, mocket.SSLContext()) + response = requests_session.get("https://" + HOST + PATH) + + sock1.connect.assert_called_once_with((HOST, 443)) + sock2.connect.assert_called_once_with( + ("doc-14-2g-sheets.googleusercontent.com", 443) + ) + + assert response.text == str(BODY, "utf-8") + + +def test_chunked_redirect(): + do_test_chunked_redirect() From c73e0e911dc058ec89c9bfd371128c6f10732f56 Mon Sep 17 00:00:00 2001 From: klocs <7843190+klocs@users.noreply.github.com> Date: Sun, 3 Jul 2022 12:03:24 -0700 Subject: [PATCH 094/305] Update formatting based on CI feedback. --- tests/chunked_redirect_test.py | 49 +++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/tests/chunked_redirect_test.py b/tests/chunked_redirect_test.py index 949c903..5e11e79 100644 --- a/tests/chunked_redirect_test.py +++ b/tests/chunked_redirect_test.py @@ -6,12 +6,15 @@ from unittest import mock import mocket -import adafruit_requests from chunk_test import _chunk +import adafruit_requests IP = "1.2.3.4" HOST = "docs.google.com" -PATH = "/spreadsheets/d/e/2PACX-1vR1WjUKz35-ek6SiR5droDfvPp51MTds4wUs57vEZNh2uDfihSTPhTaiiRovLbNe1mkeRgurppRJ_Zy/pub?output=tsv" +PATH = ( + "/spreadsheets/d/e/2PACX-1vR1WjUKz35-ek6SiR5droDfvPp51MTds4wUs57vEZNh2uDfihSTPhTaiiRo" + "vLbNe1mkeRgurppRJ_Zy/pub?output=tsv" +) # response headers returned from the initial request HEADERS_REDIRECT = ( @@ -21,16 +24,21 @@ b"Pragma: no-cache\r\n" b"Expires: Mon, 01 Jan 1990 00:00:00 GMT\r\n" b"Date: Sat, 25 Jun 2022 21:08:48 GMT\r\n" - b"Location: https://doc-14-2g-sheets.googleusercontent.com/pub/70cmver1f290kjsnpar5ku2h9g/3llvt5u8njbvat22m9l19db1h4/1656191325000" - b"/109226138307867586192/*/e@2PACX-1vR1WjUKz35-ek6SiR5droDfvPp51MTds4wUs57vEZNh2uDfihSTPhTaiiRovLbNe1mkeRgurppRJ_Zy?output=tsv\r\n" + b"Location: https://doc-14-2g-sheets.googleusercontent.com/pub/70cmver1f290kjsnpar5ku2h9g/3" + b"llvt5u8njbvat22m9l19db1h4/1656191325000" + b"/109226138307867586192/*/e@2PACX-1vR1WjUKz35-ek6SiR5droDfvPp51MTds4wUs57vEZNh2uDfihSTPhTai" + b"iRovLbNe1mkeRgurppRJ_Zy?output=tsv\r\n" b'P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."\r\n' b"X-Content-Type-Options: nosniff\r\n" b"X-XSS-Protection: 1; mode=block\r\n" b"Server: GSE\r\n" - b"Set-Cookie: NID=511=EcnO010Porg0NIrxM8tSG6MhfQiVtWrQS42CjhKEpzwIvzBj2PFYH0-H_N--EAXaPBkR2jFjAWEAxIJNqhvKb0vswOWp9hqcCrO51S8kO5I4C3" - b"Is2ctWe1b_ysRU-6hjnJyLHzqjXotAWzEmr_qA3bpqWDwlRaQIiC6SvxM8L0M; expires=Sun, 25-Dec-2022 21:08:48 GMT; path=/; " + b"Set-Cookie: NID=511=EcnO010Porg0NIrxM8tSG6MhfQiVtWrQS42CjhKEpzwIvzBj2PFYH0-H_N--EAXaPBkR2j" + b"FjAWEAxIJNqhvKb0vswOWp9hqcCrO51S8kO5I4C3" + b"Is2ctWe1b_ysRU-6hjnJyLHzqjXotAWzEmr_qA3bpqWDwlRaQIiC6SvxM8L0M; expires=Sun, 25-Dec-2022 " + b"21:08:48 GMT; path=/; " b"domain=.google.com; HttpOnly\r\n" - b'Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443";' + b'Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ' + b'ma=2592000,h3-Q046=":443";' b' ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"\r\n' b"Accept-Ranges: none\r\n" b"Vary: Accept-Encoding\r\n" @@ -39,10 +47,14 @@ # response body returned from the initial request (needs to be chunked.) BODY_REDIRECT = ( - b'\n\nTemporary Redirect\n\n\n' - b'

Temporary Redirect

\nThe document has moved here.\n\n\n' + b"\n\nTemporary Redirect\n\n" + b'\n' + b"

Temporary Redirect

\nThe document has moved " + b'here.\n\n\n' ) # response headers from the request to the redirected location @@ -54,16 +66,21 @@ b"Expires: Sat, 25 Jun 2022 21:08:49 GMT\r\n" b"Date: Sat, 25 Jun 2022 21:08:49 GMT\r\n" b"Cache-Control: private, max-age=300\r\n" - b"Content-Disposition: attachment; filename=\"WeeklyPlanner-Sheet1.tsv\"; filename*=UTF-8''Weekly%20Planner%20-%20Sheet1.tsv\r\n" + b'Content-Disposition: attachment; filename="WeeklyPlanner-Sheet1.tsv"; ' + b"filename*=UTF-8''Weekly%20Planner%20-%20Sheet1.tsv\r\n" b"Access-Control-Allow-Origin: *\r\n" - b"Access-Control-Expose-Headers: Cache-Control,Content-Disposition,Content-Encoding,Content-Length,Content-Type,Date,Expires,Server,Transfer-Encoding\r\n" + b"Access-Control-Expose-Headers: Cache-Control,Content-Disposition,Content-Encoding," + b"Content-Length,Content-Type,Date,Expires,Server,Transfer-Encoding\r\n" b"Content-Security-Policy: frame-ancestors 'self' https://docs.google.com\r\n" - b"Content-Security-Policy: base-uri 'self';object-src 'self';report-uri https://doc-14-2g-sheets.googleusercontent.com/spreadsheets/cspreport;" - b"script-src 'report-sample' 'nonce-6V57medLoq3hw2BWeyGu_A' 'unsafe-inline' 'strict-dynamic' https: http: 'unsafe-eval';worker-src 'self' blob:\r\n" + b"Content-Security-Policy: base-uri 'self';object-src 'self';report-uri https://" + b"doc-14-2g-sheets.googleusercontent.com/spreadsheets/cspreport;" + b"script-src 'report-sample' 'nonce-6V57medLoq3hw2BWeyGu_A' 'unsafe-inline' 'strict-dynamic'" + b" https: http: 'unsafe-eval';worker-src 'self' blob:\r\n" b"X-Content-Type-Options: nosniff\r\n" b"X-XSS-Protection: 1; mode=block\r\n" b"Server: GSE\r\n" - b'Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443";' + b'Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ' + b'ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443";' b' ma=2592000,quic=":443"; ma=2592000; v="46,43"\r\n' b"Accept-Ranges: none\r\n" b"Vary: Accept-Encoding\r\n" From d27938b36e7369bb63c052dfe8d5a08b0f2a0fb6 Mon Sep 17 00:00:00 2001 From: klocs <7843190+klocs@users.noreply.github.com> Date: Sun, 3 Jul 2022 13:13:11 -0700 Subject: [PATCH 095/305] Fix more CI requirements --- tests/chunked_redirect_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/chunked_redirect_test.py b/tests/chunked_redirect_test.py index 5e11e79..c09e8c9 100644 --- a/tests/chunked_redirect_test.py +++ b/tests/chunked_redirect_test.py @@ -98,7 +98,7 @@ ) -class MocketRecvInto(mocket.Mocket): +class MocketRecvInto(mocket.Mocket): # pylint: disable=too-few-public-methods """A special Mocket to cap the number of bytes returned from recv_into()""" def __init__(self, response): From cfacf1304b8f0b3b36a6ef87b96c78d05fb94321 Mon Sep 17 00:00:00 2001 From: klocs <7843190+klocs@users.noreply.github.com> Date: Sun, 3 Jul 2022 13:27:38 -0700 Subject: [PATCH 096/305] CI required formatting changes --- adafruit_requests.py | 4 ++-- tests/chunked_redirect_test.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 574ce00..4148250 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -321,12 +321,12 @@ def _throw_away(self, nbytes: int) -> None: for _ in range(nbytes // len_buf): read = 0 while read < len_buf: - read += self._recv_into(buf, len_buf-read) + read += self._recv_into(buf, len_buf - read) remaining = nbytes % len_buf if remaining: read = 0 while read < remaining: - read += self._recv_into(buf, remaining-read) + read += self._recv_into(buf, remaining - read) def close(self) -> None: """Drain the remaining ESP socket buffers. We assume we already got what we wanted.""" diff --git a/tests/chunked_redirect_test.py b/tests/chunked_redirect_test.py index c09e8c9..bb8ce9e 100644 --- a/tests/chunked_redirect_test.py +++ b/tests/chunked_redirect_test.py @@ -98,7 +98,7 @@ ) -class MocketRecvInto(mocket.Mocket): # pylint: disable=too-few-public-methods +class MocketRecvInto(mocket.Mocket): # pylint: disable=too-few-public-methods """A special Mocket to cap the number of bytes returned from recv_into()""" def __init__(self, response): From 16b848dc14003d9cfae2b8484562f18cff92e66f Mon Sep 17 00:00:00 2001 From: klocs <7843190+klocs@users.noreply.github.com> Date: Sun, 3 Jul 2022 13:44:35 -0700 Subject: [PATCH 097/305] Update adafruit_requests.py Co-authored-by: Dan Halbert --- adafruit_requests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 4148250..8dd20ad 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -319,9 +319,9 @@ def _throw_away(self, nbytes: int) -> None: buf = self._receive_buffer len_buf = len(buf) for _ in range(nbytes // len_buf): - read = 0 - while read < len_buf: - read += self._recv_into(buf, len_buf - read) + to_read = len_buf + while to_read > 0: + to_read -= self._recv_into(buf, to_read) remaining = nbytes % len_buf if remaining: read = 0 From ee1ddce368da2e7ed2516dc24d263ebbb605b229 Mon Sep 17 00:00:00 2001 From: klocs <7843190+klocs@users.noreply.github.com> Date: Sun, 3 Jul 2022 13:44:53 -0700 Subject: [PATCH 098/305] Update adafruit_requests.py Co-authored-by: Dan Halbert --- adafruit_requests.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 8dd20ad..8c811ec 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -324,9 +324,8 @@ def _throw_away(self, nbytes: int) -> None: to_read -= self._recv_into(buf, to_read) remaining = nbytes % len_buf if remaining: - read = 0 - while read < remaining: - read += self._recv_into(buf, remaining - read) + while remaining > 0: + remaining -= self._recv_into(buf, remaining) def close(self) -> None: """Drain the remaining ESP socket buffers. We assume we already got what we wanted.""" From e0721d386a314694954207f9da1709501a8a6164 Mon Sep 17 00:00:00 2001 From: klocs <7843190+klocs@users.noreply.github.com> Date: Sun, 3 Jul 2022 16:21:36 -0700 Subject: [PATCH 099/305] clean up code in _throw_away. --- adafruit_requests.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 8c811ec..70cfe14 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -322,10 +322,9 @@ def _throw_away(self, nbytes: int) -> None: to_read = len_buf while to_read > 0: to_read -= self._recv_into(buf, to_read) - remaining = nbytes % len_buf - if remaining: - while remaining > 0: - remaining -= self._recv_into(buf, remaining) + to_read = nbytes % len_buf + while to_read > 0: + to_read -= self._recv_into(buf, to_read) def close(self) -> None: """Drain the remaining ESP socket buffers. We assume we already got what we wanted.""" From df066a68f30a5332849c2a5a7c5c30c54a069c4b Mon Sep 17 00:00:00 2001 From: evaherrada Date: Fri, 22 Jul 2022 13:59:14 -0400 Subject: [PATCH 100/305] Changed .env to .venv in README.rst --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index e8ae691..6072efb 100755 --- a/README.rst +++ b/README.rst @@ -46,8 +46,8 @@ To install in a virtual environment in your current project: .. code-block:: shell mkdir project-name && cd project-name - python3 -m venv .env - source .env/bin/activate + python3 -m venv .venv + source .venv/bin/activate pip3 install adafruit-circuitpython-requests Usage Example From a7fe2599c4ea4034039ca014a28874422974f9a3 Mon Sep 17 00:00:00 2001 From: evaherrada Date: Tue, 2 Aug 2022 17:00:59 -0400 Subject: [PATCH 101/305] Added Black formatting badge --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 6072efb..5f52cea 100755 --- a/README.rst +++ b/README.rst @@ -13,6 +13,10 @@ Introduction :target: https://github.com/adafruit/Adafruit_CircuitPython_Requests/actions/ :alt: Build Status +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Code Style: Black + A requests-like library for HTTP commands. From 095eba4ba64c0dd03dc3807460eecaf1249c0e4c Mon Sep 17 00:00:00 2001 From: Neradoc Date: Wed, 3 Aug 2022 14:24:27 +0200 Subject: [PATCH 102/305] when sending data get the bytes length not the string length --- adafruit_requests.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 70cfe14..8c622bd 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -609,13 +609,12 @@ def _send_request( for k in data: _post_data = "{}&{}={}".format(_post_data, k, data[k]) data = _post_data[1:] + if isinstance(data, str): + data = bytes(data, "utf-8") self._send(socket, b"Content-Length: %d\r\n" % len(data)) self._send(socket, b"\r\n") if data: - if isinstance(data, bytearray): - self._send(socket, bytes(data)) - else: - self._send(socket, bytes(data, "utf-8")) + self._send(socket, bytes(data)) # pylint: disable=too-many-branches, too-many-statements, unused-argument, too-many-arguments, too-many-locals def request( From cd75d542cd1d0974431c929479a553949e5cbc68 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Mon, 8 Aug 2022 22:05:55 -0400 Subject: [PATCH 103/305] Switched to pyproject.toml --- .github/workflows/build.yml | 22 ++++++-------- .github/workflows/release.yml | 19 +++++++----- optional_requirements.txt | 3 ++ pyproject.toml | 45 +++++++++++++++++++++++++++ requirements.txt | 2 +- setup.py | 57 ----------------------------------- 6 files changed, 70 insertions(+), 78 deletions(-) create mode 100644 optional_requirements.txt create mode 100644 pyproject.toml delete mode 100755 setup.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ede40dd..22f6582 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,6 +47,8 @@ jobs: pip install --force-reinstall Sphinx sphinx-rtd-theme pre-commit - name: Library version run: git describe --dirty --always --tags + - name: Setup problem matchers + uses: adafruit/circuitpython-action-library-ci-problem-matchers@v1 - name: Pre-commit hooks run: | pre-commit run --all-files @@ -60,20 +62,16 @@ jobs: - name: Build docs working-directory: docs run: sphinx-build -E -W -b html . _build/html - - name: Check For setup.py + - name: Check For pyproject.toml id: need-pypi run: | - echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) + echo ::set-output name=pyproject-toml::$( find . -wholename './pyproject.toml' ) - name: Build Python package - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') run: | - pip install --upgrade setuptools wheel twine readme_renderer testresources - python setup.py sdist - python setup.py bdist_wheel --universal + pip install --upgrade build twine + for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do + sed -i -e "s/0.0.0-auto.0/1.2.3/" $file; + done; + python -m build twine check dist/* - - name: Test Python package - run: | - pip install tox==3.24.5 - tox - - name: Setup problem matchers - uses: adafruit/circuitpython-action-library-ci-problem-matchers@v1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c10e2a8..d1b4f8d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,25 +61,28 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - - name: Check For setup.py + - name: Check For pyproject.toml id: need-pypi run: | - echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) + echo ::set-output name=pyproject-toml::$( find . -wholename './pyproject.toml' ) - name: Set up Python - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') uses: actions/setup-python@v2 with: - python-version: '3.8' + python-version: '3.x' - name: Install dependencies - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') run: | python -m pip install --upgrade pip - pip install setuptools wheel twine + pip install --upgrade build twine - name: Build and publish - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') env: TWINE_USERNAME: ${{ secrets.pypi_username }} TWINE_PASSWORD: ${{ secrets.pypi_password }} run: | - python setup.py sdist + for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do + sed -i -e "s/0.0.0-auto.0/${{github.event.release.tag_name}}/" $file; + done; + python -m build twine upload dist/* diff --git a/optional_requirements.txt b/optional_requirements.txt new file mode 100644 index 0000000..d4e27c4 --- /dev/null +++ b/optional_requirements.txt @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e0604d4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: 2022 Alec Delaney for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +[build-system] +requires = [ + "setuptools", + "wheel", +] + +[project] +name = "adafruit-circuitpython-requests" +description = "A requests-like library for web interfacing" +version = "0.0.0-auto.0" +readme = "README.rst" +authors = [ + {name = "Adafruit Industries", email = "circuitpython@adafruit.com"} +] +urls = {Homepage = "https://github.com/adafruit/Adafruit_CircuitPython_Requests"} +keywords = [ + "adafruit", + "blinka", + "circuitpython", + "micropython", + "requests", + "requests,", + "networking", +] +license = {text = "MIT"} +classifiers = [ + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Embedded Systems", + "Topic :: System :: Hardware", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", +] +dynamic = ["dependencies", "optional-dependencies"] + +[tool.setuptools] +py-modules = ["adafruit_requests"] + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} +optional-dependencies = {optional = {file = ["optional_requirements.txt"]}} diff --git a/requirements.txt b/requirements.txt index 17a850d..7a984a4 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries # # SPDX-License-Identifier: Unlicense diff --git a/setup.py b/setup.py deleted file mode 100755 index c18e1ce..0000000 --- a/setup.py +++ /dev/null @@ -1,57 +0,0 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -"""A setuptools based setup module. - -See: -https://packaging.python.org/en/latest/distributing.html -https://github.com/pypa/sampleproject -""" - -from setuptools import setup, find_packages - -# To use a consistent encoding -from codecs import open -from os import path - -here = path.abspath(path.dirname(__file__)) - -# Get the long description from the README file -with open(path.join(here, "README.rst"), encoding="utf-8") as f: - long_description = f.read() - -setup( - name="adafruit-circuitpython-requests", - use_scm_version=True, - setup_requires=["setuptools_scm"], - description="A requests-like library for web interfacing", - long_description=long_description, - long_description_content_type="text/x-rst", - # The project's main homepage. - url="https://github.com/adafruit/Adafruit_CircuitPython_Requests", - # Author details - author="Adafruit Industries", - author_email="circuitpython@adafruit.com", - python_requires=">=3.8", - install_requires=["Adafruit-Blinka"], - # Choose your license - license="MIT", - # See https://pypi.python.org/pypi?%3Aaction=list_classifiers - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Topic :: Software Development :: Libraries", - "Topic :: System :: Hardware", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - ], - # What does your project relate to? - keywords="adafruit blinka circuitpython micropython requests requests, networking", - # You can just specify the packages manually here if your project is - # simple. Or you can use find_packages(). - # TODO: IF LIBRARY FILES ARE A PACKAGE FOLDER, - # CHANGE `py_modules=['...']` TO `packages=['...']` - py_modules=["adafruit_requests"], -) From 9ad59c3bc5e2fdc341779ccbbe2d78560c5d681f Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 9 Aug 2022 12:03:54 -0400 Subject: [PATCH 104/305] Add setuptools-scm to build system requirements Signed-off-by: Alec Delaney --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index e0604d4..bd82d75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ requires = [ "setuptools", "wheel", + "setuptools-scm", ] [project] From 8f210828c03aa3924d7e7f3e4991a71058201722 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 16 Aug 2022 18:09:16 -0400 Subject: [PATCH 105/305] Update version string --- adafruit_requests.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 8c622bd..46b1780 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -33,7 +33,7 @@ """ -__version__ = "0.0.0-auto.0" +__version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Requests.git" import errno diff --git a/pyproject.toml b/pyproject.toml index bd82d75..d9ee51a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ requires = [ [project] name = "adafruit-circuitpython-requests" description = "A requests-like library for web interfacing" -version = "0.0.0-auto.0" +version = "0.0.0+auto.0" readme = "README.rst" authors = [ {name = "Adafruit Industries", email = "circuitpython@adafruit.com"} From 0f3017288e0eda1e9e331f9263071adc6f34d9c9 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 16 Aug 2022 21:09:16 -0400 Subject: [PATCH 106/305] Fix version strings in workflow files --- .github/workflows/build.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 22f6582..cb2f60e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,7 +71,7 @@ jobs: run: | pip install --upgrade build twine for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do - sed -i -e "s/0.0.0-auto.0/1.2.3/" $file; + sed -i -e "s/0.0.0+auto.0/1.2.3/" $file; done; python -m build twine check dist/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d1b4f8d..f3a0325 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -82,7 +82,7 @@ jobs: TWINE_PASSWORD: ${{ secrets.pypi_password }} run: | for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do - sed -i -e "s/0.0.0-auto.0/${{github.event.release.tag_name}}/" $file; + sed -i -e "s/0.0.0+auto.0/${{github.event.release.tag_name}}/" $file; done; python -m build twine upload dist/* From 4bec04792dd2b28ab1a7b7982b26078837b9a44f Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Mon, 22 Aug 2022 21:36:33 -0400 Subject: [PATCH 107/305] Keep copyright up to date in documentation --- docs/conf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 60616a8..003b015 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -6,6 +6,7 @@ import os import sys +import datetime sys.path.insert(0, os.path.abspath("..")) @@ -43,7 +44,8 @@ # General information about the project. project = "Adafruit Requests Library" -copyright = "2019 ladyada" +current_year = str(datetime.datetime.now().year) +copyright = current_year + " ladyada" author = "ladyada" # The version info for the project you're documenting, acts as replacement for From a5d56f3e4866c8dbb343e03500355a42c46e557a Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 23 Aug 2022 17:26:23 -0400 Subject: [PATCH 108/305] Use year duration range for copyright attribution --- docs/conf.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 003b015..1f7dec7 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,8 +44,14 @@ # General information about the project. project = "Adafruit Requests Library" +creation_year = "2019" current_year = str(datetime.datetime.now().year) -copyright = current_year + " ladyada" +year_duration = ( + current_year + if current_year == creation_year + else creation_year + " - " + current_year +) +copyright = year_duration + " ladyada" author = "ladyada" # The version info for the project you're documenting, acts as replacement for From 90f7d1aaa4c96e6993dcdb59da9db37d1b8dfedc Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sat, 22 Oct 2022 23:26:08 -0400 Subject: [PATCH 109/305] Create requests_api_youtube.py Example script for YouTube API. Includes some error correction and time based requests. Returns basic channel stats. Amount of serial prints configurable with debugger booleans. --- examples/requests_api_youtube.py | 120 +++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 examples/requests_api_youtube.py diff --git a/examples/requests_api_youtube.py b/examples/requests_api_youtube.py new file mode 100644 index 0000000..3393a64 --- /dev/null +++ b/examples/requests_api_youtube.py @@ -0,0 +1,120 @@ +# SPDX-FileCopyrightText: 2022 DJDevon3 +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 8.0 +"""DJDevon3 Adafruit Feather ESP32-S2 YouTube_API_Example""" +# pylint: disable=line-too-long +import gc +import time +import ssl +import wifi +import json +import socketpool +import adafruit_requests + +# Ensure these are uncommented and in secrets.py or .env +# "YT_username": "Your YouTube Username", +# "YT_token" : "Your long API developer token", + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + +# Time between API refreshes +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +sleep_time = 900 + +try: + from secrets import secrets +except ImportError: + print("Secrets File Import Error") + raise + +if sleep_time < 60: + sleep_time_conversion = "seconds" + sleep_int = sleep_time +elif 60 <= sleep_time < 3600: + sleep_int = sleep_time / 60 + sleep_time_conversion = "minutes" +elif 3600 <= sleep_time < 86400: + sleep_int = sleep_time / 60 / 60 + sleep_time_conversion = "hours" +else: + sleep_int = sleep_time / 60 / 60 / 24 + sleep_time_conversion = "days" + +# https://youtube.googleapis.com/youtube/v3/channels?part=statistics&forUsername=[YOUR_USERNAME]&key=[YOUR_API_KEY] +YT_SOURCE = ( + "https://youtube.googleapis.com/youtube/v3/channels?" + + "part=statistics" + + "&forUsername=" + + secrets["YT_username"] + + "&key=" + + secrets["YT_token"] +) + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +requests = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(secrets['ssid'], secrets['password']) + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) + gc.collect() +print("Connected!\n") + +while True: + try: + print("Attempting to GET YouTube Stats!") # ---------------------------------- + debug_request = False # Set true to see full request + if debug_request: + print("Full API GET URL: ", YT_SOURCE) + print("===============================") + try: + response = requests.get(YT_SOURCE).json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + + # Print Full JSON to Serial + debug_response = False # Set true to see full response + if debug_response: + dump_object = json.dumps(response) + print("JSON Dump: ", dump_object) + + # Print to Serial + yt_debug_keys = True # Set to True to print Serial data + if yt_debug_keys: + print("Matching Results: ", response['pageInfo']['totalResults']) + + YT_request_kind = response['items'][0]['kind'] + print("Request Kind: ", YT_request_kind) + + YT_response_kind = response['kind'] + print("Response Kind: ", YT_response_kind) + + YT_channel_id = response['items'][0]['id'] + print("Channel ID: ", YT_channel_id) + + YT_videoCount = response['items'][0]['statistics']['videoCount'] + print("Videos: ", YT_videoCount) + + YT_viewCount = response['items'][0]['statistics']['viewCount'] + print("Views: ", YT_viewCount) + + YT_subsCount = response['items'][0]['statistics']['subscriberCount'] + print("Subscribers: ", YT_subsCount) + print("Monotonic: ", time.monotonic()) + + print("\nFinished!") + print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) + print("===============================") + gc.collect() + # pylint: disable=broad-except + except (ValueError, RuntimeError) as e: + print("Failed to get data, retrying\n", e) + time.sleep(60) + continue + time.sleep(sleep_time) From 2c38b162d09d0a86abcdf4ae8628eb2d5cc3e60e Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 23 Oct 2022 00:02:12 -0400 Subject: [PATCH 110/305] Create requests_api_twitter.py Example script for Twitter API. Includes some error correction and time based requests. Returns basic stats. Amount of serial prints configurable with debugger booleans. --- examples/requests_api_twitter.py | 119 +++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 examples/requests_api_twitter.py diff --git a/examples/requests_api_twitter.py b/examples/requests_api_twitter.py new file mode 100644 index 0000000..4903b6f --- /dev/null +++ b/examples/requests_api_twitter.py @@ -0,0 +1,119 @@ +# SPDX-FileCopyrightText: 2022 DJDevon3 +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 8.0 +"""DJDevon3 Adafruit Feather ESP32-S2 Twitter_API_Example""" +# pylint: disable=line-too-long +import gc +import time +import ssl +import wifi +import json +import socketpool +import adafruit_requests + +# Twitter developer account bearer token required. +# Ensure these are uncommented and in secrets.py or .env +# "TW_userid": "Your Twitter user id", # numerical id not username +# "TW_bearer_token": "Your long API Bearer token", + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + +# Time between API refreshes +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +sleep_time = 900 + +try: + from secrets import secrets +except ImportError: + print("Secrets File Import Error") + raise + +if sleep_time < 60: + sleep_time_conversion = "seconds" + sleep_int = sleep_time +elif 60 <= sleep_time < 3600: + sleep_int = sleep_time / 60 + sleep_time_conversion = "minutes" +elif 3600 <= sleep_time < 86400: + sleep_int = sleep_time / 60 / 60 + sleep_time_conversion = "hours" +else: + sleep_int = sleep_time / 60 / 60 / 24 + sleep_time_conversion = "days" + +# Used with any Twitter 0auth request. +twitter_header = {'Authorization': 'Bearer ' + secrets["TW_bearer_token"]} +TW_SOURCE = ( + "https://api.twitter.com/2/users/" + + secrets["TW_userid"] + + "?user.fields=public_metrics,created_at,pinned_tweet_id" + + "&expansions=pinned_tweet_id" + + "&tweet.fields=created_at,public_metrics,source,context_annotations,entities" +) + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +requests = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(secrets['ssid'], secrets['password']) + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) + gc.collect() +print("Connected!\n") + +while True: + try: + print("\nAttempting to GET Twitter Stats!") # -------------------------------- + debug_request = False # Set true to see full request + if debug_request: + print("Full API GET URL: ", TW_SOURCE) + print("===============================") + try: + twitter_response = requests.get(url=TW_SOURCE, headers=twitter_header) + tw_json = twitter_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + + # Print Full JSON to Serial + debug_response = False # Set true to see full response + if debug_response: + dump_object = json.dumps(tw_json) + print("JSON Dump: ", dump_object) + + # Print to Serial + tw_debug_keys = True # Set true to print Serial data + if tw_debug_keys: + + tw_userid = tw_json['data']['id'] + print("User ID: ", tw_userid) + + tw_username = tw_json['data']['name'] + print("Name: ", tw_username) + + tw_join_date = tw_json['data']['created_at'] + print("Member Since: ", tw_join_date) + + tw_tweets = tw_json['data']['public_metrics']['tweet_count'] + print("Tweets: ", tw_tweets) + + tw_followers = tw_json['data']['public_metrics']['followers_count'] + print("Followers: ", tw_followers) + + print("Monotonic: ", time.monotonic()) + + print("\nFinished!") + print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) + print("===============================") + gc.collect() + # pylint: disable=broad-except + except (ValueError, RuntimeError) as e: + print("Failed to get data, retrying\n", e) + time.sleep(60) + continue + time.sleep(sleep_time) From 6985b3315ceeb935ea21950f6d8af1571a5929db Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 23 Oct 2022 00:26:14 -0400 Subject: [PATCH 111/305] Create requests_api_github.py Example script for Github API. Includes some error correction and time based requests. Returns basic stats. Amount of serial prints configurable with debugger booleans. --- examples/requests_api_github.py | 109 ++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 examples/requests_api_github.py diff --git a/examples/requests_api_github.py b/examples/requests_api_github.py new file mode 100644 index 0000000..5016f8e --- /dev/null +++ b/examples/requests_api_github.py @@ -0,0 +1,109 @@ +# SPDX-FileCopyrightText: 2022 DJDevon3 +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 8.0 +"""DJDevon3 Adafruit Feather ESP32-S2 Github_API_Example""" +# pylint: disable=line-too-long +import gc +import time +import ssl +import wifi +import json +import socketpool +import adafruit_requests + +# Github developer token required. +# Ensure these are uncommented and in secrets.py or .env +# "Github_username": "Your Github Username", +# "Github_token": "Your long API token", + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + +# Time between API refreshes +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +sleep_time = 900 + +try: + from secrets import secrets +except ImportError: + print("Secrets File Import Error") + raise + +if sleep_time < 60: + sleep_time_conversion = "seconds" + sleep_int = sleep_time +elif 60 <= sleep_time < 3600: + sleep_int = sleep_time / 60 + sleep_time_conversion = "minutes" +elif 3600 <= sleep_time < 86400: + sleep_int = sleep_time / 60 / 60 + sleep_time_conversion = "hours" +else: + sleep_int = sleep_time / 60 / 60 / 24 + sleep_time_conversion = "days" + +github_header = {'Authorization':' token ' + secrets["Github_token"]} +GH_SOURCE = ( + "https://api.github.com/users/" + + secrets["Github_username"] +) + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +requests = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(secrets['ssid'], secrets['password']) + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) + gc.collect() +print("Connected!\n") + +while True: + try: + print("\nAttempting to GET GITHUB Stats!") # -------------------------------- + # Print Request to Serial + debug_request = False # Set true to see full request + if debug_request: + print("Full API GET URL: ", GH_SOURCE) + print("===============================") + try: + github_response = requests.get(url=GH_SOURCE, headers=github_header).json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + + # Print Response to Serial + debug_response = False # Set true to see full response + if debug_response: + dump_object = json.dumps(github_response) + print("JSON Dump: ", dump_object) + + # Print Keys to Serial + gh_debug_keys = True # Set True to print Serial data + if gh_debug_keys: + + github_id = github_response['id'] + print("UserID: ", github_id) + + github_username = github_response['name'] + print("Username: ", github_username) + + github_followers = github_response['followers'] + print("Followers: ", github_followers) + + print("Monotonic: ", time.monotonic()) + + print("\nFinished!") + print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) + print("===============================") + gc.collect() + # pylint: disable=broad-except + except (ValueError, RuntimeError) as e: + print("Failed to get data, retrying\n", e) + time.sleep(60) + continue + time.sleep(sleep_time) From ddae8af3516ae8cbc61e232635f27b0af218e892 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 23 Oct 2022 00:38:11 -0400 Subject: [PATCH 112/305] Create requests_adafruit_discord_active_online.py Example script for Adafruit Discord Active Online Members. Includes some error correction and time based requests. Returns basic json data. Amount of serial prints configurable with debugger booleans. --- ...requests_adafruit_discord_active_online.py | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 examples/requests_adafruit_discord_active_online.py diff --git a/examples/requests_adafruit_discord_active_online.py b/examples/requests_adafruit_discord_active_online.py new file mode 100644 index 0000000..6c87978 --- /dev/null +++ b/examples/requests_adafruit_discord_active_online.py @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: 2022 DJDevon3 +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 8.0 +"""DJDevon3 Adafruit Feather ESP32-S2 Adafruit_Discord_Active_Users_Example""" +# pylint: disable=line-too-long +import gc +import re +import time +import ssl +import wifi +import json +import socketpool +import adafruit_requests + +# No user or token required, web scrape. + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + +# Time between API refreshes +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +sleep_time = 900 + +try: + from secrets import secrets +except ImportError: + print("Secrets File Import Error") + raise + +if sleep_time < 60: + sleep_time_conversion = "seconds" + sleep_int = sleep_time +elif 60 <= sleep_time < 3600: + sleep_int = sleep_time / 60 + sleep_time_conversion = "minutes" +elif 3600 <= sleep_time < 86400: + sleep_int = sleep_time / 60 / 60 + sleep_time_conversion = "hours" +else: + sleep_int = sleep_time / 60 / 60 / 24 + sleep_time_conversion = "days" + +# https://img.shields.io/discord/327254708534116352.svg +ADA_DISCORD_SVG = ( + "https://img.shields.io/discord/327254708534116352.json" +) + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +requests = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(secrets['ssid'], secrets['password']) + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) + gc.collect() +print("Connected!\n") + +while True: + try: + print("\nAttempting to GET DISCORD SVG!") # -------------------------------- + # Print Request to Serial + debug_request = True # Set true to see full request + if debug_request: + print("Full API GET URL: ", ADA_DISCORD_SVG) + print("===============================") + try: + ada_SVG_response = requests.get(ADA_DISCORD_SVG).json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + + # Print Full JSON to Serial + full_ada_SVG_json_response = True # Change to true to see full response + if full_ada_SVG_json_response: + ada_SVG_dump_object = json.dumps(ada_SVG_response) + print("JSON Dump: ", ada_SVG_dump_object) + + # Print Debugging to Serial + ada_SVG_debug = True # Set to True to print Serial data + if ada_SVG_debug: + ada_SVG_users = ada_SVG_response['value'] + print("SVG Value: ", ada_SVG_users) + regex = " online" + replace_with_nothing = "" + regex_users = re.sub(regex, replace_with_nothing, ada_SVG_users) + print("Regex Value: ", regex_users) + print("Monotonic: ", time.monotonic()) + + print("\nFinished!") + print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) + print("===============================") + gc.collect() + # pylint: disable=broad-except + except (ValueError, RuntimeError) as e: + print("Failed to get data, retrying\n", e) + time.sleep(60) + continue + time.sleep(sleep_time) From e52c73aa1bb73b943eb201d8dd6a92f089e0f297 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 23 Oct 2022 00:55:31 -0400 Subject: [PATCH 113/305] Create requests_api_discord.py Example script for Discord API. Includes some error correction and time based requests. Returns basic stats. Amount of serial prints configurable with debugger booleans. --- examples/requests_api_discord.py | 109 +++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 examples/requests_api_discord.py diff --git a/examples/requests_api_discord.py b/examples/requests_api_discord.py new file mode 100644 index 0000000..1f0f575 --- /dev/null +++ b/examples/requests_api_discord.py @@ -0,0 +1,109 @@ +# SPDX-FileCopyrightText: 2022 DJDevon3 +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 8.0 +"""DJDevon3 Adafruit Feather ESP32-S2 Discord_API_Example""" +# pylint: disable=line-too-long +import gc +import time +import ssl +import wifi +import json +import socketpool +import adafruit_requests + +# Web scrape authorization key required +# Learn how: https://github.com/lorenz234/Discord-Data-Scraping + +# Ensure these are uncommented and in secrets.py or .env +# "Discord_Adafruit_Channel": "327254708534116352", # Adafruit Channel ID +# "Discord_Authorization": "Discord Authorization from browser console" + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + +# Time between API refreshes +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +sleep_time = 900 + +try: + from secrets import secrets +except ImportError: + print("Secrets File Import Error") + raise + +if sleep_time < 60: + sleep_time_conversion = "seconds" + sleep_int = sleep_time +elif 60 <= sleep_time < 3600: + sleep_int = sleep_time / 60 + sleep_time_conversion = "minutes" +elif 3600 <= sleep_time < 86400: + sleep_int = sleep_time / 60 / 60 + sleep_time_conversion = "hours" +else: + sleep_int = sleep_time / 60 / 60 / 24 + sleep_time_conversion = "days" + +discord_header = {'Authorization': '' + secrets['Discord_Authorization']} +ADA_SOURCE = ( + "https://discord.com/api/v10/guilds/" + + secrets['Discord_Adafruit_Channel'] + + "/preview" +) + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +requests = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(secrets['ssid'], secrets['password']) + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) + gc.collect() +print("Connected!\n") + +while True: + try: + print("\nAttempting to GET DISCORD PREVIEW!") # -------------------------------- + # Print Request to Serial + debug_request = False # Set true to see full request + if debug_request: + print("Full API GET URL: ", ADA_SOURCE) + print("===============================") + try: + ada_res = requests.get(url=ADA_SOURCE, headers=discord_header).json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + + # Print Full JSON to Serial + discord_debug_response = False # Change to true to see full response + if discord_debug_response: + ada_discord_dump_object = json.dumps(ada_res) + print("JSON Dump: ", ada_discord_dump_object) + + # Print keys to Serial + discord_debug_keys = True # Set to True to print Serial data + if discord_debug_keys: + + ada_discord_all_members = ada_res['approximate_member_count'] + print("Members: ", ada_discord_all_members) + + ada_discord_all_members_online = ada_res['approximate_presence_count'] + print("Online: ", ada_discord_all_members_online) + + print("Monotonic: ", time.monotonic()) + + print("\nFinished!") + print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) + print("===============================") + gc.collect() + # pylint: disable=broad-except + except (ValueError, RuntimeError) as e: + print("Failed to get data, retrying\n", e) + time.sleep(60) + continue + time.sleep(sleep_time) From 898bc18cb69d9cfc1755d974f69d465244160db3 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 23 Oct 2022 10:50:29 -0400 Subject: [PATCH 114/305] fixing minor pre-commit issues reordered json before wifi import --- examples/requests_adafruit_discord_active_online.py | 2 +- examples/requests_api_discord.py | 2 +- examples/requests_api_github.py | 2 +- examples/requests_api_twitter.py | 2 +- examples/requests_api_youtube.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/requests_adafruit_discord_active_online.py b/examples/requests_adafruit_discord_active_online.py index 6c87978..6d7da30 100644 --- a/examples/requests_adafruit_discord_active_online.py +++ b/examples/requests_adafruit_discord_active_online.py @@ -7,8 +7,8 @@ import re import time import ssl -import wifi import json +import wifi import socketpool import adafruit_requests diff --git a/examples/requests_api_discord.py b/examples/requests_api_discord.py index 1f0f575..97086ce 100644 --- a/examples/requests_api_discord.py +++ b/examples/requests_api_discord.py @@ -6,8 +6,8 @@ import gc import time import ssl -import wifi import json +import wifi import socketpool import adafruit_requests diff --git a/examples/requests_api_github.py b/examples/requests_api_github.py index 5016f8e..089e3af 100644 --- a/examples/requests_api_github.py +++ b/examples/requests_api_github.py @@ -6,8 +6,8 @@ import gc import time import ssl -import wifi import json +import wifi import socketpool import adafruit_requests diff --git a/examples/requests_api_twitter.py b/examples/requests_api_twitter.py index 4903b6f..9a72892 100644 --- a/examples/requests_api_twitter.py +++ b/examples/requests_api_twitter.py @@ -6,8 +6,8 @@ import gc import time import ssl -import wifi import json +import wifi import socketpool import adafruit_requests diff --git a/examples/requests_api_youtube.py b/examples/requests_api_youtube.py index 3393a64..09299d9 100644 --- a/examples/requests_api_youtube.py +++ b/examples/requests_api_youtube.py @@ -6,8 +6,8 @@ import gc import time import ssl -import wifi import json +import wifi import socketpool import adafruit_requests From 918aeaa3f9fe90780521dcc6deafdedf2e789de1 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 23 Oct 2022 11:19:54 -0400 Subject: [PATCH 115/305] ran black on individual files first pre-commit with black formatting --- ...requests_adafruit_discord_active_online.py | 21 +++++------ examples/requests_api_discord.py | 29 ++++++++------- examples/requests_api_github.py | 36 ++++++++---------- examples/requests_api_twitter.py | 37 +++++++++---------- examples/requests_api_youtube.py | 25 ++++++------- 5 files changed, 70 insertions(+), 78 deletions(-) diff --git a/examples/requests_adafruit_discord_active_online.py b/examples/requests_adafruit_discord_active_online.py index 6d7da30..c7ad471 100644 --- a/examples/requests_adafruit_discord_active_online.py +++ b/examples/requests_adafruit_discord_active_online.py @@ -1,8 +1,7 @@ -# SPDX-FileCopyrightText: 2022 DJDevon3 +# SPDX-FileCopyrightText: 2022 DJDevon3 for Adafruit Industries # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.0 """DJDevon3 Adafruit Feather ESP32-S2 Adafruit_Discord_Active_Users_Example""" -# pylint: disable=line-too-long import gc import re import time @@ -41,9 +40,7 @@ sleep_time_conversion = "days" # https://img.shields.io/discord/327254708534116352.svg -ADA_DISCORD_SVG = ( - "https://img.shields.io/discord/327254708534116352.json" -) +ADA_DISCORD_SVG = "https://img.shields.io/discord/327254708534116352.json" # Connect to Wi-Fi print("\n===============================") @@ -51,7 +48,7 @@ requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(secrets['ssid'], secrets['password']) + wifi.radio.connect(secrets["ssid"], secrets["password"]) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") @@ -72,17 +69,17 @@ except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") - + # Print Full JSON to Serial - full_ada_SVG_json_response = True # Change to true to see full response + full_ada_SVG_json_response = True # Change to true to see full response if full_ada_SVG_json_response: ada_SVG_dump_object = json.dumps(ada_SVG_response) print("JSON Dump: ", ada_SVG_dump_object) - + # Print Debugging to Serial - ada_SVG_debug = True # Set to True to print Serial data + ada_SVG_debug = True # Set to True to print Serial data if ada_SVG_debug: - ada_SVG_users = ada_SVG_response['value'] + ada_SVG_users = ada_SVG_response["value"] print("SVG Value: ", ada_SVG_users) regex = " online" replace_with_nothing = "" @@ -94,7 +91,7 @@ print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) print("===============================") gc.collect() - # pylint: disable=broad-except + except (ValueError, RuntimeError) as e: print("Failed to get data, retrying\n", e) time.sleep(60) diff --git a/examples/requests_api_discord.py b/examples/requests_api_discord.py index 97086ce..2671c27 100644 --- a/examples/requests_api_discord.py +++ b/examples/requests_api_discord.py @@ -1,8 +1,7 @@ -# SPDX-FileCopyrightText: 2022 DJDevon3 +# SPDX-FileCopyrightText: 2022 DJDevon3 for Adafruit Industries # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.0 """DJDevon3 Adafruit Feather ESP32-S2 Discord_API_Example""" -# pylint: disable=line-too-long import gc import time import ssl @@ -44,10 +43,10 @@ sleep_int = sleep_time / 60 / 60 / 24 sleep_time_conversion = "days" -discord_header = {'Authorization': '' + secrets['Discord_Authorization']} +discord_header = {"Authorization": "" + secrets["Discord_Authorization"]} ADA_SOURCE = ( "https://discord.com/api/v10/guilds/" - + secrets['Discord_Adafruit_Channel'] + + secrets["Discord_Adafruit_Channel"] + "/preview" ) @@ -57,7 +56,7 @@ requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(secrets['ssid'], secrets['password']) + wifi.radio.connect(secrets["ssid"], secrets["password"]) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") @@ -67,7 +66,9 @@ while True: try: - print("\nAttempting to GET DISCORD PREVIEW!") # -------------------------------- + print( + "\nAttempting to GET DISCORD PREVIEW!" + ) # -------------------------------- # Print Request to Serial debug_request = False # Set true to see full request if debug_request: @@ -78,30 +79,30 @@ except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") - + # Print Full JSON to Serial discord_debug_response = False # Change to true to see full response if discord_debug_response: ada_discord_dump_object = json.dumps(ada_res) print("JSON Dump: ", ada_discord_dump_object) - + # Print keys to Serial discord_debug_keys = True # Set to True to print Serial data if discord_debug_keys: - - ada_discord_all_members = ada_res['approximate_member_count'] + + ada_discord_all_members = ada_res["approximate_member_count"] print("Members: ", ada_discord_all_members) - - ada_discord_all_members_online = ada_res['approximate_presence_count'] + + ada_discord_all_members_online = ada_res["approximate_presence_count"] print("Online: ", ada_discord_all_members_online) - + print("Monotonic: ", time.monotonic()) print("\nFinished!") print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) print("===============================") gc.collect() - # pylint: disable=broad-except + except (ValueError, RuntimeError) as e: print("Failed to get data, retrying\n", e) time.sleep(60) diff --git a/examples/requests_api_github.py b/examples/requests_api_github.py index 089e3af..4775a54 100644 --- a/examples/requests_api_github.py +++ b/examples/requests_api_github.py @@ -1,8 +1,7 @@ -# SPDX-FileCopyrightText: 2022 DJDevon3 +# SPDX-FileCopyrightText: 2022 DJDevon3 for Adafruit Industries # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.0 """DJDevon3 Adafruit Feather ESP32-S2 Github_API_Example""" -# pylint: disable=line-too-long import gc import time import ssl @@ -12,7 +11,7 @@ import adafruit_requests # Github developer token required. -# Ensure these are uncommented and in secrets.py or .env +# Ensure these are uncommented and in secrets.py or .env # "Github_username": "Your Github Username", # "Github_token": "Your long API token", @@ -42,11 +41,8 @@ sleep_int = sleep_time / 60 / 60 / 24 sleep_time_conversion = "days" -github_header = {'Authorization':' token ' + secrets["Github_token"]} -GH_SOURCE = ( - "https://api.github.com/users/" - + secrets["Github_username"] -) +github_header = {"Authorization": " token " + secrets["Github_token"]} +GH_SOURCE = "https://api.github.com/users/" + secrets["Github_username"] # Connect to Wi-Fi print("\n===============================") @@ -54,7 +50,7 @@ requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(secrets['ssid'], secrets['password']) + wifi.radio.connect(secrets["ssid"], secrets["password"]) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") @@ -75,33 +71,33 @@ except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") - + # Print Response to Serial debug_response = False # Set true to see full response if debug_response: dump_object = json.dumps(github_response) print("JSON Dump: ", dump_object) - + # Print Keys to Serial - gh_debug_keys = True # Set True to print Serial data + gh_debug_keys = True # Set True to print Serial data if gh_debug_keys: - - github_id = github_response['id'] + + github_id = github_response["id"] print("UserID: ", github_id) - - github_username = github_response['name'] + + github_username = github_response["name"] print("Username: ", github_username) - - github_followers = github_response['followers'] + + github_followers = github_response["followers"] print("Followers: ", github_followers) - + print("Monotonic: ", time.monotonic()) print("\nFinished!") print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) print("===============================") gc.collect() - # pylint: disable=broad-except + except (ValueError, RuntimeError) as e: print("Failed to get data, retrying\n", e) time.sleep(60) diff --git a/examples/requests_api_twitter.py b/examples/requests_api_twitter.py index 9a72892..6d0645e 100644 --- a/examples/requests_api_twitter.py +++ b/examples/requests_api_twitter.py @@ -1,8 +1,7 @@ -# SPDX-FileCopyrightText: 2022 DJDevon3 +# SPDX-FileCopyrightText: 2022 DJDevon3 for Adafruit Industries # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.0 """DJDevon3 Adafruit Feather ESP32-S2 Twitter_API_Example""" -# pylint: disable=line-too-long import gc import time import ssl @@ -12,7 +11,7 @@ import adafruit_requests # Twitter developer account bearer token required. -# Ensure these are uncommented and in secrets.py or .env +# Ensure these are uncommented and in secrets.py or .env # "TW_userid": "Your Twitter user id", # numerical id not username # "TW_bearer_token": "Your long API Bearer token", @@ -43,7 +42,7 @@ sleep_time_conversion = "days" # Used with any Twitter 0auth request. -twitter_header = {'Authorization': 'Bearer ' + secrets["TW_bearer_token"]} +twitter_header = {"Authorization": "Bearer " + secrets["TW_bearer_token"]} TW_SOURCE = ( "https://api.twitter.com/2/users/" + secrets["TW_userid"] @@ -58,7 +57,7 @@ requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(secrets['ssid'], secrets['password']) + wifi.radio.connect(secrets["ssid"], secrets["password"]) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") @@ -79,39 +78,39 @@ except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") - + # Print Full JSON to Serial debug_response = False # Set true to see full response if debug_response: dump_object = json.dumps(tw_json) print("JSON Dump: ", dump_object) - + # Print to Serial tw_debug_keys = True # Set true to print Serial data if tw_debug_keys: - - tw_userid = tw_json['data']['id'] + + tw_userid = tw_json["data"]["id"] print("User ID: ", tw_userid) - - tw_username = tw_json['data']['name'] + + tw_username = tw_json["data"]["name"] print("Name: ", tw_username) - - tw_join_date = tw_json['data']['created_at'] + + tw_join_date = tw_json["data"]["created_at"] print("Member Since: ", tw_join_date) - - tw_tweets = tw_json['data']['public_metrics']['tweet_count'] + + tw_tweets = tw_json["data"]["public_metrics"]["tweet_count"] print("Tweets: ", tw_tweets) - - tw_followers = tw_json['data']['public_metrics']['followers_count'] + + tw_followers = tw_json["data"]["public_metrics"]["followers_count"] print("Followers: ", tw_followers) - + print("Monotonic: ", time.monotonic()) print("\nFinished!") print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) print("===============================") gc.collect() - # pylint: disable=broad-except + except (ValueError, RuntimeError) as e: print("Failed to get data, retrying\n", e) time.sleep(60) diff --git a/examples/requests_api_youtube.py b/examples/requests_api_youtube.py index 09299d9..5fdacbb 100644 --- a/examples/requests_api_youtube.py +++ b/examples/requests_api_youtube.py @@ -1,8 +1,7 @@ -# SPDX-FileCopyrightText: 2022 DJDevon3 +# SPDX-FileCopyrightText: 2022 DJDevon3 for Adafruit Industries # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.0 """DJDevon3 Adafruit Feather ESP32-S2 YouTube_API_Example""" -# pylint: disable=line-too-long import gc import time import ssl @@ -11,7 +10,7 @@ import socketpool import adafruit_requests -# Ensure these are uncommented and in secrets.py or .env +# Ensure these are uncommented and in secrets.py or .env # "YT_username": "Your YouTube Username", # "YT_token" : "Your long API developer token", @@ -57,7 +56,7 @@ requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(secrets['ssid'], secrets['password']) + wifi.radio.connect(secrets["ssid"], secrets["password"]) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") @@ -87,24 +86,24 @@ # Print to Serial yt_debug_keys = True # Set to True to print Serial data if yt_debug_keys: - print("Matching Results: ", response['pageInfo']['totalResults']) + print("Matching Results: ", response["pageInfo"]["totalResults"]) - YT_request_kind = response['items'][0]['kind'] + YT_request_kind = response["items"][0]["kind"] print("Request Kind: ", YT_request_kind) - - YT_response_kind = response['kind'] + + YT_response_kind = response["kind"] print("Response Kind: ", YT_response_kind) - YT_channel_id = response['items'][0]['id'] + YT_channel_id = response["items"][0]["id"] print("Channel ID: ", YT_channel_id) - YT_videoCount = response['items'][0]['statistics']['videoCount'] + YT_videoCount = response["items"][0]["statistics"]["videoCount"] print("Videos: ", YT_videoCount) - YT_viewCount = response['items'][0]['statistics']['viewCount'] + YT_viewCount = response["items"][0]["statistics"]["viewCount"] print("Views: ", YT_viewCount) - YT_subsCount = response['items'][0]['statistics']['subscriberCount'] + YT_subsCount = response["items"][0]["statistics"]["subscriberCount"] print("Subscribers: ", YT_subsCount) print("Monotonic: ", time.monotonic()) @@ -112,7 +111,7 @@ print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) print("===============================") gc.collect() - # pylint: disable=broad-except + except (ValueError, RuntimeError) as e: print("Failed to get data, retrying\n", e) time.sleep(60) From a963f53e2aebb197695e8d3c78740702cb69baf2 Mon Sep 17 00:00:00 2001 From: gustavomfb Date: Wed, 26 Oct 2022 11:51:01 -0300 Subject: [PATCH 116/305] Removed List as annotation for headers --- adafruit_requests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 46b1780..9ac4a1f 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -575,7 +575,7 @@ def _send_request( host: str, method: str, path: str, - headers: List[Dict[str, str]], + headers: Dict[str, str], data: Any, json: Any, ): @@ -623,7 +623,7 @@ def request( url: str, data: Optional[Any] = None, json: Optional[Any] = None, - headers: Optional[List[Dict[str, str]]] = None, + headers: Optional[Dict[str, str]] = None, stream: bool = False, timeout: float = 60, ) -> Response: @@ -793,7 +793,7 @@ def request( url: str, data: Optional[Any] = None, json: Optional[Any] = None, - headers: Optional[List[Dict[str, str]]] = None, + headers: Optional[Dict[str, str]] = None, stream: bool = False, timeout: float = 1, ) -> None: From 415083839d602ec723625ec6c209d5c8be349f40 Mon Sep 17 00:00:00 2001 From: gustavomfb Date: Wed, 26 Oct 2022 11:57:58 -0300 Subject: [PATCH 117/305] Rmevode List from import - unused --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 9ac4a1f..48c54c0 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -50,7 +50,7 @@ def cast(_t, value): else: from ssl import SSLContext from types import ModuleType, TracebackType - from typing import Any, Dict, List, Optional, Tuple, Type, Union, cast + from typing import Any, Dict, Optional, Tuple, Type, Union, cast try: from typing import Protocol From 53ada6d18a43eea8c7271d88007ee28226d8ac0d Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 4 Nov 2022 00:02:50 -0400 Subject: [PATCH 118/305] Switching to composite actions --- .github/workflows/build.yml | 67 +---------------------- .github/workflows/release.yml | 88 ------------------------------ .github/workflows/release_gh.yml | 14 +++++ .github/workflows/release_pypi.yml | 14 +++++ 4 files changed, 30 insertions(+), 153 deletions(-) delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/release_gh.yml create mode 100644 .github/workflows/release_pypi.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cb2f60e..041a337 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,68 +10,5 @@ jobs: test: runs-on: ubuntu-latest steps: - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Translate Repo Name For Build Tools filename_prefix - id: repo-name - run: | - echo ::set-output name=repo-name::$( - echo ${{ github.repository }} | - awk -F '\/' '{ print tolower($2) }' | - tr '_' '-' - ) - - name: Set up Python 3.x - uses: actions/setup-python@v2 - with: - python-version: "3.x" - - name: Versions - run: | - python3 --version - - name: Checkout Current Repo - uses: actions/checkout@v1 - with: - submodules: true - - name: Checkout tools repo - uses: actions/checkout@v2 - with: - repository: adafruit/actions-ci-circuitpython-libs - path: actions-ci - - name: Install dependencies - # (e.g. - apt-get: gettext, etc; pip: circuitpython-build-tools, requirements.txt; etc.) - run: | - source actions-ci/install.sh - - name: Pip install Sphinx, pre-commit - run: | - pip install --force-reinstall Sphinx sphinx-rtd-theme pre-commit - - name: Library version - run: git describe --dirty --always --tags - - name: Setup problem matchers - uses: adafruit/circuitpython-action-library-ci-problem-matchers@v1 - - name: Pre-commit hooks - run: | - pre-commit run --all-files - - name: Build assets - run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . - - name: Archive bundles - uses: actions/upload-artifact@v2 - with: - name: bundles - path: ${{ github.workspace }}/bundles/ - - name: Build docs - working-directory: docs - run: sphinx-build -E -W -b html . _build/html - - name: Check For pyproject.toml - id: need-pypi - run: | - echo ::set-output name=pyproject-toml::$( find . -wholename './pyproject.toml' ) - - name: Build Python package - if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') - run: | - pip install --upgrade build twine - for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do - sed -i -e "s/0.0.0+auto.0/1.2.3/" $file; - done; - python -m build - twine check dist/* + - name: Run Build CI workflow + uses: adafruit/workflows-circuitpython-libs/build@main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index f3a0325..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,88 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -name: Release Actions - -on: - release: - types: [published] - -jobs: - upload-release-assets: - runs-on: ubuntu-latest - steps: - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Translate Repo Name For Build Tools filename_prefix - id: repo-name - run: | - echo ::set-output name=repo-name::$( - echo ${{ github.repository }} | - awk -F '\/' '{ print tolower($2) }' | - tr '_' '-' - ) - - name: Set up Python 3.x - uses: actions/setup-python@v2 - with: - python-version: "3.x" - - name: Versions - run: | - python3 --version - - name: Checkout Current Repo - uses: actions/checkout@v1 - with: - submodules: true - - name: Checkout tools repo - uses: actions/checkout@v2 - with: - repository: adafruit/actions-ci-circuitpython-libs - path: actions-ci - - name: Install deps - run: | - source actions-ci/install.sh - - name: Build assets - run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . - - name: Upload Release Assets - # the 'official' actions version does not yet support dynamically - # supplying asset names to upload. @csexton's version chosen based on - # discussion in the issue below, as its the simplest to implement and - # allows for selecting files with a pattern. - # https://github.com/actions/upload-release-asset/issues/4 - #uses: actions/upload-release-asset@v1.0.1 - uses: csexton/release-asset-action@master - with: - pattern: "bundles/*" - github-token: ${{ secrets.GITHUB_TOKEN }} - - upload-pypi: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Check For pyproject.toml - id: need-pypi - run: | - echo ::set-output name=pyproject-toml::$( find . -wholename './pyproject.toml' ) - - name: Set up Python - if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - name: Install dependencies - if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') - run: | - python -m pip install --upgrade pip - pip install --upgrade build twine - - name: Build and publish - if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') - env: - TWINE_USERNAME: ${{ secrets.pypi_username }} - TWINE_PASSWORD: ${{ secrets.pypi_password }} - run: | - for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do - sed -i -e "s/0.0.0+auto.0/${{github.event.release.tag_name}}/" $file; - done; - python -m build - twine upload dist/* diff --git a/.github/workflows/release_gh.yml b/.github/workflows/release_gh.yml new file mode 100644 index 0000000..041a337 --- /dev/null +++ b/.github/workflows/release_gh.yml @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +name: Build CI + +on: [pull_request, push] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Run Build CI workflow + uses: adafruit/workflows-circuitpython-libs/build@main diff --git a/.github/workflows/release_pypi.yml b/.github/workflows/release_pypi.yml new file mode 100644 index 0000000..041a337 --- /dev/null +++ b/.github/workflows/release_pypi.yml @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +name: Build CI + +on: [pull_request, push] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Run Build CI workflow + uses: adafruit/workflows-circuitpython-libs/build@main From 2111cc2df7b3cc1bf1804f950b4dbfcffcdc7782 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 4 Nov 2022 00:47:01 -0400 Subject: [PATCH 119/305] Updated pylint version to 2.13.0 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3343606..4c43710 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/pylint - rev: v2.11.1 + rev: v2.13.0 hooks: - id: pylint name: pylint (library code) From 6369ca67168927cd03e88cfd406fb2b475a1b345 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 4 Nov 2022 08:15:21 -0400 Subject: [PATCH 120/305] Update pylint to 2.15.5 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4c43710..0e5fccc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/pylint - rev: v2.13.0 + rev: v2.15.5 hooks: - id: pylint name: pylint (library code) From f99359ff9528e560a8718c4e8eac0f0cc886cfa3 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 4 Nov 2022 09:12:46 -0400 Subject: [PATCH 121/305] Fix release CI files --- .github/workflows/release_gh.yml | 14 +++++++++----- .github/workflows/release_pypi.yml | 15 ++++++++++----- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release_gh.yml b/.github/workflows/release_gh.yml index 041a337..b8aa8d6 100644 --- a/.github/workflows/release_gh.yml +++ b/.github/workflows/release_gh.yml @@ -2,13 +2,17 @@ # # SPDX-License-Identifier: MIT -name: Build CI +name: GitHub Release Actions -on: [pull_request, push] +on: + release: + types: [published] jobs: - test: + upload-release-assets: runs-on: ubuntu-latest steps: - - name: Run Build CI workflow - uses: adafruit/workflows-circuitpython-libs/build@main + - name: Run GitHub Release CI workflow + uses: adafruit/workflows-circuitpython-libs/release-gh@main + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release_pypi.yml b/.github/workflows/release_pypi.yml index 041a337..65775b7 100644 --- a/.github/workflows/release_pypi.yml +++ b/.github/workflows/release_pypi.yml @@ -2,13 +2,18 @@ # # SPDX-License-Identifier: MIT -name: Build CI +name: PyPI Release Actions -on: [pull_request, push] +on: + release: + types: [published] jobs: - test: + upload-release-assets: runs-on: ubuntu-latest steps: - - name: Run Build CI workflow - uses: adafruit/workflows-circuitpython-libs/build@main + - name: Run PyPI Release CI workflow + uses: adafruit/workflows-circuitpython-libs/release-pypi@main + with: + pypi-username: ${{ secrets.pypi_username }} + pypi-password: ${{ secrets.pypi_password }} From d26db3714c07f76a82b40f840c535c017e7baedd Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 4 Nov 2022 18:34:33 -0400 Subject: [PATCH 122/305] Update .pylintrc for v2.15.5 --- .pylintrc | 45 ++++----------------------------------------- 1 file changed, 4 insertions(+), 41 deletions(-) mode change 100755 => 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc old mode 100755 new mode 100644 index f772971..40208c3 --- a/.pylintrc +++ b/.pylintrc @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries # # SPDX-License-Identifier: Unlicense @@ -26,7 +26,7 @@ jobs=1 # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. -load-plugins= +load-plugins=pylint.extensions.no_self_use # Pickle collected data for later comparisons. persistent=yes @@ -54,8 +54,8 @@ confidence= # --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=import-error,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call -disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error,bad-continuation,unspecified-encoding +# disable=import-error,raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,deprecated-str-translate-call +disable=raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,import-error,pointless-string-statement,unspecified-encoding # 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 @@ -225,12 +225,6 @@ max-line-length=100 # Maximum number of lines in a module max-module-lines=1000 -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - # 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 @@ -257,38 +251,22 @@ min-similarity-lines=12 [BASIC] -# Naming hint for argument names -argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct argument names argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ -# Naming hint for attribute names -attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct attribute names attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ -# Naming hint for class names -# class-name-hint=[A-Z_][a-zA-Z0-9]+$ -class-name-hint=[A-Z_][a-zA-Z0-9_]+$ - # Regular expression matching correct class names # class-rgx=[A-Z_][a-zA-Z0-9]+$ class-rgx=[A-Z_][a-zA-Z0-9_]+$ -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - # Regular expression matching correct constant names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ @@ -296,9 +274,6 @@ const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # ones are exempt. docstring-min-length=-1 -# Naming hint for function names -function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct function names function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ @@ -309,21 +284,12 @@ good-names=r,g,b,w,i,j,k,n,x,y,z,ex,ok,Run,_ # Include a hint for the correct naming format with invalid-name include-naming-hint=no -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ -# Naming hint for method names -method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct method names method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ @@ -339,9 +305,6 @@ no-docstring-rgx=^_ # to this list to register other decorators that produce valid properties. property-classes=abc.abstractproperty -# Naming hint for variable names -variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct variable names variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ From 2bcf3e5ef90033066d4f13aeea939924acf2f04b Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Mon, 7 Nov 2022 20:51:12 -0500 Subject: [PATCH 123/305] Fix pylint errors --- adafruit_requests.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 48c54c0..98fe140 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -64,15 +64,12 @@ class CommonSocketType(Protocol): def send(self, data: bytes, flags: int = ...) -> None: """Send data to the socket. The meaning of the optional flags kwarg is implementation-specific.""" - ... def settimeout(self, value: Optional[float]) -> None: """Set a timeout on blocking socket operations.""" - ... def close(self) -> None: """Close the socket.""" - ... class CommonCircuitPythonSocketType(CommonSocketType, Protocol): """Describes the common structure every CircuitPython socket type must have.""" @@ -84,7 +81,6 @@ def connect( ) -> None: """Connect to a remote socket at the provided (host, port) address. The conntype kwarg optionally may indicate SSL or not, depending on the underlying interface.""" - ... class LegacyCircuitPythonSocketType(CommonCircuitPythonSocketType, Protocol): """Describes the structure a legacy CircuitPython socket type must have.""" @@ -93,7 +89,6 @@ def recv(self, bufsize: int = ...) -> bytes: """Receive data from the socket. The return value is a bytes object representing the data received. The maximum amount of data to be received at once is specified by bufsize.""" - ... class SupportsRecvWithFlags(Protocol): """Describes a type that posseses a socket recv() method supporting the flags kwarg.""" @@ -102,7 +97,6 @@ def recv(self, bufsize: int = ..., flags: int = ...) -> bytes: """Receive data from the socket. The return value is a bytes object representing the data received. The maximum amount of data to be received at once is specified by bufsize. The meaning of the optional flags kwarg is implementation-specific.""" - ... class SupportsRecvInto(Protocol): """Describes a type that possesses a socket recv_into() method.""" @@ -114,7 +108,6 @@ def recv_into( buffer. If nbytes is not specified (or 0), receive up to the size available in the given buffer. The meaning of the optional flags kwarg is implementation-specific. Returns the number of bytes received.""" - ... class CircuitPythonSocketType( CommonCircuitPythonSocketType, @@ -124,7 +117,6 @@ class CircuitPythonSocketType( ): # pylint: disable=too-many-ancestors """Describes the structure every modern CircuitPython socket type must have.""" - ... class StandardPythonSocketType( CommonSocketType, SupportsRecvInto, SupportsRecvWithFlags, Protocol @@ -133,7 +125,6 @@ class StandardPythonSocketType( def connect(self, address: Union[Tuple[Any, ...], str, bytes]) -> None: """Connect to a remote socket at the provided address.""" - ... SocketType = Union[ LegacyCircuitPythonSocketType, @@ -149,7 +140,6 @@ class InterfaceType(Protocol): @property def TLS_MODE(self) -> int: # pylint: disable=invalid-name """Constant representing that a socket's connection mode is TLS.""" - ... SSLContextType = Union[SSLContext, "_FakeSSLContext"] From 8bf04d75c6c27a1ab7b1052345978ae1d1ff0e9e Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Mon, 7 Nov 2022 20:59:59 -0500 Subject: [PATCH 124/305] Reformatted per pre-commit --- adafruit_requests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 98fe140..73993ba 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -117,7 +117,6 @@ class CircuitPythonSocketType( ): # pylint: disable=too-many-ancestors """Describes the structure every modern CircuitPython socket type must have.""" - class StandardPythonSocketType( CommonSocketType, SupportsRecvInto, SupportsRecvWithFlags, Protocol ): From e76d9fa607ba1b9a32357e3da77a841a1c6be346 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Wed, 9 Nov 2022 19:31:40 -0500 Subject: [PATCH 125/305] removed regex and SVG references Updated as recommended by FoamyGuy during his livestream. No more regex needed, using string replace. --- ...requests_adafruit_discord_active_online.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/examples/requests_adafruit_discord_active_online.py b/examples/requests_adafruit_discord_active_online.py index c7ad471..808cdaf 100644 --- a/examples/requests_adafruit_discord_active_online.py +++ b/examples/requests_adafruit_discord_active_online.py @@ -3,7 +3,6 @@ # Coded for Circuit Python 8.0 """DJDevon3 Adafruit Feather ESP32-S2 Adafruit_Discord_Active_Users_Example""" import gc -import re import time import ssl import json @@ -11,7 +10,8 @@ import socketpool import adafruit_requests -# No user or token required, web scrape. +# No user or token required, 100% JSON web scrape from SHIELDS.IO +# Adafruit uses Shields.IO to see online users # Initialize WiFi Pool (There can be only 1 pool & top of script) pool = socketpool.SocketPool(wifi.radio) @@ -39,8 +39,9 @@ sleep_int = sleep_time / 60 / 60 / 24 sleep_time_conversion = "days" +# Originally attempted to use SVG. Found JSON exists with same filename. # https://img.shields.io/discord/327254708534116352.svg -ADA_DISCORD_SVG = "https://img.shields.io/discord/327254708534116352.json" +ADA_DISCORD_JSON = "https://img.shields.io/discord/327254708534116352.json" # Connect to Wi-Fi print("\n===============================") @@ -58,33 +59,33 @@ while True: try: - print("\nAttempting to GET DISCORD SVG!") # -------------------------------- + print("\nAttempting to GET DISCORD SHIELD JSON!") # -------------------------------- # Print Request to Serial debug_request = True # Set true to see full request if debug_request: - print("Full API GET URL: ", ADA_DISCORD_SVG) + print("Full API GET URL: ", ADA_DISCORD_JSON) print("===============================") try: - ada_SVG_response = requests.get(ADA_DISCORD_SVG).json() + ada_response = requests.get(ADA_DISCORD_JSON).json() except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") # Print Full JSON to Serial - full_ada_SVG_json_response = True # Change to true to see full response - if full_ada_SVG_json_response: - ada_SVG_dump_object = json.dumps(ada_SVG_response) - print("JSON Dump: ", ada_SVG_dump_object) + full_ada_json_response = True # Change to true to see full response + if full_ada_json_response: + ada_dump_object = json.dumps(ada_response) + print("JSON Dump: ", ada_dump_object) # Print Debugging to Serial - ada_SVG_debug = True # Set to True to print Serial data - if ada_SVG_debug: - ada_SVG_users = ada_SVG_response["value"] - print("SVG Value: ", ada_SVG_users) - regex = " online" + ada_debug = True # Set to True to print Serial data + if ada_debug: + ada_users = ada_response["value"] + print("JSON Value: ", ada_users) + online_string = " online" replace_with_nothing = "" - regex_users = re.sub(regex, replace_with_nothing, ada_SVG_users) - print("Regex Value: ", regex_users) + string_replace_users = ada_users.replace(online_string, replace_with_nothing) + print("Replaced Value: ", string_replace_users) print("Monotonic: ", time.monotonic()) print("\nFinished!") From 70043274f599664cd9e2ac711fa26f1f6ed8542e Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Wed, 9 Nov 2022 21:24:47 -0500 Subject: [PATCH 126/305] ran black --- examples/requests_adafruit_discord_active_online.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/requests_adafruit_discord_active_online.py b/examples/requests_adafruit_discord_active_online.py index 808cdaf..0175593 100644 --- a/examples/requests_adafruit_discord_active_online.py +++ b/examples/requests_adafruit_discord_active_online.py @@ -59,7 +59,9 @@ while True: try: - print("\nAttempting to GET DISCORD SHIELD JSON!") # -------------------------------- + print( + "\nAttempting to GET DISCORD SHIELD JSON!" + ) # -------------------------------- # Print Request to Serial debug_request = True # Set true to see full request if debug_request: @@ -84,7 +86,9 @@ print("JSON Value: ", ada_users) online_string = " online" replace_with_nothing = "" - string_replace_users = ada_users.replace(online_string, replace_with_nothing) + string_replace_users = ada_users.replace( + online_string, replace_with_nothing + ) print("Replaced Value: ", string_replace_users) print("Monotonic: ", time.monotonic()) From c6626055cc7c5025c65e8807bc7287136fe73af3 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Wed, 9 Nov 2022 21:56:41 -0500 Subject: [PATCH 127/305] move channel ID out of secrets It's public info so doesn't need to be in secrets.py --- examples/requests_api_discord.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/requests_api_discord.py b/examples/requests_api_discord.py index 2671c27..e233d8a 100644 --- a/examples/requests_api_discord.py +++ b/examples/requests_api_discord.py @@ -10,11 +10,11 @@ import socketpool import adafruit_requests -# Web scrape authorization key required +# Active Logged in User Account Required, no tokens required +# WEB SCRAPE authorization key required. Visit URL below. # Learn how: https://github.com/lorenz234/Discord-Data-Scraping -# Ensure these are uncommented and in secrets.py or .env -# "Discord_Adafruit_Channel": "327254708534116352", # Adafruit Channel ID +# Ensure this is in secrets.py or .env # "Discord_Authorization": "Discord Authorization from browser console" # Initialize WiFi Pool (There can be only 1 pool & top of script) @@ -46,7 +46,7 @@ discord_header = {"Authorization": "" + secrets["Discord_Authorization"]} ADA_SOURCE = ( "https://discord.com/api/v10/guilds/" - + secrets["Discord_Adafruit_Channel"] + + "327254708534116352" # Adafruit Discord ID + "/preview" ) From 46b1d12a0892befe0a9b6dc2718d29c7d3c29803 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 15 Nov 2022 05:44:31 -0500 Subject: [PATCH 128/305] Twitch API using 0Auth token For FoamyGuy, thank you for the help. --- examples/requests_api_twitch.py | 167 ++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 examples/requests_api_twitch.py diff --git a/examples/requests_api_twitch.py b/examples/requests_api_twitch.py new file mode 100644 index 0000000..eb4300a --- /dev/null +++ b/examples/requests_api_twitch.py @@ -0,0 +1,167 @@ +# SPDX-FileCopyrightText: 2022 DJDevon3 for Adafruit Industries +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 8.0 +"""DJDevon3 Adafruit Feather ESP32-S2 Twitch_API_Example""" +import gc +import time +import ssl +import json +import wifi +import socketpool +import adafruit_requests + +# Twitch Developer Account & 0Auth App Required: +# Ensure Twitch_ClientID & Twitch_Client_Secret are in secrets.py or .env + +# "Twitch_ClientID": "Your Developer APP ID Here", +# "Twitch_Client_Secret": "APP ID secret here", + +# For finding your Twitch User ID +# https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/ +Twitch_UserID = "0000000" # Set User ID you want endpoints from + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + +# Time between API refreshes +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +sleep_time = 900 + +try: + from secrets import secrets +except ImportError: + print("Secrets File Import Error") + raise + +# Converts seconds in minutes/hours/days +def time_calc(time): + if time < 60: + sleep_int = time + time_output = f"{sleep_int:.0f} seconds" + return time_output + elif 60 <= time < 3600: + sleep_int = time / 60 + time_output = f"{sleep_int:.0f} minutes" + return time_output + elif 3600 <= time < 86400: + sleep_int = time / 60 / 60 + time_output = f"{sleep_int:.0f} hours" + return time_output + elif 86400 <= time < 432000: + sleep_int = time / 60 / 60 / 24 + time_output = f"{sleep_int:.1f} days" + return time_output + else: # if > 5 days convert float to int & display whole days + sleep_int = time / 60 / 60 / 24 + time_output = f"{sleep_int:.0f} days" + return time_output + + +# First we use Client ID & Client Secret to create a token with POST +# No user interaction is required for this type of scope (implicit grant flow) +twitch_0auth_header = {'Content-Type': 'application/x-www-form-urlencoded'} +TWITCH_0AUTH_TOKEN = ( + "https://id.twitch.tv/oauth2/token" +) + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +requests = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(secrets["ssid"], secrets["password"]) + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) + gc.collect() +print("Connected!\n") + +while True: + try: + # ----------------------------- POST FOR BEARER TOKEN ----------------------------------------------- + print("\nAttempting to GENERATE Twitch Bearer Token!") # --------------------------------------- + # Print Request to Serial + debug_bearer_request = False # STREAMER WARNING: your client secret will be viewable + if debug_bearer_request: + print("Full API GET URL: ", TWITCH_0AUTH_TOKEN) + print("===============================") + twitch_0auth_data = ("&client_id=" + + secrets["Twitch_ClientID"] + + "&client_secret=" + + secrets["Twitch_Client_Secret"] + + "&grant_type=client_credentials" + ) + + # POST REQUEST + twitch_0auth_response = requests.post(url=TWITCH_0AUTH_TOKEN, data=twitch_0auth_data, headers=twitch_0auth_header) + try: + twitch_0auth_json = twitch_0auth_response.json() + twitch_access_token = twitch_0auth_json['access_token'] + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + + # Print Response to Serial + debug_bearer_response = False # STREAMER WARNING: your client secret will be viewable + if debug_bearer_response: + print("JSON Dump: ", twitch_0auth_json) + print("Header: ", twitch_0auth_header) + print("Access Token: ", twitch_access_token) + + twitch_token_expiration = twitch_0auth_json['expires_in'] + print("Token Expires in: ", time_calc(twitch_token_expiration)) + twitch_token_type = twitch_0auth_json['token_type'] + print("Token Type: ", twitch_token_type) + print("Monotonic: ", time.monotonic()) + + # ----------------------------- GET DATA ----------------------------------------------- + # Bearer token is refreshed every time script runs :) + # Twitch sets token expiration to about 64 days + # Helix is the name of the current Twitch API + # Now that we have POST bearer token we can do a GET for data + # -------------------------------------------------------------------------------------- + twitch_header = { + 'Authorization': 'Bearer '+twitch_access_token+'', + 'Client-Id': ''+ secrets["Twitch_ClientID"] +'' + } + TWITCH_FOLLOWERS_SOURCE = ( + "https://api.twitch.tv/helix/users" + + "/follows?" + + "to_id=" + + Twitch_UserID + + "&first=1" + ) + print("\nAttempting to GET TWITCH Stats!") # ------------------------------------------------ + print("===============================") + twitch_followers_response = requests.get(url=TWITCH_FOLLOWERS_SOURCE, headers=twitch_header) + try: + twitch_followers_json = twitch_followers_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + + # Print Response to Serial + debug_bearer_response = False # STREAMER WARNING: your bearer token will be viewable + if debug_bearer_response: + print("Full API GET URL: ", TWITCH_FOLLOWERS_SOURCE) + print("Header: ", twitch_header) + print("JSON Full Response: ", twitch_followers_json) + + twitch_username = twitch_followers_json['data'][0]['to_name'] + print("Username: ", twitch_username) + twitch_followers = twitch_followers_json['total'] + print("Followers: ", twitch_followers) + print("Monotonic: ", time.monotonic()) # Board Up-Time seconds + + print("\nFinished!") + print("Next Update in: ", time_calc(sleep_time)) + print("===============================") + gc.collect() + + except (ValueError, RuntimeError) as e: + print("Failed to get data, retrying\n", e) + time.sleep(60) + continue + time.sleep(sleep_time) From 2b84dfab22099a7a27c96a1b5d14aace8613714e Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 15 Nov 2022 06:10:27 -0500 Subject: [PATCH 129/305] Making Black & Pylint happy black you vex me --- examples/requests_api_twitch.py | 112 +++++++++++++++++--------------- 1 file changed, 60 insertions(+), 52 deletions(-) diff --git a/examples/requests_api_twitch.py b/examples/requests_api_twitch.py index eb4300a..910bea6 100644 --- a/examples/requests_api_twitch.py +++ b/examples/requests_api_twitch.py @@ -5,7 +5,6 @@ import gc import time import ssl -import json import wifi import socketpool import adafruit_requests @@ -14,7 +13,7 @@ # Ensure Twitch_ClientID & Twitch_Client_Secret are in secrets.py or .env # "Twitch_ClientID": "Your Developer APP ID Here", -# "Twitch_Client_Secret": "APP ID secret here", +# "Twitch_Client_Secret": "APP ID secret here", # For finding your Twitch User ID # https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/ @@ -34,35 +33,29 @@ raise # Converts seconds in minutes/hours/days -def time_calc(time): - if time < 60: - sleep_int = time +def time_calc(input_time): + if input_time < 60: + sleep_int = input_time time_output = f"{sleep_int:.0f} seconds" - return time_output - elif 60 <= time < 3600: - sleep_int = time / 60 + elif 60 <= input_time < 3600: + sleep_int = input_time / 60 time_output = f"{sleep_int:.0f} minutes" - return time_output - elif 3600 <= time < 86400: - sleep_int = time / 60 / 60 + elif 3600 <= input_time < 86400: + sleep_int = input_time / 60 / 60 time_output = f"{sleep_int:.0f} hours" - return time_output - elif 86400 <= time < 432000: - sleep_int = time / 60 / 60 / 24 + elif 86400 <= input_time < 432000: + sleep_int = input_time / 60 / 60 / 24 time_output = f"{sleep_int:.1f} days" - return time_output else: # if > 5 days convert float to int & display whole days - sleep_int = time / 60 / 60 / 24 + sleep_int = input_time / 60 / 60 / 24 time_output = f"{sleep_int:.0f} days" - return time_output + return time_output # First we use Client ID & Client Secret to create a token with POST # No user interaction is required for this type of scope (implicit grant flow) -twitch_0auth_header = {'Content-Type': 'application/x-www-form-urlencoded'} -TWITCH_0AUTH_TOKEN = ( - "https://id.twitch.tv/oauth2/token" -) +twitch_0auth_header = {"Content-Type": "application/x-www-form-urlencoded"} +TWITCH_0AUTH_TOKEN = "https://id.twitch.tv/oauth2/token" # Connect to Wi-Fi print("\n===============================") @@ -80,51 +73,60 @@ def time_calc(time): while True: try: - # ----------------------------- POST FOR BEARER TOKEN ----------------------------------------------- - print("\nAttempting to GENERATE Twitch Bearer Token!") # --------------------------------------- + # ----------------------------- POST FOR BEARER TOKEN ----------------------- + print( + "\nAttempting to GENERATE Twitch Bearer Token!" + ) # --------------------------------------- # Print Request to Serial - debug_bearer_request = False # STREAMER WARNING: your client secret will be viewable - if debug_bearer_request: + debug_bearer_request = ( + False # STREAMER WARNING: your client secret will be viewable + ) + if debug_bearer_request: print("Full API GET URL: ", TWITCH_0AUTH_TOKEN) print("===============================") - twitch_0auth_data = ("&client_id=" - + secrets["Twitch_ClientID"] - + "&client_secret=" - + secrets["Twitch_Client_Secret"] - + "&grant_type=client_credentials" + twitch_0auth_data = ( + "&client_id=" + + secrets["Twitch_ClientID"] + + "&client_secret=" + + secrets["Twitch_Client_Secret"] + + "&grant_type=client_credentials" ) - + # POST REQUEST - twitch_0auth_response = requests.post(url=TWITCH_0AUTH_TOKEN, data=twitch_0auth_data, headers=twitch_0auth_header) + twitch_0auth_response = requests.post( + url=TWITCH_0AUTH_TOKEN, data=twitch_0auth_data, headers=twitch_0auth_header + ) try: twitch_0auth_json = twitch_0auth_response.json() - twitch_access_token = twitch_0auth_json['access_token'] + twitch_access_token = twitch_0auth_json["access_token"] except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") - + # Print Response to Serial - debug_bearer_response = False # STREAMER WARNING: your client secret will be viewable - if debug_bearer_response: + debug_bearer_response = ( + False # STREAMER WARNING: your client secret will be viewable + ) + if debug_bearer_response: print("JSON Dump: ", twitch_0auth_json) print("Header: ", twitch_0auth_header) print("Access Token: ", twitch_access_token) - - twitch_token_expiration = twitch_0auth_json['expires_in'] + + twitch_token_expiration = twitch_0auth_json["expires_in"] print("Token Expires in: ", time_calc(twitch_token_expiration)) - twitch_token_type = twitch_0auth_json['token_type'] + twitch_token_type = twitch_0auth_json["token_type"] print("Token Type: ", twitch_token_type) print("Monotonic: ", time.monotonic()) - - # ----------------------------- GET DATA ----------------------------------------------- + + # ----------------------------- GET DATA ------------------------------------- # Bearer token is refreshed every time script runs :) # Twitch sets token expiration to about 64 days # Helix is the name of the current Twitch API # Now that we have POST bearer token we can do a GET for data - # -------------------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- twitch_header = { - 'Authorization': 'Bearer '+twitch_access_token+'', - 'Client-Id': ''+ secrets["Twitch_ClientID"] +'' + "Authorization": "Bearer " + twitch_access_token + "", + "Client-Id": "" + secrets["Twitch_ClientID"] + "", } TWITCH_FOLLOWERS_SOURCE = ( "https://api.twitch.tv/helix/users" @@ -133,25 +135,31 @@ def time_calc(time): + Twitch_UserID + "&first=1" ) - print("\nAttempting to GET TWITCH Stats!") # ------------------------------------------------ + print( + "\nAttempting to GET TWITCH Stats!" + ) # ------------------------------------------------ print("===============================") - twitch_followers_response = requests.get(url=TWITCH_FOLLOWERS_SOURCE, headers=twitch_header) + twitch_followers_response = requests.get( + url=TWITCH_FOLLOWERS_SOURCE, headers=twitch_header + ) try: twitch_followers_json = twitch_followers_response.json() except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") - + # Print Response to Serial - debug_bearer_response = False # STREAMER WARNING: your bearer token will be viewable - if debug_bearer_response: + debug_bearer_response = ( + False # STREAMER WARNING: your bearer token will be viewable + ) + if debug_bearer_response: print("Full API GET URL: ", TWITCH_FOLLOWERS_SOURCE) print("Header: ", twitch_header) print("JSON Full Response: ", twitch_followers_json) - - twitch_username = twitch_followers_json['data'][0]['to_name'] + + twitch_username = twitch_followers_json["data"][0]["to_name"] print("Username: ", twitch_username) - twitch_followers = twitch_followers_json['total'] + twitch_followers = twitch_followers_json["total"] print("Followers: ", twitch_followers) print("Monotonic: ", time.monotonic()) # Board Up-Time seconds From 6f88d7a546a335664645a1aa22615775bb2f7e22 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 15 Nov 2022 06:24:36 -0500 Subject: [PATCH 130/305] added url to twitch dev app Because searching for a link to the place where you're supposed to create the 0auth app is sometimes like trying to find a needle in a haystack. --- examples/requests_api_twitch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/requests_api_twitch.py b/examples/requests_api_twitch.py index 910bea6..40b8343 100644 --- a/examples/requests_api_twitch.py +++ b/examples/requests_api_twitch.py @@ -10,6 +10,7 @@ import adafruit_requests # Twitch Developer Account & 0Auth App Required: +# Visit https://dev.twitch.tv/console to create an app # Ensure Twitch_ClientID & Twitch_Client_Secret are in secrets.py or .env # "Twitch_ClientID": "Your Developer APP ID Here", From 64d59f60e4b80bc3c01896d49978cc28bb9258c9 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 17 Nov 2022 03:34:33 -0500 Subject: [PATCH 131/305] Add Mastodon API No token required to get your own followers, publicly accessible. --- examples/requests_api_mastodon.py | 116 ++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 examples/requests_api_mastodon.py diff --git a/examples/requests_api_mastodon.py b/examples/requests_api_mastodon.py new file mode 100644 index 0000000..600fed5 --- /dev/null +++ b/examples/requests_api_mastodon.py @@ -0,0 +1,116 @@ +# SPDX-FileCopyrightText: 2022 DJDevon3 +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 8.0 +"""DJDevon3 Adafruit Feather ESP32-S2 Mastodon_API_Example""" +import gc +import time +import ssl +import wifi +import socketpool +import adafruit_requests + +# Mastodon V1 API - Public access (no dev creds or app required) +# Visit https://docs.joinmastodon.org/client/public/ for API docs +# For finding your Mastodon User ID +# Login to your mastodon server in a browser, visit your profile, UserID is in the URL. +# Example: https://mastodon.YOURSERVER/web/accounts/YOURUSERIDISHERE + +Mastodon_UserID = "000000000000000000" # Set User ID you want endpoints from +# Test in browser first, this will pull up a JSON webpage +# https://mastodon.YOURSERVER/api/v1/accounts/YOURUSERIDHERE/statuses?limit=1 + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + +# Time between API refreshes +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +sleep_time = 900 + +try: + from secrets import secrets +except ImportError: + print("Secrets File Import Error") + raise + +# Converts seconds in minutes/hours/days +def time_calc(input_time): + if input_time < 60: + sleep_int = input_time + time_output = f"{sleep_int:.0f} seconds" + elif 60 <= input_time < 3600: + sleep_int = input_time / 60 + time_output = f"{sleep_int:.0f} minutes" + elif 3600 <= input_time < 86400: + sleep_int = input_time / 60 / 60 + time_output = f"{sleep_int:.0f} hours" + elif 86400 <= input_time < 432000: + sleep_int = input_time / 60 / 60 / 24 + time_output = f"{sleep_int:.1f} days" + else: # if > 5 days convert float to int & display whole days + sleep_int = input_time / 60 / 60 / 24 + time_output = f"{sleep_int:.0f} days" + return time_output + + +# Publicly available data no header required +MAST_SOURCE = ( + "https://mastodon.cloud/api/v1/accounts/" + + Mastodon_UserID + + "/statuses?limit=1" +) + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +requests = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(secrets["ssid"], secrets["password"]) + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) + gc.collect() +print("Connected!\n") + +while True: + try: + print("\nAttempting to GET MASTODON Stats!") # ----------------------------- + # Print Request to Serial + debug_mastodon_full_response = ( + False # STREAMER WARNING: your client secret will be viewable + ) + print("===============================") + mastodon_response = requests.get(url=MAST_SOURCE) + try: + mastodon_json = mastodon_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + mastodon_json = mastodon_json[0] + if debug_mastodon_full_response: + print("Full API GET URL: ", MAST_SOURCE) + print(mastodon_json) + mastodon_userid = mastodon_json['account']['id'] + print("User ID: ", mastodon_userid) + + mastodon_username = mastodon_json['account']['display_name'] + print("Name: ", mastodon_username) + mastodon_join_date = mastodon_json['account']['created_at'] + print("Member Since: ", mastodon_join_date) + mastodon_toot_count = mastodon_json['account']['statuses_count'] + print("Toots: ", mastodon_toot_count) + mastodon_follower_count = mastodon_json['account']['followers_count'] + print("Followers: ", mastodon_follower_count) + print("Monotonic: ", time.monotonic()) + + print("\nFinished!") + print("Next Update in: ", time_calc(sleep_time)) + print("===============================") + gc.collect() + + except (ValueError, RuntimeError) as e: + print("Failed to get data, retrying\n", e) + time.sleep(60) + continue + time.sleep(sleep_time) From bc7a4680e61f3b402f064c98b0a311e0fa58a647 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 17 Nov 2022 03:39:08 -0500 Subject: [PATCH 132/305] ran black on Mastodon file addition --- examples/requests_api_mastodon.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/examples/requests_api_mastodon.py b/examples/requests_api_mastodon.py index 600fed5..99c651d 100644 --- a/examples/requests_api_mastodon.py +++ b/examples/requests_api_mastodon.py @@ -54,9 +54,7 @@ def time_calc(input_time): # Publicly available data no header required MAST_SOURCE = ( - "https://mastodon.cloud/api/v1/accounts/" - + Mastodon_UserID - + "/statuses?limit=1" + "https://mastodon.cloud/api/v1/accounts/" + Mastodon_UserID + "/statuses?limit=1" ) # Connect to Wi-Fi @@ -91,16 +89,16 @@ def time_calc(input_time): if debug_mastodon_full_response: print("Full API GET URL: ", MAST_SOURCE) print(mastodon_json) - mastodon_userid = mastodon_json['account']['id'] + mastodon_userid = mastodon_json["account"]["id"] print("User ID: ", mastodon_userid) - - mastodon_username = mastodon_json['account']['display_name'] + + mastodon_username = mastodon_json["account"]["display_name"] print("Name: ", mastodon_username) - mastodon_join_date = mastodon_json['account']['created_at'] + mastodon_join_date = mastodon_json["account"]["created_at"] print("Member Since: ", mastodon_join_date) - mastodon_toot_count = mastodon_json['account']['statuses_count'] + mastodon_toot_count = mastodon_json["account"]["statuses_count"] print("Toots: ", mastodon_toot_count) - mastodon_follower_count = mastodon_json['account']['followers_count'] + mastodon_follower_count = mastodon_json["account"]["followers_count"] print("Followers: ", mastodon_follower_count) print("Monotonic: ", time.monotonic()) From 0ef218df4c7351164a19ba0049366977f06dce3f Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:06:38 -0500 Subject: [PATCH 133/305] replace hardcoded server with configurable server name Fixing requested change --- examples/requests_api_mastodon.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/requests_api_mastodon.py b/examples/requests_api_mastodon.py index 99c651d..09ab59f 100644 --- a/examples/requests_api_mastodon.py +++ b/examples/requests_api_mastodon.py @@ -15,6 +15,7 @@ # Login to your mastodon server in a browser, visit your profile, UserID is in the URL. # Example: https://mastodon.YOURSERVER/web/accounts/YOURUSERIDISHERE +Mastodon_Server = "mastodon.social" # Set server instance Mastodon_UserID = "000000000000000000" # Set User ID you want endpoints from # Test in browser first, this will pull up a JSON webpage # https://mastodon.YOURSERVER/api/v1/accounts/YOURUSERIDHERE/statuses?limit=1 @@ -53,8 +54,11 @@ def time_calc(input_time): # Publicly available data no header required -MAST_SOURCE = ( - "https://mastodon.cloud/api/v1/accounts/" + Mastodon_UserID + "/statuses?limit=1" +MAST_SOURCE = ("https://" + + Mastodon_Server + + "/api/v1/accounts/" + + Mastodon_UserID + + "/statuses?limit=1" ) # Connect to Wi-Fi From 0c12ada522ba599770473c9f511f7073e543ef49 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:13:02 -0500 Subject: [PATCH 134/305] ran black manually --- examples/requests_api_mastodon.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/requests_api_mastodon.py b/examples/requests_api_mastodon.py index 09ab59f..03b4328 100644 --- a/examples/requests_api_mastodon.py +++ b/examples/requests_api_mastodon.py @@ -54,7 +54,8 @@ def time_calc(input_time): # Publicly available data no header required -MAST_SOURCE = ("https://" +MAST_SOURCE = ( + "https://" + Mastodon_Server + "/api/v1/accounts/" + Mastodon_UserID From 449fc1ba8261b60c33d66875c205d0262566a61d Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Thu, 1 Sep 2022 20:16:31 -0400 Subject: [PATCH 135/305] Add .venv to .gitignore Signed-off-by: Alec Delaney <89490472+tekktrik@users.noreply.github.com> --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 544ec4a..db3d538 100755 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ _build # Virtual environment-specific files .env +.venv # MacOS-specific files *.DS_Store From fa019441b54782fe08cd91454e4569b5cf6618ba Mon Sep 17 00:00:00 2001 From: Alexandre Devienne Date: Thu, 1 Dec 2022 22:44:39 +0100 Subject: [PATCH 136/305] Add support for response without Content-Length --- adafruit_requests.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 73993ba..11a325d 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -288,17 +288,24 @@ def _readinto(self, buf: bytearray) -> int: self._parse_headers() return 0 self._remaining = http_chunk_size + elif self._remaining is None: + # the Content-Length is not provided in the HTTP header + # so try parsing as long as their is data in the socket + pass else: return 0 nbytes = len(buf) - if nbytes > self._remaining: - nbytes = self._remaining + if self._remaining and nbytes > self._remaining: + # if Content-Length was provided and remaining bytes larges than buffer + nbytes = self._remaining # adjust read amount read = self._read_from_buffer(buf, nbytes) if read == 0: read = self._recv_into(buf, nbytes) - self._remaining -= read + if self._remaining: + # if Content-Length was provided, adjust the remaining amount to still read + self._remaining -= read return read From 51cdd37a74e58224c7f735a5ea6ebe3681b88b4d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 9 Jan 2023 09:15:22 -0600 Subject: [PATCH 137/305] preserve last exception when things are going wrong CircuitPython 8 supports exception chaining, so that the original problem can be shown. Here's how it looks on desktop python3: ``` Traceback (most recent call last): File "/home/jepler/src/bundle/libraries/helpers/requests/adafruit_requests.py", line 527, in _get_socket sock.connect((connect_host, port)) ConnectionRefusedError: [Errno 111] Connection refused The above exception was the direct cause of the following exception: Traceback (most recent call last): File "", line 1, in File "/home/jepler/src/bundle/libraries/helpers/requests/adafruit_requests.py", line 721, in get return self.request("GET", url, **kw) File "/home/jepler/src/bundle/libraries/helpers/requests/adafruit_requests.py", line 661, in request socket = self._get_socket(host, port, proto, timeout=timeout) File "/home/jepler/src/bundle/libraries/helpers/requests/adafruit_requests.py", line 508, in _get_socket raise RuntimeError("Sending request failed") from last_exc RuntimeError: Sending request failed ``` --- adafruit_requests.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 73993ba..73e4dda 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -499,19 +499,22 @@ def _get_socket( )[0] retry_count = 0 sock = None + last_exc = None while retry_count < 5 and sock is None: if retry_count > 0: if any(self._socket_free.items()): self._free_sockets() else: - raise RuntimeError("Sending request failed") + raise RuntimeError("Sending request failed") from last_exc retry_count += 1 try: sock = self._socket_pool.socket(addr_info[0], addr_info[1]) - except OSError: + except OSError as exc: + last_exc = exc continue - except RuntimeError: + except RuntimeError as exc: + last_exc = exc continue connect_host = addr_info[-1][0] @@ -522,15 +525,17 @@ def _get_socket( try: sock.connect((connect_host, port)) - except MemoryError: + except MemoryError as exc: + last_exc = exc sock.close() sock = None - except OSError: + except OSError as exc: + last_exc = exc sock.close() sock = None if sock is None: - raise RuntimeError("Repeated socket failures") + raise RuntimeError("Repeated socket failures") from last_exc self._open_sockets[key] = sock self._socket_free[sock] = False @@ -650,13 +655,15 @@ def request( # We may fail to send the request if the socket we got is closed already. So, try a second # time in that case. retry_count = 0 + last_exc = None while retry_count < 2: retry_count += 1 socket = self._get_socket(host, port, proto, timeout=timeout) ok = True try: self._send_request(socket, host, method, path, headers, data, json) - except OSError: + except OSError as exc: + last_exc = exc ok = False if ok: # Read the H of "HTTP/1.1" to make sure the socket is alive. send can appear to work @@ -676,7 +683,7 @@ def request( socket = None if not socket: - raise OutOfRetries("Repeated socket failures") + raise OutOfRetries("Repeated socket failures") from last_exc resp = Response(socket, self) # our response if "location" in resp.headers and 300 <= resp.status_code <= 399: From 268d6627ee28220cf965621fe54b2866507afffb Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Thu, 19 Jan 2023 23:39:55 -0500 Subject: [PATCH 138/305] Add upload url to release action Signed-off-by: Alec Delaney <89490472+tekktrik@users.noreply.github.com> --- .github/workflows/release_gh.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release_gh.yml b/.github/workflows/release_gh.yml index b8aa8d6..9acec60 100644 --- a/.github/workflows/release_gh.yml +++ b/.github/workflows/release_gh.yml @@ -16,3 +16,4 @@ jobs: uses: adafruit/workflows-circuitpython-libs/release-gh@main with: github-token: ${{ secrets.GITHUB_TOKEN }} + upload-url: ${{ github.event.release.upload_url }} From 84ab63a2f16a831377d1c51a818351c666bbdf26 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 9 Mar 2023 15:23:58 -0500 Subject: [PATCH 139/305] Steam API example Example displays the sum total amount of time in hours and days playing video games on steam. --- examples/requests_api_steam.py | 112 +++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 examples/requests_api_steam.py diff --git a/examples/requests_api_steam.py b/examples/requests_api_steam.py new file mode 100644 index 0000000..8707350 --- /dev/null +++ b/examples/requests_api_steam.py @@ -0,0 +1,112 @@ +# SPDX-FileCopyrightText: 2022 DJDevon3 (Neradoc & Deshipu helped) for Adafruit Industries +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 8.0 +"""DJDevon3 Adafruit Feather ESP32-S2 api_steam Example""" +import os +import gc +import time +import ssl +import json +import wifi +import socketpool +import adafruit_requests + +# Ensure these are setup in settings.toml +# Requires Steam Developer API key +ssid = os.getenv('AP_SSID') +appw = os.getenv('AP_PASSWORD') +steam_usernumber = os.getenv('steam_id') +steam_apikey = os.getenv('steam_api_key') + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + +# Time between API refreshes +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +sleep_time = 900 + +# Deconstruct URL +# http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=XXXXXXXXXXXXXXXXXXXXX&steamid=XXXXXXXXXXXXXXXX&format=json +Steam_OwnedGames_URL = ("http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?" + + "key=" + + steam_apikey + + "&steamid=" + + steam_usernumber + + "&format=json" + ) + +if sleep_time < 60: + sleep_time_conversion = "seconds" + sleep_int = sleep_time +elif 60 <= sleep_time < 3600: + sleep_int = sleep_time / 60 + sleep_time_conversion = "minutes" +elif 3600 <= sleep_time < 86400: + sleep_int = sleep_time / 60 / 60 + sleep_time_conversion = "hours" +else: + sleep_int = sleep_time / 60 / 60 / 24 + sleep_time_conversion = "days" + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +requests = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, appw) + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) + gc.collect() +print("Connected!\n") + + +while True: + try: + print("\nAttempting to GET STEAM Stats!") # -------------------------------- + # Print Request to Serial + debug_request = False # Set true to see full request + if debug_request: + print("Full API GET URL: ", Steam_OwnedGames_URL) + print("===============================") + try: + steam_response = requests.get(url=Steam_OwnedGames_URL).json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + + # Print Response to Serial + debug_response = False # Set true to see full response + if debug_response: + dump_object = json.dumps(steam_response) + print("JSON Dump: ", dump_object) + + # Print Keys to Serial + steam_debug_keys = True # Set True to print Serial data + if steam_debug_keys: + + game_count = steam_response["response"]["game_count"] + print("Total Games: ", game_count) + total_minutes = 0 + + for game in steam_response["response"]["games"]: + total_minutes += game["playtime_forever"] + total_hours = total_minutes/60 + total_days = total_minutes/60/24 + print(f'Total Hours: {total_hours}') + print(f'Total Days: {total_days}') + + print("Monotonic: ", time.monotonic()) + + print("\nFinished!") + print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) + print("===============================") + gc.collect() + + except (ValueError, RuntimeError) as e: + print("Failed to get data, retrying\n", e) + time.sleep(60) + continue + time.sleep(sleep_time) \ No newline at end of file From 95c736df5a8c6f70372ad8c6acf3755bb36f838e Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 9 Mar 2023 15:44:13 -0500 Subject: [PATCH 140/305] ran black python -m black requests_api_steam.py --- examples/requests_api_steam.py | 38 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/examples/requests_api_steam.py b/examples/requests_api_steam.py index 8707350..fb87c83 100644 --- a/examples/requests_api_steam.py +++ b/examples/requests_api_steam.py @@ -13,10 +13,10 @@ # Ensure these are setup in settings.toml # Requires Steam Developer API key -ssid = os.getenv('AP_SSID') -appw = os.getenv('AP_PASSWORD') -steam_usernumber = os.getenv('steam_id') -steam_apikey = os.getenv('steam_api_key') +ssid = os.getenv("AP_SSID") +appw = os.getenv("AP_PASSWORD") +steam_usernumber = os.getenv("steam_id") +steam_apikey = os.getenv("steam_api_key") # Initialize WiFi Pool (There can be only 1 pool & top of script) pool = socketpool.SocketPool(wifi.radio) @@ -27,13 +27,14 @@ # Deconstruct URL # http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=XXXXXXXXXXXXXXXXXXXXX&steamid=XXXXXXXXXXXXXXXX&format=json -Steam_OwnedGames_URL = ("http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?" - + "key=" - + steam_apikey - + "&steamid=" - + steam_usernumber - + "&format=json" - ) +Steam_OwnedGames_URL = ( + "http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?" + + "key=" + + steam_apikey + + "&steamid=" + + steam_usernumber + + "&format=json" +) if sleep_time < 60: sleep_time_conversion = "seconds" @@ -86,18 +87,17 @@ # Print Keys to Serial steam_debug_keys = True # Set True to print Serial data if steam_debug_keys: - game_count = steam_response["response"]["game_count"] print("Total Games: ", game_count) total_minutes = 0 - + for game in steam_response["response"]["games"]: total_minutes += game["playtime_forever"] - total_hours = total_minutes/60 - total_days = total_minutes/60/24 - print(f'Total Hours: {total_hours}') - print(f'Total Days: {total_days}') - + total_hours = total_minutes / 60 + total_days = total_minutes / 60 / 24 + print(f"Total Hours: {total_hours}") + print(f"Total Days: {total_days}") + print("Monotonic: ", time.monotonic()) print("\nFinished!") @@ -109,4 +109,4 @@ print("Failed to get data, retrying\n", e) time.sleep(60) continue - time.sleep(sleep_time) \ No newline at end of file + time.sleep(sleep_time) From d0a189fcd43771241bda2bac55938e65a327c3e3 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 9 Mar 2023 16:16:32 -0500 Subject: [PATCH 141/305] attempt #2 at black --- examples/requests_api_steam.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/requests_api_steam.py b/examples/requests_api_steam.py index fb87c83..ee0ea72 100644 --- a/examples/requests_api_steam.py +++ b/examples/requests_api_steam.py @@ -63,7 +63,6 @@ gc.collect() print("Connected!\n") - while True: try: print("\nAttempting to GET STEAM Stats!") # -------------------------------- @@ -99,7 +98,6 @@ print(f"Total Days: {total_days}") print("Monotonic: ", time.monotonic()) - print("\nFinished!") print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) print("===============================") From 259ac16e6b3103a71e382071dcacbf614ef42f1d Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 21 Mar 2023 02:02:19 -0400 Subject: [PATCH 142/305] Updated with requested changes Added URLs for developer docs. Request now pulls all free games too. --- examples/requests_api_steam.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/requests_api_steam.py b/examples/requests_api_steam.py index ee0ea72..373ebd0 100644 --- a/examples/requests_api_steam.py +++ b/examples/requests_api_steam.py @@ -11,6 +11,10 @@ import socketpool import adafruit_requests +# Steam API Docs: https://steamcommunity.com/dev +# Steam API Key: https://steamcommunity.com/dev/apikey +# Steam Usernumber: Visit https://steamcommunity.com, click on your profile icon, your usernumber will be in the browser url. + # Ensure these are setup in settings.toml # Requires Steam Developer API key ssid = os.getenv("AP_SSID") @@ -26,11 +30,12 @@ sleep_time = 900 # Deconstruct URL -# http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=XXXXXXXXXXXXXXXXXXXXX&steamid=XXXXXXXXXXXXXXXX&format=json +# http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=XXXXXXXXXXXXXXXXXXXXX&include_played_free_games=1&steamid=XXXXXXXXXXXXXXXX&format=json Steam_OwnedGames_URL = ( "http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?" + "key=" + steam_apikey + + "&include_played_free_games=1" + "&steamid=" + steam_usernumber + "&format=json" From c427f54870e86d514d4bc47f72e28f90c9b07a1f Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 21 Mar 2023 02:07:29 -0400 Subject: [PATCH 143/305] pylint puked on the long lines split up long lines --- examples/requests_api_steam.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/requests_api_steam.py b/examples/requests_api_steam.py index 373ebd0..2f12517 100644 --- a/examples/requests_api_steam.py +++ b/examples/requests_api_steam.py @@ -13,7 +13,8 @@ # Steam API Docs: https://steamcommunity.com/dev # Steam API Key: https://steamcommunity.com/dev/apikey -# Steam Usernumber: Visit https://steamcommunity.com, click on your profile icon, your usernumber will be in the browser url. +# Steam Usernumber: Visit https://steamcommunity.com +# click on your profile icon, your usernumber will be in the browser url. # Ensure these are setup in settings.toml # Requires Steam Developer API key @@ -29,8 +30,9 @@ # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour sleep_time = 900 -# Deconstruct URL -# http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=XXXXXXXXXXXXXXXXXXXXX&include_played_free_games=1&steamid=XXXXXXXXXXXXXXXX&format=json +# Deconstruct URL (pylint hates long lines) +# http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/ +#?key=XXXXXXXXXXXXXXXXXXXXX&include_played_free_games=1&steamid=XXXXXXXXXXXXXXXX&format=json Steam_OwnedGames_URL = ( "http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?" + "key=" From 32c45a0d3e23724d75dbc55ba42414fbb5e02019 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 21 Mar 2023 02:13:50 -0400 Subject: [PATCH 144/305] ran black on the entire /examples directory have to run black EVERY time a file changes?? --- examples/requests_api_discord.py | 1 - examples/requests_api_github.py | 1 - examples/requests_api_mastodon.py | 1 + examples/requests_api_steam.py | 2 +- examples/requests_api_twitch.py | 1 + examples/requests_api_twitter.py | 1 - 6 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/requests_api_discord.py b/examples/requests_api_discord.py index e233d8a..501e09e 100644 --- a/examples/requests_api_discord.py +++ b/examples/requests_api_discord.py @@ -89,7 +89,6 @@ # Print keys to Serial discord_debug_keys = True # Set to True to print Serial data if discord_debug_keys: - ada_discord_all_members = ada_res["approximate_member_count"] print("Members: ", ada_discord_all_members) diff --git a/examples/requests_api_github.py b/examples/requests_api_github.py index 4775a54..a220df7 100644 --- a/examples/requests_api_github.py +++ b/examples/requests_api_github.py @@ -81,7 +81,6 @@ # Print Keys to Serial gh_debug_keys = True # Set True to print Serial data if gh_debug_keys: - github_id = github_response["id"] print("UserID: ", github_id) diff --git a/examples/requests_api_mastodon.py b/examples/requests_api_mastodon.py index 03b4328..893746c 100644 --- a/examples/requests_api_mastodon.py +++ b/examples/requests_api_mastodon.py @@ -33,6 +33,7 @@ print("Secrets File Import Error") raise + # Converts seconds in minutes/hours/days def time_calc(input_time): if input_time < 60: diff --git a/examples/requests_api_steam.py b/examples/requests_api_steam.py index 2f12517..d29406c 100644 --- a/examples/requests_api_steam.py +++ b/examples/requests_api_steam.py @@ -32,7 +32,7 @@ # Deconstruct URL (pylint hates long lines) # http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/ -#?key=XXXXXXXXXXXXXXXXXXXXX&include_played_free_games=1&steamid=XXXXXXXXXXXXXXXX&format=json +# ?key=XXXXXXXXXXXXXXXXXXXXX&include_played_free_games=1&steamid=XXXXXXXXXXXXXXXX&format=json Steam_OwnedGames_URL = ( "http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?" + "key=" diff --git a/examples/requests_api_twitch.py b/examples/requests_api_twitch.py index 40b8343..e4b0dbb 100644 --- a/examples/requests_api_twitch.py +++ b/examples/requests_api_twitch.py @@ -33,6 +33,7 @@ print("Secrets File Import Error") raise + # Converts seconds in minutes/hours/days def time_calc(input_time): if input_time < 60: diff --git a/examples/requests_api_twitter.py b/examples/requests_api_twitter.py index 6d0645e..995c790 100644 --- a/examples/requests_api_twitter.py +++ b/examples/requests_api_twitter.py @@ -88,7 +88,6 @@ # Print to Serial tw_debug_keys = True # Set true to print Serial data if tw_debug_keys: - tw_userid = tw_json["data"]["id"] print("User ID: ", tw_userid) From e573362c329e598c3842dba5a1941ea1465847a0 Mon Sep 17 00:00:00 2001 From: Neradoc Date: Fri, 21 Apr 2023 17:09:46 +0200 Subject: [PATCH 145/305] add status_code docs --- adafruit_requests.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 61bd5f3..9d361cd 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -195,8 +195,10 @@ def __init__(self, sock: SocketType, session: Optional["Session"] = None) -> Non else: self.socket.close() raise RuntimeError("Unable to read HTTP response.") - self.status_code = int(bytes(self._readto(b" "))) - self.reason = self._readto(b"\r\n") + self.status_code: int = int(bytes(self._readto(b" "))) + """The status code returned by the server""" + self.reason: bytearray = self._readto(b"\r\n") + """The status reason returned by the server""" self._parse_headers() self._raw = None self._session = session From 4cce56a1ffb5feb890134096c9a29bb257c6225d Mon Sep 17 00:00:00 2001 From: Tekktrik Date: Tue, 9 May 2023 20:26:25 -0400 Subject: [PATCH 146/305] Update pre-commit hooks Signed-off-by: Tekktrik --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0e5fccc..70ade69 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,21 +4,21 @@ repos: - repo: https://github.com/python/black - rev: 22.3.0 + rev: 23.3.0 hooks: - id: black - repo: https://github.com/fsfe/reuse-tool - rev: v0.14.0 + rev: v1.1.2 hooks: - id: reuse - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.4.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/pylint - rev: v2.15.5 + rev: v2.17.4 hooks: - id: pylint name: pylint (library code) From 866e79eca1a3293395cfaef7a2ec583c49d6a1c1 Mon Sep 17 00:00:00 2001 From: Tekktrik Date: Wed, 10 May 2023 22:42:36 -0400 Subject: [PATCH 147/305] Run pre-commit --- adafruit_requests.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 9d361cd..98c725d 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -80,7 +80,8 @@ def connect( conntype: Optional[int] = ..., ) -> None: """Connect to a remote socket at the provided (host, port) address. The conntype - kwarg optionally may indicate SSL or not, depending on the underlying interface.""" + kwarg optionally may indicate SSL or not, depending on the underlying interface. + """ class LegacyCircuitPythonSocketType(CommonCircuitPythonSocketType, Protocol): """Describes the structure a legacy CircuitPython socket type must have.""" @@ -96,7 +97,8 @@ class SupportsRecvWithFlags(Protocol): def recv(self, bufsize: int = ..., flags: int = ...) -> bytes: """Receive data from the socket. The return value is a bytes object representing the data received. The maximum amount of data to be received at once is specified - by bufsize. The meaning of the optional flags kwarg is implementation-specific.""" + by bufsize. The meaning of the optional flags kwarg is implementation-specific. + """ class SupportsRecvInto(Protocol): """Describes a type that possesses a socket recv_into() method.""" From 7158a1f0d890128069dce150401bdc92b2da32ab Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 19 May 2023 12:01:33 -0500 Subject: [PATCH 148/305] implement allow_redirects argument --- adafruit_requests.py | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 98c725d..a0276f0 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -631,6 +631,7 @@ def request( headers: Optional[Dict[str, str]] = None, stream: bool = False, timeout: float = 60, + allow_redirects: bool = True, ) -> Response: """Perform an HTTP request to the given url which we will parse to determine whether to use SSL ('https://') or not. We can also send some provided 'data' @@ -697,28 +698,29 @@ def request( raise OutOfRetries("Repeated socket failures") from last_exc resp = Response(socket, self) # our response - if "location" in resp.headers and 300 <= resp.status_code <= 399: - # a naive handler for redirects - redirect = resp.headers["location"] - - if redirect.startswith("http"): - # absolute URL - url = redirect - elif redirect[0] == "/": - # relative URL, absolute path - url = "/".join([proto, dummy, host, redirect[1:]]) - else: - # relative URL, relative path - path = path.rsplit("/", 1)[0] - - while redirect.startswith("../"): + if allow_redirects: + if "location" in resp.headers and 300 <= resp.status_code <= 399: + # a naive handler for redirects + redirect = resp.headers["location"] + + if redirect.startswith("http"): + # absolute URL + url = redirect + elif redirect[0] == "/": + # relative URL, absolute path + url = "/".join([proto, dummy, host, redirect[1:]]) + else: + # relative URL, relative path path = path.rsplit("/", 1)[0] - redirect = redirect.split("../", 1)[1] - url = "/".join([proto, dummy, host, path, redirect]) + while redirect.startswith("../"): + path = path.rsplit("/", 1)[0] + redirect = redirect.split("../", 1)[1] + + url = "/".join([proto, dummy, host, path, redirect]) - self._last_response = resp - resp = self.request(method, url, data, json, headers, stream, timeout) + self._last_response = resp + resp = self.request(method, url, data, json, headers, stream, timeout) self._last_response = resp return resp From 59b1aca589cf3ce261fcf154c63bae3ca42a4390 Mon Sep 17 00:00:00 2001 From: Tekktrik Date: Sun, 14 May 2023 13:00:32 -0400 Subject: [PATCH 149/305] Update .pylintrc, fix jQuery for docs Signed-off-by: Tekktrik --- .pylintrc | 2 +- docs/conf.py | 1 + docs/requirements.txt | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 40208c3..f945e92 100644 --- a/.pylintrc +++ b/.pylintrc @@ -396,4 +396,4 @@ min-public-methods=1 # Exceptions that will emit a warning when being caught. Defaults to # "Exception" -overgeneral-exceptions=Exception +overgeneral-exceptions=builtins.Exception diff --git a/docs/conf.py b/docs/conf.py index 1f7dec7..0a6c357 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,6 +17,7 @@ # ones. extensions = [ "sphinx.ext.autodoc", + "sphinxcontrib.jquery", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", "sphinx.ext.todo", diff --git a/docs/requirements.txt b/docs/requirements.txt index 88e6733..797aa04 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -3,3 +3,4 @@ # SPDX-License-Identifier: Unlicense sphinx>=4.0.0 +sphinxcontrib-jquery From 1635b7b0fe21bacdfa6e43b22a8c918737b26448 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 29 May 2023 04:04:19 -0400 Subject: [PATCH 150/305] Add OpenSky-Network API example Free public limit is 100 calls per day and maximum of 60 seconds each or you'll get temp banned. You get 4000 calls if you login but I haven't figured out how with a microcontroller yet. They have no token system and their API is primarily built for python & curl. It's a foot in the door for public access to flight data. --- .../requests_api_openskynetwork_public.py | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 examples/requests_api_openskynetwork_public.py diff --git a/examples/requests_api_openskynetwork_public.py b/examples/requests_api_openskynetwork_public.py new file mode 100644 index 0000000..abcc698 --- /dev/null +++ b/examples/requests_api_openskynetwork_public.py @@ -0,0 +1,141 @@ +# SPDX-FileCopyrightText: 2023 DJDevon3 +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 8.1 +"""DJDevon3 Adafruit Feather ESP32-S3 OpenSkyNetwork_API_Example""" +import os +import time +import ssl +import json +import wifi +import socketpool +import adafruit_requests + +# No developer account necessary for this API +# OpenSky-Networks.org REST API: https://openskynetwork.github.io/opensky-api/rest.html +# All active flights JSON: https://opensky-network.org/api/states/all +# JSON order: transponder, callsign, country +# ACTIVE transponder you want data from +transponder = "ab1644" + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + +# Time between API refreshes +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +# OpenSky-Networks will temp ban your IP for too many requests, there is a public rate limit. +# https://openskynetwork.github.io/opensky-api/rest.html#limitations +sleep_time = 1800 + +# this example uses settings.toml for credentials +# timezone offset is in seconds plus or minus GMT +ssid = os.getenv('AP_SSID') +appw = os.getenv('AP_PASSWORD') +timezone = os.getenv('timezone') +tz_offset_seconds = os.getenv('timezone_offset') + +# https://opensky-network.org/api/states/all +# example https://opensky-network.org/api/states/all?icao24=a808c5 +# You can use states/own to pull your owned craft data without rate limit. +OPENSKY_SOURCE = ( + "https://opensky-network.org/api/states/all?" + + "icao24=" + + transponder +) + +def time_calc(input_time): + if input_time < 60: + sleep_int = input_time + time_output = f"{sleep_int:.0f} seconds" + elif 60 <= input_time < 3600: + sleep_int = input_time / 60 + time_output = f"{sleep_int:.0f} minutes" + elif 3600 <= input_time < 86400: + sleep_int = input_time / 60 / 60 + time_output = f"{sleep_int:.1f} hours" + elif 86400 <= input_time < 432000: + sleep_int = input_time / 60 / 60 / 24 + time_output = f"{sleep_int:.1f} days" + else: # if > 5 days convert float to int & display whole days + sleep_int = input_time / 60 / 60 / 24 + time_output = f"{sleep_int:.0f} days" + return time_output + +def _format_datetime(datetime): + return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( + datetime.tm_mon, + datetime.tm_mday, + datetime.tm_year, + datetime.tm_hour, + datetime.tm_min, + datetime.tm_sec, + ) + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +requests = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, appw) + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) +print("Connected!\n") + +while True: + debug_request = True # Set true to see full request + if debug_request: + print("Full API GET URL: ", OPENSKY_SOURCE) + print("===============================") + try: + print("\nAttempting to GET OpenSky-Network Stats!") + opensky_response = requests.get(url=OPENSKY_SOURCE) + osn_json = opensky_response.json() + except (ConnectionError, ValueError, NameError) as e: + print("Host No Response Error:", e) + + # Print Full JSON to Serial + debug_response = False # Set true to see full response + if debug_response: + dump_object = json.dumps(osn_json) + print("JSON Dump: ", dump_object) + + # Print to Serial + osn_debug_keys = True # Set true to print Serial data + if osn_debug_keys: + try: + osn_flight = osn_json['time'] + print("Current Unix Time: ", osn_flight) + + current_struct_time = time.localtime(osn_flight) + current_date = "{}".format(_format_datetime(current_struct_time)) + print(f"Unix to Readable Time: {current_date}") + + osn_single_flight_data = osn_json['states'] + if osn_single_flight_data is not None: + print("Flight Data: ", osn_single_flight_data) + transponder = osn_json['states'][0][0] + print("Transponder: ", transponder) + callsign = osn_json['states'][0][1] + print("Callsign: ", callsign) + country = osn_json['states'][0][2] + print("Flight Country: ", country) + else: + print("This flight has no active data or you're polling too fast.") + print("You will eventually get temp banned for polling too fast!") + print("Please read: https://openskynetwork.github.io/opensky-api/rest.html#limitations") + print("Public Limits: 10 second max poll rate & 400 weighted calls daily") + print("There is no JSON error, states/all html page will say \"Too many requests\" and the script will fail ") + + print("\nFinished!") + print("Board Uptime: ", time_calc(time.monotonic())) + print("Next Update: ", time_calc(sleep_time)) + time.sleep(sleep_time) + print("===============================") + + except (ConnectionError, ValueError, NameError) as e: + print("OSN Connection Error:", e) + print("You are likely banned for 24 hours") + print("Next Retry: ", time_calc(sleep_time)) + time.sleep(sleep_time) \ No newline at end of file From 626605f695b98f9c33c3e5b51157b2168eb9608d Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 29 May 2023 04:08:42 -0400 Subject: [PATCH 151/305] i want to give black a black eye i forget every single time --- .../requests_api_openskynetwork_public.py | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/examples/requests_api_openskynetwork_public.py b/examples/requests_api_openskynetwork_public.py index abcc698..54c1776 100644 --- a/examples/requests_api_openskynetwork_public.py +++ b/examples/requests_api_openskynetwork_public.py @@ -28,19 +28,16 @@ # this example uses settings.toml for credentials # timezone offset is in seconds plus or minus GMT -ssid = os.getenv('AP_SSID') -appw = os.getenv('AP_PASSWORD') -timezone = os.getenv('timezone') -tz_offset_seconds = os.getenv('timezone_offset') +ssid = os.getenv("AP_SSID") +appw = os.getenv("AP_PASSWORD") +timezone = os.getenv("timezone") +tz_offset_seconds = os.getenv("timezone_offset") # https://opensky-network.org/api/states/all # example https://opensky-network.org/api/states/all?icao24=a808c5 # You can use states/own to pull your owned craft data without rate limit. -OPENSKY_SOURCE = ( - "https://opensky-network.org/api/states/all?" - + "icao24=" - + transponder -) +OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + transponder + def time_calc(input_time): if input_time < 60: @@ -60,6 +57,7 @@ def time_calc(input_time): time_output = f"{sleep_int:.0f} days" return time_output + def _format_datetime(datetime): return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( datetime.tm_mon, @@ -70,6 +68,7 @@ def _format_datetime(datetime): datetime.tm_sec, ) + # Connect to Wi-Fi print("\n===============================") print("Connecting to WiFi...") @@ -105,37 +104,43 @@ def _format_datetime(datetime): osn_debug_keys = True # Set true to print Serial data if osn_debug_keys: try: - osn_flight = osn_json['time'] + osn_flight = osn_json["time"] print("Current Unix Time: ", osn_flight) - + current_struct_time = time.localtime(osn_flight) current_date = "{}".format(_format_datetime(current_struct_time)) print(f"Unix to Readable Time: {current_date}") - osn_single_flight_data = osn_json['states'] + osn_single_flight_data = osn_json["states"] if osn_single_flight_data is not None: print("Flight Data: ", osn_single_flight_data) - transponder = osn_json['states'][0][0] + transponder = osn_json["states"][0][0] print("Transponder: ", transponder) - callsign = osn_json['states'][0][1] + callsign = osn_json["states"][0][1] print("Callsign: ", callsign) - country = osn_json['states'][0][2] + country = osn_json["states"][0][2] print("Flight Country: ", country) else: print("This flight has no active data or you're polling too fast.") print("You will eventually get temp banned for polling too fast!") - print("Please read: https://openskynetwork.github.io/opensky-api/rest.html#limitations") - print("Public Limits: 10 second max poll rate & 400 weighted calls daily") - print("There is no JSON error, states/all html page will say \"Too many requests\" and the script will fail ") - + print( + "Please read: https://openskynetwork.github.io/opensky-api/rest.html#limitations" + ) + print( + "Public Limits: 10 second max poll rate & 400 weighted calls daily" + ) + print( + 'There is no JSON error, states/all html page will say "Too many requests" and the script will fail ' + ) + print("\nFinished!") - print("Board Uptime: ", time_calc(time.monotonic())) + print("Board Uptime: ", time_calc(time.monotonic())) print("Next Update: ", time_calc(sleep_time)) time.sleep(sleep_time) print("===============================") - + except (ConnectionError, ValueError, NameError) as e: print("OSN Connection Error:", e) print("You are likely banned for 24 hours") print("Next Retry: ", time_calc(sleep_time)) - time.sleep(sleep_time) \ No newline at end of file + time.sleep(sleep_time) From 3092a6e83c9a1bf55d5b55555555db1cb0c9a8fb Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 29 May 2023 04:13:30 -0400 Subject: [PATCH 152/305] pylint too --- examples/requests_api_openskynetwork_public.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/requests_api_openskynetwork_public.py b/examples/requests_api_openskynetwork_public.py index 54c1776..a6e0de4 100644 --- a/examples/requests_api_openskynetwork_public.py +++ b/examples/requests_api_openskynetwork_public.py @@ -124,14 +124,11 @@ def _format_datetime(datetime): print("This flight has no active data or you're polling too fast.") print("You will eventually get temp banned for polling too fast!") print( - "Please read: https://openskynetwork.github.io/opensky-api/rest.html#limitations" + "Read: https://openskynetwork.github.io/opensky-api/rest.html#limitations" ) print( "Public Limits: 10 second max poll rate & 400 weighted calls daily" ) - print( - 'There is no JSON error, states/all html page will say "Too many requests" and the script will fail ' - ) print("\nFinished!") print("Board Uptime: ", time_calc(time.monotonic())) From b642f141825fe0d09e9583f87ecc0029ce92f9e9 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 29 May 2023 06:34:43 -0400 Subject: [PATCH 153/305] Add OpenSkyNetwork Private API Website login required, uses circuitpython_base64 library for encoding. Allows for 4000 calls per day on any active available transponder in their network (about 40K aircraft). This was a somewhat requested API I took on.. and succeeded. --- .../requests_api_openskynetwork_private.py | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 examples/requests_api_openskynetwork_private.py diff --git a/examples/requests_api_openskynetwork_private.py b/examples/requests_api_openskynetwork_private.py new file mode 100644 index 0000000..c4d1bbd --- /dev/null +++ b/examples/requests_api_openskynetwork_private.py @@ -0,0 +1,153 @@ +# SPDX-FileCopyrightText: 2023 DJDevon3 +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 8.1 +# DJDevon3 ESP32-S3 OpenSkyNetwork_Private_API_Example + +import os +import time +import ssl +import json +import wifi +import socketpool +import adafruit_requests +import circuitpython_base64 as base64 + +# OpenSky-Network.org Login required for this API +# REST API: https://openskynetwork.github.io/opensky-api/rest.html +# All active flights JSON: https://opensky-network.org/api/states/all # PICK ONE! :) +# JSON order: transponder, callsign, country +# ACTIVE transpondes only, for multiple "c822af&icao24=cb3993&icao24=c63923" +transponder = "7c6b2d" + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + +# Time between API refreshes +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +# OpenSky-Networks IP bans for too many requests, check rate limit. +# https://openskynetwork.github.io/opensky-api/rest.html#limitations +sleep_time = 1800 + +# this example uses settings.toml for credentials +# timezone offset is in seconds plus or minus GMT +ssid = os.getenv("AP_SSID") +appw = os.getenv("AP_PASSWORD") +osnu = os.getenv("OSN_Username") +osnp = os.getenv("OSN_Password") + +osn_cred = str(osnu) + ":" + str(osnp) +bytes_to_encode = b" " + str(osn_cred) + " " +base64_string = base64.encodebytes(bytes_to_encode) +basepw = repr(base64_string)[2:-1] + +Debug_Auth = False # STREAMER WARNING this will show your credentials! +if Debug_Auth: + osn_cred = str(osnu) + ":" + str(osnp) + bytes_to_encode = b" " + str(osn_cred) + " " + print(repr(bytes_to_encode)) + base64_string = base64.encodebytes(bytes_to_encode) + print(repr(base64_string)[2:-1]) + basepw = repr(base64_string)[2:-1] + print("Decoded Bytes:", str(basepw)) + +# OSN requires your username:password to be base64 encoded +# so technically it's not transmitted in the clear but w/e +osn_header = {"Authorization": "Basic " + str(basepw)} +OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + transponder + + +def time_calc(input_time): + if input_time < 60: + sleep_int = input_time + time_output = f"{sleep_int:.0f} seconds" + elif 60 <= input_time < 3600: + sleep_int = input_time / 60 + time_output = f"{sleep_int:.0f} minutes" + elif 3600 <= input_time < 86400: + sleep_int = input_time / 60 / 60 + time_output = f"{sleep_int:.1f} hours" + elif 86400 <= input_time < 432000: + sleep_int = input_time / 60 / 60 / 24 + time_output = f"{sleep_int:.1f} days" + else: # if > 5 days convert float to int & display whole days + sleep_int = input_time / 60 / 60 / 24 + time_output = f"{sleep_int:.0f} days" + return time_output + + +def _format_datetime(datetime): + return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( + datetime.tm_mon, + datetime.tm_mday, + datetime.tm_year, + datetime.tm_hour, + datetime.tm_min, + datetime.tm_sec, + ) + + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +request = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, appw) + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) +print("Connected!\n") + +while True: + # STREAMER WARNING this will show your credentials! + debug_request = False # Set true to see full request + if debug_request: + print("Full API HEADER: ", str(osn_header)) + print("Full API GET URL: ", OPENSKY_SOURCE) + print("===============================") + + print("\nAttempting to GET OpenSky-Network Data!") + opensky_response = request.get(url=OPENSKY_SOURCE, headers=osn_header).json() + # Print Full JSON to Serial (doesn't show credentials) + debug_response = False # Set true to see full response + if debug_response: + dump_object = json.dumps(opensky_response) + print("JSON Dump: ", dump_object) + + # Print to Serial + osn_debug_keys = True # Set true to print Serial data + if osn_debug_keys: + try: + osn_flight = opensky_response["time"] + print("Current Unix Time: ", osn_flight) + + current_struct_time = time.localtime(osn_flight) + current_date = "{}".format(_format_datetime(current_struct_time)) + print(f"Unix to Readable Time: {current_date}") + + # Current flight data for single callsign (right now) + osn_single_flight_data = opensky_response["states"] + + if osn_single_flight_data is not None: + print("Flight Data: ", osn_single_flight_data) + transponder = opensky_response["states"][0][0] + print("Transponder: ", transponder) + callsign = opensky_response["states"][0][1] + print("Callsign: ", callsign) + country = opensky_response["states"][0][2] + print("Flight Country: ", country) + else: + print("Flight has no active data or you're polling too fast.") + + print("\nFinished!") + print("Board Uptime: ", time_calc(time.monotonic())) + print("Next Update: ", time_calc(sleep_time)) + time.sleep(sleep_time) + print("===============================") + + except (ConnectionError, ValueError, NameError) as e: + print("OSN Connection Error:", e) + print("You are likely banned for 24 hours") + print("Next Retry: ", time_calc(sleep_time)) + time.sleep(sleep_time) From 02b5f85d421df35c4dc367f05ef7115852547eef Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 29 May 2023 06:39:08 -0400 Subject: [PATCH 154/305] ok sometimes pylint is very helpful rearranged imports to make pylint happy --- examples/requests_api_openskynetwork_private.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/requests_api_openskynetwork_private.py b/examples/requests_api_openskynetwork_private.py index c4d1bbd..47e65d4 100644 --- a/examples/requests_api_openskynetwork_private.py +++ b/examples/requests_api_openskynetwork_private.py @@ -9,8 +9,8 @@ import json import wifi import socketpool -import adafruit_requests import circuitpython_base64 as base64 +import adafruit_requests # OpenSky-Network.org Login required for this API # REST API: https://openskynetwork.github.io/opensky-api/rest.html From f1492972a368b20ba4f64236e338578a62326ea9 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 29 May 2023 21:46:23 -0400 Subject: [PATCH 155/305] added OpenSky Geographic Area Shows all flight traffic in a lat/lon area. This one is really neat! --- ...equests_api_openskynetwork_private_area.py | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 examples/requests_api_openskynetwork_private_area.py diff --git a/examples/requests_api_openskynetwork_private_area.py b/examples/requests_api_openskynetwork_private_area.py new file mode 100644 index 0000000..fa69a3c --- /dev/null +++ b/examples/requests_api_openskynetwork_private_area.py @@ -0,0 +1,187 @@ +# SPDX-FileCopyrightText: 2023 DJDevon3 +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 8.1 +# DJDevon3 ESP32-S3 OpenSkyNetwork_Private__Area_API_Example + +import os +import time +import ssl +import json +import wifi +import socketpool +import circuitpython_base64 as base64 +import adafruit_requests + +# OpenSky-Network.org Login required for this API +# REST API: https://openskynetwork.github.io/opensky-api/rest.html +# All active flights JSON: https://opensky-network.org/api/states/all # PICK ONE! :) + +# Retrieves all traffic within a geographic area (Orlando example) +latmin = "27.22" # east bounding box +latmax = "28.8" # west bounding box +lonmin = "-81.46" # north bounding box +lonmax = "-80.40" # south bounding box + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + +# Time between API refreshes +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +# OpenSky-Networks IP bans for too many requests, check rate limit. +# https://openskynetwork.github.io/opensky-api/rest.html#limitations +sleep_time = 1800 + +# this example uses settings.toml for credentials +# No token required, only website login +ssid = os.getenv("AP_SSID") +appw = os.getenv("AP_PASSWORD") +osnu = os.getenv("OSN_Username") +osnp = os.getenv("OSN_Password") + +osn_cred = str(osnu) + ":" + str(osnp) +bytes_to_encode = b" " + str(osn_cred) + " " +base64_string = base64.encodebytes(bytes_to_encode) +basepw = repr(base64_string)[2:-1] + +Debug_Auth = False # STREAMER WARNING this will show your credentials! +if Debug_Auth: + osn_cred = str(osnu) + ":" + str(osnp) + bytes_to_encode = b" " + str(osn_cred) + " " + print(repr(bytes_to_encode)) + base64_string = base64.encodebytes(bytes_to_encode) + print(repr(base64_string)[2:-1]) + basepw = repr(base64_string)[2:-1] + print("Decoded Bytes:", str(basepw)) + +# OSN requires your username:password to be base64 encoded +# so technically it's not transmitted in the clear but w/e +osn_header = {"Authorization": "Basic " + str(basepw)} + +# Example of all traffic over Florida, geographic areas cost less per call. +# https://opensky-network.org/api/states/all?lamin=25.21&lomin=-84.36&lamax=30.0&lomax=-78.40 +OPENSKY_SOURCE = ( + "https://opensky-network.org/api/states/all?" + + "lamin=" + + latmin + + "&lomin=" + + lonmin + + "&lamax=" + + latmax + + "&lomax=" + + lonmax +) + + +def time_calc(input_time): + if input_time < 60: + sleep_int = input_time + time_output = f"{sleep_int:.0f} seconds" + elif 60 <= input_time < 3600: + sleep_int = input_time / 60 + time_output = f"{sleep_int:.0f} minutes" + elif 3600 <= input_time < 86400: + sleep_int = input_time / 60 / 60 + time_output = f"{sleep_int:.1f} hours" + elif 86400 <= input_time < 432000: + sleep_int = input_time / 60 / 60 / 24 + time_output = f"{sleep_int:.1f} days" + else: # if > 5 days convert float to int & display whole days + sleep_int = input_time / 60 / 60 / 24 + time_output = f"{sleep_int:.0f} days" + return time_output + + +def _format_datetime(datetime): + return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( + datetime.tm_mon, + datetime.tm_mday, + datetime.tm_year, + datetime.tm_hour, + datetime.tm_min, + datetime.tm_sec, + ) + + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +request = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, appw) + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) +print("Connected!\n") + +while True: + # STREAMER WARNING this will show your credentials! + debug_request = False # Set true to see full request + if debug_request: + print("Full API HEADER: ", str(osn_header)) + print("Full API GET URL: ", OPENSKY_SOURCE) + print("===============================") + + print("\nAttempting to GET OpenSky-Network Data!") + opensky_response = request.get(url=OPENSKY_SOURCE, headers=osn_header).json() + # Print Full JSON to Serial (doesn't show credentials) + debug_response = False # Set true to see full response + if debug_response: + dump_object = json.dumps(opensky_response) + print("JSON Dump: ", dump_object) + + # Print to Serial + osn_debug_keys = True # Set true to print Serial data + if osn_debug_keys: + try: + osn_flight = opensky_response["time"] + print("Current Unix Time: ", osn_flight) + + current_struct_time = time.localtime(osn_flight) + current_date = "{}".format(_format_datetime(current_struct_time)) + print(f"Unix to Readable Time: {current_date}") + + # Current flight data for single callsign (right now) + osn_all_flights = opensky_response["states"] + + if osn_all_flights is not None: + # print("Flight Data: ", osn_all_flights) + for flights in osn_all_flights: + osn_t = f"Trans:{flights[0]} " + osn_c = f"Sign:{flights[1]} " + osn_o = f"Origin:{flights[2]} " + osn_tm = f"Time:{flights[3]} " + osn_l = f"Last:{flights[4]} " + osn_lo = f"Lon:{flights[5]} " + osn_la = f"Lat:{flights[6]} " + osn_ba = f"BaroAlt:{flights[7]} " + osn_g = f"Ground:{flights[8]} " + osn_v = f"Vel:{flights[9]} " + osn_h = f"Head:{flights[10]} " + osn_vr = f"VertRate:{flights[11]} " + osn_s = f"Sens:{flights[12]} " + osn_ga = f"GeoAlt:{flights[13]} " + osn_sq = f"Squawk:{flights[14]} " + osn_pr = f"Task:{flights[15]} " + osn_ps = f"PosSys:{flights[16]} " + osn_ca = f"Cat:{flights[16]} " + # This is just because pylint complains about long lines + string1 = f"{osn_t}{osn_c}{osn_o}{osn_tm}{osn_l}{osn_lo}" + string2 = f"{osn_la}{osn_ba}{osn_g}{osn_v}{osn_h}{osn_vr}" + string3 = f"{osn_s}{osn_ga}{osn_sq}{osn_pr}{osn_ps}{osn_ca}" + print(f"{string1}{string2}{string3}") + else: + print("Flight has no active data or you're polling too fast.") + + print("\nFinished!") + print("Board Uptime: ", time_calc(time.monotonic())) + print("Next Update: ", time_calc(sleep_time)) + time.sleep(sleep_time) + print("===============================") + + except (ConnectionError, ValueError, NameError) as e: + print("OSN Connection Error:", e) + print("You are likely banned for 24 hours") + print("Next Retry: ", time_calc(sleep_time)) + time.sleep(sleep_time) From a30c5a5828b2f447387d8635021f9736822b7290 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Sat, 10 Jun 2023 17:43:44 -0500 Subject: [PATCH 156/305] remove backwards compatibility --- adafruit_requests.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index a0276f0..f253b8c 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -188,8 +188,6 @@ def __init__(self, sock: SocketType, session: Optional["Session"] = None) -> Non self._remaining = None self._chunked = False - self._backwards_compatible = not hasattr(sock, "recv_into") - http = self._readto(b" ") if not http: if session: @@ -217,12 +215,6 @@ def __exit__( self.close() def _recv_into(self, buf: bytearray, size: int = 0) -> int: - if self._backwards_compatible: - size = len(buf) if size == 0 else size - b = self.socket.recv(size) - read_size = len(b) - buf[:read_size] = b - return read_size return cast("SupportsRecvInto", self.socket).recv_into(buf, size) def _readto(self, stop: bytes) -> bytearray: @@ -763,6 +755,7 @@ def __init__(self, socket: CircuitPythonSocketType, tls_mode: int) -> None: self.send = socket.send self.recv = socket.recv self.close = socket.close + self.recv_into = socket.recv_into def connect(self, address: Tuple[str, int]) -> None: """connect wrapper to add non-standard mode parameter""" From 722ab305749a3dce55674bcf61f6d8a6857d4d18 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 13 Jun 2023 11:18:34 -0400 Subject: [PATCH 157/305] Fixing reviewer requested changes Removed unused timezone vars and assorted script clean up. Changed var for base64cred to be more obvious that a base64 encode is required in the request header. --- .../requests_api_openskynetwork_private.py | 34 ++++++++---------- ...equests_api_openskynetwork_private_area.py | 36 ++++++++----------- .../requests_api_openskynetwork_public.py | 31 ++++++---------- 3 files changed, 40 insertions(+), 61 deletions(-) diff --git a/examples/requests_api_openskynetwork_private.py b/examples/requests_api_openskynetwork_private.py index 47e65d4..e51b390 100644 --- a/examples/requests_api_openskynetwork_private.py +++ b/examples/requests_api_openskynetwork_private.py @@ -38,7 +38,7 @@ osn_cred = str(osnu) + ":" + str(osnp) bytes_to_encode = b" " + str(osn_cred) + " " base64_string = base64.encodebytes(bytes_to_encode) -basepw = repr(base64_string)[2:-1] +base64cred = repr(base64_string)[2:-1] Debug_Auth = False # STREAMER WARNING this will show your credentials! if Debug_Auth: @@ -47,16 +47,17 @@ print(repr(bytes_to_encode)) base64_string = base64.encodebytes(bytes_to_encode) print(repr(base64_string)[2:-1]) - basepw = repr(base64_string)[2:-1] - print("Decoded Bytes:", str(basepw)) + base64cred = repr(base64_string)[2:-1] + print("Decoded Bytes:", str(base64cred)) -# OSN requires your username:password to be base64 encoded -# so technically it's not transmitted in the clear but w/e -osn_header = {"Authorization": "Basic " + str(basepw)} +# Requests URL - icao24 is their endpoint required for a transponder +# example https://opensky-network.org/api/states/all?icao24=a808c5 +# OSN private requires your username:password to be base64 encoded +osn_header = {"Authorization": "Basic " + str(base64cred)} OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + transponder - -def time_calc(input_time): +# Converts seconds to human readable minutes/hours/days +def time_calc(input_time): # input_time in seconds if input_time < 60: sleep_int = input_time time_output = f"{sleep_int:.0f} seconds" @@ -66,15 +67,11 @@ def time_calc(input_time): elif 3600 <= input_time < 86400: sleep_int = input_time / 60 / 60 time_output = f"{sleep_int:.1f} hours" - elif 86400 <= input_time < 432000: + else: sleep_int = input_time / 60 / 60 / 24 time_output = f"{sleep_int:.1f} days" - else: # if > 5 days convert float to int & display whole days - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.0f} days" return time_output - def _format_datetime(datetime): return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( datetime.tm_mon, @@ -85,7 +82,6 @@ def _format_datetime(datetime): datetime.tm_sec, ) - # Connect to Wi-Fi print("\n===============================") print("Connecting to WiFi...") @@ -101,7 +97,7 @@ def _format_datetime(datetime): while True: # STREAMER WARNING this will show your credentials! - debug_request = False # Set true to see full request + debug_request = False # Set True to see full request if debug_request: print("Full API HEADER: ", str(osn_header)) print("Full API GET URL: ", OPENSKY_SOURCE) @@ -109,14 +105,15 @@ def _format_datetime(datetime): print("\nAttempting to GET OpenSky-Network Data!") opensky_response = request.get(url=OPENSKY_SOURCE, headers=osn_header).json() + # Print Full JSON to Serial (doesn't show credentials) - debug_response = False # Set true to see full response + debug_response = False # Set True to see full response if debug_response: dump_object = json.dumps(opensky_response) print("JSON Dump: ", dump_object) - # Print to Serial - osn_debug_keys = True # Set true to print Serial data + # Key:Value Serial Debug (doesn't show credentials) + osn_debug_keys = True # Set True to print Serial data if osn_debug_keys: try: osn_flight = opensky_response["time"] @@ -148,6 +145,5 @@ def _format_datetime(datetime): except (ConnectionError, ValueError, NameError) as e: print("OSN Connection Error:", e) - print("You are likely banned for 24 hours") print("Next Retry: ", time_calc(sleep_time)) time.sleep(sleep_time) diff --git a/examples/requests_api_openskynetwork_private_area.py b/examples/requests_api_openskynetwork_private_area.py index fa69a3c..49185b3 100644 --- a/examples/requests_api_openskynetwork_private_area.py +++ b/examples/requests_api_openskynetwork_private_area.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2023 DJDevon3 # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.1 -# DJDevon3 ESP32-S3 OpenSkyNetwork_Private__Area_API_Example +# DJDevon3 ESP32-S3 OpenSkyNetwork_Private_Area_API_Example import os import time @@ -12,9 +12,8 @@ import circuitpython_base64 as base64 import adafruit_requests -# OpenSky-Network.org Login required for this API +# OpenSky-Network.org Website Login required for this API # REST API: https://openskynetwork.github.io/opensky-api/rest.html -# All active flights JSON: https://opensky-network.org/api/states/all # PICK ONE! :) # Retrieves all traffic within a geographic area (Orlando example) latmin = "27.22" # east bounding box @@ -41,7 +40,7 @@ osn_cred = str(osnu) + ":" + str(osnp) bytes_to_encode = b" " + str(osn_cred) + " " base64_string = base64.encodebytes(bytes_to_encode) -basepw = repr(base64_string)[2:-1] +base64cred = repr(base64_string)[2:-1] Debug_Auth = False # STREAMER WARNING this will show your credentials! if Debug_Auth: @@ -50,14 +49,14 @@ print(repr(bytes_to_encode)) base64_string = base64.encodebytes(bytes_to_encode) print(repr(base64_string)[2:-1]) - basepw = repr(base64_string)[2:-1] - print("Decoded Bytes:", str(basepw)) + base64cred = repr(base64_string)[2:-1] + print("Decoded Bytes:", str(base64cred)) # OSN requires your username:password to be base64 encoded # so technically it's not transmitted in the clear but w/e -osn_header = {"Authorization": "Basic " + str(basepw)} +osn_header = {"Authorization": "Basic " + str(base64cred)} -# Example of all traffic over Florida, geographic areas cost less per call. +# Example request of all traffic over Florida, geographic areas cost less per call. # https://opensky-network.org/api/states/all?lamin=25.21&lomin=-84.36&lamax=30.0&lomax=-78.40 OPENSKY_SOURCE = ( "https://opensky-network.org/api/states/all?" @@ -71,8 +70,8 @@ + lonmax ) - -def time_calc(input_time): +# Converts seconds to human readable minutes/hours/days +def time_calc(input_time): # input_time in seconds if input_time < 60: sleep_int = input_time time_output = f"{sleep_int:.0f} seconds" @@ -82,15 +81,11 @@ def time_calc(input_time): elif 3600 <= input_time < 86400: sleep_int = input_time / 60 / 60 time_output = f"{sleep_int:.1f} hours" - elif 86400 <= input_time < 432000: + else: sleep_int = input_time / 60 / 60 / 24 time_output = f"{sleep_int:.1f} days" - else: # if > 5 days convert float to int & display whole days - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.0f} days" return time_output - def _format_datetime(datetime): return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( datetime.tm_mon, @@ -101,7 +96,6 @@ def _format_datetime(datetime): datetime.tm_sec, ) - # Connect to Wi-Fi print("\n===============================") print("Connecting to WiFi...") @@ -117,7 +111,7 @@ def _format_datetime(datetime): while True: # STREAMER WARNING this will show your credentials! - debug_request = False # Set true to see full request + debug_request = False # Set True to see full request if debug_request: print("Full API HEADER: ", str(osn_header)) print("Full API GET URL: ", OPENSKY_SOURCE) @@ -125,14 +119,15 @@ def _format_datetime(datetime): print("\nAttempting to GET OpenSky-Network Data!") opensky_response = request.get(url=OPENSKY_SOURCE, headers=osn_header).json() + # Print Full JSON to Serial (doesn't show credentials) - debug_response = False # Set true to see full response + debug_response = False # Set True to see full response if debug_response: dump_object = json.dumps(opensky_response) print("JSON Dump: ", dump_object) - # Print to Serial - osn_debug_keys = True # Set true to print Serial data + # Key:Value Serial Debug (doesn't show credentials) + osn_debug_keys = True # Set True to print Serial data if osn_debug_keys: try: osn_flight = opensky_response["time"] @@ -182,6 +177,5 @@ def _format_datetime(datetime): except (ConnectionError, ValueError, NameError) as e: print("OSN Connection Error:", e) - print("You are likely banned for 24 hours") print("Next Retry: ", time_calc(sleep_time)) time.sleep(sleep_time) diff --git a/examples/requests_api_openskynetwork_public.py b/examples/requests_api_openskynetwork_public.py index a6e0de4..41b4a18 100644 --- a/examples/requests_api_openskynetwork_public.py +++ b/examples/requests_api_openskynetwork_public.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2023 DJDevon3 # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.1 -"""DJDevon3 Adafruit Feather ESP32-S3 OpenSkyNetwork_API_Example""" +# Adafruit Feather ESP32-S3 OpenSkyNetwork_Public_API_Example import os import time import ssl @@ -10,11 +10,11 @@ import socketpool import adafruit_requests -# No developer account necessary for this API +# No login necessary for Public API. Drastically reduced daily limit vs Private # OpenSky-Networks.org REST API: https://openskynetwork.github.io/opensky-api/rest.html -# All active flights JSON: https://opensky-network.org/api/states/all +# All active flights JSON: https://opensky-network.org/api/states/all PICK ONE! # JSON order: transponder, callsign, country -# ACTIVE transponder you want data from +# ACTIVE transpondes only, for multiple "c822af&icao24=cb3993&icao24=c63923" transponder = "ab1644" # Initialize WiFi Pool (There can be only 1 pool & top of script) @@ -22,24 +22,20 @@ # Time between API refreshes # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour -# OpenSky-Networks will temp ban your IP for too many requests, there is a public rate limit. +# OpenSky-Networks will temp ban your IP for too many daily requests. # https://openskynetwork.github.io/opensky-api/rest.html#limitations sleep_time = 1800 -# this example uses settings.toml for credentials -# timezone offset is in seconds plus or minus GMT +# Wifi credentials pulled from settings.toml ssid = os.getenv("AP_SSID") appw = os.getenv("AP_PASSWORD") -timezone = os.getenv("timezone") -tz_offset_seconds = os.getenv("timezone_offset") -# https://opensky-network.org/api/states/all +# Requests URL - icao24 is their endpoint required for a transponder # example https://opensky-network.org/api/states/all?icao24=a808c5 -# You can use states/own to pull your owned craft data without rate limit. OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + transponder - -def time_calc(input_time): +# Converts seconds to human readable minutes/hours/days +def time_calc(input_time): # input_time in seconds if input_time < 60: sleep_int = input_time time_output = f"{sleep_int:.0f} seconds" @@ -49,15 +45,11 @@ def time_calc(input_time): elif 3600 <= input_time < 86400: sleep_int = input_time / 60 / 60 time_output = f"{sleep_int:.1f} hours" - elif 86400 <= input_time < 432000: + else: sleep_int = input_time / 60 / 60 / 24 time_output = f"{sleep_int:.1f} days" - else: # if > 5 days convert float to int & display whole days - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.0f} days" return time_output - def _format_datetime(datetime): return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( datetime.tm_mon, @@ -68,7 +60,6 @@ def _format_datetime(datetime): datetime.tm_sec, ) - # Connect to Wi-Fi print("\n===============================") print("Connecting to WiFi...") @@ -122,7 +113,6 @@ def _format_datetime(datetime): print("Flight Country: ", country) else: print("This flight has no active data or you're polling too fast.") - print("You will eventually get temp banned for polling too fast!") print( "Read: https://openskynetwork.github.io/opensky-api/rest.html#limitations" ) @@ -138,6 +128,5 @@ def _format_datetime(datetime): except (ConnectionError, ValueError, NameError) as e: print("OSN Connection Error:", e) - print("You are likely banned for 24 hours") print("Next Retry: ", time_calc(sleep_time)) time.sleep(sleep_time) From 72a3536df845314ae05bf7be1b0b640c629f879f Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 13 Jun 2023 14:19:51 -0400 Subject: [PATCH 158/305] ran black again --- examples/requests_api_openskynetwork_private.py | 5 ++++- examples/requests_api_openskynetwork_private_area.py | 5 ++++- examples/requests_api_openskynetwork_public.py | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/examples/requests_api_openskynetwork_private.py b/examples/requests_api_openskynetwork_private.py index e51b390..a69eba8 100644 --- a/examples/requests_api_openskynetwork_private.py +++ b/examples/requests_api_openskynetwork_private.py @@ -56,6 +56,7 @@ osn_header = {"Authorization": "Basic " + str(base64cred)} OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + transponder + # Converts seconds to human readable minutes/hours/days def time_calc(input_time): # input_time in seconds if input_time < 60: @@ -72,6 +73,7 @@ def time_calc(input_time): # input_time in seconds time_output = f"{sleep_int:.1f} days" return time_output + def _format_datetime(datetime): return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( datetime.tm_mon, @@ -82,6 +84,7 @@ def _format_datetime(datetime): datetime.tm_sec, ) + # Connect to Wi-Fi print("\n===============================") print("Connecting to WiFi...") @@ -105,7 +108,7 @@ def _format_datetime(datetime): print("\nAttempting to GET OpenSky-Network Data!") opensky_response = request.get(url=OPENSKY_SOURCE, headers=osn_header).json() - + # Print Full JSON to Serial (doesn't show credentials) debug_response = False # Set True to see full response if debug_response: diff --git a/examples/requests_api_openskynetwork_private_area.py b/examples/requests_api_openskynetwork_private_area.py index 49185b3..e474e48 100644 --- a/examples/requests_api_openskynetwork_private_area.py +++ b/examples/requests_api_openskynetwork_private_area.py @@ -70,6 +70,7 @@ + lonmax ) + # Converts seconds to human readable minutes/hours/days def time_calc(input_time): # input_time in seconds if input_time < 60: @@ -86,6 +87,7 @@ def time_calc(input_time): # input_time in seconds time_output = f"{sleep_int:.1f} days" return time_output + def _format_datetime(datetime): return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( datetime.tm_mon, @@ -96,6 +98,7 @@ def _format_datetime(datetime): datetime.tm_sec, ) + # Connect to Wi-Fi print("\n===============================") print("Connecting to WiFi...") @@ -119,7 +122,7 @@ def _format_datetime(datetime): print("\nAttempting to GET OpenSky-Network Data!") opensky_response = request.get(url=OPENSKY_SOURCE, headers=osn_header).json() - + # Print Full JSON to Serial (doesn't show credentials) debug_response = False # Set True to see full response if debug_response: diff --git a/examples/requests_api_openskynetwork_public.py b/examples/requests_api_openskynetwork_public.py index 41b4a18..e114cb7 100644 --- a/examples/requests_api_openskynetwork_public.py +++ b/examples/requests_api_openskynetwork_public.py @@ -34,6 +34,7 @@ # example https://opensky-network.org/api/states/all?icao24=a808c5 OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + transponder + # Converts seconds to human readable minutes/hours/days def time_calc(input_time): # input_time in seconds if input_time < 60: @@ -50,6 +51,7 @@ def time_calc(input_time): # input_time in seconds time_output = f"{sleep_int:.1f} days" return time_output + def _format_datetime(datetime): return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( datetime.tm_mon, @@ -60,6 +62,7 @@ def _format_datetime(datetime): datetime.tm_sec, ) + # Connect to Wi-Fi print("\n===============================") print("Connecting to WiFi...") From 330080e63bf7c3c65f1bfb9f9db09206ef2aa537 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 22 Jun 2023 23:17:11 -0400 Subject: [PATCH 159/305] update Discord API Example to Settings.toml One API at a time. This should not affect any learn guides. This is the web scrape example that requires you to get the auth key from the request header with a browser developer tool. Still works fine. --- examples/requests_api_discord.py | 80 ++++++++++++++++---------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/examples/requests_api_discord.py b/examples/requests_api_discord.py index 501e09e..6f2bceb 100644 --- a/examples/requests_api_discord.py +++ b/examples/requests_api_discord.py @@ -1,8 +1,8 @@ -# SPDX-FileCopyrightText: 2022 DJDevon3 for Adafruit Industries +# SPDX-FileCopyrightText: 2023 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.0 -"""DJDevon3 Adafruit Feather ESP32-S2 Discord_API_Example""" -import gc +# Coded for Circuit Python 8.2 +# DJDevon3 Adafruit Feather ESP32-S3 Discord API Example +import os import time import ssl import json @@ -14,36 +14,38 @@ # WEB SCRAPE authorization key required. Visit URL below. # Learn how: https://github.com/lorenz234/Discord-Data-Scraping -# Ensure this is in secrets.py or .env -# "Discord_Authorization": "Discord Authorization from browser console" +# Ensure this is in settings.toml +# "Discord_Authorization": "Request Header Auth here" + +# Uses settings.toml for credentials +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +Discord_Auth = os.getenv("Discord_Authorization") # Initialize WiFi Pool (There can be only 1 pool & top of script) pool = socketpool.SocketPool(wifi.radio) -# Time between API refreshes +# API Polling Rate # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour sleep_time = 900 -try: - from secrets import secrets -except ImportError: - print("Secrets File Import Error") - raise - -if sleep_time < 60: - sleep_time_conversion = "seconds" - sleep_int = sleep_time -elif 60 <= sleep_time < 3600: - sleep_int = sleep_time / 60 - sleep_time_conversion = "minutes" -elif 3600 <= sleep_time < 86400: - sleep_int = sleep_time / 60 / 60 - sleep_time_conversion = "hours" -else: - sleep_int = sleep_time / 60 / 60 / 24 - sleep_time_conversion = "days" +# Converts seconds to human readable minutes/hours/days +def time_calc(input_time): # input_time in seconds + if input_time < 60: + sleep_int = input_time + time_output = f"{sleep_int:.0f} seconds" + elif 60 <= input_time < 3600: + sleep_int = input_time / 60 + time_output = f"{sleep_int:.0f} minutes" + elif 3600 <= input_time < 86400: + sleep_int = input_time / 60 / 60 + time_output = f"{sleep_int:.1f} hours" + else: + sleep_int = input_time / 60 / 60 / 24 + time_output = f"{sleep_int:.1f} days" + return time_output -discord_header = {"Authorization": "" + secrets["Discord_Authorization"]} +discord_header = {"Authorization": "" + Discord_Auth} ADA_SOURCE = ( "https://discord.com/api/v10/guilds/" + "327254708534116352" # Adafruit Discord ID @@ -56,21 +58,20 @@ requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(secrets["ssid"], secrets["password"]) + wifi.radio.connect(ssid, appw) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") time.sleep(10) - gc.collect() -print("Connected!\n") +print("Connected!✅") while True: try: print( - "\nAttempting to GET DISCORD PREVIEW!" + "\nAttempting to GET Discord Data!" ) # -------------------------------- - # Print Request to Serial - debug_request = False # Set true to see full request + # STREAMER WARNING this will show your credentials! + debug_request = False # Set True to see full request if debug_request: print("Full API GET URL: ", ADA_SOURCE) print("===============================") @@ -81,13 +82,13 @@ print("Retrying in 10 seconds") # Print Full JSON to Serial - discord_debug_response = False # Change to true to see full response + discord_debug_response = False # Set True to see full response if discord_debug_response: ada_discord_dump_object = json.dumps(ada_res) print("JSON Dump: ", ada_discord_dump_object) # Print keys to Serial - discord_debug_keys = True # Set to True to print Serial data + discord_debug_keys = True # Set True to print Serial data if discord_debug_keys: ada_discord_all_members = ada_res["approximate_member_count"] print("Members: ", ada_discord_all_members) @@ -95,15 +96,14 @@ ada_discord_all_members_online = ada_res["approximate_presence_count"] print("Online: ", ada_discord_all_members_online) - print("Monotonic: ", time.monotonic()) - - print("\nFinished!") - print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) + print("Finished ✅") + print("Board Uptime: ", time_calc(time.monotonic())) + print("Next Update: ", time_calc(sleep_time)) print("===============================") - gc.collect() - except (ValueError, RuntimeError) as e: + except (ConnectionError, ValueError, NameError) as e: print("Failed to get data, retrying\n", e) time.sleep(60) continue time.sleep(sleep_time) + From b62fc368643a63e3c1fe65ef588eb5ee69588230 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 22 Jun 2023 23:28:05 -0400 Subject: [PATCH 160/305] ran black --- examples/requests_api_discord.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/requests_api_discord.py b/examples/requests_api_discord.py index 6f2bceb..b6e0348 100644 --- a/examples/requests_api_discord.py +++ b/examples/requests_api_discord.py @@ -29,6 +29,7 @@ # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour sleep_time = 900 + # Converts seconds to human readable minutes/hours/days def time_calc(input_time): # input_time in seconds if input_time < 60: @@ -45,6 +46,7 @@ def time_calc(input_time): # input_time in seconds time_output = f"{sleep_int:.1f} days" return time_output + discord_header = {"Authorization": "" + Discord_Auth} ADA_SOURCE = ( "https://discord.com/api/v10/guilds/" @@ -67,9 +69,7 @@ def time_calc(input_time): # input_time in seconds while True: try: - print( - "\nAttempting to GET Discord Data!" - ) # -------------------------------- + print("\nAttempting to GET Discord Data!") # -------------------------------- # STREAMER WARNING this will show your credentials! debug_request = False # Set True to see full request if debug_request: @@ -106,4 +106,3 @@ def time_calc(input_time): # input_time in seconds time.sleep(60) continue time.sleep(sleep_time) - From b151039d16ee9e44398cdebe470bb4c67b34180d Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 26 Jun 2023 16:35:16 -0500 Subject: [PATCH 161/305] remove legacy tests --- tests/legacy_mocket.py | 47 ---------- tests/legacy_test.py | 208 ----------------------------------------- 2 files changed, 255 deletions(-) delete mode 100644 tests/legacy_mocket.py delete mode 100644 tests/legacy_test.py diff --git a/tests/legacy_mocket.py b/tests/legacy_mocket.py deleted file mode 100644 index 17bf209..0000000 --- a/tests/legacy_mocket.py +++ /dev/null @@ -1,47 +0,0 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -""" Mock for Legacy Socket """ - -from unittest import mock - -SOCK_STREAM = 0 - -set_interface = mock.Mock() -interface = mock.MagicMock() -getaddrinfo = mock.Mock() -socket = mock.Mock() - - -class Mocket: # pylint: disable=too-few-public-methods - """Mock Socket""" - - def __init__(self, response): - self.settimeout = mock.Mock() - self.close = mock.Mock() - self.connect = mock.Mock() - self.send = mock.Mock(side_effect=self._send) - self.readline = mock.Mock(side_effect=self._readline) - self.recv = mock.Mock(side_effect=self._recv) - self.fail_next_send = False - self._response = response - self._position = 0 - - def _send(self, data): # pylint: disable=unused-argument - if self.fail_next_send: - self.fail_next_send = False - raise RuntimeError("Send failed") - - def _readline(self): - i = self._response.find(b"\r\n", self._position) - response = self._response[self._position : i + 2] - self._position = i + 2 - return response - - def _recv(self, count): - end = self._position + count - response = self._response[self._position : end] - self._position = end - print(response) - return response diff --git a/tests/legacy_test.py b/tests/legacy_test.py deleted file mode 100644 index cba604d..0000000 --- a/tests/legacy_test.py +++ /dev/null @@ -1,208 +0,0 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -""" Legacy Tests """ - -from unittest import mock -import json -import legacy_mocket as mocket -import adafruit_requests - -IP = "1.2.3.4" -HOST = "httpbin.org" -RESPONSE = {"Date": "July 25, 2019"} -ENCODED = json.dumps(RESPONSE).encode("utf-8") -HEADERS = "HTTP/1.0 200 OK\r\nContent-Length: {}\r\n\r\n".format(len(ENCODED)).encode( - "utf-8" -) - - -def test_get_json(): - mocket.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(HEADERS + ENCODED) - mocket.socket.return_value = sock - - adafruit_requests.set_socket(mocket, mocket.interface) - response = adafruit_requests.get("http://" + HOST + "/get") - - sock.connect.assert_called_once_with((IP, 80)) - assert response.json() == RESPONSE - response.close() - - -def test_tls_mode(): - mocket.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(HEADERS + ENCODED) - mocket.socket.return_value = sock - - adafruit_requests.set_socket(mocket, mocket.interface) - response = adafruit_requests.get("https://" + HOST + "/get") - - sock.connect.assert_called_once_with((HOST, 443), mocket.interface.TLS_MODE) - assert response.json() == RESPONSE - response.close() - - -def test_post_string(): - mocket.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(HEADERS + ENCODED) - mocket.socket.return_value = sock - - adafruit_requests.set_socket(mocket, mocket.interface) - data = "31F" - response = adafruit_requests.post("http://" + HOST + "/post", data=data) - sock.connect.assert_called_once_with((IP, 80)) - sock.send.assert_called_with(b"31F") - response.close() - - -def test_second_tls_send_fails(): - mocket.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(HEADERS + ENCODED) - sock2 = mocket.Mocket(HEADERS + ENCODED) - mocket.socket.call_count = 0 # Reset call count - mocket.socket.side_effect = [sock, sock2] - - adafruit_requests.set_socket(mocket, mocket.interface) - response = adafruit_requests.get("https://" + HOST + "/testwifi/index.html") - - sock.send.assert_has_calls( - [ - mock.call(b"testwifi/index.html"), - ] - ) - - sock.send.assert_has_calls( - [ - mock.call(b"Host: "), - mock.call(HOST.encode("utf-8")), - mock.call(b"\r\n"), - ] - ) - assert response.text == str(ENCODED, "utf-8") - - sock.fail_next_send = True - adafruit_requests.get("https://" + HOST + "/get2") - - sock.connect.assert_called_once_with((HOST, 443), mocket.interface.TLS_MODE) - sock2.connect.assert_called_once_with((HOST, 443), mocket.interface.TLS_MODE) - # Make sure that the socket is closed after send fails. - sock.close.assert_called_once() - assert sock2.close.call_count == 0 - assert mocket.socket.call_count == 2 - - -def test_second_send_fails(): - mocket.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(HEADERS + ENCODED) - sock2 = mocket.Mocket(HEADERS + ENCODED) - mocket.socket.call_count = 0 # Reset call count - mocket.socket.side_effect = [sock, sock2] - - adafruit_requests.set_socket(mocket, mocket.interface) - response = adafruit_requests.get("http://" + HOST + "/testwifi/index.html") - - sock.send.assert_has_calls( - [ - mock.call(b"testwifi/index.html"), - ] - ) - - sock.send.assert_has_calls( - [ - mock.call(b"Host: "), - mock.call(HOST.encode("utf-8")), - mock.call(b"\r\n"), - ] - ) - assert response.text == str(ENCODED, "utf-8") - - sock.fail_next_send = True - adafruit_requests.get("http://" + HOST + "/get2") - - sock.connect.assert_called_once_with((IP, 80)) - sock2.connect.assert_called_once_with((IP, 80)) - # Make sure that the socket is closed after send fails. - sock.close.assert_called_once() - assert sock2.close.call_count == 0 - assert mocket.socket.call_count == 2 - - -def test_first_read_fails(): - mocket.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(b"") - sock2 = mocket.Mocket(HEADERS + ENCODED) - mocket.socket.call_count = 0 # Reset call count - mocket.socket.side_effect = [sock, sock2] - - adafruit_requests.set_socket(mocket, mocket.interface) - adafruit_requests.get("http://" + HOST + "/testwifi/index.html") - - sock.send.assert_has_calls( - [ - mock.call(b"testwifi/index.html"), - ] - ) - - sock.send.assert_has_calls( - [ - mock.call(b"Host: "), - mock.call(HOST.encode("utf-8")), - mock.call(b"\r\n"), - ] - ) - - sock2.send.assert_has_calls( - [ - mock.call(b"Host: "), - mock.call(HOST.encode("utf-8")), - mock.call(b"\r\n"), - ] - ) - - sock.connect.assert_called_once_with((IP, 80)) - sock2.connect.assert_called_once_with((IP, 80)) - # Make sure that the socket is closed after the first receive fails. - sock.close.assert_called_once() - assert mocket.socket.call_count == 2 - - -def test_second_tls_connect_fails(): - mocket.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(HEADERS + ENCODED) - sock2 = mocket.Mocket(HEADERS + ENCODED) - sock3 = mocket.Mocket(HEADERS + ENCODED) - mocket.socket.call_count = 0 # Reset call count - mocket.socket.side_effect = [sock, sock2, sock3] - sock2.connect.side_effect = RuntimeError("error connecting") - - adafruit_requests.set_socket(mocket, mocket.interface) - response = adafruit_requests.get("https://" + HOST + "/testwifi/index.html") - - sock.send.assert_has_calls( - [ - mock.call(b"testwifi/index.html"), - ] - ) - - sock.send.assert_has_calls( - [ - mock.call(b"Host: "), - mock.call(HOST.encode("utf-8")), - mock.call(b"\r\n"), - ] - ) - assert response.text == str(ENCODED, "utf-8") - - host2 = "test.adafruit.com" - response = adafruit_requests.get("https://" + host2 + "/get2") - - sock.connect.assert_called_once_with((HOST, 443), mocket.interface.TLS_MODE) - sock2.connect.assert_called_once_with((host2, 443), mocket.interface.TLS_MODE) - sock3.connect.assert_called_once_with((host2, 443), mocket.interface.TLS_MODE) - # Make sure that the socket is closed after send fails. - sock.close.assert_called_once() - sock2.close.assert_called_once() - assert sock3.close.call_count == 0 - assert mocket.socket.call_count == 3 From f4478ceb1d958a9e28f321b7be424df0b315e90e Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 27 Jun 2023 18:44:22 -0500 Subject: [PATCH 162/305] remove cast to support recv_into --- adafruit_requests.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index f253b8c..a015f61 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -41,16 +41,10 @@ import json as json_module -if sys.implementation.name == "circuitpython": - - def cast(_t, value): - """No-op shim for the typing.cast() function which is not available in CircuitPython.""" - return value - -else: +if not sys.implementation.name == "circuitpython": from ssl import SSLContext from types import ModuleType, TracebackType - from typing import Any, Dict, Optional, Tuple, Type, Union, cast + from typing import Any, Dict, Optional, Tuple, Type, Union try: from typing import Protocol @@ -215,7 +209,7 @@ def __exit__( self.close() def _recv_into(self, buf: bytearray, size: int = 0) -> int: - return cast("SupportsRecvInto", self.socket).recv_into(buf, size) + return self.socket.recv_into(buf, size) def _readto(self, stop: bytes) -> bytearray: buf = self._receive_buffer From c5e1a4ce53f8bf6c909ce748a6a8266a9c341fa5 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 27 Jun 2023 18:47:38 -0500 Subject: [PATCH 163/305] remove LegacyCircuitPythonSocketType --- adafruit_requests.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index a015f61..363abb4 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -77,14 +77,6 @@ def connect( kwarg optionally may indicate SSL or not, depending on the underlying interface. """ - class LegacyCircuitPythonSocketType(CommonCircuitPythonSocketType, Protocol): - """Describes the structure a legacy CircuitPython socket type must have.""" - - def recv(self, bufsize: int = ...) -> bytes: - """Receive data from the socket. The return value is a bytes object representing - the data received. The maximum amount of data to be received at once is specified - by bufsize.""" - class SupportsRecvWithFlags(Protocol): """Describes a type that posseses a socket recv() method supporting the flags kwarg.""" @@ -122,7 +114,6 @@ def connect(self, address: Union[Tuple[Any, ...], str, bytes]) -> None: """Connect to a remote socket at the provided address.""" SocketType = Union[ - LegacyCircuitPythonSocketType, CircuitPythonSocketType, StandardPythonSocketType, ] From 2f8fcc1281b7802b62025e112efd8cbea7abab5e Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 3 Aug 2023 06:52:40 -0400 Subject: [PATCH 164/305] add basic Fitbit API example requires google account, fitbit device, fitbit developer app, api tokens. Non-graphing display example just serial ouptut. --- examples/requests_api_fitbit.py | 292 ++++++++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 examples/requests_api_fitbit.py diff --git a/examples/requests_api_fitbit.py b/examples/requests_api_fitbit.py new file mode 100644 index 0000000..389f9b4 --- /dev/null +++ b/examples/requests_api_fitbit.py @@ -0,0 +1,292 @@ +# SPDX-FileCopyrightText: 2023 DJDevon3 +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 8.2 + +import os +import board +import time +import microcontroller +import ssl +import wifi +import socketpool +import adafruit_requests + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + +# STREAMER WARNING: private data will be viewable while debug True +debug = False # Set True for full debug view + +# Can use to confirm first instance of NVM is correct refresh token +top_nvm = microcontroller.nvm[0:64].decode() +if debug: + print(f"Top NVM: {top_nvm}") # NVM before settings.toml loaded + +# --- Fitbit Developer Account & oAuth App Required: --- +# Required: Google Login (Fitbit owned by Google) & Fitbit Device +# Step 1: Create a personal app here: https://dev.fitbit.com +# Step 2: Use their Tutorial to get the Token and first Refresh Token +# Fitbit's Tutorial Step 4 is as far as you need to go. +# https://dev.fitbit.com/build/reference/web-api/troubleshooting-guide/oauth2-tutorial/ + +# Ensure these are in settings.toml +# Fitbit_ClientID = "YourAppClientID" +# Fitbit_Token = "Long 256 character string (SHA-256)" +# Fitbit_First_Refresh_Token = "64 character string" +# Fitbit_UserID = "UserID authorizing the ClientID" + +Fitbit_ClientID = os.getenv("Fitbit_ClientID") +Fitbit_Token = os.getenv("Fitbit_Token") +Fitbit_First_Refresh_Token = os.getenv( + "Fitbit_First_Refresh_Token" +) # overides nvm first run only +Fitbit_UserID = os.getenv("Fitbit_UserID") + +wifi_ssid = os.getenv("CIRCUITPY_WIFI_SSID") +wifi_pw = os.getenv("CIRCUITPY_WIFI_PASSWORD") + +# Time between API refreshes +# 300 = 5 mins, 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +sleep_time = 900 + + +# Converts seconds in minutes/hours/days +def time_calc(input_time): + if input_time < 60: + sleep_int = input_time + time_output = f"{sleep_int:.0f} seconds" + elif 60 <= input_time < 3600: + sleep_int = input_time / 60 + time_output = f"{sleep_int:.0f} minutes" + elif 3600 <= input_time < 86400: + sleep_int = input_time / 60 / 60 + time_output = f"{sleep_int:.1f} hours" + else: + sleep_int = input_time / 60 / 60 / 24 + time_output = f"{sleep_int:.1f} days" + return time_output + + +# Authenticates Client ID & SHA-256 Token to POST +fitbit_oauth_header = {"Content-Type": "application/x-www-form-urlencoded"} +fitbit_oauth_token = "https://api.fitbit.com/oauth2/token" + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +requests = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(wifi_ssid, wifi_pw) + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) +print("Connected!\n") + +# First run uses settings.toml token +Refresh_Token = Fitbit_First_Refresh_Token + +if debug: + print(f"Top NVM Again (just to make sure): {top_nvm}") + print(f"Settings.toml Initial Refresh Token: {Fitbit_First_Refresh_Token}") + +while True: + # Use Settings.toml refresh token on first run + if top_nvm != Fitbit_First_Refresh_Token: + Refresh_Token = microcontroller.nvm[0:64].decode() + if debug: + # NVM 64 should match Current Refresh Token + print(f"NVM 64: {microcontroller.nvm[0:64].decode()}") + print(f"Current Refresh_Token: {Refresh_Token}") + else: + if debug: + # First run use settings.toml refresh token instead + print(f"Initial_Refresh_Token: {Refresh_Token}") + + try: + if debug: + print("\n-----Token Refresh POST Attempt -------") + fitbit_oauth_refresh_token = ( + "&grant_type=refresh_token" + + "&client_id=" + + str(Fitbit_ClientID) + + "&refresh_token=" + + str(Refresh_Token) + ) + + # ----------------------------- POST FOR REFRESH TOKEN ----------------------- + if debug: + print( + f"FULL REFRESH TOKEN POST:{fitbit_oauth_token}{fitbit_oauth_refresh_token}" + ) + print(f"Current Refresh Token: {Refresh_Token}") + # TOKEN REFRESH POST + fitbit_oauth_refresh_POST = requests.post( + url=fitbit_oauth_token, + data=fitbit_oauth_refresh_token, + headers=fitbit_oauth_header, + ) + try: + fitbit_refresh_oauth_json = fitbit_oauth_refresh_POST.json() + + fitbit_new_token = fitbit_refresh_oauth_json["access_token"] + if debug: + print("Your Private SHA-256 Token: ", fitbit_new_token) + fitbit_access_token = fitbit_new_token # NEW FULL TOKEN + + # If current token valid will respond + fitbit_new_refesh_token = fitbit_refresh_oauth_json["refresh_token"] + Refresh_Token = fitbit_new_refesh_token + fitbit_token_expiration = fitbit_refresh_oauth_json["expires_in"] + fitbit_scope = fitbit_refresh_oauth_json["scope"] + fitbit_token_type = fitbit_refresh_oauth_json["token_type"] + fitbit_user_id = fitbit_refresh_oauth_json["user_id"] + if debug: + print("Next Refresh Token: ", Refresh_Token) + + # Store Next Token into NVM + try: + nvmtoken = b"" + fitbit_new_refesh_token + microcontroller.nvm[0:64] = nvmtoken + if debug: + print(f"Next Token for NVM: {nvmtoken.decode()}") + print(f"Next token written to NVM Successfully!") + except OSError as e: + print("OS Error:", e) + continue + + if debug: + # Extraneous token data for debugging + print("Token Expires in: ", time_calc(fitbit_token_expiration)) + print("Scope: ", fitbit_scope) + print("Token Type: ", fitbit_token_type) + print("UserID: ", fitbit_user_id) + + except KeyError as e: + print("Key Error:", e) + print("Expired token, invalid permission, or (key:value) pair error.") + time.sleep(300) + continue + + # ----------------------------- GET DATA ------------------------------------- + # POST should respond with current & next refresh token we can GET for data + # 64-bit Refresh tokens will "keep alive" SHA-256 token indefinitely + # Fitbit main SHA-256 token expires in 8 hours unless refreshed! + # ---------------------------------------------------------------------------- + detail_level = "1min" # Supported: 1sec | 1min | 5min | 15min + requested_date = "today" # Date format yyyy-MM-dd or today + fitbit_header = { + "Authorization": "Bearer " + fitbit_access_token + "", + "Client-Id": "" + Fitbit_ClientID + "", + } + # Heart Intraday Scope + FITBIT_SOURCE = ( + "https://api.fitbit.com/1/user/" + + Fitbit_UserID + + "/activities/heart/date/today" + + "/1d/" + + detail_level + + ".json" + ) + + print("\nAttempting to GET FITBIT Stats!") + print("===============================") + fitbit_get_response = requests.get(url=FITBIT_SOURCE, headers=fitbit_header) + try: + fitbit_json = fitbit_get_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + + if debug: + print(f"Full API GET URL: {FITBIT_SOURCE}") + print(f"Header: {fitbit_header}") + # print(f"JSON Full Response: {fitbit_json}") + # print(f"Intraday Full Response: {fitbit_json["activities-heart-intraday"]["dataset"]}") + + try: + # Fitbit's sync to your mobile device & server every 15 minutes in chunks. + # Pointless to poll their API faster than 15 minute intervals. + activities_heart_value = fitbit_json["activities-heart-intraday"]["dataset"] + response_length = len(activities_heart_value) + if response_length >= 15: + activities_timestamp = fitbit_json["activities-heart"][0]["dateTime"] + print(f"Fitbit Date: {activities_timestamp}") + activities_latest_heart_time = fitbit_json["activities-heart-intraday"][ + "dataset" + ][response_length - 1]["time"] + print(f"Fitbit Time: {activities_latest_heart_time[0:-3]}") + print(f"Today's Logged Pulses : {response_length}") + + # Each 1min heart rate is a 60 second average + activities_latest_heart_value0 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 1]["value"] + activities_latest_heart_value1 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 2]["value"] + activities_latest_heart_value2 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 3]["value"] + activities_latest_heart_value3 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 4]["value"] + activities_latest_heart_value4 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 5]["value"] + activities_latest_heart_value5 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 6]["value"] + activities_latest_heart_value6 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 7]["value"] + activities_latest_heart_value7 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 8]["value"] + activities_latest_heart_value8 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 9]["value"] + activities_latest_heart_value9 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 10]["value"] + activities_latest_heart_value10 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 11]["value"] + activities_latest_heart_value11 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 12]["value"] + activities_latest_heart_value12 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 13]["value"] + activities_latest_heart_value13 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 14]["value"] + activities_latest_heart_value14 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 15]["value"] + print( + f"Latest 15 Minute Averages: {activities_latest_heart_value14},{activities_latest_heart_value13},{activities_latest_heart_value12},{activities_latest_heart_value11},{activities_latest_heart_value10},{activities_latest_heart_value9},{activities_latest_heart_value8},{activities_latest_heart_value7},{activities_latest_heart_value6},{activities_latest_heart_value5},{activities_latest_heart_value4},{activities_latest_heart_value3},{activities_latest_heart_value2},{activities_latest_heart_value1},{activities_latest_heart_value0}" + ) + else: + print(f"Waiting for latest 15 values sync...") + print(f"Not enough values for today to display yet.") + print(f"No display from midnight to 00:15") + + except KeyError as keyerror: + print(f"Key Error: {keyerror}") + print( + f"Too Many Requests, Expired token, invalid permission, or (key:value) pair error." + ) + continue + + print("Board Uptime: ", time_calc(time.monotonic())) # Board Up-Time seconds + print("\nFinished!") + print("Next Update in: ", time_calc(sleep_time)) + print("===============================") + + except (ValueError, RuntimeError) as e: + print("Failed to get data, retrying\n", e) + time.sleep(60) + continue + time.sleep(sleep_time) From c2e3d3128aa538a294ed19aac5ad2cc47145d97e Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 3 Aug 2023 07:44:17 -0400 Subject: [PATCH 165/305] reformatted too long strings and unnecessary f-strings with Mu & Black --- examples/requests_api_fitbit.py | 319 +++++++++++++++++++++++++++++++- 1 file changed, 310 insertions(+), 9 deletions(-) diff --git a/examples/requests_api_fitbit.py b/examples/requests_api_fitbit.py index 389f9b4..d82a84b 100644 --- a/examples/requests_api_fitbit.py +++ b/examples/requests_api_fitbit.py @@ -3,7 +3,6 @@ # Coded for Circuit Python 8.2 import os -import board import time import microcontroller import ssl @@ -91,6 +90,7 @@ def time_calc(input_time): print(f"Top NVM Again (just to make sure): {top_nvm}") print(f"Settings.toml Initial Refresh Token: {Fitbit_First_Refresh_Token}") +latest_15_avg = "Latest 15 Minute Averages" while True: # Use Settings.toml refresh token on first run if top_nvm != Fitbit_First_Refresh_Token: @@ -118,7 +118,8 @@ def time_calc(input_time): # ----------------------------- POST FOR REFRESH TOKEN ----------------------- if debug: print( - f"FULL REFRESH TOKEN POST:{fitbit_oauth_token}{fitbit_oauth_refresh_token}" + f"FULL REFRESH TOKEN POST:{fitbit_oauth_token}" + + f"{fitbit_oauth_refresh_token}" ) print(f"Current Refresh Token: {Refresh_Token}") # TOKEN REFRESH POST @@ -151,7 +152,7 @@ def time_calc(input_time): microcontroller.nvm[0:64] = nvmtoken if debug: print(f"Next Token for NVM: {nvmtoken.decode()}") - print(f"Next token written to NVM Successfully!") + print("Next token written to NVM Successfully!") except OSError as e: print("OS Error:", e) continue @@ -195,6 +196,7 @@ def time_calc(input_time): fitbit_get_response = requests.get(url=FITBIT_SOURCE, headers=fitbit_header) try: fitbit_json = fitbit_get_response.json() + intraday_response = fitbit_json["activities-heart-intraday"]["dataset"] except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") @@ -203,7 +205,7 @@ def time_calc(input_time): print(f"Full API GET URL: {FITBIT_SOURCE}") print(f"Header: {fitbit_header}") # print(f"JSON Full Response: {fitbit_json}") - # print(f"Intraday Full Response: {fitbit_json["activities-heart-intraday"]["dataset"]}") + # print(f"Intraday Full Response: {intraday_response}") try: # Fitbit's sync to your mobile device & server every 15 minutes in chunks. @@ -265,18 +267,317 @@ def time_calc(input_time): activities_latest_heart_value14 = fitbit_json[ "activities-heart-intraday" ]["dataset"][response_length - 15]["value"] + latest_15_avg = "Latest 15 Minute Averages" print( - f"Latest 15 Minute Averages: {activities_latest_heart_value14},{activities_latest_heart_value13},{activities_latest_heart_value12},{activities_latest_heart_value11},{activities_latest_heart_value10},{activities_latest_heart_value9},{activities_latest_heart_value8},{activities_latest_heart_value7},{activities_latest_heart_value6},{activities_latest_heart_value5},{activities_latest_heart_value4},{activities_latest_heart_value3},{activities_latest_heart_value2},{activities_latest_heart_value1},{activities_latest_heart_value0}" + f"{latest_15_avg}" + + f"{activities_latest_heart_value14}," + + f"{activities_latest_heart_value13}," + + f"{activities_latest_heart_value12}," + + f"{activities_latest_heart_value11}," + + f"{activities_latest_heart_value10}," + + f"{activities_latest_heart_value9}," + + f"{activities_latest_heart_value8}," + + f"{activities_latest_heart_value7}," + + f"{activities_latest_heart_value6}," + + f"{activities_latest_heart_value5}," + + f"{activities_latest_heart_value4}," + + f"{activities_latest_heart_value3}," + + f"{activities_latest_heart_value2}," + + f"{activities_latest_heart_value1}," + + f"{activities_latest_heart_value0}" ) else: - print(f"Waiting for latest 15 values sync...") - print(f"Not enough values for today to display yet.") - print(f"No display from midnight to 00:15") + print("Waiting for latest 15 values sync...") + print("Not enough values for today to display yet.") + print("No display from midnight to 00:15") except KeyError as keyerror: print(f"Key Error: {keyerror}") print( - f"Too Many Requests, Expired token, invalid permission, or (key:value) pair error." + "Too Many Requests, Expired token," + + "invalid permission," + + "or (key:value) pair error." + ) + continue + + print("Board Uptime: ", time_calc(time.monotonic())) # Board Up-Time seconds + print("\nFinished!") + print("Next Update in: ", time_calc(sleep_time)) + print("===============================") + + except (ValueError, RuntimeError) as e: + print("Failed to get data, retrying\n", e) + time.sleep(60) + continue + time.sleep(sleep_time) + +# Fitbit_ClientID = "YourAppClientID" +# Fitbit_Token = "Long 256 character string (SHA-256)" +# Fitbit_First_Refresh_Token = "64 character string" +# Fitbit_UserID = "UserID authorizing the ClientID" + +Fitbit_ClientID = os.getenv("Fitbit_ClientID") +Fitbit_Token = os.getenv("Fitbit_Token") +# overides nvm first run only +Fitbit_First_Refresh_Token = os.getenv("Fitbit_First_Refresh_Token") +Fitbit_UserID = os.getenv("Fitbit_UserID") + +wifi_ssid = os.getenv("CIRCUITPY_WIFI_SSID") +wifi_pw = os.getenv("CIRCUITPY_WIFI_PASSWORD") + +# Time between API refreshes +# 300 = 5 mins, 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +sleep_time = 900 + + +# Converts seconds in minutes/hours/days +def time_calc(input_time): + if input_time < 60: + sleep_int = input_time + time_output = f"{sleep_int:.0f} seconds" + elif 60 <= input_time < 3600: + sleep_int = input_time / 60 + time_output = f"{sleep_int:.0f} minutes" + elif 3600 <= input_time < 86400: + sleep_int = input_time / 60 / 60 + time_output = f"{sleep_int:.1f} hours" + else: + sleep_int = input_time / 60 / 60 / 24 + time_output = f"{sleep_int:.1f} days" + return time_output + + +# Authenticates Client ID & SHA-256 Token to POST +fitbit_oauth_header = {"Content-Type": "application/x-www-form-urlencoded"} +fitbit_oauth_token = "https://api.fitbit.com/oauth2/token" + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +requests = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(wifi_ssid, wifi_pw) + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) +print("Connected!\n") + +# First run uses settings.toml token +Refresh_Token = Fitbit_First_Refresh_Token + +if debug: + print(f"Top NVM Again (just to make sure): {top_nvm}") + print(f"Settings.toml Initial Refresh Token: {Fitbit_First_Refresh_Token}") + +while True: + # Use Settings.toml refresh token on first run + if top_nvm != Fitbit_First_Refresh_Token: + Refresh_Token = microcontroller.nvm[0:64].decode() + if debug: + # NVM 64 should match Current Refresh Token + print(f"NVM 64: {microcontroller.nvm[0:64].decode()}") + print(f"Current Refresh_Token: {Refresh_Token}") + else: + if debug: + # First run use settings.toml refresh token instead + print(f"Initial_Refresh_Token: {Refresh_Token}") + + try: + if debug: + print("\n-----Token Refresh POST Attempt -------") + fitbit_oauth_refresh_token = ( + "&grant_type=refresh_token" + + "&client_id=" + + str(Fitbit_ClientID) + + "&refresh_token=" + + str(Refresh_Token) + ) + + # ----------------------------- POST FOR REFRESH TOKEN ----------------------- + if debug: + print( + f"FULL REFRESH TOKEN POST:{fitbit_oauth_token}" + + f"{fitbit_oauth_refresh_token}" + ) + print(f"Current Refresh Token: {Refresh_Token}") + # TOKEN REFRESH POST + fitbit_oauth_refresh_POST = requests.post( + url=fitbit_oauth_token, + data=fitbit_oauth_refresh_token, + headers=fitbit_oauth_header, + ) + try: + fitbit_refresh_oauth_json = fitbit_oauth_refresh_POST.json() + + fitbit_new_token = fitbit_refresh_oauth_json["access_token"] + if debug: + print("Your Private SHA-256 Token: ", fitbit_new_token) + fitbit_access_token = fitbit_new_token # NEW FULL TOKEN + + # If current token valid will respond + fitbit_new_refesh_token = fitbit_refresh_oauth_json["refresh_token"] + Refresh_Token = fitbit_new_refesh_token + fitbit_token_expiration = fitbit_refresh_oauth_json["expires_in"] + fitbit_scope = fitbit_refresh_oauth_json["scope"] + fitbit_token_type = fitbit_refresh_oauth_json["token_type"] + fitbit_user_id = fitbit_refresh_oauth_json["user_id"] + if debug: + print("Next Refresh Token: ", Refresh_Token) + + # Store Next Token into NVM + try: + nvmtoken = b"" + fitbit_new_refesh_token + microcontroller.nvm[0:64] = nvmtoken + if debug: + print(f"Next Token for NVM: {nvmtoken.decode()}") + print("Next token written to NVM Successfully!") + except OSError as e: + print("OS Error:", e) + continue + + if debug: + # Extraneous token data for debugging + print("Token Expires in: ", time_calc(fitbit_token_expiration)) + print("Scope: ", fitbit_scope) + print("Token Type: ", fitbit_token_type) + print("UserID: ", fitbit_user_id) + + except KeyError as e: + print("Key Error:", e) + print("Expired token, invalid permission, or (key:value) pair error.") + time.sleep(300) + continue + + # ----------------------------- GET DATA ------------------------------------- + # POST should respond with current & next refresh token we can GET for data + # 64-bit Refresh tokens will "keep alive" SHA-256 token indefinitely + # Fitbit main SHA-256 token expires in 8 hours unless refreshed! + # ---------------------------------------------------------------------------- + detail_level = "1min" # Supported: 1sec | 1min | 5min | 15min + requested_date = "today" # Date format yyyy-MM-dd or today + fitbit_header = { + "Authorization": "Bearer " + fitbit_access_token + "", + "Client-Id": "" + Fitbit_ClientID + "", + } + # Heart Intraday Scope + FITBIT_SOURCE = ( + "https://api.fitbit.com/1/user/" + + Fitbit_UserID + + "/activities/heart/date/today" + + "/1d/" + + detail_level + + ".json" + ) + + print("\nAttempting to GET FITBIT Stats!") + print("===============================") + fitbit_get_response = requests.get(url=FITBIT_SOURCE, headers=fitbit_header) + try: + fitbit_json = fitbit_get_response.json() + intraday_response = fitbit_json["activities-heart-intraday"]["dataset"] + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + + if debug: + print(f"Full API GET URL: {FITBIT_SOURCE}") + print(f"Header: {fitbit_header}") + # print(f"JSON Full Response: {fitbit_json}") + print(f"Intraday Full Response: {intraday_response}") + + try: + # Fitbit's sync to your mobile device & server every 15 minutes in chunks. + # Pointless to poll their API faster than 15 minute intervals. + activities_heart_value = fitbit_json["activities-heart-intraday"]["dataset"] + response_length = len(activities_heart_value) + if response_length >= 15: + activities_timestamp = fitbit_json["activities-heart"][0]["dateTime"] + print(f"Fitbit Date: {activities_timestamp}") + activities_latest_heart_time = fitbit_json["activities-heart-intraday"][ + "dataset" + ][response_length - 1]["time"] + print(f"Fitbit Time: {activities_latest_heart_time[0:-3]}") + print(f"Today's Logged Pulses : {response_length}") + + # Each 1min heart rate is a 60 second average + activities_latest_heart_value0 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 1]["value"] + activities_latest_heart_value1 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 2]["value"] + activities_latest_heart_value2 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 3]["value"] + activities_latest_heart_value3 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 4]["value"] + activities_latest_heart_value4 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 5]["value"] + activities_latest_heart_value5 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 6]["value"] + activities_latest_heart_value6 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 7]["value"] + activities_latest_heart_value7 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 8]["value"] + activities_latest_heart_value8 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 9]["value"] + activities_latest_heart_value9 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 10]["value"] + activities_latest_heart_value10 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 11]["value"] + activities_latest_heart_value11 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 12]["value"] + activities_latest_heart_value12 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 13]["value"] + activities_latest_heart_value13 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 14]["value"] + activities_latest_heart_value14 = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][response_length - 15]["value"] + + print( + f"{latest_15_avg}" + + f"{activities_latest_heart_value14}," + + f"{activities_latest_heart_value13}," + + f"{activities_latest_heart_value12}," + + f"{activities_latest_heart_value11}," + + f"{activities_latest_heart_value10}," + + f"{activities_latest_heart_value9}," + + f"{activities_latest_heart_value8}," + + f"{activities_latest_heart_value7}," + + f"{activities_latest_heart_value6}," + + f"{activities_latest_heart_value5}," + + f"{activities_latest_heart_value4}," + + f"{activities_latest_heart_value3}," + + f"{activities_latest_heart_value2}," + + f"{activities_latest_heart_value1}," + + f"{activities_latest_heart_value0}" + ) + else: + print("Waiting for latest 15 values sync...") + print("Not enough values for today to display yet.") + print("No display from midnight to 00:15") + + except KeyError as keyerror: + print(f"Key Error: {keyerror}") + print( + "Too Many Requests, " + + "Expired token, " + + "invalid permission, or " + + "(key:value) pair error." ) continue From 4f6ca91cb42d8ca592b4c91c3e88dce238444334 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 3 Aug 2023 07:53:00 -0400 Subject: [PATCH 166/305] reordered imports for some reason black duplicated the entire script twice. cleaned up hopefully this time. --- examples/requests_api_fitbit.py | 283 +------------------------------- 1 file changed, 1 insertion(+), 282 deletions(-) diff --git a/examples/requests_api_fitbit.py b/examples/requests_api_fitbit.py index d82a84b..6494237 100644 --- a/examples/requests_api_fitbit.py +++ b/examples/requests_api_fitbit.py @@ -4,10 +4,10 @@ import os import time -import microcontroller import ssl import wifi import socketpool +import microcontroller import adafruit_requests # Initialize WiFi Pool (There can be only 1 pool & top of script) @@ -310,284 +310,3 @@ def time_calc(input_time): time.sleep(60) continue time.sleep(sleep_time) - -# Fitbit_ClientID = "YourAppClientID" -# Fitbit_Token = "Long 256 character string (SHA-256)" -# Fitbit_First_Refresh_Token = "64 character string" -# Fitbit_UserID = "UserID authorizing the ClientID" - -Fitbit_ClientID = os.getenv("Fitbit_ClientID") -Fitbit_Token = os.getenv("Fitbit_Token") -# overides nvm first run only -Fitbit_First_Refresh_Token = os.getenv("Fitbit_First_Refresh_Token") -Fitbit_UserID = os.getenv("Fitbit_UserID") - -wifi_ssid = os.getenv("CIRCUITPY_WIFI_SSID") -wifi_pw = os.getenv("CIRCUITPY_WIFI_PASSWORD") - -# Time between API refreshes -# 300 = 5 mins, 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour -sleep_time = 900 - - -# Converts seconds in minutes/hours/days -def time_calc(input_time): - if input_time < 60: - sleep_int = input_time - time_output = f"{sleep_int:.0f} seconds" - elif 60 <= input_time < 3600: - sleep_int = input_time / 60 - time_output = f"{sleep_int:.0f} minutes" - elif 3600 <= input_time < 86400: - sleep_int = input_time / 60 / 60 - time_output = f"{sleep_int:.1f} hours" - else: - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.1f} days" - return time_output - - -# Authenticates Client ID & SHA-256 Token to POST -fitbit_oauth_header = {"Content-Type": "application/x-www-form-urlencoded"} -fitbit_oauth_token = "https://api.fitbit.com/oauth2/token" - -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -requests = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.ipv4_address: - try: - wifi.radio.connect(wifi_ssid, wifi_pw) - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) -print("Connected!\n") - -# First run uses settings.toml token -Refresh_Token = Fitbit_First_Refresh_Token - -if debug: - print(f"Top NVM Again (just to make sure): {top_nvm}") - print(f"Settings.toml Initial Refresh Token: {Fitbit_First_Refresh_Token}") - -while True: - # Use Settings.toml refresh token on first run - if top_nvm != Fitbit_First_Refresh_Token: - Refresh_Token = microcontroller.nvm[0:64].decode() - if debug: - # NVM 64 should match Current Refresh Token - print(f"NVM 64: {microcontroller.nvm[0:64].decode()}") - print(f"Current Refresh_Token: {Refresh_Token}") - else: - if debug: - # First run use settings.toml refresh token instead - print(f"Initial_Refresh_Token: {Refresh_Token}") - - try: - if debug: - print("\n-----Token Refresh POST Attempt -------") - fitbit_oauth_refresh_token = ( - "&grant_type=refresh_token" - + "&client_id=" - + str(Fitbit_ClientID) - + "&refresh_token=" - + str(Refresh_Token) - ) - - # ----------------------------- POST FOR REFRESH TOKEN ----------------------- - if debug: - print( - f"FULL REFRESH TOKEN POST:{fitbit_oauth_token}" - + f"{fitbit_oauth_refresh_token}" - ) - print(f"Current Refresh Token: {Refresh_Token}") - # TOKEN REFRESH POST - fitbit_oauth_refresh_POST = requests.post( - url=fitbit_oauth_token, - data=fitbit_oauth_refresh_token, - headers=fitbit_oauth_header, - ) - try: - fitbit_refresh_oauth_json = fitbit_oauth_refresh_POST.json() - - fitbit_new_token = fitbit_refresh_oauth_json["access_token"] - if debug: - print("Your Private SHA-256 Token: ", fitbit_new_token) - fitbit_access_token = fitbit_new_token # NEW FULL TOKEN - - # If current token valid will respond - fitbit_new_refesh_token = fitbit_refresh_oauth_json["refresh_token"] - Refresh_Token = fitbit_new_refesh_token - fitbit_token_expiration = fitbit_refresh_oauth_json["expires_in"] - fitbit_scope = fitbit_refresh_oauth_json["scope"] - fitbit_token_type = fitbit_refresh_oauth_json["token_type"] - fitbit_user_id = fitbit_refresh_oauth_json["user_id"] - if debug: - print("Next Refresh Token: ", Refresh_Token) - - # Store Next Token into NVM - try: - nvmtoken = b"" + fitbit_new_refesh_token - microcontroller.nvm[0:64] = nvmtoken - if debug: - print(f"Next Token for NVM: {nvmtoken.decode()}") - print("Next token written to NVM Successfully!") - except OSError as e: - print("OS Error:", e) - continue - - if debug: - # Extraneous token data for debugging - print("Token Expires in: ", time_calc(fitbit_token_expiration)) - print("Scope: ", fitbit_scope) - print("Token Type: ", fitbit_token_type) - print("UserID: ", fitbit_user_id) - - except KeyError as e: - print("Key Error:", e) - print("Expired token, invalid permission, or (key:value) pair error.") - time.sleep(300) - continue - - # ----------------------------- GET DATA ------------------------------------- - # POST should respond with current & next refresh token we can GET for data - # 64-bit Refresh tokens will "keep alive" SHA-256 token indefinitely - # Fitbit main SHA-256 token expires in 8 hours unless refreshed! - # ---------------------------------------------------------------------------- - detail_level = "1min" # Supported: 1sec | 1min | 5min | 15min - requested_date = "today" # Date format yyyy-MM-dd or today - fitbit_header = { - "Authorization": "Bearer " + fitbit_access_token + "", - "Client-Id": "" + Fitbit_ClientID + "", - } - # Heart Intraday Scope - FITBIT_SOURCE = ( - "https://api.fitbit.com/1/user/" - + Fitbit_UserID - + "/activities/heart/date/today" - + "/1d/" - + detail_level - + ".json" - ) - - print("\nAttempting to GET FITBIT Stats!") - print("===============================") - fitbit_get_response = requests.get(url=FITBIT_SOURCE, headers=fitbit_header) - try: - fitbit_json = fitbit_get_response.json() - intraday_response = fitbit_json["activities-heart-intraday"]["dataset"] - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - - if debug: - print(f"Full API GET URL: {FITBIT_SOURCE}") - print(f"Header: {fitbit_header}") - # print(f"JSON Full Response: {fitbit_json}") - print(f"Intraday Full Response: {intraday_response}") - - try: - # Fitbit's sync to your mobile device & server every 15 minutes in chunks. - # Pointless to poll their API faster than 15 minute intervals. - activities_heart_value = fitbit_json["activities-heart-intraday"]["dataset"] - response_length = len(activities_heart_value) - if response_length >= 15: - activities_timestamp = fitbit_json["activities-heart"][0]["dateTime"] - print(f"Fitbit Date: {activities_timestamp}") - activities_latest_heart_time = fitbit_json["activities-heart-intraday"][ - "dataset" - ][response_length - 1]["time"] - print(f"Fitbit Time: {activities_latest_heart_time[0:-3]}") - print(f"Today's Logged Pulses : {response_length}") - - # Each 1min heart rate is a 60 second average - activities_latest_heart_value0 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 1]["value"] - activities_latest_heart_value1 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 2]["value"] - activities_latest_heart_value2 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 3]["value"] - activities_latest_heart_value3 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 4]["value"] - activities_latest_heart_value4 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 5]["value"] - activities_latest_heart_value5 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 6]["value"] - activities_latest_heart_value6 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 7]["value"] - activities_latest_heart_value7 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 8]["value"] - activities_latest_heart_value8 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 9]["value"] - activities_latest_heart_value9 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 10]["value"] - activities_latest_heart_value10 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 11]["value"] - activities_latest_heart_value11 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 12]["value"] - activities_latest_heart_value12 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 13]["value"] - activities_latest_heart_value13 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 14]["value"] - activities_latest_heart_value14 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 15]["value"] - - print( - f"{latest_15_avg}" - + f"{activities_latest_heart_value14}," - + f"{activities_latest_heart_value13}," - + f"{activities_latest_heart_value12}," - + f"{activities_latest_heart_value11}," - + f"{activities_latest_heart_value10}," - + f"{activities_latest_heart_value9}," - + f"{activities_latest_heart_value8}," - + f"{activities_latest_heart_value7}," - + f"{activities_latest_heart_value6}," - + f"{activities_latest_heart_value5}," - + f"{activities_latest_heart_value4}," - + f"{activities_latest_heart_value3}," - + f"{activities_latest_heart_value2}," - + f"{activities_latest_heart_value1}," - + f"{activities_latest_heart_value0}" - ) - else: - print("Waiting for latest 15 values sync...") - print("Not enough values for today to display yet.") - print("No display from midnight to 00:15") - - except KeyError as keyerror: - print(f"Key Error: {keyerror}") - print( - "Too Many Requests, " - + "Expired token, " - + "invalid permission, or " - + "(key:value) pair error." - ) - continue - - print("Board Uptime: ", time_calc(time.monotonic())) # Board Up-Time seconds - print("\nFinished!") - print("Next Update in: ", time_calc(sleep_time)) - print("===============================") - - except (ValueError, RuntimeError) as e: - print("Failed to get data, retrying\n", e) - time.sleep(60) - continue - time.sleep(sleep_time) From cb1fc8b33fca355d610f0999fce8cedb7584af19 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sat, 19 Aug 2023 20:19:59 -0400 Subject: [PATCH 167/305] Update requests_adafruit_discord_active_online.py Updated with settings.toml instead of secrets.py. Other minor changes. No learn guide updates required. This example only exists on github in the requests library examples. --- ...requests_adafruit_discord_active_online.py | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/examples/requests_adafruit_discord_active_online.py b/examples/requests_adafruit_discord_active_online.py index 0175593..48a201c 100644 --- a/examples/requests_adafruit_discord_active_online.py +++ b/examples/requests_adafruit_discord_active_online.py @@ -1,8 +1,11 @@ -# SPDX-FileCopyrightText: 2022 DJDevon3 for Adafruit Industries +# SPDX-FileCopyrightText: 2023 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.0 -"""DJDevon3 Adafruit Feather ESP32-S2 Adafruit_Discord_Active_Users_Example""" +""" +Coded for Circuit Python 8.2.3 +requests_adafruit_discord_active_online +""" import gc +import os import time import ssl import json @@ -10,34 +13,38 @@ import socketpool import adafruit_requests -# No user or token required, 100% JSON web scrape from SHIELDS.IO +# Public API. No user or token required +# JSON web scrape from SHIELDS.IO # Adafruit uses Shields.IO to see online users # Initialize WiFi Pool (There can be only 1 pool & top of script) pool = socketpool.SocketPool(wifi.radio) -# Time between API refreshes -# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +# Time in seconds between updates (polling) +# 600 = 10 mins, 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour sleep_time = 900 -try: - from secrets import secrets -except ImportError: - print("Secrets File Import Error") - raise +# this example uses settings.toml for credentials +ssid = os.getenv("WIFI_SSID") +appw = os.getenv("WIFI_PASSWORD") + + +# Converts seconds to minutes/hours/days +def time_calc(input_time): + if input_time < 60: + sleep_int = input_time + time_output = f"{sleep_int:.0f} seconds" + elif 60 <= input_time < 3600: + sleep_int = input_time / 60 + time_output = f"{sleep_int:.0f} minutes" + elif 3600 <= input_time < 86400: + sleep_int = input_time / 60 / 60 + time_output = f"{sleep_int:.0f} hours" + else: + sleep_int = input_time / 60 / 60 / 24 + time_output = f"{sleep_int:.1f} days" + return time_output -if sleep_time < 60: - sleep_time_conversion = "seconds" - sleep_int = sleep_time -elif 60 <= sleep_time < 3600: - sleep_int = sleep_time / 60 - sleep_time_conversion = "minutes" -elif 3600 <= sleep_time < 86400: - sleep_int = sleep_time / 60 / 60 - sleep_time_conversion = "hours" -else: - sleep_int = sleep_time / 60 / 60 / 24 - sleep_time_conversion = "days" # Originally attempted to use SVG. Found JSON exists with same filename. # https://img.shields.io/discord/327254708534116352.svg @@ -49,7 +56,7 @@ requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(secrets["ssid"], secrets["password"]) + wifi.radio.connect(ssid, appw) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") @@ -93,7 +100,7 @@ print("Monotonic: ", time.monotonic()) print("\nFinished!") - print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) + print("Next Update: ", time_calc(sleep_time)) print("===============================") gc.collect() From 87ae429b75a4836709749f363657f71061e1b409 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 18 Sep 2023 16:23:58 -0500 Subject: [PATCH 168/305] "fix rtd theme " --- docs/conf.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 0a6c357..ed4d3f1 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -101,19 +101,10 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -on_rtd = os.environ.get("READTHEDOCS", None) == "True" - -if not on_rtd: # only import and set the theme if we're building docs locally - try: - import sphinx_rtd_theme - - html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] - except: - html_theme = "default" - html_theme_path = ["."] -else: - html_theme_path = ["."] +import sphinx_rtd_theme + +html_theme = "sphinx_rtd_theme" +html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, From 2f88f54411262c17466b149b925ee183a93fd1ac Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Fri, 24 Nov 2023 12:25:12 -0500 Subject: [PATCH 169/305] update Twitch API example Now uses settings.toml by default, minor Twitch API change to GET follower count. Updated for 8.2.7, untested on 9.0.alpha --- examples/requests_api_twitch.py | 91 ++++++++++++++------------------- 1 file changed, 38 insertions(+), 53 deletions(-) diff --git a/examples/requests_api_twitch.py b/examples/requests_api_twitch.py index e4b0dbb..396712f 100644 --- a/examples/requests_api_twitch.py +++ b/examples/requests_api_twitch.py @@ -1,57 +1,49 @@ -# SPDX-FileCopyrightText: 2022 DJDevon3 for Adafruit Industries +# SPDX-FileCopyrightText: 2023 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.0 -"""DJDevon3 Adafruit Feather ESP32-S2 Twitch_API_Example""" -import gc +# Coded for Circuit Python 8.2.x +# Twitch_API_Example + +import os import time import ssl import wifi import socketpool import adafruit_requests -# Twitch Developer Account & 0Auth App Required: +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + +# Twitch Developer Account & oauth App Required: # Visit https://dev.twitch.tv/console to create an app -# Ensure Twitch_ClientID & Twitch_Client_Secret are in secrets.py or .env +# Ensure these are in secrets.py or settings.toml # "Twitch_ClientID": "Your Developer APP ID Here", # "Twitch_Client_Secret": "APP ID secret here", +# "Twitch_UserID": "Your Twitch UserID here", +# Use settings.toml for credentials +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +twitch_client_id = os.getenv("Twitch_ClientID") +twitch_client_secret = os.getenv("Twitch_Client_Secret") # For finding your Twitch User ID # https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/ -Twitch_UserID = "0000000" # Set User ID you want endpoints from - -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) +twitch_user_id = os.getenv("Twitch_UserID") # User ID you want endpoints from # Time between API refreshes # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour sleep_time = 900 -try: - from secrets import secrets -except ImportError: - print("Secrets File Import Error") - raise - -# Converts seconds in minutes/hours/days +# Converts seconds to minutes/hours/days def time_calc(input_time): if input_time < 60: - sleep_int = input_time - time_output = f"{sleep_int:.0f} seconds" - elif 60 <= input_time < 3600: - sleep_int = input_time / 60 - time_output = f"{sleep_int:.0f} minutes" - elif 3600 <= input_time < 86400: - sleep_int = input_time / 60 / 60 - time_output = f"{sleep_int:.0f} hours" - elif 86400 <= input_time < 432000: - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.1f} days" - else: # if > 5 days convert float to int & display whole days - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.0f} days" - return time_output + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" # First we use Client ID & Client Secret to create a token with POST @@ -63,21 +55,20 @@ def time_calc(input_time): print("\n===============================") print("Connecting to WiFi...") requests = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.ipv4_address: +while not wifi.radio.connected: try: - wifi.radio.connect(secrets["ssid"], secrets["password"]) + wifi.radio.connect(ssid, appw) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") time.sleep(10) - gc.collect() print("Connected!\n") while True: try: # ----------------------------- POST FOR BEARER TOKEN ----------------------- print( - "\nAttempting to GENERATE Twitch Bearer Token!" + "Attempting Bearer Token Request!" ) # --------------------------------------- # Print Request to Serial debug_bearer_request = ( @@ -88,9 +79,9 @@ def time_calc(input_time): print("===============================") twitch_0auth_data = ( "&client_id=" - + secrets["Twitch_ClientID"] + + twitch_client_id + "&client_secret=" - + secrets["Twitch_Client_Secret"] + + twitch_client_secret + "&grant_type=client_credentials" ) @@ -113,12 +104,12 @@ def time_calc(input_time): print("JSON Dump: ", twitch_0auth_json) print("Header: ", twitch_0auth_header) print("Access Token: ", twitch_access_token) + twitch_token_type = twitch_0auth_json["token_type"] + print("Token Type: ", twitch_token_type) + print("Board Uptime: ", time_calc(time.monotonic())) twitch_token_expiration = twitch_0auth_json["expires_in"] print("Token Expires in: ", time_calc(twitch_token_expiration)) - twitch_token_type = twitch_0auth_json["token_type"] - print("Token Type: ", twitch_token_type) - print("Monotonic: ", time.monotonic()) # ----------------------------- GET DATA ------------------------------------- # Bearer token is refreshed every time script runs :) @@ -128,14 +119,13 @@ def time_calc(input_time): # ---------------------------------------------------------------------------- twitch_header = { "Authorization": "Bearer " + twitch_access_token + "", - "Client-Id": "" + secrets["Twitch_ClientID"] + "", + "Client-Id": "" + twitch_client_id + "", } TWITCH_FOLLOWERS_SOURCE = ( - "https://api.twitch.tv/helix/users" - + "/follows?" - + "to_id=" - + Twitch_UserID - + "&first=1" + "https://api.twitch.tv/helix/channels" + + "/followers?" + + "broadcaster_id=" + + twitch_user_id ) print( "\nAttempting to GET TWITCH Stats!" @@ -159,16 +149,11 @@ def time_calc(input_time): print("Header: ", twitch_header) print("JSON Full Response: ", twitch_followers_json) - twitch_username = twitch_followers_json["data"][0]["to_name"] - print("Username: ", twitch_username) twitch_followers = twitch_followers_json["total"] print("Followers: ", twitch_followers) - print("Monotonic: ", time.monotonic()) # Board Up-Time seconds - - print("\nFinished!") + print("Finished!") print("Next Update in: ", time_calc(sleep_time)) print("===============================") - gc.collect() except (ValueError, RuntimeError) as e: print("Failed to get data, retrying\n", e) From 4754bf3e5fdb559be9ea085bfb65bd435039e887 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 16 Oct 2023 14:30:31 -0500 Subject: [PATCH 170/305] unpin sphinx and add sphinx-rtd-theme to docs reqs Signed-off-by: foamyguy --- docs/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 797aa04..979f568 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,6 @@ # # SPDX-License-Identifier: Unlicense -sphinx>=4.0.0 +sphinx sphinxcontrib-jquery +sphinx-rtd-theme From 13c4d70b06189303180dd06eb2d2d29e79b7fd72 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Fri, 22 Dec 2023 07:49:39 -0800 Subject: [PATCH 171/305] Switch to using ConnectionManager --- .gitignore | 7 ++ .pre-commit-config.yaml | 2 +- adafruit_requests.py | 176 +++++++-------------------------------- conftest.py | 17 ++++ requirements.txt | 1 + tests/concurrent_test.py | 4 +- tests/reuse_test.py | 2 +- tox.ini | 33 +++++++- 8 files changed, 88 insertions(+), 154 deletions(-) create mode 100644 conftest.py diff --git a/.gitignore b/.gitignore index db3d538..a06dc67 100755 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,10 @@ _build .idea .vscode *~ + +# tox-specific files +.tox +build + +# coverage-specific files +.coverage diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 70ade69..e2c8831 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,4 +39,4 @@ repos: types: [python] files: "^tests/" args: - - --disable=missing-docstring,consider-using-f-string,duplicate-code + - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code diff --git a/adafruit_requests.py b/adafruit_requests.py index 363abb4..6336c4c 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -31,6 +31,9 @@ * Adafruit CircuitPython firmware for the supported boards: https://github.com/adafruit/circuitpython/releases +* Adafruit's Connection Manager library: + https://github.com/adafruit/Adafruit_CircuitPython_ConnectionManager + """ __version__ = "0.0.0+auto.0" @@ -41,6 +44,9 @@ import json as json_module +from adafruit_connectionmanager import get_connection_manager + + if not sys.implementation.name == "circuitpython": from ssl import SSLContext from types import ModuleType, TracebackType @@ -176,7 +182,7 @@ def __init__(self, sock: SocketType, session: Optional["Session"] = None) -> Non http = self._readto(b" ") if not http: if session: - session._close_socket(self.socket) + session._connection_manager.close_socket(self.socket) else: self.socket.close() raise RuntimeError("Unable to read HTTP response.") @@ -320,7 +326,8 @@ def close(self) -> None: self._throw_away(chunk_size + 2) self._parse_headers() if self._session: - self._session._free_socket(self.socket) # pylint: disable=protected-access + # pylint: disable=protected-access + self._session._connection_manager.free_socket(self.socket) else: self.socket.close() self.socket = None @@ -429,6 +436,9 @@ def iter_content(self, chunk_size: int = 1, decode_unicode: bool = False) -> byt self.close() +_global_session = None # pylint: disable=invalid-name + + class Session: """HTTP session that shares sockets and ssl context.""" @@ -436,98 +446,16 @@ def __init__( self, socket_pool: SocketpoolModuleType, ssl_context: Optional[SSLContextType] = None, + set_global_session: bool = True, ) -> None: - self._socket_pool = socket_pool + self._connection_manager = get_connection_manager(socket_pool) self._ssl_context = ssl_context - # Hang onto open sockets so that we can reuse them. - self._open_sockets = {} - self._socket_free = {} self._last_response = None - def _free_socket(self, socket: SocketType) -> None: - if socket not in self._open_sockets.values(): - raise RuntimeError("Socket not from session") - self._socket_free[socket] = True - - def _close_socket(self, sock: SocketType) -> None: - sock.close() - del self._socket_free[sock] - key = None - for k in self._open_sockets: # pylint: disable=consider-using-dict-items - if self._open_sockets[k] == sock: - key = k - break - if key: - del self._open_sockets[key] - - def _free_sockets(self) -> None: - free_sockets = [] - for sock, val in self._socket_free.items(): - if val: - free_sockets.append(sock) - for sock in free_sockets: - self._close_socket(sock) - - def _get_socket( - self, host: str, port: int, proto: str, *, timeout: float = 1 - ) -> CircuitPythonSocketType: - # pylint: disable=too-many-branches - key = (host, port, proto) - if key in self._open_sockets: - sock = self._open_sockets[key] - if self._socket_free[sock]: - self._socket_free[sock] = False - return sock - if proto == "https:" and not self._ssl_context: - raise RuntimeError( - "ssl_context must be set before using adafruit_requests for https" - ) - addr_info = self._socket_pool.getaddrinfo( - host, port, 0, self._socket_pool.SOCK_STREAM - )[0] - retry_count = 0 - sock = None - last_exc = None - while retry_count < 5 and sock is None: - if retry_count > 0: - if any(self._socket_free.items()): - self._free_sockets() - else: - raise RuntimeError("Sending request failed") from last_exc - retry_count += 1 - - try: - sock = self._socket_pool.socket(addr_info[0], addr_info[1]) - except OSError as exc: - last_exc = exc - continue - except RuntimeError as exc: - last_exc = exc - continue - - connect_host = addr_info[-1][0] - if proto == "https:": - sock = self._ssl_context.wrap_socket(sock, server_hostname=host) - connect_host = host - sock.settimeout(timeout) # socket read timeout - - try: - sock.connect((connect_host, port)) - except MemoryError as exc: - last_exc = exc - sock.close() - sock = None - except OSError as exc: - last_exc = exc - sock.close() - sock = None - - if sock is None: - raise RuntimeError("Repeated socket failures") from last_exc - - self._open_sockets[key] = sock - self._socket_free[sock] = False - return sock + if set_global_session: + # pylint: disable=global-statement + global _global_session + _global_session = self @staticmethod def _send(socket: SocketType, data: bytes): @@ -647,7 +575,9 @@ def request( last_exc = None while retry_count < 2: retry_count += 1 - socket = self._get_socket(host, port, proto, timeout=timeout) + socket = self._connection_manager.get_socket( + host, port, proto, timeout=timeout, ssl_context=self._ssl_context + ) ok = True try: self._send_request(socket, host, method, path, headers, data, json) @@ -668,7 +598,7 @@ def request( if result == b"H": # Things seem to be ok so break with socket set. break - self._close_socket(socket) + self._connection_manager.close_socket(socket) socket = None if not socket: @@ -727,54 +657,6 @@ def delete(self, url: str, **kw) -> Response: return self.request("DELETE", url, **kw) -# Backwards compatible API: - -_default_session = None # pylint: disable=invalid-name - - -class _FakeSSLSocket: - def __init__(self, socket: CircuitPythonSocketType, tls_mode: int) -> None: - self._socket = socket - self._mode = tls_mode - self.settimeout = socket.settimeout - self.send = socket.send - self.recv = socket.recv - self.close = socket.close - self.recv_into = socket.recv_into - - def connect(self, address: Tuple[str, int]) -> None: - """connect wrapper to add non-standard mode parameter""" - try: - return self._socket.connect(address, self._mode) - except RuntimeError as error: - raise OSError(errno.ENOMEM) from error - - -class _FakeSSLContext: - def __init__(self, iface: InterfaceType) -> None: - self._iface = iface - - def wrap_socket( - self, socket: CircuitPythonSocketType, server_hostname: Optional[str] = None - ) -> _FakeSSLSocket: - """Return the same socket""" - # pylint: disable=unused-argument - return _FakeSSLSocket(socket, self._iface.TLS_MODE) - - -def set_socket( - sock: SocketpoolModuleType, iface: Optional[InterfaceType] = None -) -> None: - """Legacy API for setting the socket and network interface. Use a `Session` instead.""" - global _default_session # pylint: disable=global-statement,invalid-name - if not iface: - # pylint: disable=protected-access - _default_session = Session(sock, _FakeSSLContext(sock._the_interface)) - else: - _default_session = Session(sock, _FakeSSLContext(iface)) - sock.set_interface(iface) - - def request( method: str, url: str, @@ -786,7 +668,7 @@ def request( ) -> None: """Send HTTP request""" # pylint: disable=too-many-arguments - _default_session.request( + _global_session.request( method, url, data=data, @@ -799,29 +681,29 @@ def request( def head(url: str, **kw): """Send HTTP HEAD request""" - return _default_session.request("HEAD", url, **kw) + return _global_session.request("HEAD", url, **kw) def get(url: str, **kw): """Send HTTP GET request""" - return _default_session.request("GET", url, **kw) + return _global_session.request("GET", url, **kw) def post(url: str, **kw): """Send HTTP POST request""" - return _default_session.request("POST", url, **kw) + return _global_session.request("POST", url, **kw) def put(url: str, **kw): """Send HTTP PUT request""" - return _default_session.request("PUT", url, **kw) + return _global_session.request("PUT", url, **kw) def patch(url: str, **kw): """Send HTTP PATCH request""" - return _default_session.request("PATCH", url, **kw) + return _global_session.request("PATCH", url, **kw) def delete(url: str, **kw): """Send HTTP DELETE request""" - return _default_session.request("DELETE", url, **kw) + return _global_session.request("DELETE", url, **kw) diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..69ae41f --- /dev/null +++ b/conftest.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2023 Justin Myers for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +""" PyTest Setup """ + +import pytest +import adafruit_connectionmanager + + +@pytest.fixture(autouse=True) +def reset_connection_manager(monkeypatch): + """Reset the ConnectionManager, since it's a singlton and will hold data""" + monkeypatch.setattr( + "adafruit_requests.get_connection_manager", + adafruit_connectionmanager.ConnectionManager, + ) diff --git a/requirements.txt b/requirements.txt index 7a984a4..7756505 100755 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ # SPDX-License-Identifier: Unlicense Adafruit-Blinka +Adafruit-Circuitpython-ConnectionManager@git+https://github.com/justmobilize/Adafruit_CircuitPython_ConnectionManager diff --git a/tests/concurrent_test.py b/tests/concurrent_test.py index 42df46c..70a46a3 100644 --- a/tests/concurrent_test.py +++ b/tests/concurrent_test.py @@ -17,7 +17,7 @@ RESPONSE = b"HTTP/1.0 200 OK\r\nContent-Length: 70\r\n\r\n" + TEXT -def test_second_connect_fails_memoryerror(): # pylint: disable=invalid-name +def test_second_connect_fails_memoryerror(): pool = mocket.MocketPool() pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) sock = mocket.Mocket(RESPONSE) @@ -59,7 +59,7 @@ def test_second_connect_fails_memoryerror(): # pylint: disable=invalid-name assert pool.socket.call_count == 3 -def test_second_connect_fails_oserror(): # pylint: disable=invalid-name +def test_second_connect_fails_oserror(): pool = mocket.MocketPool() pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) sock = mocket.Mocket(RESPONSE) diff --git a/tests/reuse_test.py b/tests/reuse_test.py index 0f9cc55..cc74956 100644 --- a/tests/reuse_test.py +++ b/tests/reuse_test.py @@ -202,7 +202,7 @@ def test_second_send_fails(): assert pool.socket.call_count == 2 -def test_second_send_lies_recv_fails(): # pylint: disable=invalid-name +def test_second_send_lies_recv_fails(): pool = mocket.MocketPool() pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) sock = mocket.Mocket(RESPONSE) diff --git a/tox.ini b/tox.ini index ab2df5e..9d1910d 100644 --- a/tox.ini +++ b/tox.ini @@ -3,9 +3,36 @@ # SPDX-License-Identifier: MIT [tox] -envlist = py38 +envlist = py311 [testenv] -changedir = {toxinidir}/tests -deps = pytest==6.2.5 +description = run tests +deps = + pytest==7.4.3 commands = pytest + +[testenv:coverage] +description = run coverage +deps = + pytest==7.4.3 + pytest-cov==4.1.0 +package = editable +commands = + coverage run --source=. --omit=tests/* --branch {posargs} -m pytest + coverage report + coverage html + +[testenv:lint] +description = run linters +deps = + pre-commit==3.6.0 +skip_install = true +commands = pre-commit run {posargs} + +[testenv:docs] +description = build docs +deps = + -r requirements.txt + -r docs/requirements.txt +skip_install = true +commands = sphinx-build -E -W -b html docs/. _build/html From 16d658d26e2d97e4e84f362c9148cfea453445d4 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Fri, 22 Dec 2023 10:41:06 -0800 Subject: [PATCH 172/305] Switch from adafruit_connectionmanager.py -> adafruit_connection_manager.py --- adafruit_requests.py | 2 +- conftest.py | 4 ++-- requirements.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 6336c4c..b037c32 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -44,7 +44,7 @@ import json as json_module -from adafruit_connectionmanager import get_connection_manager +from adafruit_connection_manager import get_connection_manager if not sys.implementation.name == "circuitpython": diff --git a/conftest.py b/conftest.py index 69ae41f..376dd7d 100644 --- a/conftest.py +++ b/conftest.py @@ -5,7 +5,7 @@ """ PyTest Setup """ import pytest -import adafruit_connectionmanager +import adafruit_connection_manager @pytest.fixture(autouse=True) @@ -13,5 +13,5 @@ def reset_connection_manager(monkeypatch): """Reset the ConnectionManager, since it's a singlton and will hold data""" monkeypatch.setattr( "adafruit_requests.get_connection_manager", - adafruit_connectionmanager.ConnectionManager, + adafruit_connection_manager.ConnectionManager, ) diff --git a/requirements.txt b/requirements.txt index 7756505..d83a678 100755 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ # SPDX-License-Identifier: Unlicense Adafruit-Blinka -Adafruit-Circuitpython-ConnectionManager@git+https://github.com/justmobilize/Adafruit_CircuitPython_ConnectionManager +Adafruit-Circuitpython-ConnectionManager@git+https://github.com/justmobilize/Adafruit_CircuitPython_ConnectionManager@connection-manager From cb32847ae9bddee2fc763fcdc8efae817cd676db Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Thu, 28 Dec 2023 10:56:45 -0800 Subject: [PATCH 173/305] Fix duplicate header issue --- adafruit_requests.py | 85 +++++++++++++++++---------- tests/chunk_test.py | 9 ++- tests/concurrent_test.py | 6 +- tests/header_test.py | 124 +++++++++++++++++++++++++++++++++++++-- tests/post_test.py | 27 +++++++-- tests/protocol_test.py | 9 ++- tests/reuse_test.py | 24 +++++--- 7 files changed, 229 insertions(+), 55 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 363abb4..f509784 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -529,6 +529,13 @@ def _get_socket( self._socket_free[sock] = False return sock + @staticmethod + def _header_suppplied(header, supplied_headers): + for supplied_header in supplied_headers: + if supplied_header.lower() == header.lower(): + return True + return False + @staticmethod def _send(socket: SocketType, data: bytes): total_sent = 0 @@ -551,6 +558,16 @@ def _send(socket: SocketType, data: bytes): raise OSError(errno.EIO) total_sent += sent + def _send_as_bytes(self, socket: SocketType, data: str): + return self._send(socket, bytes(data, "utf-8")) + + def _send_header(self, socket, header, value): + self._send_as_bytes(socket, header) + self._send_as_bytes(socket, ": ") + self._send_as_bytes(socket, value) + self._send_as_bytes(socket, "\r\n") + + # pylint: disable=too-many-arguments def _send_request( self, socket: SocketType, @@ -561,40 +578,48 @@ def _send_request( data: Any, json: Any, ): - # pylint: disable=too-many-arguments - self._send(socket, bytes(method, "utf-8")) - self._send(socket, b" /") - self._send(socket, bytes(path, "utf-8")) - self._send(socket, b" HTTP/1.1\r\n") - if "Host" not in headers: - self._send(socket, b"Host: ") - self._send(socket, bytes(host, "utf-8")) - self._send(socket, b"\r\n") - if "User-Agent" not in headers: - self._send(socket, b"User-Agent: Adafruit CircuitPython\r\n") - # Iterate over keys to avoid tuple alloc - for k in headers: - self._send(socket, k.encode()) - self._send(socket, b": ") - self._send(socket, headers[k].encode()) - self._send(socket, b"\r\n") + # Convert data + content_type_header = None + + # If json is sent, set content type header and convert to string if json is not None: assert data is None + content_type_header = "application/json" data = json_module.dumps(json) - self._send(socket, b"Content-Type: application/json\r\n") - if data: - if isinstance(data, dict): - self._send( - socket, b"Content-Type: application/x-www-form-urlencoded\r\n" - ) - _post_data = "" - for k in data: - _post_data = "{}&{}={}".format(_post_data, k, data[k]) - data = _post_data[1:] - if isinstance(data, str): - data = bytes(data, "utf-8") - self._send(socket, b"Content-Length: %d\r\n" % len(data)) + + # If data is sent and it's a dict, set content type header and convert to string + if data and isinstance(data, dict): + content_type_header = "application/x-www-form-urlencoded" + _post_data = "" + for k in data: + _post_data = "{}&{}={}".format(_post_data, k, data[k]) + # remove first "&" from concatenation + data = _post_data[1:] + + # Convert str data to bytes + if data and isinstance(data, str): + data = bytes(data, "utf-8") + + self._send_as_bytes(socket, method) + self._send_as_bytes(socket, " /") + self._send_as_bytes(socket, path) + self._send_as_bytes(socket, " HTTP/1.1\r\n") + + # Send headers + if not self._header_suppplied("Host", headers): + self._send_header(socket, "Host", host) + if not self._header_suppplied("User-Agent", headers): + self._send_header(socket, "User-Agent", "Adafruit CircuitPython") + if content_type_header and not self._header_suppplied("Content-Type", headers): + self._send_header(socket, "Content-Type", content_type_header) + if data and not self._header_suppplied("Content-Length", headers): + self._send_header(socket, "Content-Length", str(len(data))) + # Iterate over keys to avoid tuple alloc + for header in headers: + self._send_header(socket, header, headers[header]) self._send(socket, b"\r\n") + + # Send data if data: self._send(socket, bytes(data)) diff --git a/tests/chunk_test.py b/tests/chunk_test.py index a03eec8..df18bfe 100644 --- a/tests/chunk_test.py +++ b/tests/chunk_test.py @@ -63,7 +63,8 @@ def do_test_get_text( ) sock.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"wifitest.adafruit.com"), ] ) @@ -105,7 +106,8 @@ def do_test_close_flush( ) sock.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"wifitest.adafruit.com"), ] ) @@ -146,7 +148,8 @@ def do_test_get_text_extra_space( ) sock.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"wifitest.adafruit.com"), ] ) diff --git a/tests/concurrent_test.py b/tests/concurrent_test.py index 42df46c..79a32c5 100644 --- a/tests/concurrent_test.py +++ b/tests/concurrent_test.py @@ -40,7 +40,8 @@ def test_second_connect_fails_memoryerror(): # pylint: disable=invalid-name sock.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"wifitest.adafruit.com"), mock.call(b"\r\n"), ] @@ -82,7 +83,8 @@ def test_second_connect_fails_oserror(): # pylint: disable=invalid-name sock.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"wifitest.adafruit.com"), mock.call(b"\r\n"), ] diff --git a/tests/header_test.py b/tests/header_test.py index 05bf88f..ee0fad4 100644 --- a/tests/header_test.py +++ b/tests/header_test.py @@ -12,7 +12,75 @@ RESPONSE_HEADERS = b"HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n" -def test_json(): +def test_host(): + pool = mocket.MocketPool() + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE_HEADERS) + pool.socket.return_value = sock + sent = [] + + def _send(data): + sent.append(data) # pylint: disable=no-member + return len(data) + + sock.send.side_effect = _send + + requests_session = adafruit_requests.Session(pool) + headers = {} + requests_session.get("http://" + HOST + "/get", headers=headers) + + sock.connect.assert_called_once_with((IP, 80)) + sent = b"".join(sent) + assert b"Host: httpbin.org\r\n" in sent + + +def test_host_replace(): + pool = mocket.MocketPool() + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE_HEADERS) + pool.socket.return_value = sock + sent = [] + + def _send(data): + sent.append(data) # pylint: disable=no-member + return len(data) + + sock.send.side_effect = _send + + requests_session = adafruit_requests.Session(pool) + headers = {"host": IP} + requests_session.get("http://" + HOST + "/get", headers=headers) + + sock.connect.assert_called_once_with((IP, 80)) + sent = b"".join(sent) + assert b"host: 1.2.3.4\r\n" in sent + assert b"Host: httpbin.org\r\n" not in sent + assert sent.lower().count(b"host:") == 1 + + +def test_user_agent(): + pool = mocket.MocketPool() + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE_HEADERS) + pool.socket.return_value = sock + sent = [] + + def _send(data): + sent.append(data) # pylint: disable=no-member + return len(data) + + sock.send.side_effect = _send + + requests_session = adafruit_requests.Session(pool) + headers = {} + requests_session.get("http://" + HOST + "/get", headers=headers) + + sock.connect.assert_called_once_with((IP, 80)) + sent = b"".join(sent) + assert b"User-Agent: Adafruit CircuitPython\r\n" in sent + + +def test_user_agent_replace(): pool = mocket.MocketPool() pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) sock = mocket.Mocket(RESPONSE_HEADERS) @@ -30,7 +98,55 @@ def _send(data): requests_session.get("http://" + HOST + "/get", headers=headers) sock.connect.assert_called_once_with((IP, 80)) - sent = b"".join(sent).lower() + sent = b"".join(sent) assert b"user-agent: blinka/1.0.0\r\n" in sent - # The current implementation sends two user agents. Fix it, and uncomment below. - # assert sent.count(b"user-agent:") == 1 + assert b"User-Agent: Adafruit CircuitPython\r\n" not in sent + assert sent.lower().count(b"user-agent:") == 1 + + +def test_content_type(): + pool = mocket.MocketPool() + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE_HEADERS) + pool.socket.return_value = sock + sent = [] + + def _send(data): + sent.append(data) # pylint: disable=no-member + return len(data) + + sock.send.side_effect = _send + + requests_session = adafruit_requests.Session(pool) + headers = {} + data = {"test": True} + requests_session.post("http://" + HOST + "/get", data=data, headers=headers) + + sock.connect.assert_called_once_with((IP, 80)) + sent = b"".join(sent) + assert b"Content-Type: application/x-www-form-urlencoded\r\n" in sent + + +def test_content_type_replace(): + pool = mocket.MocketPool() + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) + sock = mocket.Mocket(RESPONSE_HEADERS) + pool.socket.return_value = sock + sent = [] + + def _send(data): + sent.append(data) # pylint: disable=no-member + return len(data) + + sock.send.side_effect = _send + + requests_session = adafruit_requests.Session(pool) + headers = {"content-type": "application/test"} + data = {"test": True} + requests_session.post("http://" + HOST + "/get", data=data, headers=headers) + + sock.connect.assert_called_once_with((IP, 80)) + sent = b"".join(sent) + assert b"content-type: application/test\r\n" in sent + assert b"Content-Type: application/x-www-form-urlencoded\r\n" not in sent + assert sent.lower().count(b"content-type:") == 1 diff --git a/tests/post_test.py b/tests/post_test.py index 1d0364f..226efd4 100644 --- a/tests/post_test.py +++ b/tests/post_test.py @@ -38,7 +38,8 @@ def test_method(): ) sock.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"httpbin.org"), ] ) @@ -64,10 +65,18 @@ def test_form(): pool.socket.return_value = sock requests_session = adafruit_requests.Session(pool) - data = {"Date": "July 25, 2019"} + data = {"Date": "July 25, 2019", "Time": "12:00"} requests_session.post("http://" + HOST + "/post", data=data) sock.connect.assert_called_once_with((IP, 80)) - sock.send.assert_called_with(b"Date=July 25, 2019") + sock.send.assert_has_calls( + [ + mock.call(b"Content-Type"), + mock.call(b": "), + mock.call(b"application/x-www-form-urlencoded"), + mock.call(b"\r\n"), + ] + ) + sock.send.assert_called_with(b"Date=July 25, 2019&Time=12:00") def test_json(): @@ -77,7 +86,15 @@ def test_json(): pool.socket.return_value = sock requests_session = adafruit_requests.Session(pool) - json_data = {"Date": "July 25, 2019"} + json_data = {"Date": "July 25, 2019", "Time": "12:00"} requests_session.post("http://" + HOST + "/post", json=json_data) sock.connect.assert_called_once_with((IP, 80)) - sock.send.assert_called_with(b'{"Date": "July 25, 2019"}') + sock.send.assert_has_calls( + [ + mock.call(b"Content-Type"), + mock.call(b": "), + mock.call(b"application/json"), + mock.call(b"\r\n"), + ] + ) + sock.send.assert_called_with(b'{"Date": "July 25, 2019", "Time": "12:00"}') diff --git a/tests/protocol_test.py b/tests/protocol_test.py index fb0471b..2a5a930 100644 --- a/tests/protocol_test.py +++ b/tests/protocol_test.py @@ -49,7 +49,8 @@ def test_get_https_text(): ) sock.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"wifitest.adafruit.com"), ] ) @@ -80,7 +81,8 @@ def test_get_http_text(): ) sock.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"wifitest.adafruit.com"), ] ) @@ -109,7 +111,8 @@ def test_get_close(): ) sock.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"wifitest.adafruit.com"), ] ) diff --git a/tests/reuse_test.py b/tests/reuse_test.py index 0f9cc55..b768a58 100644 --- a/tests/reuse_test.py +++ b/tests/reuse_test.py @@ -37,7 +37,8 @@ def test_get_twice(): ) sock.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"wifitest.adafruit.com"), ] ) @@ -55,7 +56,8 @@ def test_get_twice(): ) sock.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"wifitest.adafruit.com"), ] ) @@ -85,7 +87,8 @@ def test_get_twice_after_second(): ) sock.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"wifitest.adafruit.com"), ] ) @@ -102,7 +105,8 @@ def test_get_twice_after_second(): ) sock.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"wifitest.adafruit.com"), ] ) @@ -136,7 +140,8 @@ def test_connect_out_of_memory(): ) sock.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"wifitest.adafruit.com"), ] ) @@ -153,7 +158,8 @@ def test_connect_out_of_memory(): ) sock3.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"wifitest2.adafruit.com"), ] ) @@ -184,7 +190,8 @@ def test_second_send_fails(): sock.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"wifitest.adafruit.com"), mock.call(b"\r\n"), ] @@ -222,7 +229,8 @@ def test_second_send_lies_recv_fails(): # pylint: disable=invalid-name sock.send.assert_has_calls( [ - mock.call(b"Host: "), + mock.call(b"Host"), + mock.call(b": "), mock.call(b"wifitest.adafruit.com"), mock.call(b"\r\n"), ] From 5fa5aa218bbaecdd7b81c1e33a0894de635a8eff Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Thu, 28 Dec 2023 14:54:13 -0800 Subject: [PATCH 174/305] PR comments --- adafruit_requests.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index f509784..26b40f0 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -529,13 +529,6 @@ def _get_socket( self._socket_free[sock] = False return sock - @staticmethod - def _header_suppplied(header, supplied_headers): - for supplied_header in supplied_headers: - if supplied_header.lower() == header.lower(): - return True - return False - @staticmethod def _send(socket: SocketType, data: bytes): total_sent = 0 @@ -563,9 +556,9 @@ def _send_as_bytes(self, socket: SocketType, data: str): def _send_header(self, socket, header, value): self._send_as_bytes(socket, header) - self._send_as_bytes(socket, ": ") + self._send(socket, b": ") self._send_as_bytes(socket, value) - self._send_as_bytes(socket, "\r\n") + self._send(socket, b"\r\n") # pylint: disable=too-many-arguments def _send_request( @@ -601,18 +594,21 @@ def _send_request( data = bytes(data, "utf-8") self._send_as_bytes(socket, method) - self._send_as_bytes(socket, " /") + self._send(socket, b" /") self._send_as_bytes(socket, path) - self._send_as_bytes(socket, " HTTP/1.1\r\n") + self._send(socket, b" HTTP/1.1\r\n") + + # create lower-case supplied header list + supplied_headers = {header.lower() for header in headers} # Send headers - if not self._header_suppplied("Host", headers): + if not "host" in supplied_headers: self._send_header(socket, "Host", host) - if not self._header_suppplied("User-Agent", headers): + if not "user-agent" in supplied_headers: self._send_header(socket, "User-Agent", "Adafruit CircuitPython") - if content_type_header and not self._header_suppplied("Content-Type", headers): + if content_type_header and not "content-type" in supplied_headers: self._send_header(socket, "Content-Type", content_type_header) - if data and not self._header_suppplied("Content-Length", headers): + if data and not "content-length" in supplied_headers: self._send_header(socket, "Content-Length", str(len(data))) # Iterate over keys to avoid tuple alloc for header in headers: From 66fb48c2a66f4a3aa251acfc1d854c95b1b11dbc Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Mon, 1 Jan 2024 17:17:22 -0800 Subject: [PATCH 175/305] Remove duplicate typing --- adafruit_requests.py | 94 ++++---------------------------------------- 1 file changed, 7 insertions(+), 87 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index b037c32..4dfdb6d 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -46,94 +46,14 @@ from adafruit_connection_manager import get_connection_manager - if not sys.implementation.name == "circuitpython": - from ssl import SSLContext - from types import ModuleType, TracebackType - from typing import Any, Dict, Optional, Tuple, Type, Union - - try: - from typing import Protocol - except ImportError: - from typing_extensions import Protocol - - # Based on https://github.com/python/typeshed/blob/master/stdlib/_socket.pyi - class CommonSocketType(Protocol): - """Describes the common structure every socket type must have.""" - - def send(self, data: bytes, flags: int = ...) -> None: - """Send data to the socket. The meaning of the optional flags kwarg is - implementation-specific.""" - - def settimeout(self, value: Optional[float]) -> None: - """Set a timeout on blocking socket operations.""" - - def close(self) -> None: - """Close the socket.""" - - class CommonCircuitPythonSocketType(CommonSocketType, Protocol): - """Describes the common structure every CircuitPython socket type must have.""" - - def connect( - self, - address: Tuple[str, int], - conntype: Optional[int] = ..., - ) -> None: - """Connect to a remote socket at the provided (host, port) address. The conntype - kwarg optionally may indicate SSL or not, depending on the underlying interface. - """ - - class SupportsRecvWithFlags(Protocol): - """Describes a type that posseses a socket recv() method supporting the flags kwarg.""" - - def recv(self, bufsize: int = ..., flags: int = ...) -> bytes: - """Receive data from the socket. The return value is a bytes object representing - the data received. The maximum amount of data to be received at once is specified - by bufsize. The meaning of the optional flags kwarg is implementation-specific. - """ - - class SupportsRecvInto(Protocol): - """Describes a type that possesses a socket recv_into() method.""" - - def recv_into( - self, buffer: bytearray, nbytes: int = ..., flags: int = ... - ) -> int: - """Receive up to nbytes bytes from the socket, storing the data into the provided - buffer. If nbytes is not specified (or 0), receive up to the size available in the - given buffer. The meaning of the optional flags kwarg is implementation-specific. - Returns the number of bytes received.""" - - class CircuitPythonSocketType( - CommonCircuitPythonSocketType, - SupportsRecvInto, - SupportsRecvWithFlags, - Protocol, - ): # pylint: disable=too-many-ancestors - """Describes the structure every modern CircuitPython socket type must have.""" - - class StandardPythonSocketType( - CommonSocketType, SupportsRecvInto, SupportsRecvWithFlags, Protocol - ): - """Describes the structure every standard Python socket type must have.""" - - def connect(self, address: Union[Tuple[Any, ...], str, bytes]) -> None: - """Connect to a remote socket at the provided address.""" - - SocketType = Union[ - CircuitPythonSocketType, - StandardPythonSocketType, - ] - - SocketpoolModuleType = ModuleType - - class InterfaceType(Protocol): - """Describes the structure every interface type must have.""" - - @property - def TLS_MODE(self) -> int: # pylint: disable=invalid-name - """Constant representing that a socket's connection mode is TLS.""" - - SSLContextType = Union[SSLContext, "_FakeSSLContext"] + from types import TracebackType + from typing import Any, Dict, Optional, Type + from adafruit_connection_manager import ( + SocketType, + SocketpoolModuleType, + SSLContextType, + ) class _RawResponse: From 3bf143fac74a0691ac10d6db370a12e0e3ed7c7f Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Fri, 26 Jan 2024 08:52:18 -0800 Subject: [PATCH 176/305] Correctly handle headers of different types --- adafruit_requests.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 26b40f0..c700a8d 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -529,6 +529,18 @@ def _get_socket( self._socket_free[sock] = False return sock + @staticmethod + def _check_headers(headers: Dict[str, str]): + if not isinstance(headers, dict): + raise AttributeError("headers must be in dict format") + + for key, value in headers.items(): + if isinstance(value, (str, bytes)) or value is None: + continue + raise AttributeError( + f"Header part ({value}) from {key} must be of type str or bytes, not {type(value)}" + ) + @staticmethod def _send(socket: SocketType, data: bytes): total_sent = 0 @@ -555,9 +567,14 @@ def _send_as_bytes(self, socket: SocketType, data: str): return self._send(socket, bytes(data, "utf-8")) def _send_header(self, socket, header, value): + if value is None: + return self._send_as_bytes(socket, header) self._send(socket, b": ") - self._send_as_bytes(socket, value) + if isinstance(value, bytes): + self._send(socket, value) + else: + self._send_as_bytes(socket, value) self._send(socket, b"\r\n") # pylint: disable=too-many-arguments @@ -571,6 +588,9 @@ def _send_request( data: Any, json: Any, ): + # Check headers + self._check_headers(headers) + # Convert data content_type_header = None From 9af1adc52fb8ccbcce1424aa0043fa96de4919cb Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 3 Feb 2024 14:20:21 -0800 Subject: [PATCH 177/305] Remove _global_session and replace with create_default_session and add session_id support --- adafruit_requests.py | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 4dd0650..d7d0f66 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -356,9 +356,6 @@ def iter_content(self, chunk_size: int = 1, decode_unicode: bool = False) -> byt self.close() -_global_session = None # pylint: disable=invalid-name - - class Session: """HTTP session that shares sockets and ssl context.""" @@ -366,17 +363,13 @@ def __init__( self, socket_pool: SocketpoolModuleType, ssl_context: Optional[SSLContextType] = None, - set_global_session: bool = True, + session_id: Optional[str] = None, ) -> None: self._connection_manager = get_connection_manager(socket_pool) self._ssl_context = ssl_context + self._session_id = session_id self._last_response = None - if set_global_session: - # pylint: disable=global-statement - global _global_session - _global_session = self - @staticmethod def _check_headers(headers: Dict[str, str]): if not isinstance(headers, dict): @@ -537,7 +530,12 @@ def request( while retry_count < 2: retry_count += 1 socket = self._connection_manager.get_socket( - host, port, proto, timeout=timeout, ssl_context=self._ssl_context + host, + port, + proto, + session_id=self._session_id, + timeout=timeout, + ssl_context=self._ssl_context, ) ok = True try: @@ -618,6 +616,18 @@ def delete(self, url: str, **kw) -> Response: return self.request("DELETE", url, **kw) +_default_session = None # pylint: disable=invalid-name + + +def create_default_session( + socket_pool: SocketpoolModuleType, + ssl_context: Optional[SSLContextType] = None, +): + """Create a default session for using globally""" + global _default_session # pylint: disable=global-statement + _default_session = Session(socket_pool, ssl_context) + + def request( method: str, url: str, @@ -629,7 +639,7 @@ def request( ) -> None: """Send HTTP request""" # pylint: disable=too-many-arguments - _global_session.request( + _default_session.request( method, url, data=data, @@ -642,29 +652,29 @@ def request( def head(url: str, **kw): """Send HTTP HEAD request""" - return _global_session.request("HEAD", url, **kw) + return _default_session.request("HEAD", url, **kw) def get(url: str, **kw): """Send HTTP GET request""" - return _global_session.request("GET", url, **kw) + return _default_session.request("GET", url, **kw) def post(url: str, **kw): """Send HTTP POST request""" - return _global_session.request("POST", url, **kw) + return _default_session.request("POST", url, **kw) def put(url: str, **kw): """Send HTTP PUT request""" - return _global_session.request("PUT", url, **kw) + return _default_session.request("PUT", url, **kw) def patch(url: str, **kw): """Send HTTP PATCH request""" - return _global_session.request("PATCH", url, **kw) + return _default_session.request("PATCH", url, **kw) def delete(url: str, **kw): """Send HTTP DELETE request""" - return _global_session.request("DELETE", url, **kw) + return _default_session.request("DELETE", url, **kw) From dbd43e91d086d968d7756c2792ac4a765cdeb2af Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Wed, 14 Feb 2024 08:59:00 -0800 Subject: [PATCH 178/305] Update typing to pull from circuitpython_typing --- adafruit_requests.py | 2 +- tests/protocol_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index d7d0f66..9776a44 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -49,7 +49,7 @@ if not sys.implementation.name == "circuitpython": from types import TracebackType from typing import Any, Dict, Optional, Type - from adafruit_connection_manager import ( + from circuitpython_typing.socket import ( SocketType, SocketpoolModuleType, SSLContextType, diff --git a/tests/protocol_test.py b/tests/protocol_test.py index 2a5a930..fc9e210 100644 --- a/tests/protocol_test.py +++ b/tests/protocol_test.py @@ -23,7 +23,7 @@ def test_get_https_no_ssl(): pool.socket.return_value = sock requests_session = adafruit_requests.Session(pool) - with pytest.raises(RuntimeError): + with pytest.raises(AttributeError): requests_session.get("https://" + HOST + PATH) From c397f84c4c65105d69507a134743f02d28812ecd Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Tue, 20 Feb 2024 16:18:25 -0800 Subject: [PATCH 179/305] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d83a678..2505288 100755 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ # SPDX-License-Identifier: Unlicense Adafruit-Blinka -Adafruit-Circuitpython-ConnectionManager@git+https://github.com/justmobilize/Adafruit_CircuitPython_ConnectionManager@connection-manager +Adafruit-Circuitpython-ConnectionManager From 9e7e701d22ccfa879b94626257450703cca8403a Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Wed, 21 Feb 2024 07:16:20 -0800 Subject: [PATCH 180/305] Add isort --- .pre-commit-config.yaml | 11 ++++++++--- adafruit_requests.py | 6 +++--- conftest.py | 2 +- docs/conf.py | 2 +- examples/requests_adafruit_discord_active_online.py | 8 +++++--- examples/requests_advanced.py | 5 +++-- examples/requests_advanced_cellular.py | 6 ++++-- examples/requests_advanced_cpython.py | 1 + examples/requests_advanced_ethernet.py | 5 +++-- examples/requests_api_discord.py | 8 +++++--- examples/requests_api_fitbit.py | 8 +++++--- examples/requests_api_github.py | 8 +++++--- examples/requests_api_mastodon.py | 6 ++++-- examples/requests_api_openskynetwork_private.py | 10 ++++++---- examples/requests_api_openskynetwork_private_area.py | 10 ++++++---- examples/requests_api_openskynetwork_public.py | 8 +++++--- examples/requests_api_steam.py | 10 ++++++---- examples/requests_api_twitch.py | 6 ++++-- examples/requests_api_twitter.py | 8 +++++--- examples/requests_api_youtube.py | 8 +++++--- examples/requests_github_cpython.py | 1 + examples/requests_https_circuitpython.py | 4 ++-- examples/requests_https_cpython.py | 1 + examples/requests_multiple_cookies.py | 4 +++- examples/requests_simpletest.py | 6 ++++-- examples/requests_simpletest_cellular.py | 6 ++++-- examples/requests_simpletest_cpython.py | 1 + examples/requests_simpletest_ethernet.py | 5 +++-- tests/chunk_test.py | 2 ++ tests/chunked_redirect_test.py | 2 ++ tests/concurrent_test.py | 2 ++ tests/header_test.py | 1 + tests/parse_test.py | 2 ++ tests/post_test.py | 4 +++- tests/protocol_test.py | 2 ++ tests/reuse_test.py | 2 ++ tox.ini | 1 + 37 files changed, 121 insertions(+), 61 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e2c8831..77ed663 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,9 +4,14 @@ repos: - repo: https://github.com/python/black - rev: 23.3.0 + rev: 24.2.0 hooks: - id: black + - repo: https://github.com/PyCQA/isort + rev: 5.13.2 + hooks: + - id: isort + args: ["--profile", "black", "--filter-files"] - repo: https://github.com/fsfe/reuse-tool rev: v1.1.2 hooks: @@ -32,11 +37,11 @@ repos: types: [python] files: "^examples/" args: - - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code + - --disable=consider-using-f-string,duplicate-code,missing-docstring,invalid-name - id: pylint name: pylint (test code) description: Run pylint rules on "tests/*.py" files types: [python] files: "^tests/" args: - - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code + - --disable=consider-using-f-string,duplicate-code,missing-docstring,invalid-name,protected-access diff --git a/adafruit_requests.py b/adafruit_requests.py index 9776a44..b20cf23 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -40,18 +40,18 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Requests.git" import errno -import sys - import json as json_module +import sys from adafruit_connection_manager import get_connection_manager if not sys.implementation.name == "circuitpython": from types import TracebackType from typing import Any, Dict, Optional, Type + from circuitpython_typing.socket import ( - SocketType, SocketpoolModuleType, + SocketType, SSLContextType, ) diff --git a/conftest.py b/conftest.py index 376dd7d..01126ba 100644 --- a/conftest.py +++ b/conftest.py @@ -4,8 +4,8 @@ """ PyTest Setup """ -import pytest import adafruit_connection_manager +import pytest @pytest.fixture(autouse=True) diff --git a/docs/conf.py b/docs/conf.py index ed4d3f1..4e19482 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,9 +4,9 @@ # # SPDX-License-Identifier: MIT +import datetime import os import sys -import datetime sys.path.insert(0, os.path.abspath("..")) diff --git a/examples/requests_adafruit_discord_active_online.py b/examples/requests_adafruit_discord_active_online.py index 48a201c..ff81e0b 100644 --- a/examples/requests_adafruit_discord_active_online.py +++ b/examples/requests_adafruit_discord_active_online.py @@ -5,12 +5,14 @@ requests_adafruit_discord_active_online """ import gc +import json import os -import time import ssl -import json -import wifi +import time + import socketpool +import wifi + import adafruit_requests # Public API. No user or token required diff --git a/examples/requests_advanced.py b/examples/requests_advanced.py index 65ec126..99869b9 100644 --- a/examples/requests_advanced.py +++ b/examples/requests_advanced.py @@ -1,11 +1,12 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import adafruit_esp32spi.adafruit_esp32spi_socket as socket import board import busio -from digitalio import DigitalInOut -import adafruit_esp32spi.adafruit_esp32spi_socket as socket from adafruit_esp32spi import adafruit_esp32spi +from digitalio import DigitalInOut + import adafruit_requests as requests # Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and diff --git a/examples/requests_advanced_cellular.py b/examples/requests_advanced_cellular.py index ea8d512..2c5baed 100755 --- a/examples/requests_advanced_cellular.py +++ b/examples/requests_advanced_cellular.py @@ -3,13 +3,15 @@ # pylint: disable=unused-import import time + +import adafruit_fona.adafruit_fona_network as network +import adafruit_fona.adafruit_fona_socket as cellular_socket import board import busio import digitalio from adafruit_fona.adafruit_fona import FONA from adafruit_fona.fona_3g import FONA3G -import adafruit_fona.adafruit_fona_network as network -import adafruit_fona.adafruit_fona_socket as cellular_socket + import adafruit_requests as requests # Get GPRS details and more from a secrets.py file diff --git a/examples/requests_advanced_cpython.py b/examples/requests_advanced_cpython.py index 6a9e109..e2d14bc 100644 --- a/examples/requests_advanced_cpython.py +++ b/examples/requests_advanced_cpython.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: MIT import socket + import adafruit_requests http = adafruit_requests.Session(socket) diff --git a/examples/requests_advanced_ethernet.py b/examples/requests_advanced_ethernet.py index e9ecf57..a4171ce 100644 --- a/examples/requests_advanced_ethernet.py +++ b/examples/requests_advanced_ethernet.py @@ -1,11 +1,12 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket import board import busio -from digitalio import DigitalInOut from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K -import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket +from digitalio import DigitalInOut + import adafruit_requests as requests cs = DigitalInOut(board.D10) diff --git a/examples/requests_api_discord.py b/examples/requests_api_discord.py index b6e0348..b6eb0f0 100644 --- a/examples/requests_api_discord.py +++ b/examples/requests_api_discord.py @@ -2,12 +2,14 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2 # DJDevon3 Adafruit Feather ESP32-S3 Discord API Example +import json import os -import time import ssl -import json -import wifi +import time + import socketpool +import wifi + import adafruit_requests # Active Logged in User Account Required, no tokens required diff --git a/examples/requests_api_fitbit.py b/examples/requests_api_fitbit.py index 6494237..cf1c9bd 100644 --- a/examples/requests_api_fitbit.py +++ b/examples/requests_api_fitbit.py @@ -3,11 +3,13 @@ # Coded for Circuit Python 8.2 import os -import time import ssl -import wifi -import socketpool +import time + import microcontroller +import socketpool +import wifi + import adafruit_requests # Initialize WiFi Pool (There can be only 1 pool & top of script) diff --git a/examples/requests_api_github.py b/examples/requests_api_github.py index a220df7..5159d62 100644 --- a/examples/requests_api_github.py +++ b/examples/requests_api_github.py @@ -3,11 +3,13 @@ # Coded for Circuit Python 8.0 """DJDevon3 Adafruit Feather ESP32-S2 Github_API_Example""" import gc -import time -import ssl import json -import wifi +import ssl +import time + import socketpool +import wifi + import adafruit_requests # Github developer token required. diff --git a/examples/requests_api_mastodon.py b/examples/requests_api_mastodon.py index 893746c..4e28432 100644 --- a/examples/requests_api_mastodon.py +++ b/examples/requests_api_mastodon.py @@ -3,10 +3,12 @@ # Coded for Circuit Python 8.0 """DJDevon3 Adafruit Feather ESP32-S2 Mastodon_API_Example""" import gc -import time import ssl -import wifi +import time + import socketpool +import wifi + import adafruit_requests # Mastodon V1 API - Public access (no dev creds or app required) diff --git a/examples/requests_api_openskynetwork_private.py b/examples/requests_api_openskynetwork_private.py index a69eba8..c1999e1 100644 --- a/examples/requests_api_openskynetwork_private.py +++ b/examples/requests_api_openskynetwork_private.py @@ -3,13 +3,15 @@ # Coded for Circuit Python 8.1 # DJDevon3 ESP32-S3 OpenSkyNetwork_Private_API_Example +import json import os -import time import ssl -import json -import wifi -import socketpool +import time + import circuitpython_base64 as base64 +import socketpool +import wifi + import adafruit_requests # OpenSky-Network.org Login required for this API diff --git a/examples/requests_api_openskynetwork_private_area.py b/examples/requests_api_openskynetwork_private_area.py index e474e48..2baabbe 100644 --- a/examples/requests_api_openskynetwork_private_area.py +++ b/examples/requests_api_openskynetwork_private_area.py @@ -3,13 +3,15 @@ # Coded for Circuit Python 8.1 # DJDevon3 ESP32-S3 OpenSkyNetwork_Private_Area_API_Example +import json import os -import time import ssl -import json -import wifi -import socketpool +import time + import circuitpython_base64 as base64 +import socketpool +import wifi + import adafruit_requests # OpenSky-Network.org Website Login required for this API diff --git a/examples/requests_api_openskynetwork_public.py b/examples/requests_api_openskynetwork_public.py index e114cb7..1388efd 100644 --- a/examples/requests_api_openskynetwork_public.py +++ b/examples/requests_api_openskynetwork_public.py @@ -2,12 +2,14 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.1 # Adafruit Feather ESP32-S3 OpenSkyNetwork_Public_API_Example +import json import os -import time import ssl -import json -import wifi +import time + import socketpool +import wifi + import adafruit_requests # No login necessary for Public API. Drastically reduced daily limit vs Private diff --git a/examples/requests_api_steam.py b/examples/requests_api_steam.py index d29406c..7496c04 100644 --- a/examples/requests_api_steam.py +++ b/examples/requests_api_steam.py @@ -2,13 +2,15 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.0 """DJDevon3 Adafruit Feather ESP32-S2 api_steam Example""" -import os import gc -import time -import ssl import json -import wifi +import os +import ssl +import time + import socketpool +import wifi + import adafruit_requests # Steam API Docs: https://steamcommunity.com/dev diff --git a/examples/requests_api_twitch.py b/examples/requests_api_twitch.py index 396712f..ad5cf3a 100644 --- a/examples/requests_api_twitch.py +++ b/examples/requests_api_twitch.py @@ -4,10 +4,12 @@ # Twitch_API_Example import os -import time import ssl -import wifi +import time + import socketpool +import wifi + import adafruit_requests # Initialize WiFi Pool (There can be only 1 pool & top of script) diff --git a/examples/requests_api_twitter.py b/examples/requests_api_twitter.py index 995c790..be06f6a 100644 --- a/examples/requests_api_twitter.py +++ b/examples/requests_api_twitter.py @@ -3,11 +3,13 @@ # Coded for Circuit Python 8.0 """DJDevon3 Adafruit Feather ESP32-S2 Twitter_API_Example""" import gc -import time -import ssl import json -import wifi +import ssl +import time + import socketpool +import wifi + import adafruit_requests # Twitter developer account bearer token required. diff --git a/examples/requests_api_youtube.py b/examples/requests_api_youtube.py index 5fdacbb..22cb9b3 100644 --- a/examples/requests_api_youtube.py +++ b/examples/requests_api_youtube.py @@ -3,11 +3,13 @@ # Coded for Circuit Python 8.0 """DJDevon3 Adafruit Feather ESP32-S2 YouTube_API_Example""" import gc -import time -import ssl import json -import wifi +import ssl +import time + import socketpool +import wifi + import adafruit_requests # Ensure these are uncommented and in secrets.py or .env diff --git a/examples/requests_github_cpython.py b/examples/requests_github_cpython.py index 88d7135..b8e729f 100755 --- a/examples/requests_github_cpython.py +++ b/examples/requests_github_cpython.py @@ -4,6 +4,7 @@ # adafruit_requests usage with a CPython socket import socket import ssl + import adafruit_requests http = adafruit_requests.Session(socket, ssl.create_default_context()) diff --git a/examples/requests_https_circuitpython.py b/examples/requests_https_circuitpython.py index 026aa2d..63b5c81 100755 --- a/examples/requests_https_circuitpython.py +++ b/examples/requests_https_circuitpython.py @@ -5,12 +5,12 @@ # this has been tested with Adafruit Metro ESP32-S2 Express import ssl -import wifi + import socketpool +import wifi import adafruit_requests as requests - # Get wifi details and more from a secrets.py file try: from secrets import secrets diff --git a/examples/requests_https_cpython.py b/examples/requests_https_cpython.py index 39bd6dc..8cb1587 100755 --- a/examples/requests_https_cpython.py +++ b/examples/requests_https_cpython.py @@ -4,6 +4,7 @@ # adafruit_requests usage with a CPython socket import socket import ssl + import adafruit_requests as requests https = requests.Session(socket, ssl.create_default_context()) diff --git a/examples/requests_multiple_cookies.py b/examples/requests_multiple_cookies.py index 45ae81f..653d3e0 100644 --- a/examples/requests_multiple_cookies.py +++ b/examples/requests_multiple_cookies.py @@ -7,8 +7,10 @@ """ import ssl -import wifi + import socketpool +import wifi + import adafruit_requests COOKIE_TEST_URL = "https://www.adafruit.com" diff --git a/examples/requests_simpletest.py b/examples/requests_simpletest.py index 14cf66c..fb93b9a 100755 --- a/examples/requests_simpletest.py +++ b/examples/requests_simpletest.py @@ -1,12 +1,14 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import adafruit_esp32spi.adafruit_esp32spi_socket as socket + # adafruit_requests usage with an esp32spi_socket import board import busio -from digitalio import DigitalInOut -import adafruit_esp32spi.adafruit_esp32spi_socket as socket from adafruit_esp32spi import adafruit_esp32spi +from digitalio import DigitalInOut + import adafruit_requests as requests # Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and diff --git a/examples/requests_simpletest_cellular.py b/examples/requests_simpletest_cellular.py index 6b4d1a7..2710147 100755 --- a/examples/requests_simpletest_cellular.py +++ b/examples/requests_simpletest_cellular.py @@ -3,13 +3,15 @@ # pylint: disable=unused-import import time + +import adafruit_fona.adafruit_fona_network as network +import adafruit_fona.adafruit_fona_socket as cellular_socket import board import busio import digitalio from adafruit_fona.adafruit_fona import FONA from adafruit_fona.fona_3g import FONA3G -import adafruit_fona.adafruit_fona_network as network -import adafruit_fona.adafruit_fona_socket as cellular_socket + import adafruit_requests as requests # Get GPRS details and more from a secrets.py file diff --git a/examples/requests_simpletest_cpython.py b/examples/requests_simpletest_cpython.py index be51d57..58719f6 100755 --- a/examples/requests_simpletest_cpython.py +++ b/examples/requests_simpletest_cpython.py @@ -3,6 +3,7 @@ # adafruit_requests usage with a CPython socket import socket + import adafruit_requests http = adafruit_requests.Session(socket) diff --git a/examples/requests_simpletest_ethernet.py b/examples/requests_simpletest_ethernet.py index a432bea..1489c3a 100644 --- a/examples/requests_simpletest_ethernet.py +++ b/examples/requests_simpletest_ethernet.py @@ -1,11 +1,12 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket import board import busio -from digitalio import DigitalInOut from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K -import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket +from digitalio import DigitalInOut + import adafruit_requests as requests cs = DigitalInOut(board.D10) diff --git a/tests/chunk_test.py b/tests/chunk_test.py index df18bfe..ec4606d 100644 --- a/tests/chunk_test.py +++ b/tests/chunk_test.py @@ -5,7 +5,9 @@ """ Chunk Tests """ from unittest import mock + import mocket + import adafruit_requests IP = "1.2.3.4" diff --git a/tests/chunked_redirect_test.py b/tests/chunked_redirect_test.py index bb8ce9e..2a9392a 100644 --- a/tests/chunked_redirect_test.py +++ b/tests/chunked_redirect_test.py @@ -5,8 +5,10 @@ """ Redirection Tests """ from unittest import mock + import mocket from chunk_test import _chunk + import adafruit_requests IP = "1.2.3.4" diff --git a/tests/concurrent_test.py b/tests/concurrent_test.py index ec972ef..c70b7c2 100644 --- a/tests/concurrent_test.py +++ b/tests/concurrent_test.py @@ -6,7 +6,9 @@ import errno from unittest import mock + import mocket + import adafruit_requests IP = "1.2.3.4" diff --git a/tests/header_test.py b/tests/header_test.py index ee0fad4..f17501d 100644 --- a/tests/header_test.py +++ b/tests/header_test.py @@ -5,6 +5,7 @@ """ Header Tests """ import mocket + import adafruit_requests IP = "1.2.3.4" diff --git a/tests/parse_test.py b/tests/parse_test.py index 36ccd10..c13a720 100644 --- a/tests/parse_test.py +++ b/tests/parse_test.py @@ -5,7 +5,9 @@ """ Parse Tests """ import json + import mocket + import adafruit_requests IP = "1.2.3.4" diff --git a/tests/post_test.py b/tests/post_test.py index 226efd4..4ead7d1 100644 --- a/tests/post_test.py +++ b/tests/post_test.py @@ -4,9 +4,11 @@ """ Post Tests """ -from unittest import mock import json +from unittest import mock + import mocket + import adafruit_requests IP = "1.2.3.4" diff --git a/tests/protocol_test.py b/tests/protocol_test.py index fc9e210..33bf11c 100644 --- a/tests/protocol_test.py +++ b/tests/protocol_test.py @@ -5,8 +5,10 @@ """ Protocol Tests """ from unittest import mock + import mocket import pytest + import adafruit_requests IP = "1.2.3.4" diff --git a/tests/reuse_test.py b/tests/reuse_test.py index b778c0a..0cf42a1 100644 --- a/tests/reuse_test.py +++ b/tests/reuse_test.py @@ -5,8 +5,10 @@ """ Reuse Tests """ from unittest import mock + import mocket import pytest + import adafruit_requests IP = "1.2.3.4" diff --git a/tox.ini b/tox.ini index 9d1910d..85530c9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,5 @@ # SPDX-FileCopyrightText: 2022 Kevin Conley +# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries # # SPDX-License-Identifier: MIT From 3d3e3031d8167dfeebf789427cfec8764096aabe Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Wed, 21 Feb 2024 07:38:28 -0800 Subject: [PATCH 181/305] Rename examples --- docs/examples.rst | 4 +- ...lular.py => requests_cellular_advanced.py} | 0 ...lar.py => requests_cellular_simpletest.py} | 0 ...python.py => requests_cpython_advanced.py} | 0 ..._cpython.py => requests_cpython_github.py} | 0 ...s_cpython.py => requests_cpython_https.py} | 0 ...thon.py => requests_cpython_simpletest.py} | 0 ...anced.py => requests_esp32spi_advanced.py} | 0 ...est.py => requests_esp32spi_simpletest.py} | 0 ...ts_wifi_adafruit_discord_active_online.py} | 0 ...iscord.py => requests_wifi_api_discord.py} | 0 ..._fitbit.py => requests_wifi_api_fitbit.py} | 0 ..._github.py => requests_wifi_api_github.py} | 0 ...todon.py => requests_wifi_api_mastodon.py} | 0 ...quests_wifi_api_openskynetwork_private.py} | 0 ...s_wifi_api_openskynetwork_private_area.py} | 0 ...equests_wifi_api_openskynetwork_public.py} | 0 ...pi_steam.py => requests_wifi_api_steam.py} | 0 ..._twitch.py => requests_wifi_api_twitch.py} | 0 ...witter.py => requests_wifi_api_twitter.py} | 0 ...outube.py => requests_wifi_api_youtube.py} | 0 ...y => requests_wifi_https_circuitpython.py} | 0 ...s.py => requests_wifi_multiple_cookies.py} | 0 examples/requests_wifi_simpletest.py | 76 +++++++++++++++++++ ...ernet.py => requests_wiznet5k_advanced.py} | 0 ...net.py => requests_wiznet5k_simpletest.py} | 0 26 files changed, 78 insertions(+), 2 deletions(-) rename examples/{requests_advanced_cellular.py => requests_cellular_advanced.py} (100%) mode change 100755 => 100644 rename examples/{requests_simpletest_cellular.py => requests_cellular_simpletest.py} (100%) mode change 100755 => 100644 rename examples/{requests_advanced_cpython.py => requests_cpython_advanced.py} (100%) rename examples/{requests_github_cpython.py => requests_cpython_github.py} (100%) mode change 100755 => 100644 rename examples/{requests_https_cpython.py => requests_cpython_https.py} (100%) mode change 100755 => 100644 rename examples/{requests_simpletest_cpython.py => requests_cpython_simpletest.py} (100%) mode change 100755 => 100644 rename examples/{requests_advanced.py => requests_esp32spi_advanced.py} (100%) rename examples/{requests_simpletest.py => requests_esp32spi_simpletest.py} (100%) mode change 100755 => 100644 rename examples/{requests_adafruit_discord_active_online.py => requests_wifi_adafruit_discord_active_online.py} (100%) rename examples/{requests_api_discord.py => requests_wifi_api_discord.py} (100%) rename examples/{requests_api_fitbit.py => requests_wifi_api_fitbit.py} (100%) rename examples/{requests_api_github.py => requests_wifi_api_github.py} (100%) rename examples/{requests_api_mastodon.py => requests_wifi_api_mastodon.py} (100%) rename examples/{requests_api_openskynetwork_private.py => requests_wifi_api_openskynetwork_private.py} (100%) rename examples/{requests_api_openskynetwork_private_area.py => requests_wifi_api_openskynetwork_private_area.py} (100%) rename examples/{requests_api_openskynetwork_public.py => requests_wifi_api_openskynetwork_public.py} (100%) rename examples/{requests_api_steam.py => requests_wifi_api_steam.py} (100%) rename examples/{requests_api_twitch.py => requests_wifi_api_twitch.py} (100%) rename examples/{requests_api_twitter.py => requests_wifi_api_twitter.py} (100%) rename examples/{requests_api_youtube.py => requests_wifi_api_youtube.py} (100%) rename examples/{requests_https_circuitpython.py => requests_wifi_https_circuitpython.py} (100%) mode change 100755 => 100644 rename examples/{requests_multiple_cookies.py => requests_wifi_multiple_cookies.py} (100%) create mode 100644 examples/requests_wifi_simpletest.py rename examples/{requests_advanced_ethernet.py => requests_wiznet5k_advanced.py} (100%) rename examples/{requests_simpletest_ethernet.py => requests_wiznet5k_simpletest.py} (100%) diff --git a/docs/examples.rst b/docs/examples.rst index 4c95536..83de490 100755 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -3,6 +3,6 @@ Simple test Ensure your device works with this simple test. -.. literalinclude:: ../examples/requests_simpletest.py - :caption: examples/requests_simpletest.py +.. literalinclude:: ../examples/requests_wifi_simpletest.py + :caption: examples/requests_wifi_simpletest.py :linenos: diff --git a/examples/requests_advanced_cellular.py b/examples/requests_cellular_advanced.py old mode 100755 new mode 100644 similarity index 100% rename from examples/requests_advanced_cellular.py rename to examples/requests_cellular_advanced.py diff --git a/examples/requests_simpletest_cellular.py b/examples/requests_cellular_simpletest.py old mode 100755 new mode 100644 similarity index 100% rename from examples/requests_simpletest_cellular.py rename to examples/requests_cellular_simpletest.py diff --git a/examples/requests_advanced_cpython.py b/examples/requests_cpython_advanced.py similarity index 100% rename from examples/requests_advanced_cpython.py rename to examples/requests_cpython_advanced.py diff --git a/examples/requests_github_cpython.py b/examples/requests_cpython_github.py old mode 100755 new mode 100644 similarity index 100% rename from examples/requests_github_cpython.py rename to examples/requests_cpython_github.py diff --git a/examples/requests_https_cpython.py b/examples/requests_cpython_https.py old mode 100755 new mode 100644 similarity index 100% rename from examples/requests_https_cpython.py rename to examples/requests_cpython_https.py diff --git a/examples/requests_simpletest_cpython.py b/examples/requests_cpython_simpletest.py old mode 100755 new mode 100644 similarity index 100% rename from examples/requests_simpletest_cpython.py rename to examples/requests_cpython_simpletest.py diff --git a/examples/requests_advanced.py b/examples/requests_esp32spi_advanced.py similarity index 100% rename from examples/requests_advanced.py rename to examples/requests_esp32spi_advanced.py diff --git a/examples/requests_simpletest.py b/examples/requests_esp32spi_simpletest.py old mode 100755 new mode 100644 similarity index 100% rename from examples/requests_simpletest.py rename to examples/requests_esp32spi_simpletest.py diff --git a/examples/requests_adafruit_discord_active_online.py b/examples/requests_wifi_adafruit_discord_active_online.py similarity index 100% rename from examples/requests_adafruit_discord_active_online.py rename to examples/requests_wifi_adafruit_discord_active_online.py diff --git a/examples/requests_api_discord.py b/examples/requests_wifi_api_discord.py similarity index 100% rename from examples/requests_api_discord.py rename to examples/requests_wifi_api_discord.py diff --git a/examples/requests_api_fitbit.py b/examples/requests_wifi_api_fitbit.py similarity index 100% rename from examples/requests_api_fitbit.py rename to examples/requests_wifi_api_fitbit.py diff --git a/examples/requests_api_github.py b/examples/requests_wifi_api_github.py similarity index 100% rename from examples/requests_api_github.py rename to examples/requests_wifi_api_github.py diff --git a/examples/requests_api_mastodon.py b/examples/requests_wifi_api_mastodon.py similarity index 100% rename from examples/requests_api_mastodon.py rename to examples/requests_wifi_api_mastodon.py diff --git a/examples/requests_api_openskynetwork_private.py b/examples/requests_wifi_api_openskynetwork_private.py similarity index 100% rename from examples/requests_api_openskynetwork_private.py rename to examples/requests_wifi_api_openskynetwork_private.py diff --git a/examples/requests_api_openskynetwork_private_area.py b/examples/requests_wifi_api_openskynetwork_private_area.py similarity index 100% rename from examples/requests_api_openskynetwork_private_area.py rename to examples/requests_wifi_api_openskynetwork_private_area.py diff --git a/examples/requests_api_openskynetwork_public.py b/examples/requests_wifi_api_openskynetwork_public.py similarity index 100% rename from examples/requests_api_openskynetwork_public.py rename to examples/requests_wifi_api_openskynetwork_public.py diff --git a/examples/requests_api_steam.py b/examples/requests_wifi_api_steam.py similarity index 100% rename from examples/requests_api_steam.py rename to examples/requests_wifi_api_steam.py diff --git a/examples/requests_api_twitch.py b/examples/requests_wifi_api_twitch.py similarity index 100% rename from examples/requests_api_twitch.py rename to examples/requests_wifi_api_twitch.py diff --git a/examples/requests_api_twitter.py b/examples/requests_wifi_api_twitter.py similarity index 100% rename from examples/requests_api_twitter.py rename to examples/requests_wifi_api_twitter.py diff --git a/examples/requests_api_youtube.py b/examples/requests_wifi_api_youtube.py similarity index 100% rename from examples/requests_api_youtube.py rename to examples/requests_wifi_api_youtube.py diff --git a/examples/requests_https_circuitpython.py b/examples/requests_wifi_https_circuitpython.py old mode 100755 new mode 100644 similarity index 100% rename from examples/requests_https_circuitpython.py rename to examples/requests_wifi_https_circuitpython.py diff --git a/examples/requests_multiple_cookies.py b/examples/requests_wifi_multiple_cookies.py similarity index 100% rename from examples/requests_multiple_cookies.py rename to examples/requests_wifi_multiple_cookies.py diff --git a/examples/requests_wifi_simpletest.py b/examples/requests_wifi_simpletest.py new file mode 100644 index 0000000..6720362 --- /dev/null +++ b/examples/requests_wifi_simpletest.py @@ -0,0 +1,76 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + +# adafruit_requests usage with onboard wifi +import os +import ssl +import time + +import socketpool +import wifi + +import adafruit_requests + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + + +# Ensure these are setup in settings.toml +ssid = os.getenv("AP_SSID") +appw = os.getenv("AP_PASSWORD") + + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +requests = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, appw) + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) +print("Connected!\n") + +TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" +JSON_GET_URL = "https://httpbin.org/get" +JSON_POST_URL = "https://httpbin.org/post" + +print("Fetching text from %s" % TEXT_URL) +response = requests.get(TEXT_URL) +print("-" * 40) + +print("Text Response: ", response.text) +print("-" * 40) +response.close() + +print("Fetching JSON data from %s" % JSON_GET_URL) +response = requests.get(JSON_GET_URL) +print("-" * 40) + +print("JSON Response: ", response.json()) +print("-" * 40) +response.close() + +data = "31F" +print("POSTing data to {0}: {1}".format(JSON_POST_URL, data)) +response = requests.post(JSON_POST_URL, data=data) +print("-" * 40) + +json_resp = response.json() +# Parse out the 'data' key from json_resp dict. +print("Data received from server:", json_resp["data"]) +print("-" * 40) +response.close() + +json_data = {"Date": "July 25, 2019"} +print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data)) +response = requests.post(JSON_POST_URL, json=json_data) +print("-" * 40) + +json_resp = response.json() +# Parse out the 'json' key from json_resp dict. +print("JSON Data received from server:", json_resp["json"]) +print("-" * 40) +response.close() diff --git a/examples/requests_advanced_ethernet.py b/examples/requests_wiznet5k_advanced.py similarity index 100% rename from examples/requests_advanced_ethernet.py rename to examples/requests_wiznet5k_advanced.py diff --git a/examples/requests_simpletest_ethernet.py b/examples/requests_wiznet5k_simpletest.py similarity index 100% rename from examples/requests_simpletest_ethernet.py rename to examples/requests_wiznet5k_simpletest.py From 4f985541419f558a5de4cd0f91d725ec875cf32a Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Wed, 21 Feb 2024 08:25:41 -0800 Subject: [PATCH 182/305] Standardize simple and advanced examples --- examples/requests_cellular_advanced.py | 39 ++++++------ examples/requests_cellular_simpletest.py | 34 +++++------ examples/requests_cpython_advanced.py | 13 ++-- examples/requests_cpython_github.py | 17 ------ examples/requests_cpython_https.py | 49 ---------------- examples/requests_cpython_simpletest.py | 46 ++++----------- examples/requests_esp32spi_advanced.py | 40 +++++++------ examples/requests_esp32spi_simpletest.py | 33 +++++------ examples/requests_wifi_advanced.py | 51 ++++++++++++++++ examples/requests_wifi_simpletest.py | 30 ++++------ examples/requests_wiznet5k_advanced.py | 33 +++-------- examples/requests_wiznet5k_simpletest.py | 75 ++++-------------------- 12 files changed, 169 insertions(+), 291 deletions(-) delete mode 100644 examples/requests_cpython_github.py delete mode 100644 examples/requests_cpython_https.py create mode 100644 examples/requests_wifi_advanced.py diff --git a/examples/requests_cellular_advanced.py b/examples/requests_cellular_advanced.py index 2c5baed..33bc6f0 100644 --- a/examples/requests_cellular_advanced.py +++ b/examples/requests_cellular_advanced.py @@ -1,40 +1,37 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -# pylint: disable=unused-import +import os import time +import adafruit_connection_manager import adafruit_fona.adafruit_fona_network as network -import adafruit_fona.adafruit_fona_socket as cellular_socket +import adafruit_fona.adafruit_fona_socket as pool import board import busio import digitalio -from adafruit_fona.adafruit_fona import FONA -from adafruit_fona.fona_3g import FONA3G +from adafruit_fona.adafruit_fona import FONA # pylint: disable=unused-import +from adafruit_fona.fona_3g import FONA3G # pylint: disable=unused-import -import adafruit_requests as requests +import adafruit_requests -# Get GPRS details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("GPRS secrets are kept in secrets.py, please add them there!") - raise +# Get GPRS details, ensure these are setup in settings.toml +apn = os.getenv("APN") +apn_username = os.getenv("APN_USERNAME") +apn_password = os.getenv("APN_PASSWORD") # Create a serial connection for the FONA connection uart = busio.UART(board.TX, board.RX) -rst = digitalio.DigitalInOut(board.D9) +rst = digitalio.DigitalInOut(board.D4) # Use this for FONA800 and FONA808 -# fona = FONA(uart, rst) +radio = FONA(uart, rst) # Use this for FONA3G -fona = FONA3G(uart, rst) +# radio = FONA3G(uart, rst) # Initialize cellular data network -network = network.CELLULAR( - fona, (secrets["apn"], secrets["apn_username"], secrets["apn_password"]) -) +network = network.CELLULAR(radio, (apn, apn_username, apn_password)) while not network.is_attached: print("Attaching to network...") @@ -47,8 +44,9 @@ time.sleep(0.5) print("Network Connected!") -# Initialize a requests object with a socket and cellular interface -requests.set_socket(cellular_socket, fona) +# Initialize a requests session +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, radio) +requests = adafruit_requests.Session(pool, ssl_context) JSON_GET_URL = "http://httpbin.org/get" @@ -68,8 +66,5 @@ print("Response HTTP Status Code: ", response.status_code) print("-" * 60) -# Read Response, as raw bytes instead of pretty text -print("Raw Response: ", response.content) - # Close, delete and collect the response data response.close() diff --git a/examples/requests_cellular_simpletest.py b/examples/requests_cellular_simpletest.py index 2710147..8841d3d 100644 --- a/examples/requests_cellular_simpletest.py +++ b/examples/requests_cellular_simpletest.py @@ -1,40 +1,37 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -# pylint: disable=unused-import +import os import time +import adafruit_connection_manager import adafruit_fona.adafruit_fona_network as network -import adafruit_fona.adafruit_fona_socket as cellular_socket +import adafruit_fona.adafruit_fona_socket as pool import board import busio import digitalio -from adafruit_fona.adafruit_fona import FONA -from adafruit_fona.fona_3g import FONA3G +from adafruit_fona.adafruit_fona import FONA # pylint: disable=unused-import +from adafruit_fona.fona_3g import FONA3G # pylint: disable=unused-import -import adafruit_requests as requests +import adafruit_requests -# Get GPRS details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("GPRS secrets are kept in secrets.py, please add them there!") - raise +# Get GPRS details, ensure these are setup in settings.toml +apn = os.getenv("APN") +apn_username = os.getenv("APN_USERNAME") +apn_password = os.getenv("APN_PASSWORD") # Create a serial connection for the FONA connection uart = busio.UART(board.TX, board.RX) rst = digitalio.DigitalInOut(board.D4) # Use this for FONA800 and FONA808 -fona = FONA(uart, rst) +radio = FONA(uart, rst) # Use this for FONA3G -# fona = FONA3G(uart, rst) +# radio = FONA3G(uart, rst) # Initialize cellular data network -network = network.CELLULAR( - fona, (secrets["apn"], secrets["apn_username"], secrets["apn_password"]) -) +network = network.CELLULAR(radio, (apn, apn_username, apn_password)) while not network.is_attached: print("Attaching to network...") @@ -47,8 +44,9 @@ time.sleep(0.5) print("Network Connected!") -# Initialize a requests object with a socket and cellular interface -requests.set_socket(cellular_socket, fona) +# Initialize a requests session +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, radio) +requests = adafruit_requests.Session(pool, ssl_context) TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" JSON_GET_URL = "http://httpbin.org/get" diff --git a/examples/requests_cpython_advanced.py b/examples/requests_cpython_advanced.py index e2d14bc..89d715b 100644 --- a/examples/requests_cpython_advanced.py +++ b/examples/requests_cpython_advanced.py @@ -1,19 +1,21 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import socket +import socket as pool +import ssl import adafruit_requests -http = adafruit_requests.Session(socket) +# Initialize a requests session +requests = adafruit_requests.Session(pool, ssl.create_default_context()) -JSON_GET_URL = "http://httpbin.org/get" +JSON_GET_URL = "https://httpbin.org/get" # Define a custom header as a dict. headers = {"user-agent": "blinka/1.0.0"} print("Fetching JSON data from %s..." % JSON_GET_URL) -response = http.get(JSON_GET_URL, headers=headers) +response = requests.get(JSON_GET_URL, headers=headers) print("-" * 60) json_data = response.json() @@ -25,8 +27,5 @@ print("Response HTTP Status Code: ", response.status_code) print("-" * 60) -# Read Response, as raw bytes instead of pretty text -print("Raw Response: ", response.content) - # Close, delete and collect the response data response.close() diff --git a/examples/requests_cpython_github.py b/examples/requests_cpython_github.py deleted file mode 100644 index b8e729f..0000000 --- a/examples/requests_cpython_github.py +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# SPDX-License-Identifier: MIT - -# adafruit_requests usage with a CPython socket -import socket -import ssl - -import adafruit_requests - -http = adafruit_requests.Session(socket, ssl.create_default_context()) - -print("Getting CircuitPython star count") -headers = {"Transfer-Encoding": "chunked"} -response = http.get( - "https://api.github.com/repos/adafruit/circuitpython", headers=headers -) -print("circuitpython stars", response.json()["stargazers_count"]) diff --git a/examples/requests_cpython_https.py b/examples/requests_cpython_https.py deleted file mode 100644 index 8cb1587..0000000 --- a/examples/requests_cpython_https.py +++ /dev/null @@ -1,49 +0,0 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# SPDX-License-Identifier: MIT - -# adafruit_requests usage with a CPython socket -import socket -import ssl - -import adafruit_requests as requests - -https = requests.Session(socket, ssl.create_default_context()) - -TEXT_URL = "https://httpbin.org/get" -JSON_GET_URL = "https://httpbin.org/get" -JSON_POST_URL = "https://httpbin.org/post" - -# print("Fetching text from %s" % TEXT_URL) -# response = requests.get(TEXT_URL) -# print("-" * 40) - -# print("Text Response: ", response.text) -# print("-" * 40) -# response.close() - -print("Fetching JSON data from %s" % JSON_GET_URL) -response = https.get(JSON_GET_URL) -print("-" * 40) - -print("JSON Response: ", response.json()) -print("-" * 40) - -data = "31F" -print("POSTing data to {0}: {1}".format(JSON_POST_URL, data)) -response = https.post(JSON_POST_URL, data=data) -print("-" * 40) - -json_resp = response.json() -# Parse out the 'data' key from json_resp dict. -print("Data received from server:", json_resp["data"]) -print("-" * 40) - -json_data = {"Date": "July 25, 2019"} -print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data)) -response = https.post(JSON_POST_URL, json=json_data) -print("-" * 40) - -json_resp = response.json() -# Parse out the 'json' key from json_resp dict. -print("JSON Data received from server:", json_resp["json"]) -print("-" * 40) diff --git a/examples/requests_cpython_simpletest.py b/examples/requests_cpython_simpletest.py index 58719f6..70bdfde 100644 --- a/examples/requests_cpython_simpletest.py +++ b/examples/requests_cpython_simpletest.py @@ -1,29 +1,28 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -# adafruit_requests usage with a CPython socket -import socket +import socket as pool +import ssl import adafruit_requests -http = adafruit_requests.Session(socket) +# Initialize a requests session +requests = adafruit_requests.Session(pool, ssl.create_default_context()) TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" -JSON_GET_URL = "http://httpbin.org/get" -JSON_POST_URL = "http://httpbin.org/post" -REDIRECT_URL = "http://httpbingo.org/redirect/1" -RELATIVE_REDIRECT_URL = "http://httpbingo.org/relative-redirect/1" -ABSOLUTE_REDIRECT_URL = "http://httpbingo.org/absolute-redirect/1" +JSON_GET_URL = "https://httpbin.org/get" +JSON_POST_URL = "https://httpbin.org/post" print("Fetching text from %s" % TEXT_URL) -response = http.get(TEXT_URL) +response = requests.get(TEXT_URL) print("-" * 40) print("Text Response: ", response.text) print("-" * 40) +response.close() print("Fetching JSON data from %s" % JSON_GET_URL) -response = http.get(JSON_GET_URL) +response = requests.get(JSON_GET_URL) print("-" * 40) print("JSON Response: ", response.json()) @@ -32,43 +31,22 @@ data = "31F" print("POSTing data to {0}: {1}".format(JSON_POST_URL, data)) -response = http.post(JSON_POST_URL, data=data) +response = requests.post(JSON_POST_URL, data=data) print("-" * 40) json_resp = response.json() # Parse out the 'data' key from json_resp dict. print("Data received from server:", json_resp["data"]) print("-" * 40) +response.close() json_data = {"Date": "July 25, 2019"} print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data)) -response = http.post(JSON_POST_URL, json=json_data) +response = requests.post(JSON_POST_URL, json=json_data) print("-" * 40) json_resp = response.json() # Parse out the 'json' key from json_resp dict. print("JSON Data received from server:", json_resp["json"]) print("-" * 40) - -print("Fetching JSON data from redirect url %s" % REDIRECT_URL) -response = http.get(REDIRECT_URL) -print("-" * 40) - -print("JSON Response: ", response.json()) -print("-" * 40) - -print("Fetching JSON data from relative redirect url %s" % RELATIVE_REDIRECT_URL) -response = http.get(RELATIVE_REDIRECT_URL) -print("-" * 40) - -print("JSON Response: ", response.json()) -print("-" * 40) - -print("Fetching JSON data from aboslute redirect url %s" % ABSOLUTE_REDIRECT_URL) -response = http.get(ABSOLUTE_REDIRECT_URL) -print("-" * 40) - -print("JSON Response: ", response.json()) -print("-" * 40) - response.close() diff --git a/examples/requests_esp32spi_advanced.py b/examples/requests_esp32spi_advanced.py index 99869b9..4e5a15e 100644 --- a/examples/requests_esp32spi_advanced.py +++ b/examples/requests_esp32spi_advanced.py @@ -1,23 +1,20 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import os + +import adafruit_connection_manager +import adafruit_esp32spi.adafruit_esp32spi_socket as pool import board import busio from adafruit_esp32spi import adafruit_esp32spi from digitalio import DigitalInOut -import adafruit_requests as requests +import adafruit_requests -# Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and -# "password" keys with your WiFi credentials. DO NOT share that file or commit it into Git or other -# source control. -# pylint: disable=no-name-in-module,wrong-import-order -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -29,23 +26,28 @@ # esp32_ready = DigitalInOut(board.D10) # esp32_reset = DigitalInOut(board.D5) +# If you have an AirLift Featherwing or ItsyBitsy Airlift: +# esp32_cs = DigitalInOut(board.D13) +# esp32_ready = DigitalInOut(board.D11) +# esp32_reset = DigitalInOut(board.D12) + spi = busio.SPI(board.SCK, board.MOSI, board.MISO) -esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) +radio = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) print("Connecting to AP...") -while not esp.is_connected: +while not radio.is_connected: try: - esp.connect_AP(secrets["ssid"], secrets["password"]) + radio.connect_AP(ssid, appw) except RuntimeError as e: print("could not connect to AP, retrying: ", e) continue -print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi) +print("Connected to", str(radio.ssid, "utf-8"), "\tRSSI:", radio.rssi) -# Initialize a requests object with a socket and esp32spi interface -socket.set_interface(esp) -requests.set_socket(socket, esp) +# Initialize a requests session +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, radio) +requests = adafruit_requests.Session(pool, ssl_context) -JSON_GET_URL = "http://httpbin.org/get" +JSON_GET_URL = "https://httpbin.org/get" # Define a custom header as a dict. headers = {"user-agent": "blinka/1.0.0"} diff --git a/examples/requests_esp32spi_simpletest.py b/examples/requests_esp32spi_simpletest.py index fb93b9a..8c72797 100644 --- a/examples/requests_esp32spi_simpletest.py +++ b/examples/requests_esp32spi_simpletest.py @@ -1,25 +1,20 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import os -# adafruit_requests usage with an esp32spi_socket +import adafruit_connection_manager +import adafruit_esp32spi.adafruit_esp32spi_socket as pool import board import busio from adafruit_esp32spi import adafruit_esp32spi from digitalio import DigitalInOut -import adafruit_requests as requests +import adafruit_requests -# Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and -# "password" keys with your WiFi credentials. DO NOT share that file or commit it into Git or other -# source control. -# pylint: disable=no-name-in-module,wrong-import-order -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -37,20 +32,20 @@ # esp32_reset = DigitalInOut(board.D12) spi = busio.SPI(board.SCK, board.MOSI, board.MISO) -esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) +radio = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) print("Connecting to AP...") -while not esp.is_connected: +while not radio.is_connected: try: - esp.connect_AP(secrets["ssid"], secrets["password"]) + radio.connect_AP(ssid, appw) except RuntimeError as e: print("could not connect to AP, retrying: ", e) continue -print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi) +print("Connected to", str(radio.ssid, "utf-8"), "\tRSSI:", radio.rssi) -# Initialize a requests object with a socket and esp32spi interface -socket.set_interface(esp) -requests.set_socket(socket, esp) +# Initialize a requests session +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, radio) +requests = adafruit_requests.Session(pool, ssl_context) TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" JSON_GET_URL = "https://httpbin.org/get" diff --git a/examples/requests_wifi_advanced.py b/examples/requests_wifi_advanced.py new file mode 100644 index 0000000..e6985a4 --- /dev/null +++ b/examples/requests_wifi_advanced.py @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + +import os +import ssl + +import socketpool +import wifi + +import adafruit_requests + +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +radio = wifi.radio +pool = socketpool.SocketPool(radio) + +print("Connecting to AP...") +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, appw) + except ConnectionError as e: + print("could not connect to AP, retrying: ", e) +print("Connected to", str(radio.ap_info.ssid, "utf-8"), "\tRSSI:", radio.ap_info.rssi) + +# Initialize a requests session +ssl_context = ssl.create_default_context() +requests = adafruit_requests.Session(pool, ssl_context) + +JSON_GET_URL = "https://httpbin.org/get" + +# Define a custom header as a dict. +headers = {"user-agent": "blinka/1.0.0"} + +print("Fetching JSON data from %s..." % JSON_GET_URL) +response = requests.get(JSON_GET_URL, headers=headers) +print("-" * 60) + +json_data = response.json() +headers = json_data["headers"] +print("Response's Custom User-Agent Header: {0}".format(headers["User-Agent"])) +print("-" * 60) + +# Read Response's HTTP status code +print("Response HTTP Status Code: ", response.status_code) +print("-" * 60) + +# Close, delete and collect the response data +response.close() diff --git a/examples/requests_wifi_simpletest.py b/examples/requests_wifi_simpletest.py index 6720362..033b2db 100644 --- a/examples/requests_wifi_simpletest.py +++ b/examples/requests_wifi_simpletest.py @@ -1,37 +1,33 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -# adafruit_requests usage with onboard wifi import os import ssl -import time import socketpool import wifi import adafruit_requests -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) - - -# Ensure these are setup in settings.toml -ssid = os.getenv("AP_SSID") -appw = os.getenv("AP_PASSWORD") +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +# Initialize WiFi Pool (There can be only 1 pool & top of script) +radio = wifi.radio +pool = socketpool.SocketPool(radio) -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -requests = adafruit_requests.Session(pool, ssl.create_default_context()) +print("Connecting to AP...") while not wifi.radio.ipv4_address: try: wifi.radio.connect(ssid, appw) except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) -print("Connected!\n") + print("could not connect to AP, retrying: ", e) +print("Connected to", str(radio.ap_info.ssid, "utf-8"), "\tRSSI:", radio.ap_info.rssi) + +# Initialize a requests session +ssl_context = ssl.create_default_context() +requests = adafruit_requests.Session(pool, ssl_context) TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" JSON_GET_URL = "https://httpbin.org/get" diff --git a/examples/requests_wiznet5k_advanced.py b/examples/requests_wiznet5k_advanced.py index a4171ce..a6d9909 100644 --- a/examples/requests_wiznet5k_advanced.py +++ b/examples/requests_wiznet5k_advanced.py @@ -1,46 +1,32 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket +import adafruit_connection_manager +import adafruit_wiznet5k.adafruit_wiznet5k_socket as pool import board import busio from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K from digitalio import DigitalInOut -import adafruit_requests as requests +import adafruit_requests cs = DigitalInOut(board.D10) spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) # Initialize ethernet interface with DHCP -eth = WIZNET5K(spi_bus, cs) +radio = WIZNET5K(spi_bus, cs) -# Initialize a requests object with a socket and ethernet interface -requests.set_socket(socket, eth) +# Initialize a requests session +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, radio) +requests = adafruit_requests.Session(pool, ssl_context) JSON_GET_URL = "http://httpbin.org/get" -attempts = 3 # Number of attempts to retry each request -failure_count = 0 -response = None - # Define a custom header as a dict. headers = {"user-agent": "blinka/1.0.0"} print("Fetching JSON data from %s..." % JSON_GET_URL) -while not response: - try: - response = requests.get(JSON_GET_URL, headers=headers) - failure_count = 0 - except AssertionError as error: - print("Request failed, retrying...\n", error) - failure_count += 1 - if failure_count >= attempts: - raise AssertionError( - "Failed to resolve hostname, \ - please check your router's DNS configuration." - ) from error - continue +response = requests.get(JSON_GET_URL, headers=headers) print("-" * 60) json_data = response.json() @@ -52,8 +38,5 @@ print("Response HTTP Status Code: ", response.status_code) print("-" * 60) -# Read Response, as raw bytes instead of pretty text -print("Raw Response: ", response.content) - # Close, delete and collect the response data response.close() diff --git a/examples/requests_wiznet5k_simpletest.py b/examples/requests_wiznet5k_simpletest.py index 1489c3a..646107f 100644 --- a/examples/requests_wiznet5k_simpletest.py +++ b/examples/requests_wiznet5k_simpletest.py @@ -1,88 +1,48 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket +import adafruit_connection_manager +import adafruit_wiznet5k.adafruit_wiznet5k_socket as pool import board import busio from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K from digitalio import DigitalInOut -import adafruit_requests as requests +import adafruit_requests cs = DigitalInOut(board.D10) spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) # Initialize ethernet interface with DHCP -eth = WIZNET5K(spi_bus, cs) +radio = WIZNET5K(spi_bus, cs) -# Initialize a requests object with a socket and ethernet interface -requests.set_socket(socket, eth) +# Initialize a requests session +ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, radio) +requests = adafruit_requests.Session(pool, ssl_context) TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" JSON_GET_URL = "http://httpbin.org/get" JSON_POST_URL = "http://httpbin.org/post" -attempts = 3 # Number of attempts to retry each request -failure_count = 0 -response = None - print("Fetching text from %s" % TEXT_URL) -while not response: - try: - response = requests.get(TEXT_URL) - failure_count = 0 - except AssertionError as error: - print("Request failed, retrying...\n", error) - failure_count += 1 - if failure_count >= attempts: - raise AssertionError( - "Failed to resolve hostname, \ - please check your router's DNS configuration." - ) from error - continue +response = requests.get(TEXT_URL) print("-" * 40) print("Text Response: ", response.text) print("-" * 40) response.close() -response = None print("Fetching JSON data from %s" % JSON_GET_URL) -while not response: - try: - response = requests.get(JSON_GET_URL) - failure_count = 0 - except AssertionError as error: - print("Request failed, retrying...\n", error) - failure_count += 1 - if failure_count >= attempts: - raise AssertionError( - "Failed to resolve hostname, \ - please check your router's DNS configuration." - ) from error - continue +response = requests.get(JSON_GET_URL) print("-" * 40) print("JSON Response: ", response.json()) print("-" * 40) response.close() -response = None data = "31F" print("POSTing data to {0}: {1}".format(JSON_POST_URL, data)) -while not response: - try: - response = requests.post(JSON_POST_URL, data=data) - failure_count = 0 - except AssertionError as error: - print("Request failed, retrying...\n", error) - failure_count += 1 - if failure_count >= attempts: - raise AssertionError( - "Failed to resolve hostname, \ - please check your router's DNS configuration." - ) from error - continue +response = requests.post(JSON_POST_URL, data=data) print("-" * 40) json_resp = response.json() @@ -90,23 +50,10 @@ print("Data received from server:", json_resp["data"]) print("-" * 40) response.close() -response = None json_data = {"Date": "July 25, 2019"} print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data)) -while not response: - try: - response = requests.post(JSON_POST_URL, json=json_data) - failure_count = 0 - except AssertionError as error: - print("Request failed, retrying...\n", error) - failure_count += 1 - if failure_count >= attempts: - raise AssertionError( - "Failed to resolve hostname, \ - please check your router's DNS configuration." - ) from error - continue +response = requests.post(JSON_POST_URL, json=json_data) print("-" * 40) json_resp = response.json() From 65d8f705dfe0a742d5e78d496d814f40c288c4a4 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Wed, 21 Feb 2024 08:42:16 -0800 Subject: [PATCH 183/305] Don't use secrets.py --- ...sts_wifi_adafruit_discord_active_online.py | 6 +- examples/requests_wifi_api_discord.py | 2 +- examples/requests_wifi_api_fitbit.py | 9 +-- examples/requests_wifi_api_github.py | 23 +++---- examples/requests_wifi_api_mastodon.py | 11 ++-- ...equests_wifi_api_openskynetwork_private.py | 7 +- ...ts_wifi_api_openskynetwork_private_area.py | 6 +- ...requests_wifi_api_openskynetwork_public.py | 6 +- examples/requests_wifi_api_steam.py | 6 +- examples/requests_wifi_api_twitch.py | 2 +- examples/requests_wifi_api_twitter.py | 17 ++--- examples/requests_wifi_api_youtube.py | 18 ++--- examples/requests_wifi_https_circuitpython.py | 65 ------------------- examples/requests_wifi_multiple_cookies.py | 14 ++-- 14 files changed, 62 insertions(+), 130 deletions(-) delete mode 100644 examples/requests_wifi_https_circuitpython.py diff --git a/examples/requests_wifi_adafruit_discord_active_online.py b/examples/requests_wifi_adafruit_discord_active_online.py index ff81e0b..9262e51 100644 --- a/examples/requests_wifi_adafruit_discord_active_online.py +++ b/examples/requests_wifi_adafruit_discord_active_online.py @@ -26,9 +26,9 @@ # 600 = 10 mins, 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour sleep_time = 900 -# this example uses settings.toml for credentials -ssid = os.getenv("WIFI_SSID") -appw = os.getenv("WIFI_PASSWORD") +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Converts seconds to minutes/hours/days diff --git a/examples/requests_wifi_api_discord.py b/examples/requests_wifi_api_discord.py index b6eb0f0..0202fc0 100644 --- a/examples/requests_wifi_api_discord.py +++ b/examples/requests_wifi_api_discord.py @@ -19,7 +19,7 @@ # Ensure this is in settings.toml # "Discord_Authorization": "Request Header Auth here" -# Uses settings.toml for credentials +# Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") Discord_Auth = os.getenv("Discord_Authorization") diff --git a/examples/requests_wifi_api_fitbit.py b/examples/requests_wifi_api_fitbit.py index cf1c9bd..9eba799 100644 --- a/examples/requests_wifi_api_fitbit.py +++ b/examples/requests_wifi_api_fitbit.py @@ -36,6 +36,10 @@ # Fitbit_First_Refresh_Token = "64 character string" # Fitbit_UserID = "UserID authorizing the ClientID" +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") + Fitbit_ClientID = os.getenv("Fitbit_ClientID") Fitbit_Token = os.getenv("Fitbit_Token") Fitbit_First_Refresh_Token = os.getenv( @@ -43,9 +47,6 @@ ) # overides nvm first run only Fitbit_UserID = os.getenv("Fitbit_UserID") -wifi_ssid = os.getenv("CIRCUITPY_WIFI_SSID") -wifi_pw = os.getenv("CIRCUITPY_WIFI_PASSWORD") - # Time between API refreshes # 300 = 5 mins, 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour sleep_time = 900 @@ -78,7 +79,7 @@ def time_calc(input_time): requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(wifi_ssid, wifi_pw) + wifi.radio.connect(ssid, appw) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/requests_wifi_api_github.py b/examples/requests_wifi_api_github.py index 5159d62..1153e93 100644 --- a/examples/requests_wifi_api_github.py +++ b/examples/requests_wifi_api_github.py @@ -4,6 +4,7 @@ """DJDevon3 Adafruit Feather ESP32-S2 Github_API_Example""" import gc import json +import os import ssl import time @@ -12,11 +13,6 @@ import adafruit_requests -# Github developer token required. -# Ensure these are uncommented and in secrets.py or .env -# "Github_username": "Your Github Username", -# "Github_token": "Your long API token", - # Initialize WiFi Pool (There can be only 1 pool & top of script) pool = socketpool.SocketPool(wifi.radio) @@ -24,11 +20,12 @@ # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour sleep_time = 900 -try: - from secrets import secrets -except ImportError: - print("Secrets File Import Error") - raise +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +# Github developer token required. +github_username = os.getenv("Github_username") +github_token = os.getenv("Github_token") if sleep_time < 60: sleep_time_conversion = "seconds" @@ -43,8 +40,8 @@ sleep_int = sleep_time / 60 / 60 / 24 sleep_time_conversion = "days" -github_header = {"Authorization": " token " + secrets["Github_token"]} -GH_SOURCE = "https://api.github.com/users/" + secrets["Github_username"] +github_header = {"Authorization": " token " + github_token} +GH_SOURCE = "https://api.github.com/users/" + github_username # Connect to Wi-Fi print("\n===============================") @@ -52,7 +49,7 @@ requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(secrets["ssid"], secrets["password"]) + wifi.radio.connect(ssid, appw) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/requests_wifi_api_mastodon.py b/examples/requests_wifi_api_mastodon.py index 4e28432..4136491 100644 --- a/examples/requests_wifi_api_mastodon.py +++ b/examples/requests_wifi_api_mastodon.py @@ -3,6 +3,7 @@ # Coded for Circuit Python 8.0 """DJDevon3 Adafruit Feather ESP32-S2 Mastodon_API_Example""" import gc +import os import ssl import time @@ -29,11 +30,9 @@ # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour sleep_time = 900 -try: - from secrets import secrets -except ImportError: - print("Secrets File Import Error") - raise +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Converts seconds in minutes/hours/days @@ -71,7 +70,7 @@ def time_calc(input_time): requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(secrets["ssid"], secrets["password"]) + wifi.radio.connect(ssid, appw) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/requests_wifi_api_openskynetwork_private.py b/examples/requests_wifi_api_openskynetwork_private.py index c1999e1..ebdd420 100644 --- a/examples/requests_wifi_api_openskynetwork_private.py +++ b/examples/requests_wifi_api_openskynetwork_private.py @@ -30,10 +30,9 @@ # https://openskynetwork.github.io/opensky-api/rest.html#limitations sleep_time = 1800 -# this example uses settings.toml for credentials -# timezone offset is in seconds plus or minus GMT -ssid = os.getenv("AP_SSID") -appw = os.getenv("AP_PASSWORD") +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") osnu = os.getenv("OSN_Username") osnp = os.getenv("OSN_Password") diff --git a/examples/requests_wifi_api_openskynetwork_private_area.py b/examples/requests_wifi_api_openskynetwork_private_area.py index 2baabbe..79a07df 100644 --- a/examples/requests_wifi_api_openskynetwork_private_area.py +++ b/examples/requests_wifi_api_openskynetwork_private_area.py @@ -32,10 +32,10 @@ # https://openskynetwork.github.io/opensky-api/rest.html#limitations sleep_time = 1800 -# this example uses settings.toml for credentials +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") # No token required, only website login -ssid = os.getenv("AP_SSID") -appw = os.getenv("AP_PASSWORD") osnu = os.getenv("OSN_Username") osnp = os.getenv("OSN_Password") diff --git a/examples/requests_wifi_api_openskynetwork_public.py b/examples/requests_wifi_api_openskynetwork_public.py index 1388efd..ec0486a 100644 --- a/examples/requests_wifi_api_openskynetwork_public.py +++ b/examples/requests_wifi_api_openskynetwork_public.py @@ -28,9 +28,9 @@ # https://openskynetwork.github.io/opensky-api/rest.html#limitations sleep_time = 1800 -# Wifi credentials pulled from settings.toml -ssid = os.getenv("AP_SSID") -appw = os.getenv("AP_PASSWORD") +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Requests URL - icao24 is their endpoint required for a transponder # example https://opensky-network.org/api/states/all?icao24=a808c5 diff --git a/examples/requests_wifi_api_steam.py b/examples/requests_wifi_api_steam.py index 7496c04..bffda83 100644 --- a/examples/requests_wifi_api_steam.py +++ b/examples/requests_wifi_api_steam.py @@ -18,10 +18,10 @@ # Steam Usernumber: Visit https://steamcommunity.com # click on your profile icon, your usernumber will be in the browser url. -# Ensure these are setup in settings.toml +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Requires Steam Developer API key -ssid = os.getenv("AP_SSID") -appw = os.getenv("AP_PASSWORD") steam_usernumber = os.getenv("steam_id") steam_apikey = os.getenv("steam_api_key") diff --git a/examples/requests_wifi_api_twitch.py b/examples/requests_wifi_api_twitch.py index ad5cf3a..f637591 100644 --- a/examples/requests_wifi_api_twitch.py +++ b/examples/requests_wifi_api_twitch.py @@ -23,7 +23,7 @@ # "Twitch_Client_Secret": "APP ID secret here", # "Twitch_UserID": "Your Twitch UserID here", -# Use settings.toml for credentials +# Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") twitch_client_id = os.getenv("Twitch_ClientID") diff --git a/examples/requests_wifi_api_twitter.py b/examples/requests_wifi_api_twitter.py index be06f6a..1c4dc3b 100644 --- a/examples/requests_wifi_api_twitter.py +++ b/examples/requests_wifi_api_twitter.py @@ -4,6 +4,7 @@ """DJDevon3 Adafruit Feather ESP32-S2 Twitter_API_Example""" import gc import json +import os import ssl import time @@ -24,11 +25,11 @@ # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour sleep_time = 900 -try: - from secrets import secrets -except ImportError: - print("Secrets File Import Error") - raise +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +tw_userid = os.getenv("TW_userid") +tw_bearer_token = os.getenv("TW_bearer_token") if sleep_time < 60: sleep_time_conversion = "seconds" @@ -44,10 +45,10 @@ sleep_time_conversion = "days" # Used with any Twitter 0auth request. -twitter_header = {"Authorization": "Bearer " + secrets["TW_bearer_token"]} +twitter_header = {"Authorization": "Bearer " + tw_bearer_token} TW_SOURCE = ( "https://api.twitter.com/2/users/" - + secrets["TW_userid"] + + tw_userid + "?user.fields=public_metrics,created_at,pinned_tweet_id" + "&expansions=pinned_tweet_id" + "&tweet.fields=created_at,public_metrics,source,context_annotations,entities" @@ -59,7 +60,7 @@ requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(secrets["ssid"], secrets["password"]) + wifi.radio.connect(ssid, appw) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/requests_wifi_api_youtube.py b/examples/requests_wifi_api_youtube.py index 22cb9b3..fb539ff 100644 --- a/examples/requests_wifi_api_youtube.py +++ b/examples/requests_wifi_api_youtube.py @@ -4,6 +4,7 @@ """DJDevon3 Adafruit Feather ESP32-S2 YouTube_API_Example""" import gc import json +import os import ssl import time @@ -23,11 +24,12 @@ # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour sleep_time = 900 -try: - from secrets import secrets -except ImportError: - print("Secrets File Import Error") - raise +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +yt_username = os.getenv("YT_username") +yt_token = os.getenv("YT_token") + if sleep_time < 60: sleep_time_conversion = "seconds" @@ -47,9 +49,9 @@ "https://youtube.googleapis.com/youtube/v3/channels?" + "part=statistics" + "&forUsername=" - + secrets["YT_username"] + + yt_username + "&key=" - + secrets["YT_token"] + + yt_token ) # Connect to Wi-Fi @@ -58,7 +60,7 @@ requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(secrets["ssid"], secrets["password"]) + wifi.radio.connect(ssid, appw) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/requests_wifi_https_circuitpython.py b/examples/requests_wifi_https_circuitpython.py deleted file mode 100644 index 63b5c81..0000000 --- a/examples/requests_wifi_https_circuitpython.py +++ /dev/null @@ -1,65 +0,0 @@ -# SPDX-FileCopyrightText: 2021 jfabernathy for Adafruit Industries -# SPDX-License-Identifier: MIT - -# adafruit_requests usage with a CircuitPython socket -# this has been tested with Adafruit Metro ESP32-S2 Express - -import ssl - -import socketpool -import wifi - -import adafruit_requests as requests - -# Get wifi details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise - -print("Connecting to %s" % secrets["ssid"]) -wifi.radio.connect(secrets["ssid"], secrets["password"]) -print("Connected to %s!" % secrets["ssid"]) -print("My IP address is", wifi.radio.ipv4_address) - -socket = socketpool.SocketPool(wifi.radio) -https = requests.Session(socket, ssl.create_default_context()) - -TEXT_URL = "https://httpbin.org/get" -JSON_GET_URL = "https://httpbin.org/get" -JSON_POST_URL = "https://httpbin.org/post" - -print("Fetching text from %s" % TEXT_URL) -response = https.get(TEXT_URL) -print("-" * 40) -print("Text Response: ", response.text) -print("-" * 40) -response.close() - -print("Fetching JSON data from %s" % JSON_GET_URL) -response = https.get(JSON_GET_URL) -print("-" * 40) - -print("JSON Response: ", response.json()) -print("-" * 40) - -data = "31F" -print("POSTing data to {0}: {1}".format(JSON_POST_URL, data)) -response = https.post(JSON_POST_URL, data=data) -print("-" * 40) - -json_resp = response.json() -# Parse out the 'data' key from json_resp dict. -print("Data received from server:", json_resp["data"]) -print("-" * 40) - -json_data = {"Date": "July 25, 2019"} -print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data)) -response = https.post(JSON_POST_URL, json=json_data) -print("-" * 40) - -json_resp = response.json() -# Parse out the 'json' key from json_resp dict. -print("JSON Data received from server:", json_resp["json"]) -print("-" * 40) diff --git a/examples/requests_wifi_multiple_cookies.py b/examples/requests_wifi_multiple_cookies.py index 653d3e0..9e3fcf5 100644 --- a/examples/requests_wifi_multiple_cookies.py +++ b/examples/requests_wifi_multiple_cookies.py @@ -6,6 +6,7 @@ for connecting to the internet depending on your device. """ +import os import ssl import socketpool @@ -15,16 +16,13 @@ COOKIE_TEST_URL = "https://www.adafruit.com" -# Get wifi details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Connect to the Wi-Fi network -print("Connecting to %s" % secrets["ssid"]) -wifi.radio.connect(secrets["ssid"], secrets["password"]) +print("Connecting to %s" % ssid) +wifi.radio.connect(ssid, appw) # Set up the requests library pool = socketpool.SocketPool(wifi.radio) From c533f184d7e1af70a8aac281055f38f555c93641 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Wed, 21 Feb 2024 08:47:24 -0800 Subject: [PATCH 184/305] Update examples in docs --- docs/examples.rst | 30 +++++++++++++++++-- ..._advanced.py => requests_fona_advanced.py} | 0 ...pletest.py => requests_fona_simpletest.py} | 0 3 files changed, 27 insertions(+), 3 deletions(-) rename examples/{requests_cellular_advanced.py => requests_fona_advanced.py} (100%) rename examples/{requests_cellular_simpletest.py => requests_fona_simpletest.py} (100%) diff --git a/docs/examples.rst b/docs/examples.rst index 83de490..525b65f 100755 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -1,8 +1,32 @@ -Simple test ------------- +Examples +======== -Ensure your device works with this simple test. +Below are a few examples, for use with common boards. There are more in the examples folder of the library + +On-board WiFi +-------------- .. literalinclude:: ../examples/requests_wifi_simpletest.py :caption: examples/requests_wifi_simpletest.py :linenos: + +ESP32SPI +-------------- + +.. literalinclude:: ../examples/requests_esp32spi_simpletest.py + :caption: examples/requests_esp32spi_simpletest.py + :linenos: + +WIZNET5K +-------------- + +.. literalinclude:: ../examples/requests_wiznet5k_simpletest.py + :caption: examples/requests_wiznet5k_simpletest.py + :linenos: + +Fona +-------------- + +.. literalinclude:: ../examples/requests_fona_simpletest.py + :caption: examples/requests_fona_simpletest.py + :linenos: diff --git a/examples/requests_cellular_advanced.py b/examples/requests_fona_advanced.py similarity index 100% rename from examples/requests_cellular_advanced.py rename to examples/requests_fona_advanced.py diff --git a/examples/requests_cellular_simpletest.py b/examples/requests_fona_simpletest.py similarity index 100% rename from examples/requests_cellular_simpletest.py rename to examples/requests_fona_simpletest.py From 6d906cd9400f1a6c2c35bfaa8080b59dc7952379 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Wed, 21 Feb 2024 20:09:49 -0800 Subject: [PATCH 185/305] Move examples into subfolders --- docs/examples.rst | 16 ++++++++-------- .../{ => cpython}/requests_cpython_advanced.py | 0 .../{ => cpython}/requests_cpython_simpletest.py | 0 .../{ => esp32spi}/requests_esp32spi_advanced.py | 0 .../requests_esp32spi_simpletest.py | 0 examples/{ => fona}/requests_fona_advanced.py | 0 examples/{ => fona}/requests_fona_simpletest.py | 0 ...quests_wifi_adafruit_discord_active_online.py | 0 .../expanded}/requests_wifi_api_discord.py | 0 .../expanded}/requests_wifi_api_fitbit.py | 0 .../expanded}/requests_wifi_api_github.py | 0 .../expanded}/requests_wifi_api_mastodon.py | 0 .../requests_wifi_api_openskynetwork_private.py | 0 ...uests_wifi_api_openskynetwork_private_area.py | 0 .../requests_wifi_api_openskynetwork_public.py | 0 .../expanded}/requests_wifi_api_steam.py | 0 .../expanded}/requests_wifi_api_twitch.py | 0 .../expanded}/requests_wifi_api_twitter.py | 0 .../expanded}/requests_wifi_api_youtube.py | 0 .../expanded}/requests_wifi_multiple_cookies.py | 0 examples/{ => wifi}/requests_wifi_advanced.py | 0 examples/{ => wifi}/requests_wifi_simpletest.py | 0 .../{ => wiznet5k}/requests_wiznet5k_advanced.py | 0 .../requests_wiznet5k_simpletest.py | 0 24 files changed, 8 insertions(+), 8 deletions(-) rename examples/{ => cpython}/requests_cpython_advanced.py (100%) rename examples/{ => cpython}/requests_cpython_simpletest.py (100%) rename examples/{ => esp32spi}/requests_esp32spi_advanced.py (100%) rename examples/{ => esp32spi}/requests_esp32spi_simpletest.py (100%) rename examples/{ => fona}/requests_fona_advanced.py (100%) rename examples/{ => fona}/requests_fona_simpletest.py (100%) rename examples/{ => wifi/expanded}/requests_wifi_adafruit_discord_active_online.py (100%) rename examples/{ => wifi/expanded}/requests_wifi_api_discord.py (100%) rename examples/{ => wifi/expanded}/requests_wifi_api_fitbit.py (100%) rename examples/{ => wifi/expanded}/requests_wifi_api_github.py (100%) rename examples/{ => wifi/expanded}/requests_wifi_api_mastodon.py (100%) rename examples/{ => wifi/expanded}/requests_wifi_api_openskynetwork_private.py (100%) rename examples/{ => wifi/expanded}/requests_wifi_api_openskynetwork_private_area.py (100%) rename examples/{ => wifi/expanded}/requests_wifi_api_openskynetwork_public.py (100%) rename examples/{ => wifi/expanded}/requests_wifi_api_steam.py (100%) rename examples/{ => wifi/expanded}/requests_wifi_api_twitch.py (100%) rename examples/{ => wifi/expanded}/requests_wifi_api_twitter.py (100%) rename examples/{ => wifi/expanded}/requests_wifi_api_youtube.py (100%) rename examples/{ => wifi/expanded}/requests_wifi_multiple_cookies.py (100%) rename examples/{ => wifi}/requests_wifi_advanced.py (100%) rename examples/{ => wifi}/requests_wifi_simpletest.py (100%) rename examples/{ => wiznet5k}/requests_wiznet5k_advanced.py (100%) rename examples/{ => wiznet5k}/requests_wiznet5k_simpletest.py (100%) diff --git a/docs/examples.rst b/docs/examples.rst index 525b65f..3637216 100755 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -6,27 +6,27 @@ Below are a few examples, for use with common boards. There are more in the exam On-board WiFi -------------- -.. literalinclude:: ../examples/requests_wifi_simpletest.py - :caption: examples/requests_wifi_simpletest.py +.. literalinclude:: ../examples/wifi/requests_wifi_simpletest.py + :caption: examples/wifi/requests_wifi_simpletest.py :linenos: ESP32SPI -------------- -.. literalinclude:: ../examples/requests_esp32spi_simpletest.py - :caption: examples/requests_esp32spi_simpletest.py +.. literalinclude:: ../examples/esp32spi/requests_esp32spi_simpletest.py + :caption: examples/esp32spi/requests_esp32spi_simpletest.py :linenos: WIZNET5K -------------- -.. literalinclude:: ../examples/requests_wiznet5k_simpletest.py - :caption: examples/requests_wiznet5k_simpletest.py +.. literalinclude:: ../examples/wiznet5k/requests_wiznet5k_simpletest.py + :caption: examples/wiznet5k/requests_wiznet5k_simpletest.py :linenos: Fona -------------- -.. literalinclude:: ../examples/requests_fona_simpletest.py - :caption: examples/requests_fona_simpletest.py +.. literalinclude:: ../examples/fona/requests_fona_simpletest.py + :caption: examples/fona/requests_fona_simpletest.py :linenos: diff --git a/examples/requests_cpython_advanced.py b/examples/cpython/requests_cpython_advanced.py similarity index 100% rename from examples/requests_cpython_advanced.py rename to examples/cpython/requests_cpython_advanced.py diff --git a/examples/requests_cpython_simpletest.py b/examples/cpython/requests_cpython_simpletest.py similarity index 100% rename from examples/requests_cpython_simpletest.py rename to examples/cpython/requests_cpython_simpletest.py diff --git a/examples/requests_esp32spi_advanced.py b/examples/esp32spi/requests_esp32spi_advanced.py similarity index 100% rename from examples/requests_esp32spi_advanced.py rename to examples/esp32spi/requests_esp32spi_advanced.py diff --git a/examples/requests_esp32spi_simpletest.py b/examples/esp32spi/requests_esp32spi_simpletest.py similarity index 100% rename from examples/requests_esp32spi_simpletest.py rename to examples/esp32spi/requests_esp32spi_simpletest.py diff --git a/examples/requests_fona_advanced.py b/examples/fona/requests_fona_advanced.py similarity index 100% rename from examples/requests_fona_advanced.py rename to examples/fona/requests_fona_advanced.py diff --git a/examples/requests_fona_simpletest.py b/examples/fona/requests_fona_simpletest.py similarity index 100% rename from examples/requests_fona_simpletest.py rename to examples/fona/requests_fona_simpletest.py diff --git a/examples/requests_wifi_adafruit_discord_active_online.py b/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py similarity index 100% rename from examples/requests_wifi_adafruit_discord_active_online.py rename to examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py diff --git a/examples/requests_wifi_api_discord.py b/examples/wifi/expanded/requests_wifi_api_discord.py similarity index 100% rename from examples/requests_wifi_api_discord.py rename to examples/wifi/expanded/requests_wifi_api_discord.py diff --git a/examples/requests_wifi_api_fitbit.py b/examples/wifi/expanded/requests_wifi_api_fitbit.py similarity index 100% rename from examples/requests_wifi_api_fitbit.py rename to examples/wifi/expanded/requests_wifi_api_fitbit.py diff --git a/examples/requests_wifi_api_github.py b/examples/wifi/expanded/requests_wifi_api_github.py similarity index 100% rename from examples/requests_wifi_api_github.py rename to examples/wifi/expanded/requests_wifi_api_github.py diff --git a/examples/requests_wifi_api_mastodon.py b/examples/wifi/expanded/requests_wifi_api_mastodon.py similarity index 100% rename from examples/requests_wifi_api_mastodon.py rename to examples/wifi/expanded/requests_wifi_api_mastodon.py diff --git a/examples/requests_wifi_api_openskynetwork_private.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py similarity index 100% rename from examples/requests_wifi_api_openskynetwork_private.py rename to examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py diff --git a/examples/requests_wifi_api_openskynetwork_private_area.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py similarity index 100% rename from examples/requests_wifi_api_openskynetwork_private_area.py rename to examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py diff --git a/examples/requests_wifi_api_openskynetwork_public.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py similarity index 100% rename from examples/requests_wifi_api_openskynetwork_public.py rename to examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py diff --git a/examples/requests_wifi_api_steam.py b/examples/wifi/expanded/requests_wifi_api_steam.py similarity index 100% rename from examples/requests_wifi_api_steam.py rename to examples/wifi/expanded/requests_wifi_api_steam.py diff --git a/examples/requests_wifi_api_twitch.py b/examples/wifi/expanded/requests_wifi_api_twitch.py similarity index 100% rename from examples/requests_wifi_api_twitch.py rename to examples/wifi/expanded/requests_wifi_api_twitch.py diff --git a/examples/requests_wifi_api_twitter.py b/examples/wifi/expanded/requests_wifi_api_twitter.py similarity index 100% rename from examples/requests_wifi_api_twitter.py rename to examples/wifi/expanded/requests_wifi_api_twitter.py diff --git a/examples/requests_wifi_api_youtube.py b/examples/wifi/expanded/requests_wifi_api_youtube.py similarity index 100% rename from examples/requests_wifi_api_youtube.py rename to examples/wifi/expanded/requests_wifi_api_youtube.py diff --git a/examples/requests_wifi_multiple_cookies.py b/examples/wifi/expanded/requests_wifi_multiple_cookies.py similarity index 100% rename from examples/requests_wifi_multiple_cookies.py rename to examples/wifi/expanded/requests_wifi_multiple_cookies.py diff --git a/examples/requests_wifi_advanced.py b/examples/wifi/requests_wifi_advanced.py similarity index 100% rename from examples/requests_wifi_advanced.py rename to examples/wifi/requests_wifi_advanced.py diff --git a/examples/requests_wifi_simpletest.py b/examples/wifi/requests_wifi_simpletest.py similarity index 100% rename from examples/requests_wifi_simpletest.py rename to examples/wifi/requests_wifi_simpletest.py diff --git a/examples/requests_wiznet5k_advanced.py b/examples/wiznet5k/requests_wiznet5k_advanced.py similarity index 100% rename from examples/requests_wiznet5k_advanced.py rename to examples/wiznet5k/requests_wiznet5k_advanced.py diff --git a/examples/requests_wiznet5k_simpletest.py b/examples/wiznet5k/requests_wiznet5k_simpletest.py similarity index 100% rename from examples/requests_wiznet5k_simpletest.py rename to examples/wiznet5k/requests_wiznet5k_simpletest.py From 44cb16380aa4d5c24009632f57bac0578c735e4b Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Fri, 23 Feb 2024 10:36:31 -0800 Subject: [PATCH 186/305] Remove legacy base methods and add tests --- .pre-commit-config.yaml | 2 +- adafruit_requests.py | 71 +--------------- conftest.py | 17 ---- tests/chunked_redirect_test.py | 136 +++++++++++++++++++++++++++--- tests/concurrent_test.py | 61 +++++--------- tests/conftest.py | 46 ++++++++++ tests/header_test.py | 150 ++++++++++----------------------- tests/method_test.py | 80 ++++++++++++++++++ tests/mocket.py | 36 +++++++- tests/parse_test.py | 10 +-- tests/post_test.py | 102 ---------------------- tests/protocol_test.py | 59 +++---------- tests/reuse_test.py | 112 +++++++++--------------- 13 files changed, 410 insertions(+), 472 deletions(-) delete mode 100644 conftest.py create mode 100644 tests/conftest.py create mode 100644 tests/method_test.py delete mode 100644 tests/post_test.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 77ed663..7b20fd7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,4 +44,4 @@ repos: types: [python] files: "^tests/" args: - - --disable=consider-using-f-string,duplicate-code,missing-docstring,invalid-name,protected-access + - --disable=consider-using-f-string,duplicate-code,missing-docstring,invalid-name,protected-access,redefined-outer-name diff --git a/adafruit_requests.py b/adafruit_requests.py index b20cf23..771bfcc 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -86,7 +86,7 @@ class Response: encoding = None - def __init__(self, sock: SocketType, session: Optional["Session"] = None) -> None: + def __init__(self, sock: SocketType, session: "Session") -> None: self.socket = sock self.encoding = "utf-8" self._cached = None @@ -101,10 +101,7 @@ def __init__(self, sock: SocketType, session: Optional["Session"] = None) -> Non http = self._readto(b" ") if not http: - if session: - session._connection_manager.close_socket(self.socket) - else: - self.socket.close() + session._connection_manager.close_socket(self.socket) raise RuntimeError("Unable to read HTTP response.") self.status_code: int = int(bytes(self._readto(b" "))) """The status code returned by the server""" @@ -614,67 +611,3 @@ def patch(self, url: str, **kw) -> Response: def delete(self, url: str, **kw) -> Response: """Send HTTP DELETE request""" return self.request("DELETE", url, **kw) - - -_default_session = None # pylint: disable=invalid-name - - -def create_default_session( - socket_pool: SocketpoolModuleType, - ssl_context: Optional[SSLContextType] = None, -): - """Create a default session for using globally""" - global _default_session # pylint: disable=global-statement - _default_session = Session(socket_pool, ssl_context) - - -def request( - method: str, - url: str, - data: Optional[Any] = None, - json: Optional[Any] = None, - headers: Optional[Dict[str, str]] = None, - stream: bool = False, - timeout: float = 1, -) -> None: - """Send HTTP request""" - # pylint: disable=too-many-arguments - _default_session.request( - method, - url, - data=data, - json=json, - headers=headers, - stream=stream, - timeout=timeout, - ) - - -def head(url: str, **kw): - """Send HTTP HEAD request""" - return _default_session.request("HEAD", url, **kw) - - -def get(url: str, **kw): - """Send HTTP GET request""" - return _default_session.request("GET", url, **kw) - - -def post(url: str, **kw): - """Send HTTP POST request""" - return _default_session.request("POST", url, **kw) - - -def put(url: str, **kw): - """Send HTTP PUT request""" - return _default_session.request("PUT", url, **kw) - - -def patch(url: str, **kw): - """Send HTTP PATCH request""" - return _default_session.request("PATCH", url, **kw) - - -def delete(url: str, **kw): - """Send HTTP DELETE request""" - return _default_session.request("DELETE", url, **kw) diff --git a/conftest.py b/conftest.py deleted file mode 100644 index 01126ba..0000000 --- a/conftest.py +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Justin Myers for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -""" PyTest Setup """ - -import adafruit_connection_manager -import pytest - - -@pytest.fixture(autouse=True) -def reset_connection_manager(monkeypatch): - """Reset the ConnectionManager, since it's a singlton and will hold data""" - monkeypatch.setattr( - "adafruit_requests.get_connection_manager", - adafruit_connection_manager.ConnectionManager, - ) diff --git a/tests/chunked_redirect_test.py b/tests/chunked_redirect_test.py index 2a9392a..69f8b27 100644 --- a/tests/chunked_redirect_test.py +++ b/tests/chunked_redirect_test.py @@ -13,9 +13,25 @@ IP = "1.2.3.4" HOST = "docs.google.com" -PATH = ( +PATH_BASE = ( "/spreadsheets/d/e/2PACX-1vR1WjUKz35-ek6SiR5droDfvPp51MTds4wUs57vEZNh2uDfihSTPhTaiiRo" - "vLbNe1mkeRgurppRJ_Zy/pub?output=tsv" + "vLbNe1mkeRgurppRJ_Zy/" +) +PATH = PATH_BASE + "pub?output=tsv" + +FILE_REDIRECT = ( + b"e@2PACX-1vR1WjUKz35-ek6SiR5droDfvPp51MTds4wUs57vEZNh2uDfihSTPhTai" + b"iRovLbNe1mkeRgurppRJ_Zy?output=tsv" +) +RELATIVE_RELATIVE_REDIRECT = ( + b"370cmver1f290kjsnpar5ku2h9g/3llvt5u8njbvat22m9l19db1h4/1656191325000/109226138307867586192/*/" + + FILE_REDIRECT +) +RELATIVE_ABSOLUTE_REDIRECT = ( + b"/pub/70cmver1f290kjsnpar5ku2h9g/" + RELATIVE_RELATIVE_REDIRECT +) +ABSOLUTE_ABSOLUTE_REDIRECT = ( + b"https://doc-14-2g-sheets.googleusercontent.com" + RELATIVE_ABSOLUTE_REDIRECT ) # response headers returned from the initial request @@ -26,10 +42,7 @@ b"Pragma: no-cache\r\n" b"Expires: Mon, 01 Jan 1990 00:00:00 GMT\r\n" b"Date: Sat, 25 Jun 2022 21:08:48 GMT\r\n" - b"Location: https://doc-14-2g-sheets.googleusercontent.com/pub/70cmver1f290kjsnpar5ku2h9g/3" - b"llvt5u8njbvat22m9l19db1h4/1656191325000" - b"/109226138307867586192/*/e@2PACX-1vR1WjUKz35-ek6SiR5droDfvPp51MTds4wUs57vEZNh2uDfihSTPhTai" - b"iRovLbNe1mkeRgurppRJ_Zy?output=tsv\r\n" + b"Location: NEW_LOCATION_HERE\r\n" b'P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."\r\n' b"X-Content-Type-Options: nosniff\r\n" b"X-XSS-Protection: 1; mode=block\r\n" @@ -118,13 +131,16 @@ def _recv_into(self, buf, nbytes=0): return read -def do_test_chunked_redirect(): +def test_chunked_absolute_absolute_redirect(): pool = mocket.MocketPool() pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 443)),) chunk = _chunk(BODY_REDIRECT, len(BODY_REDIRECT)) chunk2 = _chunk(BODY, len(BODY)) - sock1 = MocketRecvInto(HEADERS_REDIRECT + chunk) + redirect = HEADERS_REDIRECT.replace( + b"NEW_LOCATION_HERE", ABSOLUTE_ABSOLUTE_REDIRECT + ) + sock1 = MocketRecvInto(redirect + chunk) sock2 = mocket.Mocket(HEADERS + chunk2) pool.socket.side_effect = (sock1, sock2) @@ -135,9 +151,109 @@ def do_test_chunked_redirect(): sock2.connect.assert_called_once_with( ("doc-14-2g-sheets.googleusercontent.com", 443) ) + sock2.send.assert_has_calls([mock.call(RELATIVE_ABSOLUTE_REDIRECT[1:])]) + + assert response.text == str(BODY, "utf-8") + + +def test_chunked_relative_absolute_redirect(): + pool = mocket.MocketPool() + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 443)),) + chunk = _chunk(BODY_REDIRECT, len(BODY_REDIRECT)) + chunk2 = _chunk(BODY, len(BODY)) + + redirect = HEADERS_REDIRECT.replace( + b"NEW_LOCATION_HERE", RELATIVE_ABSOLUTE_REDIRECT + ) + sock1 = MocketRecvInto(redirect + chunk) + sock2 = mocket.Mocket(HEADERS + chunk2) + pool.socket.side_effect = (sock1, sock2) + + requests_session = adafruit_requests.Session(pool, mocket.SSLContext()) + response = requests_session.get("https://" + HOST + PATH) + + sock1.connect.assert_called_once_with((HOST, 443)) + sock2.connect.assert_called_once_with(("docs.google.com", 443)) + sock2.send.assert_has_calls([mock.call(RELATIVE_ABSOLUTE_REDIRECT[1:])]) + + assert response.text == str(BODY, "utf-8") + + +def test_chunked_relative_relative_redirect(): + pool = mocket.MocketPool() + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 443)),) + chunk = _chunk(BODY_REDIRECT, len(BODY_REDIRECT)) + chunk2 = _chunk(BODY, len(BODY)) + + redirect = HEADERS_REDIRECT.replace( + b"NEW_LOCATION_HERE", RELATIVE_RELATIVE_REDIRECT + ) + sock1 = MocketRecvInto(redirect + chunk) + sock2 = mocket.Mocket(HEADERS + chunk2) + pool.socket.side_effect = (sock1, sock2) + + requests_session = adafruit_requests.Session(pool, mocket.SSLContext()) + response = requests_session.get("https://" + HOST + PATH) + + sock1.connect.assert_called_once_with((HOST, 443)) + sock2.connect.assert_called_once_with(("docs.google.com", 443)) + sock2.send.assert_has_calls( + [mock.call(bytes(PATH_BASE[1:], "utf-8") + RELATIVE_RELATIVE_REDIRECT)] + ) assert response.text == str(BODY, "utf-8") -def test_chunked_redirect(): - do_test_chunked_redirect() +def test_chunked_relative_relative_redirect_backstep(): + pool = mocket.MocketPool() + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 443)),) + chunk = _chunk(BODY_REDIRECT, len(BODY_REDIRECT)) + chunk2 = _chunk(BODY, len(BODY)) + + remove_paths = 2 + backstep = b"../" * remove_paths + path_base_parts = PATH_BASE.split("/") + # PATH_BASE starts with "/" so skip it and also remove from the count + path_base = ( + "/".join(path_base_parts[1 : len(path_base_parts) - remove_paths - 1]) + "/" + ) + + redirect = HEADERS_REDIRECT.replace( + b"NEW_LOCATION_HERE", backstep + RELATIVE_RELATIVE_REDIRECT + ) + sock1 = MocketRecvInto(redirect + chunk) + sock2 = mocket.Mocket(HEADERS + chunk2) + pool.socket.side_effect = (sock1, sock2) + + requests_session = adafruit_requests.Session(pool, mocket.SSLContext()) + response = requests_session.get("https://" + HOST + PATH) + + sock1.connect.assert_called_once_with((HOST, 443)) + sock2.connect.assert_called_once_with(("docs.google.com", 443)) + sock2.send.assert_has_calls( + [mock.call(bytes(path_base, "utf-8") + RELATIVE_RELATIVE_REDIRECT)] + ) + + assert response.text == str(BODY, "utf-8") + + +def test_chunked_allow_redirects_false(): + pool = mocket.MocketPool() + pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 443)),) + chunk = _chunk(BODY_REDIRECT, len(BODY_REDIRECT)) + chunk2 = _chunk(BODY, len(BODY)) + + redirect = HEADERS_REDIRECT.replace( + b"NEW_LOCATION_HERE", ABSOLUTE_ABSOLUTE_REDIRECT + ) + sock1 = MocketRecvInto(redirect + chunk) + sock2 = mocket.Mocket(HEADERS + chunk2) + pool.socket.side_effect = (sock1, sock2) + + requests_session = adafruit_requests.Session(pool, mocket.SSLContext()) + response = requests_session.get("https://" + HOST + PATH, allow_redirects=False) + + sock1.connect.assert_called_once_with((HOST, 443)) + sock2.connect.assert_not_called() + + assert response.text == str(BODY_REDIRECT, "utf-8") diff --git a/tests/concurrent_test.py b/tests/concurrent_test.py index c70b7c2..58c2ade 100644 --- a/tests/concurrent_test.py +++ b/tests/concurrent_test.py @@ -9,30 +9,16 @@ import mocket -import adafruit_requests - -IP = "1.2.3.4" -HOST = "wifitest.adafruit.com" -HOST2 = "test.adafruit.com" -PATH = "/testwifi/index.html" -TEXT = b"This is a test of Adafruit WiFi!\r\nIf you can read this, its working :)" -RESPONSE = b"HTTP/1.0 200 OK\r\nContent-Length: 70\r\n\r\n" + TEXT - - -def test_second_connect_fails_memoryerror(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE) - sock2 = mocket.Mocket(RESPONSE) - sock3 = mocket.Mocket(RESPONSE) + +def test_second_connect_fails_memoryerror(pool, requests_ssl): + sock = mocket.Mocket() + sock2 = mocket.Mocket() + sock3 = mocket.Mocket() pool.socket.call_count = 0 # Reset call count pool.socket.side_effect = [sock, sock2, sock3] sock2.connect.side_effect = MemoryError() - ssl = mocket.SSLContext() - - requests_session = adafruit_requests.Session(pool, ssl) - response = requests_session.get("https://" + HOST + PATH) + response = requests_ssl.get("https://" + mocket.MOCK_ENDPOINT_1) sock.send.assert_has_calls( [ @@ -48,13 +34,13 @@ def test_second_connect_fails_memoryerror(): mock.call(b"\r\n"), ] ) - assert response.text == str(TEXT, "utf-8") + assert response.text == str(mocket.MOCK_RESPONSE_TEXT, "utf-8") - requests_session.get("https://" + HOST2 + PATH) + requests_ssl.get("https://" + mocket.MOCK_ENDPOINT_2) - sock.connect.assert_called_once_with((HOST, 443)) - sock2.connect.assert_called_once_with((HOST2, 443)) - sock3.connect.assert_called_once_with((HOST2, 443)) + sock.connect.assert_called_once_with((mocket.MOCK_HOST_1, 443)) + sock2.connect.assert_called_once_with((mocket.MOCK_HOST_2, 443)) + sock3.connect.assert_called_once_with((mocket.MOCK_HOST_2, 443)) # Make sure that the socket is closed after send fails. sock.close.assert_called_once() sock2.close.assert_called_once() @@ -62,20 +48,15 @@ def test_second_connect_fails_memoryerror(): assert pool.socket.call_count == 3 -def test_second_connect_fails_oserror(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE) - sock2 = mocket.Mocket(RESPONSE) - sock3 = mocket.Mocket(RESPONSE) +def test_second_connect_fails_oserror(pool, requests_ssl): + sock = mocket.Mocket() + sock2 = mocket.Mocket() + sock3 = mocket.Mocket() pool.socket.call_count = 0 # Reset call count pool.socket.side_effect = [sock, sock2, sock3] sock2.connect.side_effect = OSError(errno.ENOMEM) - ssl = mocket.SSLContext() - - requests_session = adafruit_requests.Session(pool, ssl) - response = requests_session.get("https://" + HOST + PATH) + response = requests_ssl.get("https://" + mocket.MOCK_ENDPOINT_1) sock.send.assert_has_calls( [ @@ -91,13 +72,13 @@ def test_second_connect_fails_oserror(): mock.call(b"\r\n"), ] ) - assert response.text == str(TEXT, "utf-8") + assert response.text == str(mocket.MOCK_RESPONSE_TEXT, "utf-8") - requests_session.get("https://" + HOST2 + PATH) + requests_ssl.get("https://" + mocket.MOCK_ENDPOINT_2) - sock.connect.assert_called_once_with((HOST, 443)) - sock2.connect.assert_called_once_with((HOST2, 443)) - sock3.connect.assert_called_once_with((HOST2, 443)) + sock.connect.assert_called_once_with((mocket.MOCK_HOST_1, 443)) + sock2.connect.assert_called_once_with((mocket.MOCK_HOST_2, 443)) + sock3.connect.assert_called_once_with((mocket.MOCK_HOST_2, 443)) # Make sure that the socket is closed after send fails. sock.close.assert_called_once() sock2.close.assert_called_once() diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..94023cb --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: 2023 Justin Myers for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +""" PyTest Setup """ + +import adafruit_connection_manager +import mocket +import pytest + +import adafruit_requests + + +@pytest.fixture(autouse=True) +def reset_connection_manager(monkeypatch): + """Reset the ConnectionManager, since it's a singlton and will hold data""" + monkeypatch.setattr( + "adafruit_requests.get_connection_manager", + adafruit_connection_manager.ConnectionManager, + ) + + +@pytest.fixture +def sock(): + return mocket.Mocket(mocket.MOCK_RESPONSE) + + +@pytest.fixture +def pool(sock): + pool = mocket.MocketPool() + pool.getaddrinfo.return_value = ( + (None, None, None, None, (mocket.MOCK_POOL_IP, 80)), + ) + pool.socket.return_value = sock + return pool + + +@pytest.fixture +def requests(pool): + return adafruit_requests.Session(pool) + + +@pytest.fixture +def requests_ssl(pool): + ssl_context = mocket.SSLContext() + return adafruit_requests.Session(pool, ssl_context) diff --git a/tests/header_test.py b/tests/header_test.py index f17501d..8bcb354 100644 --- a/tests/header_test.py +++ b/tests/header_test.py @@ -5,149 +5,85 @@ """ Header Tests """ import mocket +import pytest -import adafruit_requests -IP = "1.2.3.4" -HOST = "httpbin.org" -RESPONSE_HEADERS = b"HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n" +def test_check_headers_not_dict(requests): + with pytest.raises(AttributeError) as context: + requests._check_headers("") + assert "headers must be in dict format" in str(context) -def test_host(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE_HEADERS) - pool.socket.return_value = sock - sent = [] +def test_check_headers_not_valid(requests): + with pytest.raises(AttributeError) as context: + requests._check_headers( + {"Good1": "a", "Good2": b"b", "Good3": None, "Bad1": True} + ) + assert "Header part (True) from Bad1 must be of type str or bytes" in str(context) - def _send(data): - sent.append(data) # pylint: disable=no-member - return len(data) - sock.send.side_effect = _send +def test_check_headers_valid(requests): + requests._check_headers({"Good1": "a", "Good2": b"b", "Good3": None}) + assert True - requests_session = adafruit_requests.Session(pool) - headers = {} - requests_session.get("http://" + HOST + "/get", headers=headers) - - sock.connect.assert_called_once_with((IP, 80)) - sent = b"".join(sent) - assert b"Host: httpbin.org\r\n" in sent +def test_host(sock, requests): + headers = {} + requests.get("http://" + mocket.MOCK_HOST_1 + "/get", headers=headers) -def test_host_replace(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE_HEADERS) - pool.socket.return_value = sock - sent = [] - - def _send(data): - sent.append(data) # pylint: disable=no-member - return len(data) + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + sent = b"".join(sock.sent_data) + assert b"Host: wifitest.adafruit.com\r\n" in sent - sock.send.side_effect = _send - requests_session = adafruit_requests.Session(pool) - headers = {"host": IP} - requests_session.get("http://" + HOST + "/get", headers=headers) +def test_host_replace(sock, requests): + headers = {"host": mocket.MOCK_POOL_IP} + requests.get("http://" + mocket.MOCK_HOST_1 + "/get", headers=headers) - sock.connect.assert_called_once_with((IP, 80)) - sent = b"".join(sent) - assert b"host: 1.2.3.4\r\n" in sent - assert b"Host: httpbin.org\r\n" not in sent + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + sent = b"".join(sock.sent_data) + assert b"host: 10.10.10.10\r\n" in sent + assert b"Host: wifitest.adafruit.com\r\n" not in sent assert sent.lower().count(b"host:") == 1 -def test_user_agent(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE_HEADERS) - pool.socket.return_value = sock - sent = [] - - def _send(data): - sent.append(data) # pylint: disable=no-member - return len(data) - - sock.send.side_effect = _send - - requests_session = adafruit_requests.Session(pool) +def test_user_agent(sock, requests): headers = {} - requests_session.get("http://" + HOST + "/get", headers=headers) + requests.get("http://" + mocket.MOCK_HOST_1 + "/get", headers=headers) - sock.connect.assert_called_once_with((IP, 80)) - sent = b"".join(sent) + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + sent = b"".join(sock.sent_data) assert b"User-Agent: Adafruit CircuitPython\r\n" in sent -def test_user_agent_replace(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE_HEADERS) - pool.socket.return_value = sock - sent = [] - - def _send(data): - sent.append(data) # pylint: disable=no-member - return len(data) - - sock.send.side_effect = _send - - requests_session = adafruit_requests.Session(pool) +def test_user_agent_replace(sock, requests): headers = {"user-agent": "blinka/1.0.0"} - requests_session.get("http://" + HOST + "/get", headers=headers) + requests.get("http://" + mocket.MOCK_HOST_1 + "/get", headers=headers) - sock.connect.assert_called_once_with((IP, 80)) - sent = b"".join(sent) + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + sent = b"".join(sock.sent_data) assert b"user-agent: blinka/1.0.0\r\n" in sent assert b"User-Agent: Adafruit CircuitPython\r\n" not in sent assert sent.lower().count(b"user-agent:") == 1 -def test_content_type(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE_HEADERS) - pool.socket.return_value = sock - sent = [] - - def _send(data): - sent.append(data) # pylint: disable=no-member - return len(data) - - sock.send.side_effect = _send - - requests_session = adafruit_requests.Session(pool) +def test_content_type(sock, requests): headers = {} data = {"test": True} - requests_session.post("http://" + HOST + "/get", data=data, headers=headers) + requests.post("http://" + mocket.MOCK_HOST_1 + "/get", data=data, headers=headers) - sock.connect.assert_called_once_with((IP, 80)) - sent = b"".join(sent) + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + sent = b"".join(sock.sent_data) assert b"Content-Type: application/x-www-form-urlencoded\r\n" in sent -def test_content_type_replace(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE_HEADERS) - pool.socket.return_value = sock - sent = [] - - def _send(data): - sent.append(data) # pylint: disable=no-member - return len(data) - - sock.send.side_effect = _send - - requests_session = adafruit_requests.Session(pool) +def test_content_type_replace(sock, requests): headers = {"content-type": "application/test"} data = {"test": True} - requests_session.post("http://" + HOST + "/get", data=data, headers=headers) + requests.post("http://" + mocket.MOCK_HOST_1 + "/get", data=data, headers=headers) - sock.connect.assert_called_once_with((IP, 80)) - sent = b"".join(sent) + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + sent = b"".join(sock.sent_data) assert b"content-type: application/test\r\n" in sent assert b"Content-Type: application/x-www-form-urlencoded\r\n" not in sent assert sent.lower().count(b"content-type:") == 1 diff --git a/tests/method_test.py b/tests/method_test.py new file mode 100644 index 0000000..dac6d07 --- /dev/null +++ b/tests/method_test.py @@ -0,0 +1,80 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +""" Post Tests """ + +from unittest import mock + +import mocket +import pytest + + +@pytest.mark.parametrize( + "call", + ( + "DELETE", + "GET", + "HEAD", + "PATCH", + "POST", + "PUT", + ), +) +def test_methods(call, sock, requests): + method = getattr(requests, call.lower()) + method("http://" + mocket.MOCK_HOST_1 + "/" + call.lower()) + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + + sock.send.assert_has_calls( + [ + mock.call(bytes(call, "utf-8")), + mock.call(b" /"), + mock.call(bytes(call.lower(), "utf-8")), + mock.call(b" HTTP/1.1\r\n"), + ] + ) + sock.send.assert_has_calls( + [ + mock.call(b"Host"), + mock.call(b": "), + mock.call(b"wifitest.adafruit.com"), + ] + ) + + +def test_post_string(sock, requests): + data = "31F" + requests.post("http://" + mocket.MOCK_HOST_1 + "/post", data=data) + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + sock.send.assert_called_with(b"31F") + + +def test_post_form(sock, requests): + data = {"Date": "July 25, 2019", "Time": "12:00"} + requests.post("http://" + mocket.MOCK_HOST_1 + "/post", data=data) + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + sock.send.assert_has_calls( + [ + mock.call(b"Content-Type"), + mock.call(b": "), + mock.call(b"application/x-www-form-urlencoded"), + mock.call(b"\r\n"), + ] + ) + sock.send.assert_called_with(b"Date=July 25, 2019&Time=12:00") + + +def test_post_json(sock, requests): + json_data = {"Date": "July 25, 2019", "Time": "12:00"} + requests.post("http://" + mocket.MOCK_HOST_1 + "/post", json=json_data) + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + sock.send.assert_has_calls( + [ + mock.call(b"Content-Type"), + mock.call(b": "), + mock.call(b"application/json"), + mock.call(b"\r\n"), + ] + ) + sock.send.assert_called_with(b'{"Date": "July 25, 2019", "Time": "12:00"}') diff --git a/tests/mocket.py b/tests/mocket.py index 3603800..3155231 100644 --- a/tests/mocket.py +++ b/tests/mocket.py @@ -1,4 +1,5 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries # # SPDX-License-Identifier: Unlicense @@ -6,21 +7,34 @@ from unittest import mock +MOCK_POOL_IP = "10.10.10.10" +MOCK_HOST_1 = "wifitest.adafruit.com" +MOCK_HOST_2 = "wifitest2.adafruit.com" +MOCK_PATH_1 = "/testwifi/index.html" +MOCK_ENDPOINT_1 = MOCK_HOST_1 + MOCK_PATH_1 +MOCK_ENDPOINT_2 = MOCK_HOST_2 + MOCK_PATH_1 +MOCK_RESPONSE_TEXT = ( + b"This is a test of Adafruit WiFi!\r\nIf you can read this, its working :)" +) +MOCK_RESPONSE = b"HTTP/1.0 200 OK\r\nContent-Length: 70\r\n\r\n" + MOCK_RESPONSE_TEXT + class MocketPool: # pylint: disable=too-few-public-methods """Mock SocketPool""" SOCK_STREAM = 0 - def __init__(self): + # pylint: disable=unused-argument + def __init__(self, radio=None): self.getaddrinfo = mock.Mock() + self.getaddrinfo.return_value = ((None, None, None, None, (MOCK_POOL_IP, 80)),) self.socket = mock.Mock() class Mocket: # pylint: disable=too-few-public-methods """Mock Socket""" - def __init__(self, response): + def __init__(self, response=MOCK_RESPONSE): self.settimeout = mock.Mock() self.close = mock.Mock() self.connect = mock.Mock() @@ -28,14 +42,17 @@ def __init__(self, response): self.readline = mock.Mock(side_effect=self._readline) self.recv = mock.Mock(side_effect=self._recv) self.recv_into = mock.Mock(side_effect=self._recv_into) + # Test helpers self._response = response self._position = 0 self.fail_next_send = False + self.sent_data = [] def _send(self, data): if self.fail_next_send: self.fail_next_send = False return 0 + self.sent_data.append(data) return len(data) def _readline(self): @@ -71,3 +88,18 @@ def _wrap_socket( self, sock, server_hostname=None ): # pylint: disable=no-self-use,unused-argument return sock + + +# pylint: disable=too-few-public-methods +class MockRadio: + class Radio: + pass + + class ESP_SPIcontrol: + TLS_MODE = 2 + + class WIZNET5K: + pass + + class Unsupported: + pass diff --git a/tests/parse_test.py b/tests/parse_test.py index c13a720..d9e1a56 100644 --- a/tests/parse_test.py +++ b/tests/parse_test.py @@ -10,8 +10,6 @@ import adafruit_requests -IP = "1.2.3.4" -HOST = "httpbin.org" RESPONSE = {"Date": "July 25, 2019"} ENCODED = json.dumps(RESPONSE).encode("utf-8") # Padding here tests the case where a header line is exactly 32 bytes buffered by @@ -26,13 +24,11 @@ ) -def test_json(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) +def test_json(pool): sock = mocket.Mocket(HEADERS + ENCODED) pool.socket.return_value = sock requests_session = adafruit_requests.Session(pool) - response = requests_session.get("http://" + HOST + "/get") - sock.connect.assert_called_once_with((IP, 80)) + response = requests_session.get("http://" + mocket.MOCK_HOST_1 + "/get") + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) assert response.json() == RESPONSE diff --git a/tests/post_test.py b/tests/post_test.py deleted file mode 100644 index 4ead7d1..0000000 --- a/tests/post_test.py +++ /dev/null @@ -1,102 +0,0 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -""" Post Tests """ - -import json -from unittest import mock - -import mocket - -import adafruit_requests - -IP = "1.2.3.4" -HOST = "httpbin.org" -RESPONSE = {} -ENCODED = json.dumps(RESPONSE).encode("utf-8") -HEADERS = "HTTP/1.0 200 OK\r\nContent-Length: {}\r\n\r\n".format(len(ENCODED)).encode( - "utf-8" -) - - -def test_method(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(HEADERS + ENCODED) - pool.socket.return_value = sock - - requests_session = adafruit_requests.Session(pool) - requests_session.post("http://" + HOST + "/post") - sock.connect.assert_called_once_with((IP, 80)) - - sock.send.assert_has_calls( - [ - mock.call(b"POST"), - mock.call(b" /"), - mock.call(b"post"), - mock.call(b" HTTP/1.1\r\n"), - ] - ) - sock.send.assert_has_calls( - [ - mock.call(b"Host"), - mock.call(b": "), - mock.call(b"httpbin.org"), - ] - ) - - -def test_string(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(HEADERS + ENCODED) - pool.socket.return_value = sock - - requests_session = adafruit_requests.Session(pool) - data = "31F" - requests_session.post("http://" + HOST + "/post", data=data) - sock.connect.assert_called_once_with((IP, 80)) - sock.send.assert_called_with(b"31F") - - -def test_form(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(HEADERS + ENCODED) - pool.socket.return_value = sock - - requests_session = adafruit_requests.Session(pool) - data = {"Date": "July 25, 2019", "Time": "12:00"} - requests_session.post("http://" + HOST + "/post", data=data) - sock.connect.assert_called_once_with((IP, 80)) - sock.send.assert_has_calls( - [ - mock.call(b"Content-Type"), - mock.call(b": "), - mock.call(b"application/x-www-form-urlencoded"), - mock.call(b"\r\n"), - ] - ) - sock.send.assert_called_with(b"Date=July 25, 2019&Time=12:00") - - -def test_json(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(HEADERS + ENCODED) - pool.socket.return_value = sock - - requests_session = adafruit_requests.Session(pool) - json_data = {"Date": "July 25, 2019", "Time": "12:00"} - requests_session.post("http://" + HOST + "/post", json=json_data) - sock.connect.assert_called_once_with((IP, 80)) - sock.send.assert_has_calls( - [ - mock.call(b"Content-Type"), - mock.call(b": "), - mock.call(b"application/json"), - mock.call(b"\r\n"), - ] - ) - sock.send.assert_called_with(b'{"Date": "July 25, 2019", "Time": "12:00"}') diff --git a/tests/protocol_test.py b/tests/protocol_test.py index 33bf11c..4fd7770 100644 --- a/tests/protocol_test.py +++ b/tests/protocol_test.py @@ -9,37 +9,16 @@ import mocket import pytest -import adafruit_requests -IP = "1.2.3.4" -HOST = "wifitest.adafruit.com" -PATH = "/testwifi/index.html" -TEXT = b"This is a test of Adafruit WiFi!\r\nIf you can read this, its working :)" -RESPONSE = b"HTTP/1.0 200 OK\r\nContent-Length: 70\r\n\r\n" + TEXT - - -def test_get_https_no_ssl(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE) - pool.socket.return_value = sock - - requests_session = adafruit_requests.Session(pool) +def test_get_https_no_ssl(requests): with pytest.raises(AttributeError): - requests_session.get("https://" + HOST + PATH) + requests.get("https://" + mocket.MOCK_ENDPOINT_1) -def test_get_https_text(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE) - pool.socket.return_value = sock - ssl = mocket.SSLContext() +def test_get_https_text(sock, requests_ssl): + response = requests_ssl.get("https://" + mocket.MOCK_ENDPOINT_1) - requests_session = adafruit_requests.Session(pool, ssl) - response = requests_session.get("https://" + HOST + PATH) - - sock.connect.assert_called_once_with((HOST, 443)) + sock.connect.assert_called_once_with((mocket.MOCK_HOST_1, 443)) sock.send.assert_has_calls( [ @@ -56,22 +35,16 @@ def test_get_https_text(): mock.call(b"wifitest.adafruit.com"), ] ) - assert response.text == str(TEXT, "utf-8") + assert response.text == str(mocket.MOCK_RESPONSE_TEXT, "utf-8") # Close isn't needed but can be called to release the socket early. response.close() -def test_get_http_text(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE) - pool.socket.return_value = sock - - requests_session = adafruit_requests.Session(pool) - response = requests_session.get("http://" + HOST + PATH) +def test_get_http_text(sock, requests): + response = requests.get("http://" + mocket.MOCK_ENDPOINT_1) - sock.connect.assert_called_once_with((IP, 80)) + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) sock.send.assert_has_calls( [ @@ -88,20 +61,14 @@ def test_get_http_text(): mock.call(b"wifitest.adafruit.com"), ] ) - assert response.text == str(TEXT, "utf-8") + assert response.text == str(mocket.MOCK_RESPONSE_TEXT, "utf-8") -def test_get_close(): +def test_get_close(sock, requests): """Test that a response can be closed without the contents being accessed.""" - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE) - pool.socket.return_value = sock - - requests_session = adafruit_requests.Session(pool) - response = requests_session.get("http://" + HOST + PATH) + response = requests.get("http://" + mocket.MOCK_ENDPOINT_1) - sock.connect.assert_called_once_with((IP, 80)) + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) sock.send.assert_has_calls( [ diff --git a/tests/reuse_test.py b/tests/reuse_test.py index 0cf42a1..8ef5f5d 100644 --- a/tests/reuse_test.py +++ b/tests/reuse_test.py @@ -9,25 +9,12 @@ import mocket import pytest -import adafruit_requests -IP = "1.2.3.4" -HOST = "wifitest.adafruit.com" -HOST2 = "wifitest2.adafruit.com" -PATH = "/testwifi/index.html" -TEXT = b"This is a test of Adafruit WiFi!\r\nIf you can read this, its working :)" -RESPONSE = b"HTTP/1.0 200 OK\r\nContent-Length: 70\r\n\r\n" + TEXT - - -def test_get_twice(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE + RESPONSE) +def test_get_twice(pool, requests_ssl): + sock = mocket.Mocket(mocket.MOCK_RESPONSE + mocket.MOCK_RESPONSE) pool.socket.return_value = sock - ssl = mocket.SSLContext() - requests_session = adafruit_requests.Session(pool, ssl) - response = requests_session.get("https://" + HOST + PATH) + response = requests_ssl.get("https://" + mocket.MOCK_ENDPOINT_1) sock.send.assert_has_calls( [ @@ -44,9 +31,9 @@ def test_get_twice(): mock.call(b"wifitest.adafruit.com"), ] ) - assert response.text == str(TEXT, "utf-8") + assert response.text == str(mocket.MOCK_RESPONSE_TEXT, "utf-8") - response = requests_session.get("https://" + HOST + PATH + "2") + response = requests_ssl.get("https://" + mocket.MOCK_ENDPOINT_1 + "2") sock.send.assert_has_calls( [ @@ -64,20 +51,16 @@ def test_get_twice(): ] ) - assert response.text == str(TEXT, "utf-8") - sock.connect.assert_called_once_with((HOST, 443)) + assert response.text == str(mocket.MOCK_RESPONSE_TEXT, "utf-8") + sock.connect.assert_called_once_with((mocket.MOCK_HOST_1, 443)) pool.socket.assert_called_once() -def test_get_twice_after_second(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE + RESPONSE) +def test_get_twice_after_second(pool, requests_ssl): + sock = mocket.Mocket(mocket.MOCK_RESPONSE + mocket.MOCK_RESPONSE) pool.socket.return_value = sock - ssl = mocket.SSLContext() - requests_session = adafruit_requests.Session(pool, ssl) - response = requests_session.get("https://" + HOST + PATH) + response = requests_ssl.get("https://" + mocket.MOCK_ENDPOINT_1) sock.send.assert_has_calls( [ @@ -95,7 +78,7 @@ def test_get_twice_after_second(): ] ) - requests_session.get("https://" + HOST + PATH + "2") + requests_ssl.get("https://" + mocket.MOCK_ENDPOINT_1 + "2") sock.send.assert_has_calls( [ @@ -112,25 +95,22 @@ def test_get_twice_after_second(): mock.call(b"wifitest.adafruit.com"), ] ) - sock.connect.assert_called_once_with((HOST, 443)) + sock.connect.assert_called_once_with((mocket.MOCK_HOST_1, 443)) pool.socket.assert_called_once() - with pytest.raises(RuntimeError): - response.text == str(TEXT, "utf-8") # pylint: disable=expression-not-assigned + with pytest.raises(RuntimeError) as context: + result = response.text # pylint: disable=unused-variable + assert "Newer Response closed this one. Use Responses immediately." in str(context) -def test_connect_out_of_memory(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE) - sock2 = mocket.Mocket(RESPONSE) - sock3 = mocket.Mocket(RESPONSE) +def test_connect_out_of_memory(pool, requests_ssl): + sock = mocket.Mocket() + sock2 = mocket.Mocket() + sock3 = mocket.Mocket() pool.socket.side_effect = [sock, sock2, sock3] sock2.connect.side_effect = MemoryError() - ssl = mocket.SSLContext() - requests_session = adafruit_requests.Session(pool, ssl) - response = requests_session.get("https://" + HOST + PATH) + response = requests_ssl.get("https://" + mocket.MOCK_ENDPOINT_1) sock.send.assert_has_calls( [ @@ -147,9 +127,9 @@ def test_connect_out_of_memory(): mock.call(b"wifitest.adafruit.com"), ] ) - assert response.text == str(TEXT, "utf-8") + assert response.text == str(mocket.MOCK_RESPONSE_TEXT, "utf-8") - response = requests_session.get("https://" + HOST2 + PATH) + response = requests_ssl.get("https://" + mocket.MOCK_ENDPOINT_2) sock3.send.assert_has_calls( [ mock.call(b"GET"), @@ -166,23 +146,18 @@ def test_connect_out_of_memory(): ] ) - assert response.text == str(TEXT, "utf-8") + assert response.text == str(mocket.MOCK_RESPONSE_TEXT, "utf-8") sock.close.assert_called_once() - sock.connect.assert_called_once_with((HOST, 443)) - sock3.connect.assert_called_once_with((HOST2, 443)) + sock.connect.assert_called_once_with((mocket.MOCK_HOST_1, 443)) + sock3.connect.assert_called_once_with((mocket.MOCK_HOST_2, 443)) -def test_second_send_fails(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE) - sock2 = mocket.Mocket(RESPONSE) +def test_second_send_fails(pool, requests_ssl): + sock = mocket.Mocket() + sock2 = mocket.Mocket() pool.socket.side_effect = [sock, sock2] - ssl = mocket.SSLContext() - - requests_session = adafruit_requests.Session(pool, ssl) - response = requests_session.get("https://" + HOST + PATH) + response = requests_ssl.get("https://" + mocket.MOCK_ENDPOINT_1) sock.send.assert_has_calls( [ @@ -198,30 +173,25 @@ def test_second_send_fails(): mock.call(b"\r\n"), ] ) - assert response.text == str(TEXT, "utf-8") + assert response.text == str(mocket.MOCK_RESPONSE_TEXT, "utf-8") sock.fail_next_send = True - requests_session.get("https://" + HOST + PATH + "2") + requests_ssl.get("https://" + mocket.MOCK_ENDPOINT_1 + "2") - sock.connect.assert_called_once_with((HOST, 443)) - sock2.connect.assert_called_once_with((HOST, 443)) + sock.connect.assert_called_once_with((mocket.MOCK_HOST_1, 443)) + sock2.connect.assert_called_once_with((mocket.MOCK_HOST_1, 443)) # Make sure that the socket is closed after send fails. sock.close.assert_called_once() assert sock2.close.call_count == 0 assert pool.socket.call_count == 2 -def test_second_send_lies_recv_fails(): - pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ((None, None, None, None, (IP, 80)),) - sock = mocket.Mocket(RESPONSE) - sock2 = mocket.Mocket(RESPONSE) +def test_second_send_lies_recv_fails(pool, requests_ssl): + sock = mocket.Mocket() + sock2 = mocket.Mocket() pool.socket.side_effect = [sock, sock2] - ssl = mocket.SSLContext() - - requests_session = adafruit_requests.Session(pool, ssl) - response = requests_session.get("https://" + HOST + PATH) + response = requests_ssl.get("https://" + mocket.MOCK_ENDPOINT_1) sock.send.assert_has_calls( [ @@ -237,12 +207,12 @@ def test_second_send_lies_recv_fails(): mock.call(b"\r\n"), ] ) - assert response.text == str(TEXT, "utf-8") + assert response.text == str(mocket.MOCK_RESPONSE_TEXT, "utf-8") - requests_session.get("https://" + HOST + PATH + "2") + requests_ssl.get("https://" + mocket.MOCK_ENDPOINT_1 + "2") - sock.connect.assert_called_once_with((HOST, 443)) - sock2.connect.assert_called_once_with((HOST, 443)) + sock.connect.assert_called_once_with((mocket.MOCK_HOST_1, 443)) + sock2.connect.assert_called_once_with((mocket.MOCK_HOST_1, 443)) # Make sure that the socket is closed after send fails. sock.close.assert_called_once() assert sock2.close.call_count == 0 From 81657b185dc5ccb2a806bab97e7fb53abc9726b2 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Fri, 23 Feb 2024 10:41:19 -0800 Subject: [PATCH 187/305] Change appw to password in examples --- examples/esp32spi/requests_esp32spi_advanced.py | 4 ++-- examples/esp32spi/requests_esp32spi_simpletest.py | 4 ++-- .../expanded/requests_wifi_adafruit_discord_active_online.py | 4 ++-- examples/wifi/expanded/requests_wifi_api_discord.py | 4 ++-- examples/wifi/expanded/requests_wifi_api_fitbit.py | 4 ++-- examples/wifi/expanded/requests_wifi_api_github.py | 4 ++-- examples/wifi/expanded/requests_wifi_api_mastodon.py | 4 ++-- .../wifi/expanded/requests_wifi_api_openskynetwork_private.py | 4 ++-- .../expanded/requests_wifi_api_openskynetwork_private_area.py | 4 ++-- .../wifi/expanded/requests_wifi_api_openskynetwork_public.py | 4 ++-- examples/wifi/expanded/requests_wifi_api_steam.py | 4 ++-- examples/wifi/expanded/requests_wifi_api_twitch.py | 4 ++-- examples/wifi/expanded/requests_wifi_api_twitter.py | 4 ++-- examples/wifi/expanded/requests_wifi_api_youtube.py | 4 ++-- examples/wifi/expanded/requests_wifi_multiple_cookies.py | 4 ++-- examples/wifi/requests_wifi_advanced.py | 4 ++-- examples/wifi/requests_wifi_simpletest.py | 4 ++-- 17 files changed, 34 insertions(+), 34 deletions(-) diff --git a/examples/esp32spi/requests_esp32spi_advanced.py b/examples/esp32spi/requests_esp32spi_advanced.py index 4e5a15e..3b0bb41 100644 --- a/examples/esp32spi/requests_esp32spi_advanced.py +++ b/examples/esp32spi/requests_esp32spi_advanced.py @@ -14,7 +14,7 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -37,7 +37,7 @@ print("Connecting to AP...") while not radio.is_connected: try: - radio.connect_AP(ssid, appw) + radio.connect_AP(ssid, password) except RuntimeError as e: print("could not connect to AP, retrying: ", e) continue diff --git a/examples/esp32spi/requests_esp32spi_simpletest.py b/examples/esp32spi/requests_esp32spi_simpletest.py index 8c72797..01d8204 100644 --- a/examples/esp32spi/requests_esp32spi_simpletest.py +++ b/examples/esp32spi/requests_esp32spi_simpletest.py @@ -14,7 +14,7 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") # If you are using a board with pre-defined ESP32 Pins: esp32_cs = DigitalInOut(board.ESP_CS) @@ -37,7 +37,7 @@ print("Connecting to AP...") while not radio.is_connected: try: - radio.connect_AP(ssid, appw) + radio.connect_AP(ssid, password) except RuntimeError as e: print("could not connect to AP, retrying: ", e) continue diff --git a/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py b/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py index 9262e51..d43f73f 100644 --- a/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py +++ b/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py @@ -28,7 +28,7 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Converts seconds to minutes/hours/days @@ -58,7 +58,7 @@ def time_calc(input_time): requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(ssid, appw) + wifi.radio.connect(ssid, password) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/wifi/expanded/requests_wifi_api_discord.py b/examples/wifi/expanded/requests_wifi_api_discord.py index 0202fc0..91b2b6c 100644 --- a/examples/wifi/expanded/requests_wifi_api_discord.py +++ b/examples/wifi/expanded/requests_wifi_api_discord.py @@ -21,7 +21,7 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") Discord_Auth = os.getenv("Discord_Authorization") # Initialize WiFi Pool (There can be only 1 pool & top of script) @@ -62,7 +62,7 @@ def time_calc(input_time): # input_time in seconds requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(ssid, appw) + wifi.radio.connect(ssid, password) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/wifi/expanded/requests_wifi_api_fitbit.py b/examples/wifi/expanded/requests_wifi_api_fitbit.py index 9eba799..022ee22 100644 --- a/examples/wifi/expanded/requests_wifi_api_fitbit.py +++ b/examples/wifi/expanded/requests_wifi_api_fitbit.py @@ -38,7 +38,7 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") Fitbit_ClientID = os.getenv("Fitbit_ClientID") Fitbit_Token = os.getenv("Fitbit_Token") @@ -79,7 +79,7 @@ def time_calc(input_time): requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(ssid, appw) + wifi.radio.connect(ssid, password) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/wifi/expanded/requests_wifi_api_github.py b/examples/wifi/expanded/requests_wifi_api_github.py index 1153e93..f1f86e3 100644 --- a/examples/wifi/expanded/requests_wifi_api_github.py +++ b/examples/wifi/expanded/requests_wifi_api_github.py @@ -22,7 +22,7 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Github developer token required. github_username = os.getenv("Github_username") github_token = os.getenv("Github_token") @@ -49,7 +49,7 @@ requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(ssid, appw) + wifi.radio.connect(ssid, password) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/wifi/expanded/requests_wifi_api_mastodon.py b/examples/wifi/expanded/requests_wifi_api_mastodon.py index 4136491..56f3e8d 100644 --- a/examples/wifi/expanded/requests_wifi_api_mastodon.py +++ b/examples/wifi/expanded/requests_wifi_api_mastodon.py @@ -32,7 +32,7 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Converts seconds in minutes/hours/days @@ -70,7 +70,7 @@ def time_calc(input_time): requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(ssid, appw) + wifi.radio.connect(ssid, password) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py index ebdd420..486a4de 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py @@ -32,7 +32,7 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") osnu = os.getenv("OSN_Username") osnp = os.getenv("OSN_Password") @@ -92,7 +92,7 @@ def _format_datetime(datetime): request = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(ssid, appw) + wifi.radio.connect(ssid, password) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py index 79a07df..8cd08a2 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py @@ -34,7 +34,7 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") # No token required, only website login osnu = os.getenv("OSN_Username") osnp = os.getenv("OSN_Password") @@ -107,7 +107,7 @@ def _format_datetime(datetime): request = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(ssid, appw) + wifi.radio.connect(ssid, password) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py index ec0486a..5bcd69b 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py @@ -30,7 +30,7 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Requests URL - icao24 is their endpoint required for a transponder # example https://opensky-network.org/api/states/all?icao24=a808c5 @@ -71,7 +71,7 @@ def _format_datetime(datetime): requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(ssid, appw) + wifi.radio.connect(ssid, password) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/wifi/expanded/requests_wifi_api_steam.py b/examples/wifi/expanded/requests_wifi_api_steam.py index bffda83..208b6d6 100644 --- a/examples/wifi/expanded/requests_wifi_api_steam.py +++ b/examples/wifi/expanded/requests_wifi_api_steam.py @@ -20,7 +20,7 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Requires Steam Developer API key steam_usernumber = os.getenv("steam_id") steam_apikey = os.getenv("steam_api_key") @@ -64,7 +64,7 @@ requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(ssid, appw) + wifi.radio.connect(ssid, password) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/wifi/expanded/requests_wifi_api_twitch.py b/examples/wifi/expanded/requests_wifi_api_twitch.py index f637591..716caa9 100644 --- a/examples/wifi/expanded/requests_wifi_api_twitch.py +++ b/examples/wifi/expanded/requests_wifi_api_twitch.py @@ -25,7 +25,7 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") twitch_client_id = os.getenv("Twitch_ClientID") twitch_client_secret = os.getenv("Twitch_Client_Secret") # For finding your Twitch User ID @@ -59,7 +59,7 @@ def time_calc(input_time): requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.connected: try: - wifi.radio.connect(ssid, appw) + wifi.radio.connect(ssid, password) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/wifi/expanded/requests_wifi_api_twitter.py b/examples/wifi/expanded/requests_wifi_api_twitter.py index 1c4dc3b..4dcdfa6 100644 --- a/examples/wifi/expanded/requests_wifi_api_twitter.py +++ b/examples/wifi/expanded/requests_wifi_api_twitter.py @@ -27,7 +27,7 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") tw_userid = os.getenv("TW_userid") tw_bearer_token = os.getenv("TW_bearer_token") @@ -60,7 +60,7 @@ requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(ssid, appw) + wifi.radio.connect(ssid, password) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/wifi/expanded/requests_wifi_api_youtube.py b/examples/wifi/expanded/requests_wifi_api_youtube.py index fb539ff..e9bc6a2 100644 --- a/examples/wifi/expanded/requests_wifi_api_youtube.py +++ b/examples/wifi/expanded/requests_wifi_api_youtube.py @@ -26,7 +26,7 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") yt_username = os.getenv("YT_username") yt_token = os.getenv("YT_token") @@ -60,7 +60,7 @@ requests = adafruit_requests.Session(pool, ssl.create_default_context()) while not wifi.radio.ipv4_address: try: - wifi.radio.connect(ssid, appw) + wifi.radio.connect(ssid, password) except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/wifi/expanded/requests_wifi_multiple_cookies.py b/examples/wifi/expanded/requests_wifi_multiple_cookies.py index 9e3fcf5..36e4616 100644 --- a/examples/wifi/expanded/requests_wifi_multiple_cookies.py +++ b/examples/wifi/expanded/requests_wifi_multiple_cookies.py @@ -18,11 +18,11 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Connect to the Wi-Fi network print("Connecting to %s" % ssid) -wifi.radio.connect(ssid, appw) +wifi.radio.connect(ssid, password) # Set up the requests library pool = socketpool.SocketPool(wifi.radio) diff --git a/examples/wifi/requests_wifi_advanced.py b/examples/wifi/requests_wifi_advanced.py index e6985a4..3bb0976 100644 --- a/examples/wifi/requests_wifi_advanced.py +++ b/examples/wifi/requests_wifi_advanced.py @@ -11,7 +11,7 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Initialize WiFi Pool (There can be only 1 pool & top of script) radio = wifi.radio @@ -20,7 +20,7 @@ print("Connecting to AP...") while not wifi.radio.ipv4_address: try: - wifi.radio.connect(ssid, appw) + wifi.radio.connect(ssid, password) except ConnectionError as e: print("could not connect to AP, retrying: ", e) print("Connected to", str(radio.ap_info.ssid, "utf-8"), "\tRSSI:", radio.ap_info.rssi) diff --git a/examples/wifi/requests_wifi_simpletest.py b/examples/wifi/requests_wifi_simpletest.py index 033b2db..35b835a 100644 --- a/examples/wifi/requests_wifi_simpletest.py +++ b/examples/wifi/requests_wifi_simpletest.py @@ -11,7 +11,7 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") -appw = os.getenv("CIRCUITPY_WIFI_PASSWORD") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Initialize WiFi Pool (There can be only 1 pool & top of script) radio = wifi.radio @@ -20,7 +20,7 @@ print("Connecting to AP...") while not wifi.radio.ipv4_address: try: - wifi.radio.connect(ssid, appw) + wifi.radio.connect(ssid, password) except ConnectionError as e: print("could not connect to AP, retrying: ", e) print("Connected to", str(radio.ap_info.ssid, "utf-8"), "\tRSSI:", radio.ap_info.rssi) From 6473e0ad78c28efb357eb627fbe1e406230bee33 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 27 Feb 2024 12:45:58 -0800 Subject: [PATCH 188/305] Update README.rst Add Connection Manager dep. --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 5f52cea..a04f450 100755 --- a/README.rst +++ b/README.rst @@ -25,6 +25,7 @@ Dependencies This driver depends on: * `Adafruit CircuitPython `_ +* `Adafruit CircuitPython ConnectionManager `_ Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading From e55e975183ab3079ceaee9399131f33cbc515b8a Mon Sep 17 00:00:00 2001 From: Bernhard Bablok Date: Wed, 28 Feb 2024 08:04:35 +0100 Subject: [PATCH 189/305] close(): fix exception and enable fast-close --- adafruit_requests.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 771bfcc..a64fd3d 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -226,22 +226,23 @@ def _throw_away(self, nbytes: int) -> None: while to_read > 0: to_read -= self._recv_into(buf, to_read) - def close(self) -> None: + def close(self, fast: bool = False) -> None: """Drain the remaining ESP socket buffers. We assume we already got what we wanted.""" if not self.socket: return # Make sure we've read all of our response. - if self._cached is None: + if self._cached is None and not fast: if self._remaining and self._remaining > 0: self._throw_away(self._remaining) elif self._chunked: while True: chunk_header = bytes(self._readto(b"\r\n")).split(b";", 1)[0] + if not chunk_header: + break chunk_size = int(bytes(chunk_header), 16) if chunk_size == 0: break self._throw_away(chunk_size + 2) - self._parse_headers() if self._session: # pylint: disable=protected-access self._session._connection_manager.free_socket(self.socket) From 6d2bef35c2fb7aa8ad001d8b3b07a1dafde98d78 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Fri, 1 Mar 2024 09:15:35 -0800 Subject: [PATCH 190/305] Fix get_radio_ssl_context --- examples/esp32spi/requests_esp32spi_advanced.py | 4 ++-- examples/esp32spi/requests_esp32spi_simpletest.py | 4 ++-- examples/wiznet5k/requests_wiznet5k_advanced.py | 4 ++-- examples/wiznet5k/requests_wiznet5k_simpletest.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/esp32spi/requests_esp32spi_advanced.py b/examples/esp32spi/requests_esp32spi_advanced.py index 3b0bb41..66d7b5c 100644 --- a/examples/esp32spi/requests_esp32spi_advanced.py +++ b/examples/esp32spi/requests_esp32spi_advanced.py @@ -4,7 +4,6 @@ import os import adafruit_connection_manager -import adafruit_esp32spi.adafruit_esp32spi_socket as pool import board import busio from adafruit_esp32spi import adafruit_esp32spi @@ -44,7 +43,8 @@ print("Connected to", str(radio.ssid, "utf-8"), "\tRSSI:", radio.rssi) # Initialize a requests session -ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, radio) +pool = adafruit_connection_manager.get_radio_socketpool(radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(radio) requests = adafruit_requests.Session(pool, ssl_context) JSON_GET_URL = "https://httpbin.org/get" diff --git a/examples/esp32spi/requests_esp32spi_simpletest.py b/examples/esp32spi/requests_esp32spi_simpletest.py index 01d8204..3ace026 100644 --- a/examples/esp32spi/requests_esp32spi_simpletest.py +++ b/examples/esp32spi/requests_esp32spi_simpletest.py @@ -4,7 +4,6 @@ import os import adafruit_connection_manager -import adafruit_esp32spi.adafruit_esp32spi_socket as pool import board import busio from adafruit_esp32spi import adafruit_esp32spi @@ -44,7 +43,8 @@ print("Connected to", str(radio.ssid, "utf-8"), "\tRSSI:", radio.rssi) # Initialize a requests session -ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, radio) +pool = adafruit_connection_manager.get_radio_socketpool(radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(radio) requests = adafruit_requests.Session(pool, ssl_context) TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" diff --git a/examples/wiznet5k/requests_wiznet5k_advanced.py b/examples/wiznet5k/requests_wiznet5k_advanced.py index a6d9909..e98e728 100644 --- a/examples/wiznet5k/requests_wiznet5k_advanced.py +++ b/examples/wiznet5k/requests_wiznet5k_advanced.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT import adafruit_connection_manager -import adafruit_wiznet5k.adafruit_wiznet5k_socket as pool import board import busio from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K @@ -17,7 +16,8 @@ radio = WIZNET5K(spi_bus, cs) # Initialize a requests session -ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, radio) +pool = adafruit_connection_manager.get_radio_socketpool(radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(radio) requests = adafruit_requests.Session(pool, ssl_context) JSON_GET_URL = "http://httpbin.org/get" diff --git a/examples/wiznet5k/requests_wiznet5k_simpletest.py b/examples/wiznet5k/requests_wiznet5k_simpletest.py index 646107f..e35b5ab 100644 --- a/examples/wiznet5k/requests_wiznet5k_simpletest.py +++ b/examples/wiznet5k/requests_wiznet5k_simpletest.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT import adafruit_connection_manager -import adafruit_wiznet5k.adafruit_wiznet5k_socket as pool import board import busio from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K @@ -17,7 +16,8 @@ radio = WIZNET5K(spi_bus, cs) # Initialize a requests session -ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, radio) +pool = adafruit_connection_manager.get_radio_socketpool(radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(radio) requests = adafruit_requests.Session(pool, ssl_context) TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" From b062ab95b2b7a4488ee42bf5464d7d9570ccc187 Mon Sep 17 00:00:00 2001 From: Bernhard Bablok Date: Tue, 5 Mar 2024 10:20:21 +0100 Subject: [PATCH 191/305] added fast_close as kw-arg to Session --- adafruit_requests.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index a64fd3d..be4dca7 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -86,11 +86,12 @@ class Response: encoding = None - def __init__(self, sock: SocketType, session: "Session") -> None: + def __init__(self, sock: SocketType, session: "Session", fast_close: bool = False) -> None: self.socket = sock self.encoding = "utf-8" self._cached = None self._headers = {} + self._fast_close = fast_close # _start_index and _receive_buffer are used when parsing headers. # _receive_buffer will grow by 32 bytes everytime it is too small. @@ -226,12 +227,12 @@ def _throw_away(self, nbytes: int) -> None: while to_read > 0: to_read -= self._recv_into(buf, to_read) - def close(self, fast: bool = False) -> None: + def close(self) -> None: """Drain the remaining ESP socket buffers. We assume we already got what we wanted.""" if not self.socket: return # Make sure we've read all of our response. - if self._cached is None and not fast: + if self._cached is None and not self._fast_close: if self._remaining and self._remaining > 0: self._throw_away(self._remaining) elif self._chunked: @@ -362,11 +363,13 @@ def __init__( socket_pool: SocketpoolModuleType, ssl_context: Optional[SSLContextType] = None, session_id: Optional[str] = None, + fast_close: Optional[Bool] = False, ) -> None: self._connection_manager = get_connection_manager(socket_pool) self._ssl_context = ssl_context self._session_id = session_id self._last_response = None + self._fast_close = fast_close @staticmethod def _check_headers(headers: Dict[str, str]): @@ -561,7 +564,7 @@ def request( if not socket: raise OutOfRetries("Repeated socket failures") from last_exc - resp = Response(socket, self) # our response + resp = Response(socket, self, fast_close=self._fast_close) # our response if allow_redirects: if "location" in resp.headers and 300 <= resp.status_code <= 399: # a naive handler for redirects From 46b0a6fb0c2db9c529c533c3ab44627b22deddad Mon Sep 17 00:00:00 2001 From: Bernhard Bablok Date: Tue, 5 Mar 2024 10:30:39 +0100 Subject: [PATCH 192/305] fixed spelling --- adafruit_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index be4dca7..856b3b7 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -363,7 +363,7 @@ def __init__( socket_pool: SocketpoolModuleType, ssl_context: Optional[SSLContextType] = None, session_id: Optional[str] = None, - fast_close: Optional[Bool] = False, + fast_close: Optional[bool] = False, ) -> None: self._connection_manager = get_connection_manager(socket_pool) self._ssl_context = ssl_context From 1d8cd63d8c56f3f08e9f8dfcc43679b60f226956 Mon Sep 17 00:00:00 2001 From: Bernhard Bablok Date: Tue, 5 Mar 2024 10:39:18 +0100 Subject: [PATCH 193/305] blackified --- adafruit_requests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 856b3b7..d6730c9 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -86,7 +86,9 @@ class Response: encoding = None - def __init__(self, sock: SocketType, session: "Session", fast_close: bool = False) -> None: + def __init__( + self, sock: SocketType, session: "Session", fast_close: bool = False + ) -> None: self.socket = sock self.encoding = "utf-8" self._cached = None From 0a9bb614ec93b774bff24d0b65801449bafcb2ba Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Tue, 12 Mar 2024 12:58:58 -0700 Subject: [PATCH 194/305] Don't read all on response.close() --- adafruit_requests.py | 32 ++++++++++---------------------- tests/method_test.py | 1 + tests/reuse_test.py | 9 ++++++++- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index d6730c9..9f38502 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -86,14 +86,11 @@ class Response: encoding = None - def __init__( - self, sock: SocketType, session: "Session", fast_close: bool = False - ) -> None: + def __init__(self, sock: SocketType, session: "Session") -> None: self.socket = sock self.encoding = "utf-8" self._cached = None self._headers = {} - self._fast_close = fast_close # _start_index and _receive_buffer are used when parsing headers. # _receive_buffer will grow by 32 bytes everytime it is too small. @@ -230,27 +227,16 @@ def _throw_away(self, nbytes: int) -> None: to_read -= self._recv_into(buf, to_read) def close(self) -> None: - """Drain the remaining ESP socket buffers. We assume we already got what we wanted.""" + """Close out the socket. If we have a session free it instead.""" if not self.socket: return - # Make sure we've read all of our response. - if self._cached is None and not self._fast_close: - if self._remaining and self._remaining > 0: - self._throw_away(self._remaining) - elif self._chunked: - while True: - chunk_header = bytes(self._readto(b"\r\n")).split(b";", 1)[0] - if not chunk_header: - break - chunk_size = int(bytes(chunk_header), 16) - if chunk_size == 0: - break - self._throw_away(chunk_size + 2) + if self._session: # pylint: disable=protected-access self._session._connection_manager.free_socket(self.socket) else: self.socket.close() + self.socket = None def _parse_headers(self) -> None: @@ -365,13 +351,11 @@ def __init__( socket_pool: SocketpoolModuleType, ssl_context: Optional[SSLContextType] = None, session_id: Optional[str] = None, - fast_close: Optional[bool] = False, ) -> None: self._connection_manager = get_connection_manager(socket_pool) self._ssl_context = ssl_context self._session_id = session_id self._last_response = None - self._fast_close = fast_close @staticmethod def _check_headers(headers: Dict[str, str]): @@ -389,7 +373,6 @@ def _check_headers(headers: Dict[str, str]): def _send(socket: SocketType, data: bytes): total_sent = 0 while total_sent < len(data): - # ESP32SPI sockets raise a RuntimeError when unable to send. try: sent = socket.send(data[total_sent:]) except OSError as exc: @@ -399,6 +382,7 @@ def _send(socket: SocketType, data: bytes): # Some worse error. raise except RuntimeError as exc: + # ESP32SPI sockets raise a RuntimeError when unable to send. raise OSError(errno.EIO) from exc if sent is None: sent = len(data) @@ -566,7 +550,7 @@ def request( if not socket: raise OutOfRetries("Repeated socket failures") from last_exc - resp = Response(socket, self, fast_close=self._fast_close) # our response + resp = Response(socket, self) # our response if allow_redirects: if "location" in resp.headers and 300 <= resp.status_code <= 399: # a naive handler for redirects @@ -594,6 +578,10 @@ def request( self._last_response = resp return resp + def options(self, url: str, **kw) -> Response: + """Send HTTP OPTIONS request""" + return self.request("OPTIONS", url, **kw) + def head(self, url: str, **kw) -> Response: """Send HTTP HEAD request""" return self.request("HEAD", url, **kw) diff --git a/tests/method_test.py b/tests/method_test.py index dac6d07..d75e754 100644 --- a/tests/method_test.py +++ b/tests/method_test.py @@ -16,6 +16,7 @@ "DELETE", "GET", "HEAD", + "OPTIONS", "PATCH", "POST", "PUT", diff --git a/tests/reuse_test.py b/tests/reuse_test.py index 8ef5f5d..0c0e9a5 100644 --- a/tests/reuse_test.py +++ b/tests/reuse_test.py @@ -57,7 +57,14 @@ def test_get_twice(pool, requests_ssl): def test_get_twice_after_second(pool, requests_ssl): - sock = mocket.Mocket(mocket.MOCK_RESPONSE + mocket.MOCK_RESPONSE) + sock = mocket.Mocket( + b"H" + b"TTP/1.0 200 OK\r\nContent-Length: " + b"70\r\n\r\nHTTP/1.0 2" + b"H" + b"TTP/1.0 200 OK\r\nContent-Length: " + b"70\r\n\r\nHTTP/1.0 2" + ) pool.socket.return_value = sock response = requests_ssl.get("https://" + mocket.MOCK_ENDPOINT_1) From 96a1380ef4b25d55ffbe148a01f8cb0aa3cd9ab2 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 14 Mar 2024 13:10:38 -0400 Subject: [PATCH 195/305] add rocketlaunch.live free API example The free API returns data on the next 5 scheduled rocket launches. One of the nice features of this API is any key:value that does not have data will return a string of "None". This almost eliminates all key:value errors. --- .../requests_wifi_api_rocketlaunch_live.py | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py diff --git a/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py b/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py new file mode 100644 index 0000000..0460e5c --- /dev/null +++ b/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py @@ -0,0 +1,114 @@ +# SPDX-FileCopyrightText: 2024 DJDevon3 +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 8.2.x +"""RocketLaunch.Live API Example""" + +import gc +import os +import ssl +import time +import socketpool +import wifi +import adafruit_requests + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + +# Time between API refreshes +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +sleep_time = 43200 + +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("WIFI_SSID") +password = os.getenv("WIFI_PASSWORD") + + +# Converts seconds in minutes/hours/days +def time_calc(input_time): + if input_time < 60: + sleep_int = input_time + time_output = f"{sleep_int:.0f} seconds" + elif 60 <= input_time < 3600: + sleep_int = input_time / 60 + time_output = f"{sleep_int:.0f} minutes" + elif 3600 <= input_time < 86400: + sleep_int = input_time / 60 / 60 + time_output = f"{sleep_int:.0f} hours" + elif 86400 <= input_time < 432000: + sleep_int = input_time / 60 / 60 / 24 + time_output = f"{sleep_int:.1f} days" + else: # if > 5 days convert float to int & display whole days + sleep_int = input_time / 60 / 60 / 24 + time_output = f"{sleep_int:.0f} days" + return time_output + + +# Free Public API, no token or header required +ROCKETLAUNCH_SOURCE = "https://fdo.rocketlaunch.live/json/launches/next/1" + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +requests = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) + gc.collect() +print("✅ WiFi!") +print("===============================") + +while True: + try: + # Print Request to Serial + print("Attempting to GET RocketLaunch.Live JSON!") + debug_rocketlaunch_full_response = False + + rocketlaunch_response = requests.get(url=ROCKETLAUNCH_SOURCE) + try: + rocketlaunch_json = rocketlaunch_response.json() + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + if debug_rocketlaunch_full_response: + print("Full API GET URL: ", ROCKETLAUNCH_SOURCE) + print(rocketlaunch_json) + + print("✅ RocketLaunch.Live JSON!") + rocketlaunch_flightname = str(rocketlaunch_json["result"][0]["name"]) + print(f" | Flight Name: {rocketlaunch_flightname}") + rocketlaunch_provider = str(rocketlaunch_json["result"][0]["provider"]["name"]) + print(f" | Provider: {rocketlaunch_provider}") + rocketlaunch_vehiclename = str( + rocketlaunch_json["result"][0]["vehicle"]["name"] + ) + print(f" | Vehicle Name: {rocketlaunch_vehiclename}") + + rocketlaunch_winopen = str(rocketlaunch_json["result"][0]["win_open"]) + rocketlaunch_winclose = str(rocketlaunch_json["result"][0]["win_close"]) + print(f" | Window: {rocketlaunch_winopen} to {rocketlaunch_winclose}") + + rocketlaunch_sitename = str( + rocketlaunch_json["result"][0]["pad"]["location"]["name"] + ) + print(f" | Launch Site: {rocketlaunch_sitename}") + + rocketlaunch_mission = str( + rocketlaunch_json["result"][0]["mission_description"] + ) + if rocketlaunch_mission != "None": + print(f" | Mission: {rocketlaunch_mission}") + + print("\nFinished!") + print("Board Uptime: ", time.monotonic()) + print("Next Update in: ", time_calc(sleep_time)) + print("===============================") + gc.collect() + except (ValueError, RuntimeError) as e: + print("Failed to get data, retrying\n", e) + time.sleep(60) + continue + time.sleep(sleep_time) From 40e0c177d4a56f1c9c83ab36ef5cde5f0953bda8 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 14 Mar 2024 15:31:01 -0400 Subject: [PATCH 196/305] pre-commit, black, pylint seems like it wants these changes but might still fail on pylint and unsure why. --- examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py b/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py index 0460e5c..4939365 100644 --- a/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py +++ b/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py @@ -7,8 +7,10 @@ import os import ssl import time + import socketpool import wifi + import adafruit_requests # Initialize WiFi Pool (There can be only 1 pool & top of script) From ac1e119d8276de6844358850bdaa1dfb3fb02af5 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Fri, 15 Mar 2024 02:08:37 -0400 Subject: [PATCH 197/305] Changed to Connection Manager refactored with Connection Manager, added pretty hierarchy serial view. --- .../requests_wifi_api_rocketlaunch_live.py | 111 ++++++++++-------- 1 file changed, 59 insertions(+), 52 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py b/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py index 4939365..d9d04ec 100644 --- a/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py +++ b/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py @@ -3,19 +3,12 @@ # Coded for Circuit Python 8.2.x """RocketLaunch.Live API Example""" -import gc import os -import ssl import time - -import socketpool import wifi - +import adafruit_connection_manager import adafruit_requests -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) - # Time between API refreshes # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour sleep_time = 43200 @@ -45,72 +38,86 @@ def time_calc(input_time): return time_output -# Free Public API, no token or header required +# Publicly available data no header required +# The number at the end is the amount of launches (max 5 free api) ROCKETLAUNCH_SOURCE = "https://fdo.rocketlaunch.live/json/launches/next/1" -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -requests = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.ipv4_address: - try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("❌ Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) - gc.collect() -print("✅ WiFi!") -print("===============================") +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) while True: + # Connect to Wi-Fi + print("\n===============================") + print("Connecting to WiFi...") + while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ Wifi!") try: # Print Request to Serial - print("Attempting to GET RocketLaunch.Live JSON!") + print(" | Attempting to GET RocketLaunch.Live JSON!") + time.sleep(2) debug_rocketlaunch_full_response = False - rocketlaunch_response = requests.get(url=ROCKETLAUNCH_SOURCE) try: + rocketlaunch_response = requests.get(url=ROCKETLAUNCH_SOURCE) rocketlaunch_json = rocketlaunch_response.json() except ConnectionError as e: - print("❌ Connection Error:", e) + print("Connection Error:", e) print("Retrying in 10 seconds") + print(" | ✅ RocketLaunch.Live JSON!") + if debug_rocketlaunch_full_response: print("Full API GET URL: ", ROCKETLAUNCH_SOURCE) print(rocketlaunch_json) - print("✅ RocketLaunch.Live JSON!") - rocketlaunch_flightname = str(rocketlaunch_json["result"][0]["name"]) - print(f" | Flight Name: {rocketlaunch_flightname}") - rocketlaunch_provider = str(rocketlaunch_json["result"][0]["provider"]["name"]) - print(f" | Provider: {rocketlaunch_provider}") - rocketlaunch_vehiclename = str( - rocketlaunch_json["result"][0]["vehicle"]["name"] - ) - print(f" | Vehicle Name: {rocketlaunch_vehiclename}") - - rocketlaunch_winopen = str(rocketlaunch_json["result"][0]["win_open"]) - rocketlaunch_winclose = str(rocketlaunch_json["result"][0]["win_close"]) - print(f" | Window: {rocketlaunch_winopen} to {rocketlaunch_winclose}") - - rocketlaunch_sitename = str( - rocketlaunch_json["result"][0]["pad"]["location"]["name"] - ) - print(f" | Launch Site: {rocketlaunch_sitename}") - - rocketlaunch_mission = str( - rocketlaunch_json["result"][0]["mission_description"] - ) - if rocketlaunch_mission != "None": - print(f" | Mission: {rocketlaunch_mission}") + # JSON Endpoints + RLFN = str(rocketlaunch_json["result"][0]["name"]) + RLWO = str(rocketlaunch_json["result"][0]["win_open"]) + TMINUS = str(rocketlaunch_json["result"][0]["t0"]) + RLWC = str(rocketlaunch_json["result"][0]["win_close"]) + RLP = str(rocketlaunch_json["result"][0]["provider"]["name"]) + RLVN = str(rocketlaunch_json["result"][0]["vehicle"]["name"]) + RLPN = str(rocketlaunch_json["result"][0]["pad"]["name"]) + RLLS = str(rocketlaunch_json["result"][0]["pad"]["location"]["name"]) + RLLD = str(rocketlaunch_json["result"][0]["launch_description"]) + RLM = str(rocketlaunch_json["result"][0]["mission_description"]) + RLDATE = str(rocketlaunch_json["result"][0]["date_str"]) + + # Print to serial & display label if endpoint not "None" + if RLDATE != "None": + print(f" | | Date: {RLDATE}") + if RLFN != "None": + print(f" | | Flight: {RLFN}") + if RLP != "None": + print(f" | | Provider: {RLP}") + if RLVN != "None": + print(f" | | Vehicle: {RLVN}") + if RLWO != "None": + print(f" | | Window: {RLWO} to {RLWC}") + elif TMINUS != "None": + print(f" | | Window: {TMINUS} to {RLWC}") + if RLLS != "None": + print(f" | | Site: {RLLS}") + if RLPN != "None": + print(f" | | Pad: {RLPN}") + if RLLD != "None": + print(f" | | Description: {RLLD}") + if RLM != "None": + print(f" | | Mission: {RLM}") print("\nFinished!") print("Board Uptime: ", time.monotonic()) print("Next Update in: ", time_calc(sleep_time)) print("===============================") - gc.collect() + except (ValueError, RuntimeError) as e: print("Failed to get data, retrying\n", e) time.sleep(60) - continue + break time.sleep(sleep_time) From fb7fcedbc6807302d3a09ba25c1c97dc6dbd9c57 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Fri, 15 Mar 2024 02:15:44 -0400 Subject: [PATCH 198/305] ran isort manually This is going to be thing now. --- examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py b/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py index d9d04ec..d85163b 100644 --- a/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py +++ b/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py @@ -5,9 +5,10 @@ import os import time -import wifi + import adafruit_connection_manager import adafruit_requests +import wifi # Time between API refreshes # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour From ef79fed9cba394637c08f34eccfc679f819fc2e8 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Fri, 15 Mar 2024 02:24:29 -0400 Subject: [PATCH 199/305] this isort thing is driving me nuts --- examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py b/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py index d85163b..768d57d 100644 --- a/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py +++ b/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py @@ -5,7 +5,6 @@ import os import time - import adafruit_connection_manager import adafruit_requests import wifi From c11f4919c070e35ccb8d87bbd4a0cc34652f952c Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Fri, 15 Mar 2024 02:39:13 -0400 Subject: [PATCH 200/305] isnt the point of isort to fix import sorting? --- examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py b/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py index 768d57d..dc497a4 100644 --- a/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py +++ b/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py @@ -5,10 +5,12 @@ import os import time + import adafruit_connection_manager -import adafruit_requests import wifi +import adafruit_requests + # Time between API refreshes # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour sleep_time = 43200 From f88e6b407434b692463a14942ecdb18a2975ff6a Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Fri, 15 Mar 2024 22:48:10 -0400 Subject: [PATCH 201/305] messge here --- .../expanded/requests_wifi_api_mastodon.py | 142 +++++++++--------- 1 file changed, 69 insertions(+), 73 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_mastodon.py b/examples/wifi/expanded/requests_wifi_api_mastodon.py index 56f3e8d..f6bce9f 100644 --- a/examples/wifi/expanded/requests_wifi_api_mastodon.py +++ b/examples/wifi/expanded/requests_wifi_api_mastodon.py @@ -1,121 +1,117 @@ -# SPDX-FileCopyrightText: 2022 DJDevon3 +# SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.0 -"""DJDevon3 Adafruit Feather ESP32-S2 Mastodon_API_Example""" -import gc +# Coded for Circuit Python 8.2.x +"""Mastodon API Example""" +# pylint: disable=import-error + import os -import ssl import time -import socketpool +import adafruit_connection_manager import wifi import adafruit_requests # Mastodon V1 API - Public access (no dev creds or app required) # Visit https://docs.joinmastodon.org/client/public/ for API docs -# For finding your Mastodon User ID -# Login to your mastodon server in a browser, visit your profile, UserID is in the URL. -# Example: https://mastodon.YOURSERVER/web/accounts/YOURUSERIDISHERE +# For finding your Mastodon numerical UserID +# Example: https://mastodon.YOURSERVER/api/v1/accounts/lookup?acct=YourUserName -Mastodon_Server = "mastodon.social" # Set server instance -Mastodon_UserID = "000000000000000000" # Set User ID you want endpoints from +MASTODON_SERVER = "mastodon.social" # Set server instance +MASTODON_USERID = "000000000000000000" # Numerical UserID you want endpoints from # Test in browser first, this will pull up a JSON webpage # https://mastodon.YOURSERVER/api/v1/accounts/YOURUSERIDHERE/statuses?limit=1 -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) - -# Time between API refreshes -# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour -sleep_time = 900 - # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") +# API Polling Rate +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +SLEEP_TIME = 900 + +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) + -# Converts seconds in minutes/hours/days def time_calc(input_time): + """Converts seconds to minutes/hours/days""" if input_time < 60: - sleep_int = input_time - time_output = f"{sleep_int:.0f} seconds" - elif 60 <= input_time < 3600: - sleep_int = input_time / 60 - time_output = f"{sleep_int:.0f} minutes" - elif 3600 <= input_time < 86400: - sleep_int = input_time / 60 / 60 - time_output = f"{sleep_int:.0f} hours" - elif 86400 <= input_time < 432000: - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.1f} days" - else: # if > 5 days convert float to int & display whole days - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.0f} days" - return time_output + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" # Publicly available data no header required MAST_SOURCE = ( "https://" - + Mastodon_Server + + MASTODON_SERVER + "/api/v1/accounts/" - + Mastodon_UserID + + MASTODON_USERID + "/statuses?limit=1" ) -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -requests = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.ipv4_address: - try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) - gc.collect() -print("Connected!\n") - while True: + # Connect to Wi-Fi + print("\nConnecting to WiFi...") + while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ Wifi!") try: - print("\nAttempting to GET MASTODON Stats!") # ----------------------------- # Print Request to Serial - debug_mastodon_full_response = ( - False # STREAMER WARNING: your client secret will be viewable - ) - print("===============================") - mastodon_response = requests.get(url=MAST_SOURCE) + print(" | Attempting to GET MASTODON JSON!") + + # Set debug to True for full JSON response. + # WARNING: may include visible credentials + # MICROCONTROLLER WARNING: might crash by returning too much data + DEBUG_RESPONSE = False + try: + mastodon_response = requests.get(url=MAST_SOURCE) mastodon_json = mastodon_response.json() except ConnectionError as e: - print("Connection Error:", e) + print(f"Connection Error: {e}") print("Retrying in 10 seconds") mastodon_json = mastodon_json[0] - if debug_mastodon_full_response: - print("Full API GET URL: ", MAST_SOURCE) - print(mastodon_json) + print(" | ✅ Mastodon JSON!") + + if DEBUG_RESPONSE: + print(" | | Full API GET URL: ", MAST_SOURCE) mastodon_userid = mastodon_json["account"]["id"] - print("User ID: ", mastodon_userid) + print(f" | | User ID: {mastodon_userid}") + print(mastodon_json) - mastodon_username = mastodon_json["account"]["display_name"] - print("Name: ", mastodon_username) + mastodon_name = mastodon_json["account"]["display_name"] + print(f" | | Name: {mastodon_name}") mastodon_join_date = mastodon_json["account"]["created_at"] - print("Member Since: ", mastodon_join_date) - mastodon_toot_count = mastodon_json["account"]["statuses_count"] - print("Toots: ", mastodon_toot_count) + print(f" | | Member Since: {mastodon_join_date}") mastodon_follower_count = mastodon_json["account"]["followers_count"] - print("Followers: ", mastodon_follower_count) - print("Monotonic: ", time.monotonic()) + print(f" | | Followers: {mastodon_follower_count}") + mastodon_following_count = mastodon_json["account"]["following_count"] + print(f" | | Following: {mastodon_following_count}") + mastodon_toot_count = mastodon_json["account"]["statuses_count"] + print(f" | | Toots: {mastodon_toot_count}") + mastodon_last_toot = mastodon_json["account"]["last_status_at"] + print(f" | | Last Toot: {mastodon_last_toot}") + mastodon_bio = mastodon_json["account"]["note"] + print(f" | | Bio: {mastodon_bio[3:-4]}") # removes included html "

&

" print("\nFinished!") - print("Next Update in: ", time_calc(sleep_time)) + print(f"Board Uptime: {time.monotonic()}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") print("===============================") - gc.collect() except (ValueError, RuntimeError) as e: - print("Failed to get data, retrying\n", e) + print(f"Failed to get data, retrying\n {e}") time.sleep(60) - continue - time.sleep(sleep_time) + break + time.sleep(SLEEP_TIME) From 84d0689027cf8c85472f688ac1d6a189ce8ecc3f Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sat, 16 Mar 2024 01:11:49 -0400 Subject: [PATCH 202/305] Update Discord Shields.io Example with Connection Manager --- ...sts_wifi_adafruit_discord_active_online.py | 129 +++++++----------- 1 file changed, 53 insertions(+), 76 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py b/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py index d43f73f..dec0e6e 100644 --- a/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py +++ b/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py @@ -1,16 +1,13 @@ -# SPDX-FileCopyrightText: 2023 DJDevon3 +# SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT -""" -Coded for Circuit Python 8.2.3 -requests_adafruit_discord_active_online -""" -import gc -import json +# Coded for Circuit Python 8.2.x +"""Discord Active Online Shields.IO Example""" +# pylint: disable=import-error + import os -import ssl import time -import socketpool +import adafruit_connection_manager import wifi import adafruit_requests @@ -19,95 +16,75 @@ # JSON web scrape from SHIELDS.IO # Adafruit uses Shields.IO to see online users -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) - -# Time in seconds between updates (polling) -# 600 = 10 mins, 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour -sleep_time = 900 - # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") +# API Polling Rate +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +SLEEP_TIME = 900 + +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) + -# Converts seconds to minutes/hours/days def time_calc(input_time): + """Converts seconds to minutes/hours/days""" if input_time < 60: - sleep_int = input_time - time_output = f"{sleep_int:.0f} seconds" - elif 60 <= input_time < 3600: - sleep_int = input_time / 60 - time_output = f"{sleep_int:.0f} minutes" - elif 3600 <= input_time < 86400: - sleep_int = input_time / 60 / 60 - time_output = f"{sleep_int:.0f} hours" - else: - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.1f} days" - return time_output + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" # Originally attempted to use SVG. Found JSON exists with same filename. # https://img.shields.io/discord/327254708534116352.svg ADA_DISCORD_JSON = "https://img.shields.io/discord/327254708534116352.json" -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -requests = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.ipv4_address: - try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) - gc.collect() -print("Connected!\n") - while True: + # Connect to Wi-Fi + print("\nConnecting to WiFi...") + while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ Wifi!") try: - print( - "\nAttempting to GET DISCORD SHIELD JSON!" - ) # -------------------------------- - # Print Request to Serial - debug_request = True # Set true to see full request - if debug_request: - print("Full API GET URL: ", ADA_DISCORD_JSON) - print("===============================") + print(" | Attempting to GET Adafruit Discord JSON!") + # Set debug to True for full JSON response. + DEBUG_RESPONSE = True + try: - ada_response = requests.get(ADA_DISCORD_JSON).json() + shieldsio_response = requests.get(url=ADA_DISCORD_JSON) + shieldsio_json = shieldsio_response.json() except ConnectionError as e: - print("Connection Error:", e) + print(f"Connection Error: {e}") print("Retrying in 10 seconds") + print(" | ✅ Adafruit Discord JSON!") + + if DEBUG_RESPONSE: + print(" | | Full API GET URL: ", ADA_DISCORD_JSON) + print(" | | JSON Dump: ", shieldsio_json) - # Print Full JSON to Serial - full_ada_json_response = True # Change to true to see full response - if full_ada_json_response: - ada_dump_object = json.dumps(ada_response) - print("JSON Dump: ", ada_dump_object) - - # Print Debugging to Serial - ada_debug = True # Set to True to print Serial data - if ada_debug: - ada_users = ada_response["value"] - print("JSON Value: ", ada_users) - online_string = " online" - replace_with_nothing = "" - string_replace_users = ada_users.replace( - online_string, replace_with_nothing - ) - print("Replaced Value: ", string_replace_users) - print("Monotonic: ", time.monotonic()) + ada_users = shieldsio_json["value"] + ONLINE_STRING = " online" + REPLACE_WITH_NOTHING = "" + active_users = ada_users.replace(ONLINE_STRING, REPLACE_WITH_NOTHING) + print(f" | | Active Online Users: {active_users}") print("\nFinished!") - print("Next Update: ", time_calc(sleep_time)) + print(f"Board Uptime: {time.monotonic()}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") print("===============================") - gc.collect() except (ValueError, RuntimeError) as e: - print("Failed to get data, retrying\n", e) + print(f"Failed to get data, retrying\n {e}") time.sleep(60) - continue - time.sleep(sleep_time) + break + time.sleep(SLEEP_TIME) From 6b1b92e334e2994ac77de68d9db11cad623747cb Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sat, 16 Mar 2024 02:31:35 -0400 Subject: [PATCH 203/305] Update Discord Web Scrape Example with Connection Manager --- .../expanded/requests_wifi_api_discord.py | 139 +++++++++--------- 1 file changed, 66 insertions(+), 73 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_discord.py b/examples/wifi/expanded/requests_wifi_api_discord.py index 91b2b6c..a7b3fa6 100644 --- a/examples/wifi/expanded/requests_wifi_api_discord.py +++ b/examples/wifi/expanded/requests_wifi_api_discord.py @@ -1,110 +1,103 @@ -# SPDX-FileCopyrightText: 2023 DJDevon3 +# SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.2 -# DJDevon3 Adafruit Feather ESP32-S3 Discord API Example -import json +# Coded for Circuit Python 8.2.x +"""Discord Web Scrape Example""" +# pylint: disable=import-error + import os -import ssl import time - -import socketpool +import adafruit_connection_manager import wifi - import adafruit_requests -# Active Logged in User Account Required, no tokens required +# Active Logged in User Account Required # WEB SCRAPE authorization key required. Visit URL below. # Learn how: https://github.com/lorenz234/Discord-Data-Scraping # Ensure this is in settings.toml -# "Discord_Authorization": "Request Header Auth here" +# DISCORD_AUTHORIZATION = "Approximately 70 Character Hash Here" # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") -Discord_Auth = os.getenv("Discord_Authorization") - -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) +discord_auth = os.getenv("DISCORD_AUTHORIZATION") # API Polling Rate # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour -sleep_time = 900 +SLEEP_TIME = 900 +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) -# Converts seconds to human readable minutes/hours/days -def time_calc(input_time): # input_time in seconds + +def time_calc(input_time): + """Converts seconds to minutes/hours/days""" if input_time < 60: - sleep_int = input_time - time_output = f"{sleep_int:.0f} seconds" - elif 60 <= input_time < 3600: - sleep_int = input_time / 60 - time_output = f"{sleep_int:.0f} minutes" - elif 3600 <= input_time < 86400: - sleep_int = input_time / 60 / 60 - time_output = f"{sleep_int:.1f} hours" - else: - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.1f} days" - return time_output - - -discord_header = {"Authorization": "" + Discord_Auth} -ADA_SOURCE = ( + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" + + +DISCORD_HEADER = {"Authorization": "" + discord_auth} +DISCORD_SOURCE = ( "https://discord.com/api/v10/guilds/" + "327254708534116352" # Adafruit Discord ID + "/preview" ) -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -requests = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.ipv4_address: - try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) -print("Connected!✅") - while True: + # Connect to Wi-Fi + print("\nConnecting to WiFi...") + while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ Wifi!") try: - print("\nAttempting to GET Discord Data!") # -------------------------------- - # STREAMER WARNING this will show your credentials! - debug_request = False # Set True to see full request - if debug_request: - print("Full API GET URL: ", ADA_SOURCE) - print("===============================") + print(" | Attempting to GET Discord JSON!") + # Set debug to True for full JSON response. + # WARNING: may include visible credentials + # MICROCONTROLLER WARNING: might crash by returning too much data + DEBUG_RESPONSE = False + try: - ada_res = requests.get(url=ADA_SOURCE, headers=discord_header).json() + discord_response = requests.get(url=DISCORD_SOURCE, headers=DISCORD_HEADER) + discord_json = discord_response.json() except ConnectionError as e: - print("Connection Error:", e) + print(f"Connection Error: {e}") print("Retrying in 10 seconds") + print(" | ✅ Discord JSON!") + + if DEBUG_RESPONSE: + print(f" | | Full API GET URL: {DISCORD_SOURCE}") + print(f" | | JSON Dump: {discord_json}") + + discord_name = discord_json["name"] + print(f" | | Name: {discord_name}") - # Print Full JSON to Serial - discord_debug_response = False # Set True to see full response - if discord_debug_response: - ada_discord_dump_object = json.dumps(ada_res) - print("JSON Dump: ", ada_discord_dump_object) + discord_description = discord_json["description"] + print(f" | | Description: {discord_description}") - # Print keys to Serial - discord_debug_keys = True # Set True to print Serial data - if discord_debug_keys: - ada_discord_all_members = ada_res["approximate_member_count"] - print("Members: ", ada_discord_all_members) + discord_all_members = discord_json["approximate_member_count"] + print(f" | | Members: {discord_all_members}") - ada_discord_all_members_online = ada_res["approximate_presence_count"] - print("Online: ", ada_discord_all_members_online) + discord_all_members_online = discord_json["approximate_presence_count"] + print(f" | | Online: {discord_all_members_online}") - print("Finished ✅") - print("Board Uptime: ", time_calc(time.monotonic())) - print("Next Update: ", time_calc(sleep_time)) + print("\nFinished!") + print(f"Board Uptime: {time.monotonic()}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") print("===============================") - except (ConnectionError, ValueError, NameError) as e: - print("Failed to get data, retrying\n", e) + except (ValueError, RuntimeError) as e: + print(f"Failed to get data, retrying\n {e}") time.sleep(60) - continue - time.sleep(sleep_time) + break + time.sleep(SLEEP_TIME) From 095570e2fb568544ccbfc30426adcad1ebe122e0 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sat, 16 Mar 2024 02:40:27 -0400 Subject: [PATCH 204/305] Update Discord Web Scrape Example with Connection Manager --- examples/wifi/expanded/requests_wifi_api_discord.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/wifi/expanded/requests_wifi_api_discord.py b/examples/wifi/expanded/requests_wifi_api_discord.py index a7b3fa6..83b8f8f 100644 --- a/examples/wifi/expanded/requests_wifi_api_discord.py +++ b/examples/wifi/expanded/requests_wifi_api_discord.py @@ -6,8 +6,10 @@ import os import time + import adafruit_connection_manager import wifi + import adafruit_requests # Active Logged in User Account Required From 44dff094987a5f971440e4afd6c5ce70e0f2af12 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 17 Mar 2024 16:50:43 -0400 Subject: [PATCH 205/305] Update Fitbit API Example with Connection Manager --- .../wifi/expanded/requests_wifi_api_fitbit.py | 576 ++++++++++-------- 1 file changed, 308 insertions(+), 268 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_fitbit.py b/examples/wifi/expanded/requests_wifi_api_fitbit.py index 022ee22..af6227b 100644 --- a/examples/wifi/expanded/requests_wifi_api_fitbit.py +++ b/examples/wifi/expanded/requests_wifi_api_fitbit.py @@ -1,31 +1,21 @@ -# SPDX-FileCopyrightText: 2023 DJDevon3 +# SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.2 +# Coded for Circuit Python 8.2.x +"""Fitbit API Example""" +# pylint: disable=import-error, disable=no-member import os -import ssl import time import microcontroller -import socketpool +import adafruit_connection_manager import wifi import adafruit_requests -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) - -# STREAMER WARNING: private data will be viewable while debug True -debug = False # Set True for full debug view - -# Can use to confirm first instance of NVM is correct refresh token -top_nvm = microcontroller.nvm[0:64].decode() -if debug: - print(f"Top NVM: {top_nvm}") # NVM before settings.toml loaded - # --- Fitbit Developer Account & oAuth App Required: --- # Required: Google Login (Fitbit owned by Google) & Fitbit Device -# Step 1: Create a personal app here: https://dev.fitbit.com +# Step 1: Register a personal app here: https://dev.fitbit.com # Step 2: Use their Tutorial to get the Token and first Refresh Token # Fitbit's Tutorial Step 4 is as far as you need to go. # https://dev.fitbit.com/build/reference/web-api/troubleshooting-guide/oauth2-tutorial/ @@ -39,277 +29,327 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") - -Fitbit_ClientID = os.getenv("Fitbit_ClientID") -Fitbit_Token = os.getenv("Fitbit_Token") +Fitbit_ClientID = os.getenv("FITBIT_CLIENTID") +Fitbit_Token = os.getenv("FITBIT_ACCESS_TOKEN") Fitbit_First_Refresh_Token = os.getenv( - "Fitbit_First_Refresh_Token" + "FITBIT_FIRST_REFRESH_TOKEN" ) # overides nvm first run only -Fitbit_UserID = os.getenv("Fitbit_UserID") +Fitbit_UserID = os.getenv("FITBIT_USERID") + +# Set debug to True for full INTRADAY JSON response. +# WARNING: may include visible credentials +# MICROCONTROLLER WARNING: might crash by returning too much data +DEBUG = False + +# Set debug to True for full DEVICE (Watch) JSON response. +# WARNING: may include visible credentials +# This will not return enough data to crash your device. +DEBUG_DEVICE = False + +# No data from midnight to 00:15 due to lack of 15 values. +# Debug midnight to display something else in this time frame. +MIDNIGHT_DEBUG = False -# Time between API refreshes -# 300 = 5 mins, 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour -sleep_time = 900 +# WARNING: Optional: Resets board nvm to factory default. Clean slate. +# Instructions will be printed to console while reset is True. +RESET_NVM = False # Set True once, then back to False +if RESET_NVM: + microcontroller.nvm[0:64] = bytearray(b"\x00" * 64) +# API Polling Rate +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +SLEEP_TIME = 900 + +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) -# Converts seconds in minutes/hours/days def time_calc(input_time): + """Converts seconds to minutes/hours/days""" if input_time < 60: - sleep_int = input_time - time_output = f"{sleep_int:.0f} seconds" - elif 60 <= input_time < 3600: - sleep_int = input_time / 60 - time_output = f"{sleep_int:.0f} minutes" - elif 3600 <= input_time < 86400: - sleep_int = input_time / 60 / 60 - time_output = f"{sleep_int:.1f} hours" - else: - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.1f} days" - return time_output + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" # Authenticates Client ID & SHA-256 Token to POST -fitbit_oauth_header = {"Content-Type": "application/x-www-form-urlencoded"} -fitbit_oauth_token = "https://api.fitbit.com/oauth2/token" +FITBIT_OAUTH_HEADER = {"Content-Type": "application/x-www-form-urlencoded"} +FITBIT_OAUTH_TOKEN = "https://api.fitbit.com/oauth2/token" -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -requests = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.ipv4_address: - try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) -print("Connected!\n") - -# First run uses settings.toml token +# Use to confirm first instance of NVM is the correct refresh token +FIRST_RUN = True Refresh_Token = Fitbit_First_Refresh_Token - -if debug: - print(f"Top NVM Again (just to make sure): {top_nvm}") - print(f"Settings.toml Initial Refresh Token: {Fitbit_First_Refresh_Token}") - -latest_15_avg = "Latest 15 Minute Averages" -while True: - # Use Settings.toml refresh token on first run - if top_nvm != Fitbit_First_Refresh_Token: +top_nvm = microcontroller.nvm[0:64].decode() +nvm_bytes = microcontroller.nvm[0:64] +top_nvm_3bytes = nvm_bytes[0:3] +print(f"Top NVM Length: {len(top_nvm)}") +print(f"Top NVM: {top_nvm}") +print(f"Top NVM bytes: {top_nvm_3bytes}") +if RESET_NVM: + microcontroller.nvm[0:64] = bytearray(b"\x00" * 64) + if top_nvm_3bytes == b"\x00\x00\x00": + print("TOP NVM IS BRAND NEW! WAITING FOR A FIRST TOKEN") + Fitbit_First_Refresh_Token = top_nvm + print(f"Top NVM RESET: {top_nvm}") # No token should appear Refresh_Token = microcontroller.nvm[0:64].decode() - if debug: - # NVM 64 should match Current Refresh Token - print(f"NVM 64: {microcontroller.nvm[0:64].decode()}") - print(f"Current Refresh_Token: {Refresh_Token}") - else: - if debug: - # First run use settings.toml refresh token instead - print(f"Initial_Refresh_Token: {Refresh_Token}") - - try: - if debug: - print("\n-----Token Refresh POST Attempt -------") - fitbit_oauth_refresh_token = ( - "&grant_type=refresh_token" - + "&client_id=" - + str(Fitbit_ClientID) - + "&refresh_token=" - + str(Refresh_Token) - ) - - # ----------------------------- POST FOR REFRESH TOKEN ----------------------- - if debug: - print( - f"FULL REFRESH TOKEN POST:{fitbit_oauth_token}" - + f"{fitbit_oauth_refresh_token}" - ) - print(f"Current Refresh Token: {Refresh_Token}") - # TOKEN REFRESH POST - fitbit_oauth_refresh_POST = requests.post( - url=fitbit_oauth_token, - data=fitbit_oauth_refresh_token, - headers=fitbit_oauth_header, - ) - try: - fitbit_refresh_oauth_json = fitbit_oauth_refresh_POST.json() - - fitbit_new_token = fitbit_refresh_oauth_json["access_token"] - if debug: - print("Your Private SHA-256 Token: ", fitbit_new_token) - fitbit_access_token = fitbit_new_token # NEW FULL TOKEN - - # If current token valid will respond - fitbit_new_refesh_token = fitbit_refresh_oauth_json["refresh_token"] - Refresh_Token = fitbit_new_refesh_token - fitbit_token_expiration = fitbit_refresh_oauth_json["expires_in"] - fitbit_scope = fitbit_refresh_oauth_json["scope"] - fitbit_token_type = fitbit_refresh_oauth_json["token_type"] - fitbit_user_id = fitbit_refresh_oauth_json["user_id"] - if debug: - print("Next Refresh Token: ", Refresh_Token) - - # Store Next Token into NVM + print(f"Refresh_Token Reset: {Refresh_Token}") # No token should appear +while True: + if not RESET_NVM: + # Connect to Wi-Fi + print("\n📡 Connecting to WiFi...") + while not wifi.radio.ipv4_address: try: - nvmtoken = b"" + fitbit_new_refesh_token + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ WiFi!") + + if top_nvm is not Refresh_Token and FIRST_RUN is False: + FIRST_RUN = False + Refresh_Token = microcontroller.nvm[0:64].decode() + print(" | INDEFINITE RUN -------") + if DEBUG: + print("Top NVM is Fitbit First Refresh Token") + # NVM 64 should match Current Refresh Token + print(f"NVM 64: {microcontroller.nvm[0:64].decode()}") + print(f"Current Refresh_Token: {Refresh_Token}") + if top_nvm != Fitbit_First_Refresh_Token and FIRST_RUN is True: + if top_nvm_3bytes == b"\x00\x00\x00": + print(" | TOP NVM IS BRAND NEW! WAITING FOR A FIRST TOKEN") + Refresh_Token = Fitbit_First_Refresh_Token + nvmtoken = b"" + Refresh_Token microcontroller.nvm[0:64] = nvmtoken - if debug: - print(f"Next Token for NVM: {nvmtoken.decode()}") - print("Next token written to NVM Successfully!") - except OSError as e: - print("OS Error:", e) - continue - - if debug: - # Extraneous token data for debugging - print("Token Expires in: ", time_calc(fitbit_token_expiration)) - print("Scope: ", fitbit_scope) - print("Token Type: ", fitbit_token_type) - print("UserID: ", fitbit_user_id) - - except KeyError as e: - print("Key Error:", e) - print("Expired token, invalid permission, or (key:value) pair error.") - time.sleep(300) - continue - - # ----------------------------- GET DATA ------------------------------------- - # POST should respond with current & next refresh token we can GET for data - # 64-bit Refresh tokens will "keep alive" SHA-256 token indefinitely - # Fitbit main SHA-256 token expires in 8 hours unless refreshed! - # ---------------------------------------------------------------------------- - detail_level = "1min" # Supported: 1sec | 1min | 5min | 15min - requested_date = "today" # Date format yyyy-MM-dd or today - fitbit_header = { - "Authorization": "Bearer " + fitbit_access_token + "", - "Client-Id": "" + Fitbit_ClientID + "", - } - # Heart Intraday Scope - FITBIT_SOURCE = ( - "https://api.fitbit.com/1/user/" - + Fitbit_UserID - + "/activities/heart/date/today" - + "/1d/" - + detail_level - + ".json" - ) - - print("\nAttempting to GET FITBIT Stats!") - print("===============================") - fitbit_get_response = requests.get(url=FITBIT_SOURCE, headers=fitbit_header) + else: + if DEBUG: + print(f"Top NVM: {top_nvm}") + print(f"First Refresh: {Refresh_Token}") + print(f"First Run: {FIRST_RUN}") + Refresh_Token = top_nvm + FIRST_RUN = False + print(" | MANUAL REBOOT TOKEN DIFFERENCE -------") + if DEBUG: + # NVM 64 should not match Current Refresh Token + print("Top NVM is NOT Fitbit First Refresh Token") + print(f"NVM 64: {microcontroller.nvm[0:64].decode()}") + print(f"Current Refresh_Token: {Refresh_Token}") + if top_nvm == Refresh_Token and FIRST_RUN is True: + if DEBUG: + print(f"Top NVM: {top_nvm}") + print(f"First Refresh: {Refresh_Token}") + print(f"First Run: {FIRST_RUN}") + Refresh_Token = Fitbit_First_Refresh_Token + nvmtoken = b"" + Refresh_Token + microcontroller.nvm[0:64] = nvmtoken + FIRST_RUN = False + print(" | FIRST RUN SETTINGS.TOML TOKEN-------") + if DEBUG: + # NVM 64 should match Current Refresh Token + print("Top NVM IS Fitbit First Refresh Token") + print(f"NVM 64: {microcontroller.nvm[0:64].decode()}") + print(f"Current Refresh_Token: {Refresh_Token}") try: - fitbit_json = fitbit_get_response.json() - intraday_response = fitbit_json["activities-heart-intraday"]["dataset"] - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") + if DEBUG: + print("\n-----Token Refresh POST Attempt -------") + FITBIT_OAUTH_REFRESH_TOKEN = ( + "&grant_type=refresh_token" + + "&client_id=" + + str(Fitbit_ClientID) + + "&refresh_token=" + + str(Refresh_Token) + ) - if debug: - print(f"Full API GET URL: {FITBIT_SOURCE}") - print(f"Header: {fitbit_header}") - # print(f"JSON Full Response: {fitbit_json}") - # print(f"Intraday Full Response: {intraday_response}") + # ------------------------- POST FOR REFRESH TOKEN -------------------- + print(" | Requesting authorization for next token") + if DEBUG: + print( + "FULL REFRESH TOKEN POST:" + + f"{FITBIT_OAUTH_TOKEN}{FITBIT_OAUTH_REFRESH_TOKEN}" + ) + print(f"Current Refresh Token: {Refresh_Token}") + # TOKEN REFRESH POST + try: + fitbit_oauth_refresh_POST = requests.post( + url=FITBIT_OAUTH_TOKEN, + data=FITBIT_OAUTH_REFRESH_TOKEN, + headers=FITBIT_OAUTH_HEADER, + ) + except adafruit_requests.OutOfRetries as ex: + print(f"OutOfRetries: {ex}") + break + try: + fitbit_refresh_oauth_json = fitbit_oauth_refresh_POST.json() + + fitbit_new_token = fitbit_refresh_oauth_json["access_token"] + if DEBUG: + print("Your Private SHA-256 Token: ", fitbit_new_token) + fitbit_access_token = fitbit_new_token # NEW FULL TOKEN + + # Overwrites Initial/Old Refresh Token with Next/New Refresh Token + fitbit_new_refesh_token = fitbit_refresh_oauth_json["refresh_token"] + Refresh_Token = fitbit_new_refesh_token + + fitbit_token_expiration = fitbit_refresh_oauth_json["expires_in"] + fitbit_scope = fitbit_refresh_oauth_json["scope"] + fitbit_token_type = fitbit_refresh_oauth_json["token_type"] + fitbit_user_id = fitbit_refresh_oauth_json["user_id"] + if DEBUG: + print("Next Refresh Token: ", Refresh_Token) + try: + # Stores Next token in NVM + nvmtoken = b"" + Refresh_Token + microcontroller.nvm[0:64] = nvmtoken + if DEBUG: + print(f"nvmtoken: {nvmtoken}") + # It's better to always have next token visible. + # You can manually set this token into settings.toml + print(f" | Next Token: {nvmtoken.decode()}") + print(" | 🔑 Next token written to NVM Successfully!") + except (OSError) as e: + print("OS Error:", e) + continue + if DEBUG: + print("Token Expires in: ", time_calc(fitbit_token_expiration)) + print("Scope: ", fitbit_scope) + print("Token Type: ", fitbit_token_type) + print("UserID: ", fitbit_user_id) + except (KeyError) as e: + print("Key Error:", e) + print("Expired token, invalid permission, or (key:value) pair error.") + time.sleep(SLEEP_TIME) + continue + # ----------------------------- GET DATA --------------------------------- + # Now that we have POST response with next refresh token we can GET for data + # 64-bit Refresh tokens will "keep alive" SHA-256 token indefinitely + # Fitbit main SHA-256 token expires in 8 hours unless refreshed! + # ------------------------------------------------------------------------ + DETAIL_LEVEL = "1min" # Supported: 1sec | 1min | 5min | 15min + REQUESTED_DATE = "today" # Date format yyyy-MM-dd or "today" + fitbit_header = { + "Authorization": "Bearer " + fitbit_access_token + "", + "Client-Id": "" + Fitbit_ClientID + "", + } + # Heart Intraday Scope + FITBIT_INTRADAY_SOURCE = ( + "https://api.fitbit.com/1/user/" + + Fitbit_UserID + + "/activities/heart/date/" + + REQUESTED_DATE + + "/1d/" + + DETAIL_LEVEL + + ".json" + ) + # Device Details + FITBIT_DEVICE_SOURCE = ( + "https://api.fitbit.com/1/user/" + Fitbit_UserID + "/devices.json" + ) - try: - # Fitbit's sync to your mobile device & server every 15 minutes in chunks. - # Pointless to poll their API faster than 15 minute intervals. - activities_heart_value = fitbit_json["activities-heart-intraday"]["dataset"] - response_length = len(activities_heart_value) - if response_length >= 15: - activities_timestamp = fitbit_json["activities-heart"][0]["dateTime"] - print(f"Fitbit Date: {activities_timestamp}") - activities_latest_heart_time = fitbit_json["activities-heart-intraday"][ + print(" | Attempting to GET Fitbit JSON!") + FBIS = FITBIT_INTRADAY_SOURCE + FBH = fitbit_header + fitbit_get_response = requests.get(url=FBIS, headers=FBH) + try: + fitbit_json = fitbit_get_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + print(" | ✅ Fitbit Intraday JSON!") + + if DEBUG: + print(f"Full API GET URL: {FBIS}") + print(f"Header: {fitbit_header}") + # This might crash your microcontroller. + # Commented out even in debug. Use only if absolutely necessary. + + # print(f"JSON Full Response: {fitbit_json}") + Intraday_Response = fitbit_json["activities-heart-intraday"]["dataset"] + # print(f"Intraday Full Response: {Intraday_Response}") + try: + # Fitbit's sync to mobile device & server every 15 minutes in chunks. + # Pointless to poll their API faster than 15 minute intervals. + activities_heart_value = fitbit_json["activities-heart-intraday"][ "dataset" - ][response_length - 1]["time"] - print(f"Fitbit Time: {activities_latest_heart_time[0:-3]}") - print(f"Today's Logged Pulses : {response_length}") - - # Each 1min heart rate is a 60 second average - activities_latest_heart_value0 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 1]["value"] - activities_latest_heart_value1 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 2]["value"] - activities_latest_heart_value2 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 3]["value"] - activities_latest_heart_value3 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 4]["value"] - activities_latest_heart_value4 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 5]["value"] - activities_latest_heart_value5 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 6]["value"] - activities_latest_heart_value6 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 7]["value"] - activities_latest_heart_value7 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 8]["value"] - activities_latest_heart_value8 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 9]["value"] - activities_latest_heart_value9 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 10]["value"] - activities_latest_heart_value10 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 11]["value"] - activities_latest_heart_value11 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 12]["value"] - activities_latest_heart_value12 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 13]["value"] - activities_latest_heart_value13 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 14]["value"] - activities_latest_heart_value14 = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][response_length - 15]["value"] - latest_15_avg = "Latest 15 Minute Averages" + ] + if MIDNIGHT_DEBUG: + RESPONSE_LENGTH = 0 + else: + RESPONSE_LENGTH = len(activities_heart_value) + if RESPONSE_LENGTH >= 15: + activities_timestamp = fitbit_json["activities-heart"][0][ + "dateTime" + ] + print(f" | | Fitbit Date: {activities_timestamp}") + if MIDNIGHT_DEBUG: + ACTIVITIES_LATEST_HEART_TIME = str("00:05:00") + else: + ACTIVITIES_LATEST_HEART_TIME = fitbit_json[ + "activities-heart-intraday" + ]["dataset"][RESPONSE_LENGTH - 1]["time"] + print(f" | | Fitbit Time: {ACTIVITIES_LATEST_HEART_TIME[0:-3]}") + print(f" | | Today's Logged Pulses: {RESPONSE_LENGTH}") + + # Each 1min heart rate is a 60 second average + LATEST_15_AVG = " | | Latest 15 Minute Averages: " + LATEST_15_VALUES = ", ".join( + str(activities_heart_value[i]["value"]) + for i in range(RESPONSE_LENGTH - 1, RESPONSE_LENGTH - 16, -1) + ) + print(f"{LATEST_15_AVG}{LATEST_15_VALUES}") + else: + print(" | Waiting for latest sync...") + print(" | ❌ Not enough values for today to display yet.") + except (KeyError) as keyerror: + print(f"Key Error: {keyerror}") print( - f"{latest_15_avg}" - + f"{activities_latest_heart_value14}," - + f"{activities_latest_heart_value13}," - + f"{activities_latest_heart_value12}," - + f"{activities_latest_heart_value11}," - + f"{activities_latest_heart_value10}," - + f"{activities_latest_heart_value9}," - + f"{activities_latest_heart_value8}," - + f"{activities_latest_heart_value7}," - + f"{activities_latest_heart_value6}," - + f"{activities_latest_heart_value5}," - + f"{activities_latest_heart_value4}," - + f"{activities_latest_heart_value3}," - + f"{activities_latest_heart_value2}," - + f"{activities_latest_heart_value1}," - + f"{activities_latest_heart_value0}" + "Too Many Requests, " + + "Expired token, " + + "invalid permission, " + + "or (key:value) pair error." ) - else: - print("Waiting for latest 15 values sync...") - print("Not enough values for today to display yet.") - print("No display from midnight to 00:15") - - except KeyError as keyerror: - print(f"Key Error: {keyerror}") - print( - "Too Many Requests, Expired token," - + "invalid permission," - + "or (key:value) pair error." - ) + time.sleep(60) + continue + # Getting Fitbit Device JSON (separate from intraday) + # Separate call for Watch Battery Percentage. + print(" | Attempting to GET Device JSON!") + FBDS = FITBIT_DEVICE_SOURCE + FBH = fitbit_header + fitbit_get_device_response = requests.get(url=FBDS, headers=FBH) + try: + fitbit_device_json = fitbit_get_device_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + print(" | ✅ Fitbit Device JSON!") + + if DEBUG_DEVICE: + print(f"Full API GET URL: {FITBIT_DEVICE_SOURCE}") + print(f"Header: {fitbit_header}") + print(f"JSON Full Response: {fitbit_device_json}") + Device_Response = fitbit_device_json[1]["batteryLevel"] + print(f" | | Watch Battery %: {Device_Response}") + + print("\nFinished!") + print(f"Board Uptime: {time_calc(time.monotonic())}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") + print("===============================") + except (ValueError, RuntimeError) as e: + print("Failed to get data, retrying\n", e) + time.sleep(60) continue - - print("Board Uptime: ", time_calc(time.monotonic())) # Board Up-Time seconds - print("\nFinished!") - print("Next Update in: ", time_calc(sleep_time)) - print("===============================") - - except (ValueError, RuntimeError) as e: - print("Failed to get data, retrying\n", e) - time.sleep(60) - continue - time.sleep(sleep_time) + time.sleep(SLEEP_TIME) + else: + print("🚮 NVM Cleared!") + print( + "⚠️ Save your new access token & refresh token from" + "Fitbits Tutorial (Step 4) to settings.toml now." + ) + print( + "⚠️ If the script runs again" + "(due to settings.toml file save) while reset=True that's ok!" + ) + print("⚠️ Then set RESET_NVM back to False.") + break From 339294d48f9719edf528b89e2296d82a3470c55c Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 17 Mar 2024 16:57:38 -0400 Subject: [PATCH 206/305] Update Discord Web Scrape Example with Connection Manager --- examples/wifi/expanded/requests_wifi_api_fitbit.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_fitbit.py b/examples/wifi/expanded/requests_wifi_api_fitbit.py index af6227b..391a46e 100644 --- a/examples/wifi/expanded/requests_wifi_api_fitbit.py +++ b/examples/wifi/expanded/requests_wifi_api_fitbit.py @@ -86,9 +86,10 @@ def time_calc(input_time): top_nvm = microcontroller.nvm[0:64].decode() nvm_bytes = microcontroller.nvm[0:64] top_nvm_3bytes = nvm_bytes[0:3] -print(f"Top NVM Length: {len(top_nvm)}") -print(f"Top NVM: {top_nvm}") -print(f"Top NVM bytes: {top_nvm_3bytes}") +if DEBUG: + print(f"Top NVM Length: {len(top_nvm)}") + print(f"Top NVM: {top_nvm}") + print(f"Top NVM bytes: {top_nvm_3bytes}") if RESET_NVM: microcontroller.nvm[0:64] = bytearray(b"\x00" * 64) if top_nvm_3bytes == b"\x00\x00\x00": From 62927ff9e77f679aefe5dec3e0b298b3be5b2bb7 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 17 Mar 2024 17:02:21 -0400 Subject: [PATCH 207/305] ran black and pylint again --- examples/wifi/expanded/requests_wifi_api_fitbit.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_fitbit.py b/examples/wifi/expanded/requests_wifi_api_fitbit.py index 391a46e..ac79c95 100644 --- a/examples/wifi/expanded/requests_wifi_api_fitbit.py +++ b/examples/wifi/expanded/requests_wifi_api_fitbit.py @@ -7,8 +7,8 @@ import os import time -import microcontroller import adafruit_connection_manager +import microcontroller import wifi import adafruit_requests @@ -210,7 +210,7 @@ def time_calc(input_time): # You can manually set this token into settings.toml print(f" | Next Token: {nvmtoken.decode()}") print(" | 🔑 Next token written to NVM Successfully!") - except (OSError) as e: + except OSError as e: print("OS Error:", e) continue if DEBUG: @@ -218,7 +218,7 @@ def time_calc(input_time): print("Scope: ", fitbit_scope) print("Token Type: ", fitbit_token_type) print("UserID: ", fitbit_user_id) - except (KeyError) as e: + except KeyError as e: print("Key Error:", e) print("Expired token, invalid permission, or (key:value) pair error.") time.sleep(SLEEP_TIME) @@ -303,7 +303,7 @@ def time_calc(input_time): else: print(" | Waiting for latest sync...") print(" | ❌ Not enough values for today to display yet.") - except (KeyError) as keyerror: + except KeyError as keyerror: print(f"Key Error: {keyerror}") print( "Too Many Requests, " From 446a88904cc2ebd613dc8a47f9256efd52575354 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 17 Mar 2024 17:14:40 -0400 Subject: [PATCH 208/305] minor spelling edit --- examples/wifi/expanded/requests_wifi_api_fitbit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_fitbit.py b/examples/wifi/expanded/requests_wifi_api_fitbit.py index ac79c95..7658eeb 100644 --- a/examples/wifi/expanded/requests_wifi_api_fitbit.py +++ b/examples/wifi/expanded/requests_wifi_api_fitbit.py @@ -7,8 +7,8 @@ import os import time -import adafruit_connection_manager import microcontroller +import adafruit_connection_manager import wifi import adafruit_requests @@ -345,7 +345,7 @@ def time_calc(input_time): else: print("🚮 NVM Cleared!") print( - "⚠️ Save your new access token & refresh token from" + "⚠️ Save your new access token & refresh token from " "Fitbits Tutorial (Step 4) to settings.toml now." ) print( From 717e4c4d50345380772bfdc1140df2ef1299d17b Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 17 Mar 2024 17:23:30 -0400 Subject: [PATCH 209/305] minor spelling edit --- examples/wifi/expanded/requests_wifi_api_fitbit.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_fitbit.py b/examples/wifi/expanded/requests_wifi_api_fitbit.py index 7658eeb..81050fd 100644 --- a/examples/wifi/expanded/requests_wifi_api_fitbit.py +++ b/examples/wifi/expanded/requests_wifi_api_fitbit.py @@ -7,11 +7,10 @@ import os import time -import microcontroller import adafruit_connection_manager -import wifi - import adafruit_requests +import microcontroller +import wifi # --- Fitbit Developer Account & oAuth App Required: --- # Required: Google Login (Fitbit owned by Google) & Fitbit Device From a7e20145e9946d9d25b8852fcbd9e8be3036c303 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 17 Mar 2024 17:28:36 -0400 Subject: [PATCH 210/305] isort is giving me trouble --- examples/wifi/expanded/requests_wifi_api_fitbit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/wifi/expanded/requests_wifi_api_fitbit.py b/examples/wifi/expanded/requests_wifi_api_fitbit.py index 81050fd..802995c 100644 --- a/examples/wifi/expanded/requests_wifi_api_fitbit.py +++ b/examples/wifi/expanded/requests_wifi_api_fitbit.py @@ -8,10 +8,11 @@ import time import adafruit_connection_manager -import adafruit_requests import microcontroller import wifi +import adafruit_requests + # --- Fitbit Developer Account & oAuth App Required: --- # Required: Google Login (Fitbit owned by Google) & Fitbit Device # Step 1: Register a personal app here: https://dev.fitbit.com From 1d931f7cd99aa45b16302e2aa057a2a667c041f7 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 17 Mar 2024 19:25:41 -0400 Subject: [PATCH 211/305] Add Premiere League API Example --- .../requests_wifi_api_premiereleague.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 examples/wifi/expanded/requests_wifi_api_premiereleague.py diff --git a/examples/wifi/expanded/requests_wifi_api_premiereleague.py b/examples/wifi/expanded/requests_wifi_api_premiereleague.py new file mode 100644 index 0000000..0a2e607 --- /dev/null +++ b/examples/wifi/expanded/requests_wifi_api_premiereleague.py @@ -0,0 +1,86 @@ +# SPDX-FileCopyrightText: 2024 DJDevon3 +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 8.2.x +"""Premiere League Total Players API Example""" +# pylint: disable=import-error + +import os +import time + +import adafruit_connection_manager +import adafruit_json_stream as json_stream +import wifi + +import adafruit_requests + +# Public API. No user or token required + +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") + +# API Polling Rate +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +SLEEP_TIME = 900 + +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) + +# Publicly available data no header required +PREMIERE_LEAGUE_SOURCE = "https://fantasy.premierleague.com/api/bootstrap-static/" + + +def time_calc(input_time): + """Converts seconds to minutes/hours/days""" + if input_time < 60: + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" + + +while True: + # Connect to Wi-Fi + print("\nConnecting to WiFi...") + while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ Wifi!") + + try: + print(" | Attempting to GET Premiere League JSON!") + + # Set debug to True for full JSON response. + # WARNING: may include visible credentials + # MICROCONTROLLER WARNING: might crash by returning too much data + DEBUG_RESPONSE = False + + try: + PREMIERE_LEAGUE_RESPONSE = requests.get(url=PREMIERE_LEAGUE_SOURCE) + pl_json = json_stream.load(PREMIERE_LEAGUE_RESPONSE.iter_content(32)) + except ConnectionError as e: + print(f"Connection Error: {e}") + print("Retrying in 10 seconds") + print(" | ✅ Premiere League JSON!") + + print(f" | Total Premiere League Players: {pl_json['total_players']}") + PREMIERE_LEAGUE_RESPONSE.close() + print("✂️ Disconnected from Premiere League") + + print("\nFinished!") + print(f"Board Uptime: {time.monotonic()}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") + print("===============================") + + except (ValueError, RuntimeError) as e: + print(f"Failed to get data, retrying\n {e}") + time.sleep(60) + break + time.sleep(SLEEP_TIME) From e9e3944a9dbb00ebd70c8f05d044fe27102f77db Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 17 Mar 2024 22:09:58 -0400 Subject: [PATCH 212/305] Update Github API Example with Connection Manager --- .../wifi/expanded/requests_wifi_api_github.py | 151 +++++++++--------- 1 file changed, 78 insertions(+), 73 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_github.py b/examples/wifi/expanded/requests_wifi_api_github.py index f1f86e3..6fbe8fe 100644 --- a/examples/wifi/expanded/requests_wifi_api_github.py +++ b/examples/wifi/expanded/requests_wifi_api_github.py @@ -1,103 +1,108 @@ -# SPDX-FileCopyrightText: 2022 DJDevon3 for Adafruit Industries +# SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.0 -"""DJDevon3 Adafruit Feather ESP32-S2 Github_API_Example""" -import gc -import json +# Coded for Circuit Python 8.2.x +"""Github API Example""" +# pylint: disable=import-error + import os -import ssl import time -import socketpool +import adafruit_connection_manager import wifi import adafruit_requests -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) - -# Time between API refreshes -# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour -sleep_time = 900 +# Github developer token required. +username = os.getenv("GITHUB_USERNAME") +token = os.getenv("GITHUB_TOKEN") # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") -# Github developer token required. -github_username = os.getenv("Github_username") -github_token = os.getenv("Github_token") - -if sleep_time < 60: - sleep_time_conversion = "seconds" - sleep_int = sleep_time -elif 60 <= sleep_time < 3600: - sleep_int = sleep_time / 60 - sleep_time_conversion = "minutes" -elif 3600 <= sleep_time < 86400: - sleep_int = sleep_time / 60 / 60 - sleep_time_conversion = "hours" -else: - sleep_int = sleep_time / 60 / 60 / 24 - sleep_time_conversion = "days" - -github_header = {"Authorization": " token " + github_token} -GH_SOURCE = "https://api.github.com/users/" + github_username - -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -requests = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.ipv4_address: - try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) - gc.collect() -print("Connected!\n") + +# API Polling Rate +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +SLEEP_TIME = 900 + +# Set debug to True for full JSON response. +# WARNING: may include visible credentials +DEBUG = False + +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) + +GITHUB_HEADER = {"Authorization": " token " + token} +GITHUB_SOURCE = "https://api.github.com/users/" + username + + +def time_calc(input_time): + """Converts seconds to minutes/hours/days""" + if input_time < 60: + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" + while True: + # Connect to Wi-Fi + print("\nConnecting to WiFi...") + while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ Wifi!") + try: - print("\nAttempting to GET GITHUB Stats!") # -------------------------------- - # Print Request to Serial - debug_request = False # Set true to see full request - if debug_request: - print("Full API GET URL: ", GH_SOURCE) - print("===============================") + print(" | Attempting to GET Github JSON!") try: - github_response = requests.get(url=GH_SOURCE, headers=github_header).json() + github_response = requests.get(url=GITHUB_SOURCE, headers=GITHUB_HEADER) + github_json = github_response.json() except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") + print(" | ✅ Github JSON!") + + github_joined = github_json["created_at"] + print(" | | Join Date: ", github_joined) + + github_id = github_json["id"] + print(" | | UserID: ", github_id) + + github_location = github_json["location"] + print(" | | Location: ", github_location) - # Print Response to Serial - debug_response = False # Set true to see full response - if debug_response: - dump_object = json.dumps(github_response) - print("JSON Dump: ", dump_object) + github_name = github_json["name"] + print(" | | Username: ", github_name) - # Print Keys to Serial - gh_debug_keys = True # Set True to print Serial data - if gh_debug_keys: - github_id = github_response["id"] - print("UserID: ", github_id) + github_repos = github_json["public_repos"] + print(" | | Respositores: ", github_repos) - github_username = github_response["name"] - print("Username: ", github_username) + github_followers = github_json["followers"] + print(" | | Followers: ", github_followers) + github_bio = github_json["bio"] + print(" | | Bio: ", github_bio) - github_followers = github_response["followers"] - print("Followers: ", github_followers) + if DEBUG: + print("Full API GET URL: ", GITHUB_SOURCE) + print(github_json) - print("Monotonic: ", time.monotonic()) + github_response.close() + print("✂️ Disconnected from Github API") print("\nFinished!") - print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) + print(f"Board Uptime: {time_calc(time.monotonic())}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") print("===============================") - gc.collect() except (ValueError, RuntimeError) as e: - print("Failed to get data, retrying\n", e) + print(f"Failed to get data, retrying\n {e}") time.sleep(60) - continue - time.sleep(sleep_time) + break + time.sleep(SLEEP_TIME) From 6f63caa63921ce9f634657e2d8b1695bd0d597c6 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 18 Mar 2024 02:11:24 -0400 Subject: [PATCH 213/305] Update Open-Sky Network Single Flight API Example with Connection Manager --- ...equests_wifi_api_openskynetwork_private.py | 275 ++++++++++-------- 1 file changed, 160 insertions(+), 115 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py index 486a4de..2668f9f 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py @@ -1,16 +1,15 @@ -# SPDX-FileCopyrightText: 2023 DJDevon3 +# SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.1 -# DJDevon3 ESP32-S3 OpenSkyNetwork_Private_API_Example +# Coded for Circuit Python 8.2.x +"""OpenSky-Network.org Private API Example""" +# pylint: disable=import-error -import json import os -import ssl import time -import circuitpython_base64 as base64 -import socketpool +import adafruit_connection_manager import wifi +from adafruit_binascii import b2a_base64 import adafruit_requests @@ -19,135 +18,181 @@ # All active flights JSON: https://opensky-network.org/api/states/all # PICK ONE! :) # JSON order: transponder, callsign, country # ACTIVE transpondes only, for multiple "c822af&icao24=cb3993&icao24=c63923" -transponder = "7c6b2d" +TRANSPONDER = "471efd" -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) +# Github developer token required. +username = os.getenv("GITHUB_USERNAME") +token = os.getenv("GITHUB_TOKEN") -# Time between API refreshes +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") +osnusername = os.getenv("OSN_USERNAME") # Website Credentials +osnpassword = os.getenv("OSN_PASSWORD") # Website Credentials + +# API Polling Rate # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour # OpenSky-Networks IP bans for too many requests, check rate limit. # https://openskynetwork.github.io/opensky-api/rest.html#limitations -sleep_time = 1800 +SLEEP_TIME = 1800 -# Get WiFi details, ensure these are setup in settings.toml -ssid = os.getenv("CIRCUITPY_WIFI_SSID") -password = os.getenv("CIRCUITPY_WIFI_PASSWORD") -osnu = os.getenv("OSN_Username") -osnp = os.getenv("OSN_Password") - -osn_cred = str(osnu) + ":" + str(osnp) -bytes_to_encode = b" " + str(osn_cred) + " " -base64_string = base64.encodebytes(bytes_to_encode) -base64cred = repr(base64_string)[2:-1] - -Debug_Auth = False # STREAMER WARNING this will show your credentials! -if Debug_Auth: - osn_cred = str(osnu) + ":" + str(osnp) - bytes_to_encode = b" " + str(osn_cred) + " " - print(repr(bytes_to_encode)) - base64_string = base64.encodebytes(bytes_to_encode) - print(repr(base64_string)[2:-1]) - base64cred = repr(base64_string)[2:-1] - print("Decoded Bytes:", str(base64cred)) +# Set debug to True for full JSON response. +# WARNING: makes credentials visible +DEBUG = False + +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) + +# -- Base64 Conversion -- +OSN_CREDENTIALS = str(osnusername) + ":" + str(osnpassword) +OSN_CREDENTIALS_B = b"" + str(OSN_CREDENTIALS) + "" +BASE64_ASCII = b2a_base64(OSN_CREDENTIALS_B) +BASE64_STRING = str(BASE64_ASCII) # bytearray +TRUNCATED_BASE64_STRING = BASE64_STRING[2:-1] # truncate bytearray head/tail + +if DEBUG: + print("Original Binary Data: ", OSN_CREDENTIALS_B) + print("Base64 ByteArray: ", BASE64_ASCII) + print(f"Base64 String: {TRUNCATED_BASE64_STRING}") # Requests URL - icao24 is their endpoint required for a transponder # example https://opensky-network.org/api/states/all?icao24=a808c5 -# OSN private requires your username:password to be base64 encoded -osn_header = {"Authorization": "Basic " + str(base64cred)} -OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + transponder +# OSN private: requires your website username:password to be base64 encoded +OSN_HEADER = {"Authorization": "Basic " + str(TRUNCATED_BASE64_STRING)} +OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + TRANSPONDER -# Converts seconds to human readable minutes/hours/days -def time_calc(input_time): # input_time in seconds +def time_calc(input_time): + """Converts seconds to minutes/hours/days""" if input_time < 60: - sleep_int = input_time - time_output = f"{sleep_int:.0f} seconds" - elif 60 <= input_time < 3600: - sleep_int = input_time / 60 - time_output = f"{sleep_int:.0f} minutes" - elif 3600 <= input_time < 86400: - sleep_int = input_time / 60 / 60 - time_output = f"{sleep_int:.1f} hours" - else: - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.1f} days" - return time_output + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" def _format_datetime(datetime): - return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( - datetime.tm_mon, - datetime.tm_mday, - datetime.tm_year, - datetime.tm_hour, - datetime.tm_min, - datetime.tm_sec, + return ( + f"{datetime.tm_mon:02}/" + + f"{datetime.tm_mday:02}/" + + f"{datetime.tm_year:02} " + + f"{datetime.tm_hour:02}:" + + f"{datetime.tm_min:02}:" + + f"{datetime.tm_sec:02}" ) -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -request = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.ipv4_address: +while True: + # Connect to Wi-Fi + print("\nConnecting to WiFi...") + while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ Wifi!") + try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) -print("Connected!\n") + print(" | Attempting to GET OpenSky-Network Single Flight JSON!") + try: + opensky_response = requests.get(url=OPENSKY_SOURCE, headers=OSN_HEADER) + opensky_json = opensky_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") -while True: - # STREAMER WARNING this will show your credentials! - debug_request = False # Set True to see full request - if debug_request: - print("Full API HEADER: ", str(osn_header)) - print("Full API GET URL: ", OPENSKY_SOURCE) - print("===============================") + print(" | ✅ OpenSky-Network JSON!") - print("\nAttempting to GET OpenSky-Network Data!") - opensky_response = request.get(url=OPENSKY_SOURCE, headers=osn_header).json() + if DEBUG: + print("Full API GET URL: ", OPENSKY_SOURCE) + print(opensky_json) - # Print Full JSON to Serial (doesn't show credentials) - debug_response = False # Set True to see full response - if debug_response: - dump_object = json.dumps(opensky_response) - print("JSON Dump: ", dump_object) + # ERROR MESSAGE RESPONSES + if "timestamp" in opensky_json: + osn_timestamp = opensky_json["timestamp"] + print(f"❌ Timestamp: {osn_timestamp}") - # Key:Value Serial Debug (doesn't show credentials) - osn_debug_keys = True # Set True to print Serial data - if osn_debug_keys: - try: - osn_flight = opensky_response["time"] - print("Current Unix Time: ", osn_flight) - - current_struct_time = time.localtime(osn_flight) - current_date = "{}".format(_format_datetime(current_struct_time)) - print(f"Unix to Readable Time: {current_date}") - - # Current flight data for single callsign (right now) - osn_single_flight_data = opensky_response["states"] - - if osn_single_flight_data is not None: - print("Flight Data: ", osn_single_flight_data) - transponder = opensky_response["states"][0][0] - print("Transponder: ", transponder) - callsign = opensky_response["states"][0][1] - print("Callsign: ", callsign) - country = opensky_response["states"][0][2] - print("Flight Country: ", country) + if "message" in opensky_json: + osn_message = opensky_json["message"] + print(f"❌ Message: {osn_message}") + + if "error" in opensky_json: + osn_error = opensky_json["error"] + print(f"❌ Error: {osn_error}") + + if "path" in opensky_json: + osn_path = opensky_json["path"] + print(f"❌ Path: {osn_path}") + + if "status" in opensky_json: + osn_status = opensky_json["status"] + print(f"❌ Status: {osn_status}") + + # Current flight data for single callsign (right now) + osn_single_flight_data = opensky_json["states"] + + if osn_single_flight_data is not None: + if DEBUG: + print(f" | | Single Flight Data: {osn_single_flight_data}") + + last_contact = opensky_json["states"][0][4] + # print(f" | | Last Contact Unix Time: {last_contact}") + lc_struct_time = time.localtime(last_contact) + lc_readable_time = f"{_format_datetime(lc_struct_time)}" + print(f" | | Last Contact: {lc_readable_time}") + + flight_transponder = opensky_json["states"][0][0] + print(f" | | Transponder: {flight_transponder}") + + callsign = opensky_json["states"][0][1] + print(f" | | Callsign: {callsign}") + + squawk = opensky_json["states"][0][14] + print(f" | | Squawk: {squawk}") + + country = opensky_json["states"][0][2] + print(f" | | Origin: {country}") + + longitude = opensky_json["states"][0][5] + print(f" | | Longitude: {longitude}") + + latitude = opensky_json["states"][0][6] + print(f" | | Latitude: {latitude}") + + # Return Air Flight data if not on ground + on_ground = opensky_json["states"][0][8] + if on_ground is True: + print(f" | | On Ground: {on_ground}") else: - print("Flight has no active data or you're polling too fast.") - - print("\nFinished!") - print("Board Uptime: ", time_calc(time.monotonic())) - print("Next Update: ", time_calc(sleep_time)) - time.sleep(sleep_time) - print("===============================") - - except (ConnectionError, ValueError, NameError) as e: - print("OSN Connection Error:", e) - print("Next Retry: ", time_calc(sleep_time)) - time.sleep(sleep_time) + altitude = opensky_json["states"][0][7] + print(f" | | Barometric Altitude: {altitude}") + + velocity = opensky_json["states"][0][9] + if velocity != "null": + print(f" | | Velocity: {velocity}") + + vertical_rate = opensky_json["states"][0][11] + if vertical_rate != "null": + print(f" | | Vertical Rate: {vertical_rate}") + + else: + print(" | | ❌ Flight has no active data or you're polling too fast.") + + opensky_response.close() + print("✂️ Disconnected from OpenSky-Network API") + + print("\nFinished!") + print(f"Board Uptime: {time_calc(time.monotonic())}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") + print("===============================") + + except (ValueError, RuntimeError) as e: + print(f"Failed to get data, retrying\n {e}") + time.sleep(60) + break + time.sleep(SLEEP_TIME) From 55f1428f989ec7ac76cdd195a119bf85c298ed31 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 18 Mar 2024 02:55:26 -0400 Subject: [PATCH 214/305] Update Open-Sky Network Area API Example with Connection Manager --- ...ts_wifi_api_openskynetwork_private_area.py | 259 ++++++++++-------- 1 file changed, 139 insertions(+), 120 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py index 8cd08a2..f8433f3 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py @@ -1,155 +1,170 @@ -# SPDX-FileCopyrightText: 2023 DJDevon3 +# SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.1 -# DJDevon3 ESP32-S3 OpenSkyNetwork_Private_Area_API_Example +# Coded for Circuit Python 8.2.x +"""OpenSky-Network.org Private API Example""" +# pylint: disable=import-error -import json import os -import ssl import time -import circuitpython_base64 as base64 -import socketpool +import adafruit_connection_manager import wifi import adafruit_requests +from adafruit_binascii import b2a_base64 # OpenSky-Network.org Website Login required for this API # REST API: https://openskynetwork.github.io/opensky-api/rest.html - # Retrieves all traffic within a geographic area (Orlando example) -latmin = "27.22" # east bounding box -latmax = "28.8" # west bounding box -lonmin = "-81.46" # north bounding box -lonmax = "-80.40" # south bounding box +LATMIN = "27.22" # east bounding box +LATMAX = "28.8" # west bounding box +LONMIN = "-81.46" # north bounding box +LONMAX = "-80.40" # south bounding box -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) - -# Time between API refreshes -# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour -# OpenSky-Networks IP bans for too many requests, check rate limit. -# https://openskynetwork.github.io/opensky-api/rest.html#limitations -sleep_time = 1800 +# Github developer token required. +username = os.getenv("GITHUB_USERNAME") +token = os.getenv("GITHUB_TOKEN") # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") -# No token required, only website login -osnu = os.getenv("OSN_Username") -osnp = os.getenv("OSN_Password") - -osn_cred = str(osnu) + ":" + str(osnp) -bytes_to_encode = b" " + str(osn_cred) + " " -base64_string = base64.encodebytes(bytes_to_encode) -base64cred = repr(base64_string)[2:-1] - -Debug_Auth = False # STREAMER WARNING this will show your credentials! -if Debug_Auth: - osn_cred = str(osnu) + ":" + str(osnp) - bytes_to_encode = b" " + str(osn_cred) + " " - print(repr(bytes_to_encode)) - base64_string = base64.encodebytes(bytes_to_encode) - print(repr(base64_string)[2:-1]) - base64cred = repr(base64_string)[2:-1] - print("Decoded Bytes:", str(base64cred)) - -# OSN requires your username:password to be base64 encoded -# so technically it's not transmitted in the clear but w/e -osn_header = {"Authorization": "Basic " + str(base64cred)} - -# Example request of all traffic over Florida, geographic areas cost less per call. +osnusername = os.getenv("OSN_USERNAME") # Website Credentials +osnpassword = os.getenv("OSN_PASSWORD") # Website Credentials + +# API Polling Rate +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +# OpenSky-Networks IP bans for too many requests, check rate limit. +# https://openskynetwork.github.io/opensky-api/rest.html#limitations +SLEEP_TIME = 1800 + +# Set debug to True for full JSON response. +# WARNING: makes credentials visible +DEBUG = False + +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) + +# -- Base64 Conversion -- +OSN_CREDENTIALS = str(osnusername) + ":" + str(osnpassword) +OSN_CREDENTIALS_B = b"" + str(OSN_CREDENTIALS) + "" +BASE64_ASCII = b2a_base64(OSN_CREDENTIALS_B) +BASE64_STRING = str(BASE64_ASCII) # bytearray +TRUNCATED_BASE64_STRING = BASE64_STRING[2:-1] # truncate bytearray head/tail + +if DEBUG: + print("Original Binary Data: ", OSN_CREDENTIALS_B) + print("Base64 ByteArray: ", BASE64_ASCII) + print(f"Base64 String: {TRUNCATED_BASE64_STRING}") + +# Area requires OpenSky-Network.org username:password to be base64 encoded +OSN_HEADER = {"Authorization": "Basic " + str(TRUNCATED_BASE64_STRING)} + +# Example request of all traffic over Florida. +# Geographic areas calls cost less against the limit. # https://opensky-network.org/api/states/all?lamin=25.21&lomin=-84.36&lamax=30.0&lomax=-78.40 OPENSKY_SOURCE = ( "https://opensky-network.org/api/states/all?" + "lamin=" - + latmin + + LATMIN + "&lomin=" - + lonmin + + LONMIN + "&lamax=" - + latmax + + LATMAX + "&lomax=" - + lonmax + + LONMAX ) -# Converts seconds to human readable minutes/hours/days -def time_calc(input_time): # input_time in seconds +def time_calc(input_time): + """Converts seconds to minutes/hours/days""" if input_time < 60: - sleep_int = input_time - time_output = f"{sleep_int:.0f} seconds" - elif 60 <= input_time < 3600: - sleep_int = input_time / 60 - time_output = f"{sleep_int:.0f} minutes" - elif 3600 <= input_time < 86400: - sleep_int = input_time / 60 / 60 - time_output = f"{sleep_int:.1f} hours" - else: - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.1f} days" - return time_output + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" def _format_datetime(datetime): - return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( - datetime.tm_mon, - datetime.tm_mday, - datetime.tm_year, - datetime.tm_hour, - datetime.tm_min, - datetime.tm_sec, + """F-String formatted struct time conversion""" + return ( + f"{datetime.tm_mon:02}/" + + f"{datetime.tm_mday:02}/" + + f"{datetime.tm_year:02} " + + f"{datetime.tm_hour:02}:" + + f"{datetime.tm_min:02}:" + + f"{datetime.tm_sec:02}" ) -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -request = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.ipv4_address: +while True: + # Connect to Wi-Fi + print("\nConnecting to WiFi...") + while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ Wifi!") + try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) -print("Connected!\n") + print(" | Attempting to GET OpenSky-Network Area Flights JSON!") + try: + opensky_response = requests.get(url=OPENSKY_SOURCE, headers=OSN_HEADER) + opensky_json = opensky_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") -while True: - # STREAMER WARNING this will show your credentials! - debug_request = False # Set True to see full request - if debug_request: - print("Full API HEADER: ", str(osn_header)) - print("Full API GET URL: ", OPENSKY_SOURCE) - print("===============================") + print(" | ✅ OpenSky-Network JSON!") - print("\nAttempting to GET OpenSky-Network Data!") - opensky_response = request.get(url=OPENSKY_SOURCE, headers=osn_header).json() + if DEBUG: + print("Full API GET URL: ", OPENSKY_SOURCE) + print(opensky_json) - # Print Full JSON to Serial (doesn't show credentials) - debug_response = False # Set True to see full response - if debug_response: - dump_object = json.dumps(opensky_response) - print("JSON Dump: ", dump_object) + # ERROR MESSAGE RESPONSES + if "timestamp" in opensky_json: + osn_timestamp = opensky_json["timestamp"] + print(f"❌ Timestamp: {osn_timestamp}") - # Key:Value Serial Debug (doesn't show credentials) - osn_debug_keys = True # Set True to print Serial data - if osn_debug_keys: - try: - osn_flight = opensky_response["time"] - print("Current Unix Time: ", osn_flight) + if "message" in opensky_json: + osn_message = opensky_json["message"] + print(f"❌ Message: {osn_message}") + + if "error" in opensky_json: + osn_error = opensky_json["error"] + print(f"❌ Error: {osn_error}") + + if "path" in opensky_json: + osn_path = opensky_json["path"] + print(f"❌ Path: {osn_path}") + + if "status" in opensky_json: + osn_status = opensky_json["status"] + print(f"❌ Status: {osn_status}") - current_struct_time = time.localtime(osn_flight) - current_date = "{}".format(_format_datetime(current_struct_time)) - print(f"Unix to Readable Time: {current_date}") + # Current flight data for single callsign (right now) + osn_all_flights = opensky_json["states"] - # Current flight data for single callsign (right now) - osn_all_flights = opensky_response["states"] + if osn_all_flights is not None: + if DEBUG: + print(f" | | Area Flights Full Response: {osn_all_flights}") + + osn_time = opensky_json["time"] + # print(f" | | Last Contact Unix Time: {osn_time}") + osn_struct_time = time.localtime(osn_time) + osn_readable_time = f"{_format_datetime(osn_struct_time)}" + print(f" | | Last Contact: {osn_readable_time}") if osn_all_flights is not None: # print("Flight Data: ", osn_all_flights) for flights in osn_all_flights: - osn_t = f"Trans:{flights[0]} " - osn_c = f"Sign:{flights[1]} " + osn_t = f" | | Trans:{flights[0]} " + osn_c = f"Sign:{flights[1]}" osn_o = f"Origin:{flights[2]} " osn_tm = f"Time:{flights[3]} " osn_l = f"Last:{flights[4]} " @@ -171,16 +186,20 @@ def _format_datetime(datetime): string2 = f"{osn_la}{osn_ba}{osn_g}{osn_v}{osn_h}{osn_vr}" string3 = f"{osn_s}{osn_ga}{osn_sq}{osn_pr}{osn_ps}{osn_ca}" print(f"{string1}{string2}{string3}") - else: - print("Flight has no active data or you're polling too fast.") - - print("\nFinished!") - print("Board Uptime: ", time_calc(time.monotonic())) - print("Next Update: ", time_calc(sleep_time)) - time.sleep(sleep_time) - print("===============================") - - except (ConnectionError, ValueError, NameError) as e: - print("OSN Connection Error:", e) - print("Next Retry: ", time_calc(sleep_time)) - time.sleep(sleep_time) + + else: + print(" | | ❌ Area has no active data or you're polling too fast.") + + opensky_response.close() + print("✂️ Disconnected from OpenSky-Network API") + + print("\nFinished!") + print(f"Board Uptime: {time_calc(time.monotonic())}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") + print("===============================") + + except (ValueError, RuntimeError) as e: + print(f"Failed to get data, retrying\n {e}") + time.sleep(60) + break + time.sleep(SLEEP_TIME) From 967cf791920c59d060c43dab38e408c1395bfe7b Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 18 Mar 2024 03:07:32 -0400 Subject: [PATCH 215/305] minor label text mistake fix --- .../expanded/requests_wifi_api_openskynetwork_private_area.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py index f8433f3..8fc509a 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py @@ -158,7 +158,7 @@ def _format_datetime(datetime): # print(f" | | Last Contact Unix Time: {osn_time}") osn_struct_time = time.localtime(osn_time) osn_readable_time = f"{_format_datetime(osn_struct_time)}" - print(f" | | Last Contact: {osn_readable_time}") + print(f" | | Timestamp: {osn_readable_time}") if osn_all_flights is not None: # print("Flight Data: ", osn_all_flights) From b77dc62b959d5f0fcbd5ea7e9008db04798afcac Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 18 Mar 2024 03:11:01 -0400 Subject: [PATCH 216/305] isort my commit nemesis --- .../expanded/requests_wifi_api_openskynetwork_private_area.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py index 8fc509a..128fd3d 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py @@ -9,9 +9,9 @@ import adafruit_connection_manager import wifi +from adafruit_binascii import b2a_base64 import adafruit_requests -from adafruit_binascii import b2a_base64 # OpenSky-Network.org Website Login required for this API # REST API: https://openskynetwork.github.io/opensky-api/rest.html From 1ea79ef58ff39a23da13c57931dfe04be2b39db2 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 18 Mar 2024 03:19:45 -0400 Subject: [PATCH 217/305] forgot to remove github from copy/paste use, oops --- .../requests_wifi_api_openskynetwork_private_area.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py index 128fd3d..145a7ff 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py @@ -14,6 +14,7 @@ import adafruit_requests # OpenSky-Network.org Website Login required for this API +# Increased call limit vs Public. # REST API: https://openskynetwork.github.io/opensky-api/rest.html # Retrieves all traffic within a geographic area (Orlando example) LATMIN = "27.22" # east bounding box @@ -21,10 +22,6 @@ LONMIN = "-81.46" # north bounding box LONMAX = "-80.40" # south bounding box -# Github developer token required. -username = os.getenv("GITHUB_USERNAME") -token = os.getenv("GITHUB_TOKEN") - # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") From 8a34d2af2ced87c8827990d47536c39db9f26402 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 18 Mar 2024 03:23:44 -0400 Subject: [PATCH 218/305] forgot to remove github from copy/paste use, oops --- .../wifi/expanded/requests_wifi_api_openskynetwork_private.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py index 2668f9f..e3e711d 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py @@ -20,10 +20,6 @@ # ACTIVE transpondes only, for multiple "c822af&icao24=cb3993&icao24=c63923" TRANSPONDER = "471efd" -# Github developer token required. -username = os.getenv("GITHUB_USERNAME") -token = os.getenv("GITHUB_TOKEN") - # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") From 231e2eb009e19ab7e27d843e468f60d44fd174a9 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 18 Mar 2024 03:46:38 -0400 Subject: [PATCH 219/305] Update Open-Sky Network Single Flight Public API Example with Connection Manager --- ...requests_wifi_api_openskynetwork_public.py | 249 ++++++++++-------- 1 file changed, 146 insertions(+), 103 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py index 5bcd69b..19198e1 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py @@ -1,13 +1,13 @@ -# SPDX-FileCopyrightText: 2023 DJDevon3 +# SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.1 -# Adafruit Feather ESP32-S3 OpenSkyNetwork_Public_API_Example -import json +# Coded for Circuit Python 8.2.x +"""OpenSky-Network.org Public API Example""" +# pylint: disable=import-error + import os -import ssl import time -import socketpool +import adafruit_connection_manager import wifi import adafruit_requests @@ -17,121 +17,164 @@ # All active flights JSON: https://opensky-network.org/api/states/all PICK ONE! # JSON order: transponder, callsign, country # ACTIVE transpondes only, for multiple "c822af&icao24=cb3993&icao24=c63923" -transponder = "ab1644" +TRANSPONDER = "3c5ef8" -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") +osnusername = os.getenv("OSN_USERNAME") # Website Credentials +osnpassword = os.getenv("OSN_PASSWORD") # Website Credentials -# Time between API refreshes +# API Polling Rate # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour -# OpenSky-Networks will temp ban your IP for too many daily requests. +# OpenSky-Networks IP bans for too many requests, check rate limit. # https://openskynetwork.github.io/opensky-api/rest.html#limitations -sleep_time = 1800 +SLEEP_TIME = 1800 -# Get WiFi details, ensure these are setup in settings.toml -ssid = os.getenv("CIRCUITPY_WIFI_SSID") -password = os.getenv("CIRCUITPY_WIFI_PASSWORD") +# Set debug to True for full JSON response. +# WARNING: makes credentials visible +DEBUG = False + +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) # Requests URL - icao24 is their endpoint required for a transponder # example https://opensky-network.org/api/states/all?icao24=a808c5 -OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + transponder +OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + TRANSPONDER -# Converts seconds to human readable minutes/hours/days -def time_calc(input_time): # input_time in seconds +def time_calc(input_time): + """Converts seconds to minutes/hours/days""" if input_time < 60: - sleep_int = input_time - time_output = f"{sleep_int:.0f} seconds" - elif 60 <= input_time < 3600: - sleep_int = input_time / 60 - time_output = f"{sleep_int:.0f} minutes" - elif 3600 <= input_time < 86400: - sleep_int = input_time / 60 / 60 - time_output = f"{sleep_int:.1f} hours" - else: - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.1f} days" - return time_output + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" def _format_datetime(datetime): - return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( - datetime.tm_mon, - datetime.tm_mday, - datetime.tm_year, - datetime.tm_hour, - datetime.tm_min, - datetime.tm_sec, + """F-String formatted struct time conversion""" + return ( + f"{datetime.tm_mon:02}/" + + f"{datetime.tm_mday:02}/" + + f"{datetime.tm_year:02} " + + f"{datetime.tm_hour:02}:" + + f"{datetime.tm_min:02}:" + + f"{datetime.tm_sec:02}" ) -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -requests = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.ipv4_address: - try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) -print("Connected!\n") - while True: - debug_request = True # Set true to see full request - if debug_request: - print("Full API GET URL: ", OPENSKY_SOURCE) - print("===============================") + # Connect to Wi-Fi + print("\nConnecting to WiFi...") + while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ Wifi!") + try: - print("\nAttempting to GET OpenSky-Network Stats!") - opensky_response = requests.get(url=OPENSKY_SOURCE) - osn_json = opensky_response.json() - except (ConnectionError, ValueError, NameError) as e: - print("Host No Response Error:", e) - - # Print Full JSON to Serial - debug_response = False # Set true to see full response - if debug_response: - dump_object = json.dumps(osn_json) - print("JSON Dump: ", dump_object) - - # Print to Serial - osn_debug_keys = True # Set true to print Serial data - if osn_debug_keys: + print(" | Attempting to GET OpenSky-Network Single Flight JSON!") try: - osn_flight = osn_json["time"] - print("Current Unix Time: ", osn_flight) - - current_struct_time = time.localtime(osn_flight) - current_date = "{}".format(_format_datetime(current_struct_time)) - print(f"Unix to Readable Time: {current_date}") - - osn_single_flight_data = osn_json["states"] - if osn_single_flight_data is not None: - print("Flight Data: ", osn_single_flight_data) - transponder = osn_json["states"][0][0] - print("Transponder: ", transponder) - callsign = osn_json["states"][0][1] - print("Callsign: ", callsign) - country = osn_json["states"][0][2] - print("Flight Country: ", country) + opensky_response = requests.get(url=OPENSKY_SOURCE) + opensky_json = opensky_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + + print(" | ✅ OpenSky-Network JSON!") + + if DEBUG: + print("Full API GET URL: ", OPENSKY_SOURCE) + print(opensky_json) + + # ERROR MESSAGE RESPONSES + if "timestamp" in opensky_json: + osn_timestamp = opensky_json["timestamp"] + print(f"❌ Timestamp: {osn_timestamp}") + + if "message" in opensky_json: + osn_message = opensky_json["message"] + print(f"❌ Message: {osn_message}") + + if "error" in opensky_json: + osn_error = opensky_json["error"] + print(f"❌ Error: {osn_error}") + + if "path" in opensky_json: + osn_path = opensky_json["path"] + print(f"❌ Path: {osn_path}") + + if "status" in opensky_json: + osn_status = opensky_json["status"] + print(f"❌ Status: {osn_status}") + + # Current flight data for single callsign (right now) + osn_single_flight_data = opensky_json["states"] + + if osn_single_flight_data is not None: + if DEBUG: + print(f" | | Single Flight Public Data: {osn_single_flight_data}") + + last_contact = opensky_json["states"][0][4] + # print(f" | | Last Contact Unix Time: {last_contact}") + lc_struct_time = time.localtime(last_contact) + lc_readable_time = f"{_format_datetime(lc_struct_time)}" + print(f" | | Last Contact: {lc_readable_time}") + + flight_transponder = opensky_json["states"][0][0] + print(f" | | Transponder: {flight_transponder}") + + callsign = opensky_json["states"][0][1] + print(f" | | Callsign: {callsign}") + + squawk = opensky_json["states"][0][14] + print(f" | | Squawk: {squawk}") + + country = opensky_json["states"][0][2] + print(f" | | Origin: {country}") + + longitude = opensky_json["states"][0][5] + print(f" | | Longitude: {longitude}") + + latitude = opensky_json["states"][0][6] + print(f" | | Latitude: {latitude}") + + # Return Air Flight data if not on ground + on_ground = opensky_json["states"][0][8] + if on_ground is True: + print(f" | | On Ground: {on_ground}") else: - print("This flight has no active data or you're polling too fast.") - print( - "Read: https://openskynetwork.github.io/opensky-api/rest.html#limitations" - ) - print( - "Public Limits: 10 second max poll rate & 400 weighted calls daily" - ) - - print("\nFinished!") - print("Board Uptime: ", time_calc(time.monotonic())) - print("Next Update: ", time_calc(sleep_time)) - time.sleep(sleep_time) - print("===============================") - - except (ConnectionError, ValueError, NameError) as e: - print("OSN Connection Error:", e) - print("Next Retry: ", time_calc(sleep_time)) - time.sleep(sleep_time) + altitude = opensky_json["states"][0][7] + print(f" | | Barometric Altitude: {altitude}") + + velocity = opensky_json["states"][0][9] + if velocity != "null": + print(f" | | Velocity: {velocity}") + + vertical_rate = opensky_json["states"][0][11] + if vertical_rate != "null": + print(f" | | Vertical Rate: {vertical_rate}") + else: + print("This flight has no active data or you're polling too fast.") + print("Public Limits: 10 second max poll & 400 weighted calls daily") + + opensky_response.close() + print("✂️ Disconnected from OpenSky-Network API") + + print("\nFinished!") + print(f"Board Uptime: {time_calc(time.monotonic())}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") + print("===============================") + + except (ValueError, RuntimeError) as e: + print(f"Failed to get data, retrying\n {e}") + time.sleep(60) + break + time.sleep(SLEEP_TIME) From eda996915a3b010b1981be2b906006c7d03e429d Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 18 Mar 2024 04:43:12 -0400 Subject: [PATCH 220/305] Update Steam API Example with Connection Manager --- .../wifi/expanded/requests_wifi_api_steam.py | 163 ++++++++++-------- 1 file changed, 87 insertions(+), 76 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_steam.py b/examples/wifi/expanded/requests_wifi_api_steam.py index 208b6d6..a05d6c7 100644 --- a/examples/wifi/expanded/requests_wifi_api_steam.py +++ b/examples/wifi/expanded/requests_wifi_api_steam.py @@ -1,41 +1,48 @@ -# SPDX-FileCopyrightText: 2022 DJDevon3 (Neradoc & Deshipu helped) for Adafruit Industries +# SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.0 -"""DJDevon3 Adafruit Feather ESP32-S2 api_steam Example""" -import gc -import json +# Coded for Circuit Python 8.2.x +"""Steam API Get Owned Games Example""" +# pylint: disable=import-error + import os -import ssl import time -import socketpool +import adafruit_connection_manager import wifi import adafruit_requests # Steam API Docs: https://steamcommunity.com/dev # Steam API Key: https://steamcommunity.com/dev/apikey -# Steam Usernumber: Visit https://steamcommunity.com -# click on your profile icon, your usernumber will be in the browser url. +# Numerical Steam ID: Visit https://store.steampowered.com/account/ +# Your account name will be in big bold letters. +# Your numerical STEAM ID will be below in a very small font. # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Requires Steam Developer API key -steam_usernumber = os.getenv("steam_id") -steam_apikey = os.getenv("steam_api_key") - -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) +steam_usernumber = os.getenv("STEAM_ID") +steam_apikey = os.getenv("STEAM_API_KEY") -# Time between API refreshes +# API Polling Rate # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour -sleep_time = 900 +SLEEP_TIME = 3600 + +# Set debug to True for full JSON response. +# WARNING: Steam's full response will overload most microcontrollers +# SET TO TRUE IF YOU FEEL BRAVE =) +DEBUG = False + +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) # Deconstruct URL (pylint hates long lines) # http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/ # ?key=XXXXXXXXXXXXXXXXXXXXX&include_played_free_games=1&steamid=XXXXXXXXXXXXXXXX&format=json -Steam_OwnedGames_URL = ( +STEAM_SOURCE = ( "http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?" + "key=" + steam_apikey @@ -45,75 +52,79 @@ + "&format=json" ) -if sleep_time < 60: - sleep_time_conversion = "seconds" - sleep_int = sleep_time -elif 60 <= sleep_time < 3600: - sleep_int = sleep_time / 60 - sleep_time_conversion = "minutes" -elif 3600 <= sleep_time < 86400: - sleep_int = sleep_time / 60 / 60 - sleep_time_conversion = "hours" -else: - sleep_int = sleep_time / 60 / 60 / 24 - sleep_time_conversion = "days" - -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -requests = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.ipv4_address: - try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) - gc.collect() -print("Connected!\n") + +def time_calc(input_time): + """Converts seconds to minutes/hours/days""" + if input_time < 60: + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" + + +def _format_datetime(datetime): + """F-String formatted struct time conversion""" + return ( + f"{datetime.tm_mon:02}/" + + f"{datetime.tm_mday:02}/" + + f"{datetime.tm_year:02} " + + f"{datetime.tm_hour:02}:" + + f"{datetime.tm_min:02}:" + + f"{datetime.tm_sec:02}" + ) + while True: + # Connect to Wi-Fi + print("\nConnecting to WiFi...") + while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ Wifi!") + try: - print("\nAttempting to GET STEAM Stats!") # -------------------------------- - # Print Request to Serial - debug_request = False # Set true to see full request - if debug_request: - print("Full API GET URL: ", Steam_OwnedGames_URL) - print("===============================") + print(" | Attempting to GET Steam API JSON!") try: - steam_response = requests.get(url=Steam_OwnedGames_URL).json() + steam_response = requests.get(url=STEAM_SOURCE) + steam_json = steam_response.json() except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") - # Print Response to Serial - debug_response = False # Set true to see full response - if debug_response: - dump_object = json.dumps(steam_response) - print("JSON Dump: ", dump_object) - - # Print Keys to Serial - steam_debug_keys = True # Set True to print Serial data - if steam_debug_keys: - game_count = steam_response["response"]["game_count"] - print("Total Games: ", game_count) - total_minutes = 0 - - for game in steam_response["response"]["games"]: - total_minutes += game["playtime_forever"] - total_hours = total_minutes / 60 - total_days = total_minutes / 60 / 24 - print(f"Total Hours: {total_hours}") - print(f"Total Days: {total_days}") - - print("Monotonic: ", time.monotonic()) + print(" | ✅ Steam JSON!") + + if DEBUG: + print("Full API GET URL: ", STEAM_SOURCE) + print(steam_json) + + game_count = steam_json["response"]["game_count"] + print(f" | | Total Games: {game_count}") + TOTAL_MINUTES = 0 + + for game in steam_json["response"]["games"]: + TOTAL_MINUTES += game["playtime_forever"] + total_hours = TOTAL_MINUTES / 60 + total_days = TOTAL_MINUTES / 60 / 24 + total_years = TOTAL_MINUTES / 60 / 24 / 365 + print(f" | | Total Hours: {total_hours}") + print(f" | | Total Days: {total_days}") + print(f" | | Total Years: {total_years:.2f}") + + steam_response.close() + print("✂️ Disconnected from Steam API") + print("\nFinished!") - print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) + print(f"Board Uptime: {time_calc(time.monotonic())}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") print("===============================") - gc.collect() except (ValueError, RuntimeError) as e: - print("Failed to get data, retrying\n", e) + print(f"Failed to get data, retrying\n {e}") time.sleep(60) - continue - time.sleep(sleep_time) + break + time.sleep(SLEEP_TIME) From e1766b07bc0451121b600cf3d661565d5016cb1a Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 18 Mar 2024 06:31:06 -0400 Subject: [PATCH 221/305] Update Twitch API Example with Connection Manager --- .../wifi/expanded/requests_wifi_api_twitch.py | 182 ++++++++++-------- 1 file changed, 99 insertions(+), 83 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_twitch.py b/examples/wifi/expanded/requests_wifi_api_twitch.py index 716caa9..8e15098 100644 --- a/examples/wifi/expanded/requests_wifi_api_twitch.py +++ b/examples/wifi/expanded/requests_wifi_api_twitch.py @@ -1,44 +1,49 @@ -# SPDX-FileCopyrightText: 2023 DJDevon3 +# SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x -# Twitch_API_Example +"""Twitch API Example""" +# pylint: disable=import-error import os -import ssl import time -import socketpool +import adafruit_connection_manager import wifi import adafruit_requests -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) - # Twitch Developer Account & oauth App Required: # Visit https://dev.twitch.tv/console to create an app - -# Ensure these are in secrets.py or settings.toml -# "Twitch_ClientID": "Your Developer APP ID Here", -# "Twitch_Client_Secret": "APP ID secret here", -# "Twitch_UserID": "Your Twitch UserID here", +# Ensure these are in settings.toml +# TWITCH_CLIENT_ID = "Your Developer APP ID Here" +# TWITCH_CLIENT_SECRET = "APP ID secret here" +# TWITCH_USER_ID = "Your Twitch UserID here" # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") -twitch_client_id = os.getenv("Twitch_ClientID") -twitch_client_secret = os.getenv("Twitch_Client_Secret") +TWITCH_CID = os.getenv("TWITCH_CLIENT_ID") +TWITCH_CS = os.getenv("TWITCH_CLIENT_SECRET") # For finding your Twitch User ID # https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/ -twitch_user_id = os.getenv("Twitch_UserID") # User ID you want endpoints from +TWITCH_UID = os.getenv("TWITCH_USER_ID") -# Time between API refreshes +# API Polling Rate # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour -sleep_time = 900 +SLEEP_TIME = 900 + +# Set DEBUG to True for full JSON response. +# STREAMER WARNING: Credentials will be viewable +DEBUG = False + +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) -# Converts seconds to minutes/hours/days def time_calc(input_time): + """Converts seconds to minutes/hours/days""" if input_time < 60: return f"{input_time:.0f} seconds" if input_time < 3600: @@ -48,42 +53,44 @@ def time_calc(input_time): return f"{input_time / 60 / 60 / 24:.1f} days" +def _format_datetime(datetime): + """F-String formatted struct time conversion""" + return ( + f"{datetime.tm_mon:02}/" + + f"{datetime.tm_mday:02}/" + + f"{datetime.tm_year:02} " + + f"{datetime.tm_hour:02}:" + + f"{datetime.tm_min:02}:" + + f"{datetime.tm_sec:02}" + ) + + # First we use Client ID & Client Secret to create a token with POST # No user interaction is required for this type of scope (implicit grant flow) twitch_0auth_header = {"Content-Type": "application/x-www-form-urlencoded"} TWITCH_0AUTH_TOKEN = "https://id.twitch.tv/oauth2/token" -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -requests = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.connected: - try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) -print("Connected!\n") - while True: + # Connect to Wi-Fi + print("\nConnecting to WiFi...") + while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ Wifi!") + try: - # ----------------------------- POST FOR BEARER TOKEN ----------------------- - print( - "Attempting Bearer Token Request!" - ) # --------------------------------------- - # Print Request to Serial - debug_bearer_request = ( - False # STREAMER WARNING: your client secret will be viewable - ) - if debug_bearer_request: - print("Full API GET URL: ", TWITCH_0AUTH_TOKEN) - print("===============================") + # ------------- POST FOR BEARER TOKEN ----------------- + print(" | Attempting Bearer Token Request!") + if DEBUG: + print(f"Full API GET URL: {TWITCH_0AUTH_TOKEN}") twitch_0auth_data = ( "&client_id=" - + twitch_client_id + + TWITCH_CID + "&client_secret=" - + twitch_client_secret + + TWITCH_CS + "&grant_type=client_credentials" ) @@ -95,70 +102,79 @@ def time_calc(input_time): twitch_0auth_json = twitch_0auth_response.json() twitch_access_token = twitch_0auth_json["access_token"] except ConnectionError as e: - print("Connection Error:", e) + print(f"Connection Error: {e}") print("Retrying in 10 seconds") + print(" | 🔑 Token Authorized!") - # Print Response to Serial - debug_bearer_response = ( - False # STREAMER WARNING: your client secret will be viewable - ) - if debug_bearer_response: - print("JSON Dump: ", twitch_0auth_json) - print("Header: ", twitch_0auth_header) - print("Access Token: ", twitch_access_token) + # STREAMER WARNING: your client secret will be viewable + if DEBUG: + print(f"JSON Dump: {twitch_0auth_json}") + print(f"Header: {twitch_0auth_header}") + print(f"Access Token: {twitch_access_token}") twitch_token_type = twitch_0auth_json["token_type"] - print("Token Type: ", twitch_token_type) + print(f"Token Type: {twitch_token_type}") - print("Board Uptime: ", time_calc(time.monotonic())) twitch_token_expiration = twitch_0auth_json["expires_in"] - print("Token Expires in: ", time_calc(twitch_token_expiration)) + print(f" | Token Expires in: {time_calc(twitch_token_expiration)}") - # ----------------------------- GET DATA ------------------------------------- + # ----------------------------- GET DATA -------------------- # Bearer token is refreshed every time script runs :) # Twitch sets token expiration to about 64 days # Helix is the name of the current Twitch API # Now that we have POST bearer token we can do a GET for data - # ---------------------------------------------------------------------------- + # ----------------------------------------------------------- twitch_header = { "Authorization": "Bearer " + twitch_access_token + "", - "Client-Id": "" + twitch_client_id + "", + "Client-Id": "" + TWITCH_CID + "", } TWITCH_FOLLOWERS_SOURCE = ( "https://api.twitch.tv/helix/channels" + "/followers?" + "broadcaster_id=" - + twitch_user_id + + TWITCH_UID ) - print( - "\nAttempting to GET TWITCH Stats!" - ) # ------------------------------------------------ - print("===============================") - twitch_followers_response = requests.get( + print(" | Attempting to GET Twitch JSON!") + twitch_response = requests.get( url=TWITCH_FOLLOWERS_SOURCE, headers=twitch_header ) try: - twitch_followers_json = twitch_followers_response.json() + twitch_json = twitch_response.json() except ConnectionError as e: - print("Connection Error:", e) + print(f"Connection Error: {e}") print("Retrying in 10 seconds") - # Print Response to Serial - debug_bearer_response = ( - False # STREAMER WARNING: your bearer token will be viewable - ) - if debug_bearer_response: - print("Full API GET URL: ", TWITCH_FOLLOWERS_SOURCE) - print("Header: ", twitch_header) - print("JSON Full Response: ", twitch_followers_json) - - twitch_followers = twitch_followers_json["total"] - print("Followers: ", twitch_followers) - print("Finished!") - print("Next Update in: ", time_calc(sleep_time)) + if DEBUG: + print(f" | Full API GET URL: {TWITCH_FOLLOWERS_SOURCE}") + print(f" | Header: {twitch_header}") + print(f" | JSON Full Response: {twitch_json}") + + if "status" in twitch_json: + twitch_error_status = twitch_json["status"] + print(f"❌ Status: {twitch_error_status}") + + if "error" in twitch_json: + twitch_error = twitch_json["error"] + print(f"❌ Error: {twitch_error}") + + if "message" in twitch_json: + twitch_error_msg = twitch_json["message"] + print(f"❌ Message: {twitch_error_msg}") + + if "total" in twitch_json: + print(" | ✅ Twitch JSON!") + twitch_followers = twitch_json["total"] + print(f" | | Followers: {twitch_followers}") + + twitch_response.close() + print("✂️ Disconnected from Twitch API") + + print("\nFinished!") + print(f"Board Uptime: {time_calc(time.monotonic())}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") print("===============================") except (ValueError, RuntimeError) as e: - print("Failed to get data, retrying\n", e) + print(f"Failed to get data, retrying\n {e}") time.sleep(60) - continue - time.sleep(sleep_time) + break + time.sleep(SLEEP_TIME) From b42f593b6ad4424ab26d8d6e1934e93cfcc3d5f6 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 19 Mar 2024 05:49:18 -0400 Subject: [PATCH 222/305] Update OpenSky-Network Private Single Flight API Example with Connection Manager --- .../requests_wifi_api_openskynetwork_private.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py index e3e711d..b88235a 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x -"""OpenSky-Network.org Private API Example""" +"""OpenSky-Network.org Single Flight Private API Example""" # pylint: disable=import-error import os @@ -18,7 +18,7 @@ # All active flights JSON: https://opensky-network.org/api/states/all # PICK ONE! :) # JSON order: transponder, callsign, country # ACTIVE transpondes only, for multiple "c822af&icao24=cb3993&icao24=c63923" -TRANSPONDER = "471efd" +TRANSPONDER = "4b1806" # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") @@ -46,7 +46,7 @@ OSN_CREDENTIALS_B = b"" + str(OSN_CREDENTIALS) + "" BASE64_ASCII = b2a_base64(OSN_CREDENTIALS_B) BASE64_STRING = str(BASE64_ASCII) # bytearray -TRUNCATED_BASE64_STRING = BASE64_STRING[2:-1] # truncate bytearray head/tail +TRUNCATED_BASE64_STRING = BASE64_STRING[2:-3] # truncate bytearray head/tail if DEBUG: print("Original Binary Data: ", OSN_CREDENTIALS_B) @@ -56,7 +56,7 @@ # Requests URL - icao24 is their endpoint required for a transponder # example https://opensky-network.org/api/states/all?icao24=a808c5 # OSN private: requires your website username:password to be base64 encoded -OSN_HEADER = {"Authorization": "Basic " + str(TRUNCATED_BASE64_STRING)} +OPENSKY_HEADER = {"Authorization": "Basic " + str(TRUNCATED_BASE64_STRING)} OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + TRANSPONDER @@ -94,9 +94,10 @@ def _format_datetime(datetime): print("✅ Wifi!") try: - print(" | Attempting to GET OpenSky-Network Single Flight JSON!") + print(" | Attempting to GET OpenSky-Network Single Private Flight JSON!") + print(" | Website Credentials Required! Allows more daily calls than Public.") try: - opensky_response = requests.get(url=OPENSKY_SOURCE, headers=OSN_HEADER) + opensky_response = requests.get(url=OPENSKY_SOURCE, headers=OPENSKY_HEADER) opensky_json = opensky_response.json() except ConnectionError as e: print("Connection Error:", e) @@ -106,6 +107,7 @@ def _format_datetime(datetime): if DEBUG: print("Full API GET URL: ", OPENSKY_SOURCE) + print("Full API GET Header: ", OPENSKY_HEADER) print(opensky_json) # ERROR MESSAGE RESPONSES @@ -134,7 +136,7 @@ def _format_datetime(datetime): if osn_single_flight_data is not None: if DEBUG: - print(f" | | Single Flight Data: {osn_single_flight_data}") + print(f" | | Single Private Flight Data: {osn_single_flight_data}") last_contact = opensky_json["states"][0][4] # print(f" | | Last Contact Unix Time: {last_contact}") From d2f90eed5527ae052407e40cf0b2d12f2d1fa3e0 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 19 Mar 2024 06:07:42 -0400 Subject: [PATCH 223/305] Update OpenSky-Network Public Single Flight API Example with Connection Manager --- ...requests_wifi_api_openskynetwork_public.py | 248 ++++++++++-------- 1 file changed, 145 insertions(+), 103 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py index 5bcd69b..09997fa 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py @@ -1,13 +1,13 @@ -# SPDX-FileCopyrightText: 2023 DJDevon3 +# SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.1 -# Adafruit Feather ESP32-S3 OpenSkyNetwork_Public_API_Example -import json +# Coded for Circuit Python 8.2.x +"""OpenSky-Network.org Single Flight Public API Example""" +# pylint: disable=import-error + import os -import ssl import time -import socketpool +import adafruit_connection_manager import wifi import adafruit_requests @@ -17,121 +17,163 @@ # All active flights JSON: https://opensky-network.org/api/states/all PICK ONE! # JSON order: transponder, callsign, country # ACTIVE transpondes only, for multiple "c822af&icao24=cb3993&icao24=c63923" -transponder = "ab1644" +TRANSPONDER = "88044d" -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") -# Time between API refreshes +# API Polling Rate # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour -# OpenSky-Networks will temp ban your IP for too many daily requests. +# OpenSky-Networks IP bans for too many requests, check rate limit. # https://openskynetwork.github.io/opensky-api/rest.html#limitations -sleep_time = 1800 +SLEEP_TIME = 1800 -# Get WiFi details, ensure these are setup in settings.toml -ssid = os.getenv("CIRCUITPY_WIFI_SSID") -password = os.getenv("CIRCUITPY_WIFI_PASSWORD") +# Set debug to True for full JSON response. +# WARNING: makes credentials visible +DEBUG = False + +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) # Requests URL - icao24 is their endpoint required for a transponder # example https://opensky-network.org/api/states/all?icao24=a808c5 -OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + transponder +OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + TRANSPONDER -# Converts seconds to human readable minutes/hours/days -def time_calc(input_time): # input_time in seconds +def time_calc(input_time): + """Converts seconds to minutes/hours/days""" if input_time < 60: - sleep_int = input_time - time_output = f"{sleep_int:.0f} seconds" - elif 60 <= input_time < 3600: - sleep_int = input_time / 60 - time_output = f"{sleep_int:.0f} minutes" - elif 3600 <= input_time < 86400: - sleep_int = input_time / 60 / 60 - time_output = f"{sleep_int:.1f} hours" - else: - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.1f} days" - return time_output + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" def _format_datetime(datetime): - return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( - datetime.tm_mon, - datetime.tm_mday, - datetime.tm_year, - datetime.tm_hour, - datetime.tm_min, - datetime.tm_sec, + """F-String formatted struct time conversion""" + return ( + f"{datetime.tm_mon:02}/" + + f"{datetime.tm_mday:02}/" + + f"{datetime.tm_year:02} " + + f"{datetime.tm_hour:02}:" + + f"{datetime.tm_min:02}:" + + f"{datetime.tm_sec:02}" ) -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -requests = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.ipv4_address: - try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) -print("Connected!\n") - while True: - debug_request = True # Set true to see full request - if debug_request: - print("Full API GET URL: ", OPENSKY_SOURCE) - print("===============================") + # Connect to Wi-Fi + print("\nConnecting to WiFi...") + while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ Wifi!") + try: - print("\nAttempting to GET OpenSky-Network Stats!") - opensky_response = requests.get(url=OPENSKY_SOURCE) - osn_json = opensky_response.json() - except (ConnectionError, ValueError, NameError) as e: - print("Host No Response Error:", e) - - # Print Full JSON to Serial - debug_response = False # Set true to see full response - if debug_response: - dump_object = json.dumps(osn_json) - print("JSON Dump: ", dump_object) - - # Print to Serial - osn_debug_keys = True # Set true to print Serial data - if osn_debug_keys: + print(" | Attempting to GET OpenSky-Network Single Public Flight JSON!") + print(" | Website Credentials NOT Required! Less daily calls than Private.") try: - osn_flight = osn_json["time"] - print("Current Unix Time: ", osn_flight) - - current_struct_time = time.localtime(osn_flight) - current_date = "{}".format(_format_datetime(current_struct_time)) - print(f"Unix to Readable Time: {current_date}") - - osn_single_flight_data = osn_json["states"] - if osn_single_flight_data is not None: - print("Flight Data: ", osn_single_flight_data) - transponder = osn_json["states"][0][0] - print("Transponder: ", transponder) - callsign = osn_json["states"][0][1] - print("Callsign: ", callsign) - country = osn_json["states"][0][2] - print("Flight Country: ", country) + opensky_response = requests.get(url=OPENSKY_SOURCE) + opensky_json = opensky_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + + print(" | ✅ OpenSky-Network Public JSON!") + + if DEBUG: + print("Full API GET URL: ", OPENSKY_SOURCE) + print(opensky_json) + + # ERROR MESSAGE RESPONSES + if "timestamp" in opensky_json: + osn_timestamp = opensky_json["timestamp"] + print(f"❌ Timestamp: {osn_timestamp}") + + if "message" in opensky_json: + osn_message = opensky_json["message"] + print(f"❌ Message: {osn_message}") + + if "error" in opensky_json: + osn_error = opensky_json["error"] + print(f"❌ Error: {osn_error}") + + if "path" in opensky_json: + osn_path = opensky_json["path"] + print(f"❌ Path: {osn_path}") + + if "status" in opensky_json: + osn_status = opensky_json["status"] + print(f"❌ Status: {osn_status}") + + # Current flight data for single callsign (right now) + osn_single_flight_data = opensky_json["states"] + + if osn_single_flight_data is not None: + if DEBUG: + print(f" | | Single Flight Public Data: {osn_single_flight_data}") + + last_contact = opensky_json["states"][0][4] + # print(f" | | Last Contact Unix Time: {last_contact}") + lc_struct_time = time.localtime(last_contact) + lc_readable_time = f"{_format_datetime(lc_struct_time)}" + print(f" | | Last Contact: {lc_readable_time}") + + flight_transponder = opensky_json["states"][0][0] + print(f" | | Transponder: {flight_transponder}") + + callsign = opensky_json["states"][0][1] + print(f" | | Callsign: {callsign}") + + squawk = opensky_json["states"][0][14] + print(f" | | Squawk: {squawk}") + + country = opensky_json["states"][0][2] + print(f" | | Origin: {country}") + + longitude = opensky_json["states"][0][5] + print(f" | | Longitude: {longitude}") + + latitude = opensky_json["states"][0][6] + print(f" | | Latitude: {latitude}") + + # Return Air Flight data if not on ground + on_ground = opensky_json["states"][0][8] + if on_ground is True: + print(f" | | On Ground: {on_ground}") else: - print("This flight has no active data or you're polling too fast.") - print( - "Read: https://openskynetwork.github.io/opensky-api/rest.html#limitations" - ) - print( - "Public Limits: 10 second max poll rate & 400 weighted calls daily" - ) - - print("\nFinished!") - print("Board Uptime: ", time_calc(time.monotonic())) - print("Next Update: ", time_calc(sleep_time)) - time.sleep(sleep_time) - print("===============================") - - except (ConnectionError, ValueError, NameError) as e: - print("OSN Connection Error:", e) - print("Next Retry: ", time_calc(sleep_time)) - time.sleep(sleep_time) + altitude = opensky_json["states"][0][7] + print(f" | | Barometric Altitude: {altitude}") + + velocity = opensky_json["states"][0][9] + if velocity != "null": + print(f" | | Velocity: {velocity}") + + vertical_rate = opensky_json["states"][0][11] + if vertical_rate != "null": + print(f" | | Vertical Rate: {vertical_rate}") + else: + print("This flight has no active data or you're polling too fast.") + print("Public Limits: 10 second max poll & 400 weighted calls daily") + + opensky_response.close() + print("✂️ Disconnected from OpenSky-Network API") + + print("\nFinished!") + print(f"Board Uptime: {time_calc(time.monotonic())}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") + print("===============================") + + except (ValueError, RuntimeError) as e: + print(f"Failed to get data, retrying\n {e}") + time.sleep(60) + break + time.sleep(SLEEP_TIME) From 0e65d9116939fdb34c23a668645a3b92b683ccd1 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:39:07 -0400 Subject: [PATCH 224/305] remove adafruit_binascii replace with built-in binascii --- ...equests_wifi_api_openskynetwork_private.py | 12 +- .../expanded/requests_wifi_api_twitter.py | 120 ------------------ 2 files changed, 6 insertions(+), 126 deletions(-) delete mode 100644 examples/wifi/expanded/requests_wifi_api_twitter.py diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py index b88235a..aba0584 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py @@ -4,12 +4,12 @@ """OpenSky-Network.org Single Flight Private API Example""" # pylint: disable=import-error +import binascii import os import time import adafruit_connection_manager import wifi -from adafruit_binascii import b2a_base64 import adafruit_requests @@ -43,14 +43,14 @@ # -- Base64 Conversion -- OSN_CREDENTIALS = str(osnusername) + ":" + str(osnpassword) -OSN_CREDENTIALS_B = b"" + str(OSN_CREDENTIALS) + "" -BASE64_ASCII = b2a_base64(OSN_CREDENTIALS_B) -BASE64_STRING = str(BASE64_ASCII) # bytearray -TRUNCATED_BASE64_STRING = BASE64_STRING[2:-3] # truncate bytearray head/tail +# base64 encode and strip appended \n from bytearray +OSN_CREDENTIALS_B = binascii.b2a_base64(b"" + str(OSN_CREDENTIALS)).strip() +BASE64_STRING = str(OSN_CREDENTIALS_B) # bytearray +TRUNCATED_BASE64_STRING = BASE64_STRING[2:-1] # truncate bytearray head/tail if DEBUG: print("Original Binary Data: ", OSN_CREDENTIALS_B) - print("Base64 ByteArray: ", BASE64_ASCII) + print("Base64 ByteArray: ", BASE64_STRING) print(f"Base64 String: {TRUNCATED_BASE64_STRING}") # Requests URL - icao24 is their endpoint required for a transponder diff --git a/examples/wifi/expanded/requests_wifi_api_twitter.py b/examples/wifi/expanded/requests_wifi_api_twitter.py deleted file mode 100644 index 4dcdfa6..0000000 --- a/examples/wifi/expanded/requests_wifi_api_twitter.py +++ /dev/null @@ -1,120 +0,0 @@ -# SPDX-FileCopyrightText: 2022 DJDevon3 for Adafruit Industries -# SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.0 -"""DJDevon3 Adafruit Feather ESP32-S2 Twitter_API_Example""" -import gc -import json -import os -import ssl -import time - -import socketpool -import wifi - -import adafruit_requests - -# Twitter developer account bearer token required. -# Ensure these are uncommented and in secrets.py or .env -# "TW_userid": "Your Twitter user id", # numerical id not username -# "TW_bearer_token": "Your long API Bearer token", - -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) - -# Time between API refreshes -# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour -sleep_time = 900 - -# Get WiFi details, ensure these are setup in settings.toml -ssid = os.getenv("CIRCUITPY_WIFI_SSID") -password = os.getenv("CIRCUITPY_WIFI_PASSWORD") -tw_userid = os.getenv("TW_userid") -tw_bearer_token = os.getenv("TW_bearer_token") - -if sleep_time < 60: - sleep_time_conversion = "seconds" - sleep_int = sleep_time -elif 60 <= sleep_time < 3600: - sleep_int = sleep_time / 60 - sleep_time_conversion = "minutes" -elif 3600 <= sleep_time < 86400: - sleep_int = sleep_time / 60 / 60 - sleep_time_conversion = "hours" -else: - sleep_int = sleep_time / 60 / 60 / 24 - sleep_time_conversion = "days" - -# Used with any Twitter 0auth request. -twitter_header = {"Authorization": "Bearer " + tw_bearer_token} -TW_SOURCE = ( - "https://api.twitter.com/2/users/" - + tw_userid - + "?user.fields=public_metrics,created_at,pinned_tweet_id" - + "&expansions=pinned_tweet_id" - + "&tweet.fields=created_at,public_metrics,source,context_annotations,entities" -) - -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -requests = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.ipv4_address: - try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) - gc.collect() -print("Connected!\n") - -while True: - try: - print("\nAttempting to GET Twitter Stats!") # -------------------------------- - debug_request = False # Set true to see full request - if debug_request: - print("Full API GET URL: ", TW_SOURCE) - print("===============================") - try: - twitter_response = requests.get(url=TW_SOURCE, headers=twitter_header) - tw_json = twitter_response.json() - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - - # Print Full JSON to Serial - debug_response = False # Set true to see full response - if debug_response: - dump_object = json.dumps(tw_json) - print("JSON Dump: ", dump_object) - - # Print to Serial - tw_debug_keys = True # Set true to print Serial data - if tw_debug_keys: - tw_userid = tw_json["data"]["id"] - print("User ID: ", tw_userid) - - tw_username = tw_json["data"]["name"] - print("Name: ", tw_username) - - tw_join_date = tw_json["data"]["created_at"] - print("Member Since: ", tw_join_date) - - tw_tweets = tw_json["data"]["public_metrics"]["tweet_count"] - print("Tweets: ", tw_tweets) - - tw_followers = tw_json["data"]["public_metrics"]["followers_count"] - print("Followers: ", tw_followers) - - print("Monotonic: ", time.monotonic()) - - print("\nFinished!") - print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) - print("===============================") - gc.collect() - - except (ValueError, RuntimeError) as e: - print("Failed to get data, retrying\n", e) - time.sleep(60) - continue - time.sleep(sleep_time) From 4bbfec2b62a10280a55eceae452d9ee1d6476d78 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:04:03 -0400 Subject: [PATCH 225/305] changed import order and better constant names --- .../requests_wifi_api_openskynetwork_private.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py index aba0584..e206460 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py @@ -4,10 +4,10 @@ """OpenSky-Network.org Single Flight Private API Example""" # pylint: disable=import-error -import binascii import os import time +import binascii import adafruit_connection_manager import wifi @@ -18,7 +18,7 @@ # All active flights JSON: https://opensky-network.org/api/states/all # PICK ONE! :) # JSON order: transponder, callsign, country # ACTIVE transpondes only, for multiple "c822af&icao24=cb3993&icao24=c63923" -TRANSPONDER = "4b1806" +TRANSPONDER = "ad4f1c" # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") @@ -46,17 +46,16 @@ # base64 encode and strip appended \n from bytearray OSN_CREDENTIALS_B = binascii.b2a_base64(b"" + str(OSN_CREDENTIALS)).strip() BASE64_STRING = str(OSN_CREDENTIALS_B) # bytearray -TRUNCATED_BASE64_STRING = BASE64_STRING[2:-1] # truncate bytearray head/tail +SLICED_BASE64_STRING = BASE64_STRING[2:-1] # slice bytearray head/tail if DEBUG: - print("Original Binary Data: ", OSN_CREDENTIALS_B) print("Base64 ByteArray: ", BASE64_STRING) - print(f"Base64 String: {TRUNCATED_BASE64_STRING}") + print(f"Base64 Sliced String: {SLICED_BASE64_STRING}") # Requests URL - icao24 is their endpoint required for a transponder # example https://opensky-network.org/api/states/all?icao24=a808c5 # OSN private: requires your website username:password to be base64 encoded -OPENSKY_HEADER = {"Authorization": "Basic " + str(TRUNCATED_BASE64_STRING)} +OPENSKY_HEADER = {"Authorization": "Basic " + str(SLICED_BASE64_STRING)} OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + TRANSPONDER From ca41b260e316db2669e3a8d5cf2d3e8dd8cb7b26 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:08:31 -0400 Subject: [PATCH 226/305] changed import order back due to isort action fail --- .../wifi/expanded/requests_wifi_api_openskynetwork_private.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py index e206460..39dc17a 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py @@ -4,10 +4,10 @@ """OpenSky-Network.org Single Flight Private API Example""" # pylint: disable=import-error +import binascii import os import time -import binascii import adafruit_connection_manager import wifi From 5f04a60fe01d21023e2bfb717182f7b8442030d5 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:31:42 -0400 Subject: [PATCH 227/305] removed adafruit_binascii now uses built-in binascii --- ...ts_wifi_api_openskynetwork_private_area.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py index 145a7ff..ca3ea7e 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py @@ -1,15 +1,15 @@ # SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x -"""OpenSky-Network.org Private API Example""" +"""OpenSky-Network.org Private Area API Example""" # pylint: disable=import-error +import binascii import os import time import adafruit_connection_manager import wifi -from adafruit_binascii import b2a_base64 import adafruit_requests @@ -35,7 +35,8 @@ SLEEP_TIME = 1800 # Set debug to True for full JSON response. -# WARNING: makes credentials visible +# WARNING: makes credentials visible. based on how many flights +# in your area, full response could crash microcontroller DEBUG = False # Initalize Wifi, Socket Pool, Request Session @@ -45,18 +46,17 @@ # -- Base64 Conversion -- OSN_CREDENTIALS = str(osnusername) + ":" + str(osnpassword) -OSN_CREDENTIALS_B = b"" + str(OSN_CREDENTIALS) + "" -BASE64_ASCII = b2a_base64(OSN_CREDENTIALS_B) -BASE64_STRING = str(BASE64_ASCII) # bytearray -TRUNCATED_BASE64_STRING = BASE64_STRING[2:-1] # truncate bytearray head/tail +# base64 encode and strip appended \n from bytearray +OSN_CREDENTIALS_B = binascii.b2a_base64(b"" + str(OSN_CREDENTIALS)).strip() +BASE64_STRING = str(OSN_CREDENTIALS_B) # bytearray +SLICED_BASE64_STRING = BASE64_STRING[2:-1] # slice bytearray head/tail if DEBUG: - print("Original Binary Data: ", OSN_CREDENTIALS_B) - print("Base64 ByteArray: ", BASE64_ASCII) - print(f"Base64 String: {TRUNCATED_BASE64_STRING}") + print("Base64 ByteArray: ", BASE64_STRING) + print(f"Base64 Sliced String: {SLICED_BASE64_STRING}") # Area requires OpenSky-Network.org username:password to be base64 encoded -OSN_HEADER = {"Authorization": "Basic " + str(TRUNCATED_BASE64_STRING)} +OSN_HEADER = {"Authorization": "Basic " + str(SLICED_BASE64_STRING)} # Example request of all traffic over Florida. # Geographic areas calls cost less against the limit. From bd275ba9fffd4dd18f1a1519de6cb3003cdb01ce Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 23 Mar 2024 07:53:16 -0700 Subject: [PATCH 228/305] Add ruff formatter --- .gitattributes | 5 ++ .pre-commit-config.yaml | 46 ++++--------------- .../expanded/requests_wifi_api_twitter.py | 1 + .../expanded/requests_wifi_api_youtube.py | 1 + pyproject.toml | 6 +++ tests/chunk_test.py | 2 +- tests/chunked_redirect_test.py | 2 +- tests/concurrent_test.py | 2 +- tests/conftest.py | 2 +- tests/header_test.py | 2 +- tests/method_test.py | 2 +- tests/mocket.py | 6 +-- tests/parse_test.py | 2 +- tests/protocol_test.py | 2 +- tests/reuse_test.py | 2 +- 15 files changed, 33 insertions(+), 50 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d54c593 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +* text eol=lf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7b20fd7..0981ef3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,47 +1,19 @@ -# SPDX-FileCopyrightText: 2020 Diego Elio Pettenò +# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries # # SPDX-License-Identifier: Unlicense repos: - - repo: https://github.com/python/black - rev: 24.2.0 - hooks: - - id: black - - repo: https://github.com/PyCQA/isort - rev: 5.13.2 - hooks: - - id: isort - args: ["--profile", "black", "--filter-files"] - - repo: https://github.com/fsfe/reuse-tool - rev: v1.1.2 - hooks: - - id: reuse - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/pycqa/pylint - rev: v2.17.4 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.4 + hooks: + - id: ruff-format + - repo: https://github.com/fsfe/reuse-tool + rev: v3.0.1 hooks: - - id: pylint - name: pylint (library code) - types: [python] - args: - - --disable=consider-using-f-string - exclude: "^(docs/|examples/|tests/|setup.py$)" - - id: pylint - name: pylint (example code) - description: Run pylint rules on "examples/*.py" files - types: [python] - files: "^examples/" - args: - - --disable=consider-using-f-string,duplicate-code,missing-docstring,invalid-name - - id: pylint - name: pylint (test code) - description: Run pylint rules on "tests/*.py" files - types: [python] - files: "^tests/" - args: - - --disable=consider-using-f-string,duplicate-code,missing-docstring,invalid-name,protected-access,redefined-outer-name + - id: reuse diff --git a/examples/wifi/expanded/requests_wifi_api_twitter.py b/examples/wifi/expanded/requests_wifi_api_twitter.py index 4dcdfa6..062fb43 100644 --- a/examples/wifi/expanded/requests_wifi_api_twitter.py +++ b/examples/wifi/expanded/requests_wifi_api_twitter.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.0 """DJDevon3 Adafruit Feather ESP32-S2 Twitter_API_Example""" + import gc import json import os diff --git a/examples/wifi/expanded/requests_wifi_api_youtube.py b/examples/wifi/expanded/requests_wifi_api_youtube.py index e9bc6a2..8c14a19 100644 --- a/examples/wifi/expanded/requests_wifi_api_youtube.py +++ b/examples/wifi/expanded/requests_wifi_api_youtube.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.0 """DJDevon3 Adafruit Feather ESP32-S2 YouTube_API_Example""" + import gc import json import os diff --git a/pyproject.toml b/pyproject.toml index d9ee51a..be93e38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,12 @@ classifiers = [ ] dynamic = ["dependencies", "optional-dependencies"] +[tool.ruff] +target-version = "py38" + +[tool.ruff.format] +line-ending = "lf" + [tool.setuptools] py-modules = ["adafruit_requests"] diff --git a/tests/chunk_test.py b/tests/chunk_test.py index ec4606d..f67e44a 100644 --- a/tests/chunk_test.py +++ b/tests/chunk_test.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Unlicense -""" Chunk Tests """ +"""Chunk Tests""" from unittest import mock diff --git a/tests/chunked_redirect_test.py b/tests/chunked_redirect_test.py index 69f8b27..706aa4c 100644 --- a/tests/chunked_redirect_test.py +++ b/tests/chunked_redirect_test.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Unlicense -""" Redirection Tests """ +"""Redirection Tests""" from unittest import mock diff --git a/tests/concurrent_test.py b/tests/concurrent_test.py index 58c2ade..d24e3c7 100644 --- a/tests/concurrent_test.py +++ b/tests/concurrent_test.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Unlicense -""" Concurrent Tests """ +"""Concurrent Tests""" import errno from unittest import mock diff --git a/tests/conftest.py b/tests/conftest.py index 94023cb..25bac4b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Unlicense -""" PyTest Setup """ +"""PyTest Setup""" import adafruit_connection_manager import mocket diff --git a/tests/header_test.py b/tests/header_test.py index 8bcb354..e93f5da 100644 --- a/tests/header_test.py +++ b/tests/header_test.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Unlicense -""" Header Tests """ +"""Header Tests""" import mocket import pytest diff --git a/tests/method_test.py b/tests/method_test.py index d75e754..09471a3 100644 --- a/tests/method_test.py +++ b/tests/method_test.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Unlicense -""" Post Tests """ +"""Post Tests""" from unittest import mock diff --git a/tests/mocket.py b/tests/mocket.py index 3155231..7b19f66 100644 --- a/tests/mocket.py +++ b/tests/mocket.py @@ -3,7 +3,7 @@ # # SPDX-License-Identifier: Unlicense -""" Mock Socket """ +"""Mock Socket""" from unittest import mock @@ -84,9 +84,7 @@ class SSLContext: # pylint: disable=too-few-public-methods def __init__(self): self.wrap_socket = mock.Mock(side_effect=self._wrap_socket) - def _wrap_socket( - self, sock, server_hostname=None - ): # pylint: disable=no-self-use,unused-argument + def _wrap_socket(self, sock, server_hostname=None): # pylint: disable=no-self-use,unused-argument return sock diff --git a/tests/parse_test.py b/tests/parse_test.py index d9e1a56..8f3c5fe 100644 --- a/tests/parse_test.py +++ b/tests/parse_test.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Unlicense -""" Parse Tests """ +"""Parse Tests""" import json diff --git a/tests/protocol_test.py b/tests/protocol_test.py index 4fd7770..2c9ce1f 100644 --- a/tests/protocol_test.py +++ b/tests/protocol_test.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Unlicense -""" Protocol Tests """ +"""Protocol Tests""" from unittest import mock diff --git a/tests/reuse_test.py b/tests/reuse_test.py index 0c0e9a5..a7d4607 100644 --- a/tests/reuse_test.py +++ b/tests/reuse_test.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Unlicense -""" Reuse Tests """ +"""Reuse Tests""" from unittest import mock From 004b21058b60c9de2e00fc81c941afdb6068ee68 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 23 Mar 2024 08:08:58 -0700 Subject: [PATCH 229/305] Add ruff isort --- .pre-commit-config.yaml | 2 ++ pyproject.toml | 3 +++ 2 files changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0981ef3..ff19dde 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,6 +13,8 @@ repos: rev: v0.3.4 hooks: - id: ruff-format + - id: ruff + args: ["--fix"] - repo: https://github.com/fsfe/reuse-tool rev: v3.0.1 hooks: diff --git a/pyproject.toml b/pyproject.toml index be93e38..7ddf858 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,9 @@ dynamic = ["dependencies", "optional-dependencies"] [tool.ruff] target-version = "py38" +[tool.ruff.lint] +select = ["I"] + [tool.ruff.format] line-ending = "lf" From 00c1bcd97622c54c53bd25acc953e204816116d5 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 23 Mar 2024 09:13:55 -0700 Subject: [PATCH 230/305] Add ruff lint --- .pylintrc | 399 ------------------ adafruit_requests.py | 11 +- examples/fona/requests_fona_advanced.py | 4 +- examples/fona/requests_fona_simpletest.py | 4 +- ...sts_wifi_adafruit_discord_active_online.py | 1 - .../expanded/requests_wifi_api_discord.py | 1 - .../wifi/expanded/requests_wifi_api_fitbit.py | 1 - .../wifi/expanded/requests_wifi_api_github.py | 1 - .../expanded/requests_wifi_api_mastodon.py | 1 - .../requests_wifi_api_premiereleague.py | 1 - .../wifi/expanded/requests_wifi_api_steam.py | 1 - .../wifi/expanded/requests_wifi_api_twitch.py | 1 - pyproject.toml | 3 +- tests/chunked_redirect_test.py | 2 +- tests/mocket.py | 10 +- tests/reuse_test.py | 2 +- 16 files changed, 15 insertions(+), 428 deletions(-) delete mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index f945e92..0000000 --- a/.pylintrc +++ /dev/null @@ -1,399 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -[MASTER] - -# 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-whitelist= - -# Add files or directories to the ignore-list. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the ignore-list. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. -jobs=1 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins=pylint.extensions.no_self_use - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# 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 reenable 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=import-error,raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,deprecated-str-translate-call -disable=raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,import-error,pointless-string-statement,unspecified-encoding - -# 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= - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=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, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -# notes=FIXME,XXX,TODO -notes=FIXME,XXX - - -[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 missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=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 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 - -# 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=board - -# 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 - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# 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. expectedly -# not used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -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,future.builtins - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -# expected-line-ending-format= -expected-line-ending-format=LF - -# 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 - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=yes - -# Minimum lines number of a similarity. -min-similarity-lines=12 - - -[BASIC] - -# Regular expression matching correct argument names -argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct attribute names -attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct class names -# class-rgx=[A-Z_][a-zA-Z0-9]+$ -class-rgx=[A-Z_][a-zA-Z0-9_]+$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Regular expression matching correct function names -function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Good variable names which should always be accepted, separated by a comma -# good-names=i,j,k,ex,Run,_ -good-names=r,g,b,w,i,j,k,n,x,y,z,ex,ok,Run,_ - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct method names -method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# 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. -property-classes=abc.abstractproperty - -# Regular expression matching correct variable names -variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# 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 - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of internal dependencies in 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 - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - -# 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] - -# Maximum number of arguments for function / method -max-args=5 - -# Maximum number of attributes for a class (see R0902). -# max-attributes=7 -max-attributes=11 - -# Maximum number of boolean expressions in a if statement -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=1 - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=builtins.Exception diff --git a/adafruit_requests.py b/adafruit_requests.py index 9f38502..22edcb0 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -72,7 +72,7 @@ def read(self, size: int = -1) -> bytes: def readinto(self, buf: bytearray) -> int: """Read as much as available into buf or until it is full. Returns the number of bytes read into buf.""" - return self._response._readinto(buf) # pylint: disable=protected-access + return self._response._readinto(buf) class OutOfRetries(Exception): @@ -82,8 +82,6 @@ class OutOfRetries(Exception): class Response: """The response from a request, contains all the headers/content""" - # pylint: disable=too-many-instance-attributes - encoding = None def __init__(self, sock: SocketType, session: "Session") -> None: @@ -232,7 +230,6 @@ def close(self) -> None: return if self._session: - # pylint: disable=protected-access self._session._connection_manager.free_socket(self.socket) else: self.socket.close() @@ -405,8 +402,7 @@ def _send_header(self, socket, header, value): self._send_as_bytes(socket, value) self._send(socket, b"\r\n") - # pylint: disable=too-many-arguments - def _send_request( + def _send_request( # noqa: PLR0913 Too many arguments in function definition self, socket: SocketType, host: str, @@ -467,8 +463,7 @@ def _send_request( if data: self._send(socket, bytes(data)) - # pylint: disable=too-many-branches, too-many-statements, unused-argument, too-many-arguments, too-many-locals - def request( + def request( # noqa: PLR0912,PLR0913,PLR0915 Too many branches,Too many arguments in function definition,Too many statements self, method: str, url: str, diff --git a/examples/fona/requests_fona_advanced.py b/examples/fona/requests_fona_advanced.py index 33bc6f0..318f6be 100644 --- a/examples/fona/requests_fona_advanced.py +++ b/examples/fona/requests_fona_advanced.py @@ -10,8 +10,8 @@ import board import busio import digitalio -from adafruit_fona.adafruit_fona import FONA # pylint: disable=unused-import -from adafruit_fona.fona_3g import FONA3G # pylint: disable=unused-import +from adafruit_fona.adafruit_fona import FONA +from adafruit_fona.fona_3g import FONA3G import adafruit_requests diff --git a/examples/fona/requests_fona_simpletest.py b/examples/fona/requests_fona_simpletest.py index 8841d3d..516dc9d 100644 --- a/examples/fona/requests_fona_simpletest.py +++ b/examples/fona/requests_fona_simpletest.py @@ -10,8 +10,8 @@ import board import busio import digitalio -from adafruit_fona.adafruit_fona import FONA # pylint: disable=unused-import -from adafruit_fona.fona_3g import FONA3G # pylint: disable=unused-import +from adafruit_fona.adafruit_fona import FONA +from adafruit_fona.fona_3g import FONA3G import adafruit_requests diff --git a/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py b/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py index dec0e6e..3ebcaf4 100644 --- a/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py +++ b/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """Discord Active Online Shields.IO Example""" -# pylint: disable=import-error import os import time diff --git a/examples/wifi/expanded/requests_wifi_api_discord.py b/examples/wifi/expanded/requests_wifi_api_discord.py index 83b8f8f..08ad16c 100644 --- a/examples/wifi/expanded/requests_wifi_api_discord.py +++ b/examples/wifi/expanded/requests_wifi_api_discord.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """Discord Web Scrape Example""" -# pylint: disable=import-error import os import time diff --git a/examples/wifi/expanded/requests_wifi_api_fitbit.py b/examples/wifi/expanded/requests_wifi_api_fitbit.py index 802995c..b5f45b2 100644 --- a/examples/wifi/expanded/requests_wifi_api_fitbit.py +++ b/examples/wifi/expanded/requests_wifi_api_fitbit.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """Fitbit API Example""" -# pylint: disable=import-error, disable=no-member import os import time diff --git a/examples/wifi/expanded/requests_wifi_api_github.py b/examples/wifi/expanded/requests_wifi_api_github.py index 6fbe8fe..e045299 100644 --- a/examples/wifi/expanded/requests_wifi_api_github.py +++ b/examples/wifi/expanded/requests_wifi_api_github.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """Github API Example""" -# pylint: disable=import-error import os import time diff --git a/examples/wifi/expanded/requests_wifi_api_mastodon.py b/examples/wifi/expanded/requests_wifi_api_mastodon.py index f6bce9f..fd4266e 100644 --- a/examples/wifi/expanded/requests_wifi_api_mastodon.py +++ b/examples/wifi/expanded/requests_wifi_api_mastodon.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """Mastodon API Example""" -# pylint: disable=import-error import os import time diff --git a/examples/wifi/expanded/requests_wifi_api_premiereleague.py b/examples/wifi/expanded/requests_wifi_api_premiereleague.py index 0a2e607..88524e9 100644 --- a/examples/wifi/expanded/requests_wifi_api_premiereleague.py +++ b/examples/wifi/expanded/requests_wifi_api_premiereleague.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """Premiere League Total Players API Example""" -# pylint: disable=import-error import os import time diff --git a/examples/wifi/expanded/requests_wifi_api_steam.py b/examples/wifi/expanded/requests_wifi_api_steam.py index a05d6c7..fb1f7bd 100644 --- a/examples/wifi/expanded/requests_wifi_api_steam.py +++ b/examples/wifi/expanded/requests_wifi_api_steam.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """Steam API Get Owned Games Example""" -# pylint: disable=import-error import os import time diff --git a/examples/wifi/expanded/requests_wifi_api_twitch.py b/examples/wifi/expanded/requests_wifi_api_twitch.py index 8e15098..1f02e5c 100644 --- a/examples/wifi/expanded/requests_wifi_api_twitch.py +++ b/examples/wifi/expanded/requests_wifi_api_twitch.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """Twitch API Example""" -# pylint: disable=import-error import os import time diff --git a/pyproject.toml b/pyproject.toml index 7ddf858..99d4775 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,8 @@ dynamic = ["dependencies", "optional-dependencies"] target-version = "py38" [tool.ruff.lint] -select = ["I"] +select = ["I", "PL"] +ignore = ["PLR2004"] [tool.ruff.format] line-ending = "lf" diff --git a/tests/chunked_redirect_test.py b/tests/chunked_redirect_test.py index 706aa4c..0990acb 100644 --- a/tests/chunked_redirect_test.py +++ b/tests/chunked_redirect_test.py @@ -113,7 +113,7 @@ ) -class MocketRecvInto(mocket.Mocket): # pylint: disable=too-few-public-methods +class MocketRecvInto(mocket.Mocket): """A special Mocket to cap the number of bytes returned from recv_into()""" def __init__(self, response): diff --git a/tests/mocket.py b/tests/mocket.py index 7b19f66..98f2de7 100644 --- a/tests/mocket.py +++ b/tests/mocket.py @@ -19,19 +19,18 @@ MOCK_RESPONSE = b"HTTP/1.0 200 OK\r\nContent-Length: 70\r\n\r\n" + MOCK_RESPONSE_TEXT -class MocketPool: # pylint: disable=too-few-public-methods +class MocketPool: """Mock SocketPool""" SOCK_STREAM = 0 - # pylint: disable=unused-argument def __init__(self, radio=None): self.getaddrinfo = mock.Mock() self.getaddrinfo.return_value = ((None, None, None, None, (MOCK_POOL_IP, 80)),) self.socket = mock.Mock() -class Mocket: # pylint: disable=too-few-public-methods +class Mocket: """Mock Socket""" def __init__(self, response=MOCK_RESPONSE): @@ -78,17 +77,16 @@ def _recv_into(self, buf, nbytes=0): return read -class SSLContext: # pylint: disable=too-few-public-methods +class SSLContext: """Mock SSL Context""" def __init__(self): self.wrap_socket = mock.Mock(side_effect=self._wrap_socket) - def _wrap_socket(self, sock, server_hostname=None): # pylint: disable=no-self-use,unused-argument + def _wrap_socket(self, sock, server_hostname=None): return sock -# pylint: disable=too-few-public-methods class MockRadio: class Radio: pass diff --git a/tests/reuse_test.py b/tests/reuse_test.py index a7d4607..9229626 100644 --- a/tests/reuse_test.py +++ b/tests/reuse_test.py @@ -106,7 +106,7 @@ def test_get_twice_after_second(pool, requests_ssl): pool.socket.assert_called_once() with pytest.raises(RuntimeError) as context: - result = response.text # pylint: disable=unused-variable + result = response.text assert "Newer Response closed this one. Use Responses immediately." in str(context) From 2f8ae41c474fef1968fe82be0466b20016b29cea Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 23 Mar 2024 09:32:57 -0700 Subject: [PATCH 231/305] Add ruff upgrade --- docs/conf.py | 2 -- examples/wifi/expanded/requests_wifi_api_fitbit.py | 2 +- pyproject.toml | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 4e19482..c9c7548 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # # SPDX-License-Identifier: MIT diff --git a/examples/wifi/expanded/requests_wifi_api_fitbit.py b/examples/wifi/expanded/requests_wifi_api_fitbit.py index b5f45b2..bae8e86 100644 --- a/examples/wifi/expanded/requests_wifi_api_fitbit.py +++ b/examples/wifi/expanded/requests_wifi_api_fitbit.py @@ -284,7 +284,7 @@ def time_calc(input_time): ] print(f" | | Fitbit Date: {activities_timestamp}") if MIDNIGHT_DEBUG: - ACTIVITIES_LATEST_HEART_TIME = str("00:05:00") + ACTIVITIES_LATEST_HEART_TIME = "00:05:00" else: ACTIVITIES_LATEST_HEART_TIME = fitbit_json[ "activities-heart-intraday" diff --git a/pyproject.toml b/pyproject.toml index 99d4775..fd96270 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,8 +42,8 @@ dynamic = ["dependencies", "optional-dependencies"] target-version = "py38" [tool.ruff.lint] -select = ["I", "PL"] -ignore = ["PLR2004"] +select = ["I", "PL", "UP"] +ignore = ["PLR2004", "UP028", "UP030", "UP031", "UP032"] [tool.ruff.format] line-ending = "lf" From aa5bd1619277d230125db75ebc1528c1464dc355 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 23 Mar 2024 09:48:42 -0700 Subject: [PATCH 232/305] remove upgrade UP028 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fd96270..5bc3971 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ target-version = "py38" [tool.ruff.lint] select = ["I", "PL", "UP"] -ignore = ["PLR2004", "UP028", "UP030", "UP031", "UP032"] +ignore = ["PLR2004", "UP030", "UP031", "UP032"] [tool.ruff.format] line-ending = "lf" From f5a8022744fbe2dd3f1328af2c645ff25d4a8c26 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 23 Mar 2024 09:54:24 -0700 Subject: [PATCH 233/305] remove upgrade UP031 --- examples/wifi/expanded/requests_wifi_api_twitter.py | 2 +- examples/wifi/expanded/requests_wifi_api_youtube.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_twitter.py b/examples/wifi/expanded/requests_wifi_api_twitter.py index 062fb43..35e1ea5 100644 --- a/examples/wifi/expanded/requests_wifi_api_twitter.py +++ b/examples/wifi/expanded/requests_wifi_api_twitter.py @@ -110,7 +110,7 @@ print("Monotonic: ", time.monotonic()) print("\nFinished!") - print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) + print("Next Update in {} {}".format(int(sleep_int), sleep_time_conversion)) print("===============================") gc.collect() diff --git a/examples/wifi/expanded/requests_wifi_api_youtube.py b/examples/wifi/expanded/requests_wifi_api_youtube.py index 8c14a19..529217d 100644 --- a/examples/wifi/expanded/requests_wifi_api_youtube.py +++ b/examples/wifi/expanded/requests_wifi_api_youtube.py @@ -113,7 +113,7 @@ print("Monotonic: ", time.monotonic()) print("\nFinished!") - print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) + print("Next Update in {} {}".format(int(sleep_int), sleep_time_conversion)) print("===============================") gc.collect() diff --git a/pyproject.toml b/pyproject.toml index 5bc3971..0d4b773 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ target-version = "py38" [tool.ruff.lint] select = ["I", "PL", "UP"] -ignore = ["PLR2004", "UP030", "UP031", "UP032"] +ignore = ["PLR2004", "UP030", "UP032"] [tool.ruff.format] line-ending = "lf" From e26ab09fd84cff76f197287b1b7442464922a7da Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 23 Mar 2024 10:02:16 -0700 Subject: [PATCH 234/305] remove upgrade UP032 --- adafruit_requests.py | 2 +- examples/cpython/requests_cpython_simpletest.py | 4 ++-- examples/esp32spi/requests_esp32spi_simpletest.py | 4 ++-- examples/fona/requests_fona_simpletest.py | 4 ++-- .../requests_wifi_api_openskynetwork_private.py | 11 ++--------- .../requests_wifi_api_openskynetwork_private_area.py | 11 ++--------- .../requests_wifi_api_openskynetwork_public.py | 11 ++--------- examples/wifi/expanded/requests_wifi_api_twitter.py | 2 +- examples/wifi/expanded/requests_wifi_api_youtube.py | 2 +- examples/wifi/requests_wifi_simpletest.py | 4 ++-- examples/wiznet5k/requests_wiznet5k_simpletest.py | 4 ++-- pyproject.toml | 2 +- tests/parse_test.py | 10 +++------- 13 files changed, 23 insertions(+), 48 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 22edcb0..1a4e6de 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -429,7 +429,7 @@ def _send_request( # noqa: PLR0913 Too many arguments in function definition content_type_header = "application/x-www-form-urlencoded" _post_data = "" for k in data: - _post_data = "{}&{}={}".format(_post_data, k, data[k]) + _post_data = f"{_post_data}&{k}={data[k]}" # remove first "&" from concatenation data = _post_data[1:] diff --git a/examples/cpython/requests_cpython_simpletest.py b/examples/cpython/requests_cpython_simpletest.py index 70bdfde..7ab318a 100644 --- a/examples/cpython/requests_cpython_simpletest.py +++ b/examples/cpython/requests_cpython_simpletest.py @@ -30,7 +30,7 @@ response.close() data = "31F" -print("POSTing data to {0}: {1}".format(JSON_POST_URL, data)) +print(f"POSTing data to {JSON_POST_URL}: {data}") response = requests.post(JSON_POST_URL, data=data) print("-" * 40) @@ -41,7 +41,7 @@ response.close() json_data = {"Date": "July 25, 2019"} -print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data)) +print(f"POSTing data to {JSON_POST_URL}: {json_data}") response = requests.post(JSON_POST_URL, json=json_data) print("-" * 40) diff --git a/examples/esp32spi/requests_esp32spi_simpletest.py b/examples/esp32spi/requests_esp32spi_simpletest.py index 3ace026..2b7ef94 100644 --- a/examples/esp32spi/requests_esp32spi_simpletest.py +++ b/examples/esp32spi/requests_esp32spi_simpletest.py @@ -68,7 +68,7 @@ response.close() data = "31F" -print("POSTing data to {0}: {1}".format(JSON_POST_URL, data)) +print(f"POSTing data to {JSON_POST_URL}: {data}") response = requests.post(JSON_POST_URL, data=data) print("-" * 40) @@ -79,7 +79,7 @@ response.close() json_data = {"Date": "July 25, 2019"} -print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data)) +print(f"POSTing data to {JSON_POST_URL}: {json_data}") response = requests.post(JSON_POST_URL, json=json_data) print("-" * 40) diff --git a/examples/fona/requests_fona_simpletest.py b/examples/fona/requests_fona_simpletest.py index 516dc9d..2294f40 100644 --- a/examples/fona/requests_fona_simpletest.py +++ b/examples/fona/requests_fona_simpletest.py @@ -69,7 +69,7 @@ response.close() data = "31F" -print("POSTing data to {0}: {1}".format(JSON_POST_URL, data)) +print(f"POSTing data to {JSON_POST_URL}: {data}") response = requests.post(JSON_POST_URL, data=data) print("-" * 40) @@ -80,7 +80,7 @@ response.close() json_data = {"Date": "July 25, 2019"} -print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data)) +print(f"POSTing data to {JSON_POST_URL}: {json_data}") response = requests.post(JSON_POST_URL, json=json_data) print("-" * 40) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py index 486a4de..4ce0581 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py @@ -76,14 +76,7 @@ def time_calc(input_time): # input_time in seconds def _format_datetime(datetime): - return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( - datetime.tm_mon, - datetime.tm_mday, - datetime.tm_year, - datetime.tm_hour, - datetime.tm_min, - datetime.tm_sec, - ) + return f"{datetime.tm_mon:02}/{datetime.tm_mday:02}/{datetime.tm_year} {datetime.tm_hour:02}:{datetime.tm_min:02}:{datetime.tm_sec:02}" # Connect to Wi-Fi @@ -124,7 +117,7 @@ def _format_datetime(datetime): print("Current Unix Time: ", osn_flight) current_struct_time = time.localtime(osn_flight) - current_date = "{}".format(_format_datetime(current_struct_time)) + current_date = f"{_format_datetime(current_struct_time)}" print(f"Unix to Readable Time: {current_date}") # Current flight data for single callsign (right now) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py index 8cd08a2..d89f2f3 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py @@ -91,14 +91,7 @@ def time_calc(input_time): # input_time in seconds def _format_datetime(datetime): - return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( - datetime.tm_mon, - datetime.tm_mday, - datetime.tm_year, - datetime.tm_hour, - datetime.tm_min, - datetime.tm_sec, - ) + return f"{datetime.tm_mon:02}/{datetime.tm_mday:02}/{datetime.tm_year} {datetime.tm_hour:02}:{datetime.tm_min:02}:{datetime.tm_sec:02}" # Connect to Wi-Fi @@ -139,7 +132,7 @@ def _format_datetime(datetime): print("Current Unix Time: ", osn_flight) current_struct_time = time.localtime(osn_flight) - current_date = "{}".format(_format_datetime(current_struct_time)) + current_date = f"{_format_datetime(current_struct_time)}" print(f"Unix to Readable Time: {current_date}") # Current flight data for single callsign (right now) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py index 5bcd69b..4038694 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py @@ -55,14 +55,7 @@ def time_calc(input_time): # input_time in seconds def _format_datetime(datetime): - return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( - datetime.tm_mon, - datetime.tm_mday, - datetime.tm_year, - datetime.tm_hour, - datetime.tm_min, - datetime.tm_sec, - ) + return f"{datetime.tm_mon:02}/{datetime.tm_mday:02}/{datetime.tm_year} {datetime.tm_hour:02}:{datetime.tm_min:02}:{datetime.tm_sec:02}" # Connect to Wi-Fi @@ -104,7 +97,7 @@ def _format_datetime(datetime): print("Current Unix Time: ", osn_flight) current_struct_time = time.localtime(osn_flight) - current_date = "{}".format(_format_datetime(current_struct_time)) + current_date = f"{_format_datetime(current_struct_time)}" print(f"Unix to Readable Time: {current_date}") osn_single_flight_data = osn_json["states"] diff --git a/examples/wifi/expanded/requests_wifi_api_twitter.py b/examples/wifi/expanded/requests_wifi_api_twitter.py index 35e1ea5..431279a 100644 --- a/examples/wifi/expanded/requests_wifi_api_twitter.py +++ b/examples/wifi/expanded/requests_wifi_api_twitter.py @@ -110,7 +110,7 @@ print("Monotonic: ", time.monotonic()) print("\nFinished!") - print("Next Update in {} {}".format(int(sleep_int), sleep_time_conversion)) + print(f"Next Update in {int(sleep_int)} {sleep_time_conversion}") print("===============================") gc.collect() diff --git a/examples/wifi/expanded/requests_wifi_api_youtube.py b/examples/wifi/expanded/requests_wifi_api_youtube.py index 529217d..8400382 100644 --- a/examples/wifi/expanded/requests_wifi_api_youtube.py +++ b/examples/wifi/expanded/requests_wifi_api_youtube.py @@ -113,7 +113,7 @@ print("Monotonic: ", time.monotonic()) print("\nFinished!") - print("Next Update in {} {}".format(int(sleep_int), sleep_time_conversion)) + print(f"Next Update in {int(sleep_int)} {sleep_time_conversion}") print("===============================") gc.collect() diff --git a/examples/wifi/requests_wifi_simpletest.py b/examples/wifi/requests_wifi_simpletest.py index 35b835a..055551b 100644 --- a/examples/wifi/requests_wifi_simpletest.py +++ b/examples/wifi/requests_wifi_simpletest.py @@ -50,7 +50,7 @@ response.close() data = "31F" -print("POSTing data to {0}: {1}".format(JSON_POST_URL, data)) +print(f"POSTing data to {JSON_POST_URL}: {data}") response = requests.post(JSON_POST_URL, data=data) print("-" * 40) @@ -61,7 +61,7 @@ response.close() json_data = {"Date": "July 25, 2019"} -print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data)) +print(f"POSTing data to {JSON_POST_URL}: {json_data}") response = requests.post(JSON_POST_URL, json=json_data) print("-" * 40) diff --git a/examples/wiznet5k/requests_wiznet5k_simpletest.py b/examples/wiznet5k/requests_wiznet5k_simpletest.py index e35b5ab..357a77a 100644 --- a/examples/wiznet5k/requests_wiznet5k_simpletest.py +++ b/examples/wiznet5k/requests_wiznet5k_simpletest.py @@ -41,7 +41,7 @@ response.close() data = "31F" -print("POSTing data to {0}: {1}".format(JSON_POST_URL, data)) +print(f"POSTing data to {JSON_POST_URL}: {data}") response = requests.post(JSON_POST_URL, data=data) print("-" * 40) @@ -52,7 +52,7 @@ response.close() json_data = {"Date": "July 25, 2019"} -print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data)) +print(f"POSTing data to {JSON_POST_URL}: {json_data}") response = requests.post(JSON_POST_URL, json=json_data) print("-" * 40) diff --git a/pyproject.toml b/pyproject.toml index 0d4b773..291a9cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ target-version = "py38" [tool.ruff.lint] select = ["I", "PL", "UP"] -ignore = ["PLR2004", "UP030", "UP032"] +ignore = ["PLR2004", "UP030"] [tool.ruff.format] line-ending = "lf" diff --git a/tests/parse_test.py b/tests/parse_test.py index 8f3c5fe..7cff9f5 100644 --- a/tests/parse_test.py +++ b/tests/parse_test.py @@ -15,13 +15,9 @@ # Padding here tests the case where a header line is exactly 32 bytes buffered by # aligning the Content-Type header after it. HEADERS = ( - ( - "HTTP/1.0 200 OK\r\npadding: 000\r\n" - "Content-Type: application/json\r\nContent-Length: {}\r\n\r\n" - ) - .format(len(ENCODED)) - .encode("utf-8") -) + "HTTP/1.0 200 OK\r\npadding: 000\r\n" + f"Content-Type: application/json\r\nContent-Length: {len(ENCODED)}\r\n\r\n" +).encode() def test_json(pool): From 1def0b66bdfac97bb37454f9afc7bdc28b4cd14f Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 24 Mar 2024 00:26:34 -0400 Subject: [PATCH 235/305] Update YouTube API Example with Connection Manager --- .../expanded/requests_wifi_api_youtube.py | 165 +++++++++--------- 1 file changed, 81 insertions(+), 84 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_youtube.py b/examples/wifi/expanded/requests_wifi_api_youtube.py index e9bc6a2..5215b10 100644 --- a/examples/wifi/expanded/requests_wifi_api_youtube.py +++ b/examples/wifi/expanded/requests_wifi_api_youtube.py @@ -1,123 +1,120 @@ -# SPDX-FileCopyrightText: 2022 DJDevon3 for Adafruit Industries +# SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.0 -"""DJDevon3 Adafruit Feather ESP32-S2 YouTube_API_Example""" -import gc -import json +# Coded for Circuit Python 8.2.x +"""YouTube API Subscriber Count Example""" +# pylint: disable=import-error + import os -import ssl import time -import socketpool +import adafruit_connection_manager import wifi import adafruit_requests -# Ensure these are uncommented and in secrets.py or .env -# "YT_username": "Your YouTube Username", -# "YT_token" : "Your long API developer token", - -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) -# Time between API refreshes +# API Polling Rate # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour -sleep_time = 900 +SLEEP_TIME = 900 + +# Set debug to True for full JSON response. +# WARNING: Will show credentials +DEBUG = False + +# Ensure these are uncommented and in settings.toml +# YOUTUBE_USERNAME = "Your YouTube Username", +# YOUTUBE_TOKEN = "Your long API developer token", # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") -yt_username = os.getenv("YT_username") -yt_token = os.getenv("YT_token") - - -if sleep_time < 60: - sleep_time_conversion = "seconds" - sleep_int = sleep_time -elif 60 <= sleep_time < 3600: - sleep_int = sleep_time / 60 - sleep_time_conversion = "minutes" -elif 3600 <= sleep_time < 86400: - sleep_int = sleep_time / 60 / 60 - sleep_time_conversion = "hours" -else: - sleep_int = sleep_time / 60 / 60 / 24 - sleep_time_conversion = "days" +# Requires Steam Developer API key +YT_USERNAME = os.getenv("YOUTUBE_USERNAME") +YT_TOKEN = os.getenv("YOUTUBE_TOKEN") + + +def time_calc(input_time): + """Converts seconds to minutes/hours/days""" + if input_time < 60: + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" + # https://youtube.googleapis.com/youtube/v3/channels?part=statistics&forUsername=[YOUR_USERNAME]&key=[YOUR_API_KEY] -YT_SOURCE = ( - "https://youtube.googleapis.com/youtube/v3/channels?" - + "part=statistics" - + "&forUsername=" - + yt_username +YOUTUBE_SOURCE = ( + "https://youtube.googleapis.com/youtube/v3/channels?part=statistics&forUsername=" + + str(YT_USERNAME) + "&key=" - + yt_token + + str(YT_TOKEN) ) -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -requests = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.ipv4_address: - try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) - gc.collect() -print("Connected!\n") - while True: + # Connect to Wi-Fi + print("\nConnecting to WiFi...") + while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ Wifi!") try: - print("Attempting to GET YouTube Stats!") # ---------------------------------- - debug_request = False # Set true to see full request - if debug_request: - print("Full API GET URL: ", YT_SOURCE) - print("===============================") + print(" | Attempting to GET YouTube JSON...") try: - response = requests.get(YT_SOURCE).json() + youtube_response = requests.get(url=YOUTUBE_SOURCE) + youtube_json = youtube_response.json() except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") + print(" | ✅ YouTube JSON!") + + if DEBUG: + print(f" | Full API GET URL: {YOUTUBE_SOURCE}") + print(f" | Full API Dump: {youtube_json}") - # Print Full JSON to Serial - debug_response = False # Set true to see full response - if debug_response: - dump_object = json.dumps(response) - print("JSON Dump: ", dump_object) + # Key:Value RESPONSES + if "pageInfo" in youtube_json: + totalResults = youtube_json["pageInfo"]["totalResults"] + print(f" | | Matching Results: {totalResults}") - # Print to Serial - yt_debug_keys = True # Set to True to print Serial data - if yt_debug_keys: - print("Matching Results: ", response["pageInfo"]["totalResults"]) + if "items" in youtube_json: + YT_request_kind = youtube_json["items"][0]["kind"] + print(f" | | Request Kind: {YT_request_kind}") - YT_request_kind = response["items"][0]["kind"] - print("Request Kind: ", YT_request_kind) + YT_channel_id = youtube_json["items"][0]["id"] + print(f" | | Channel ID: {YT_channel_id}") - YT_response_kind = response["kind"] - print("Response Kind: ", YT_response_kind) + YT_videoCount = youtube_json["items"][0]["statistics"]["videoCount"] + print(f" | | Videos: {YT_videoCount}") - YT_channel_id = response["items"][0]["id"] - print("Channel ID: ", YT_channel_id) + YT_viewCount = youtube_json["items"][0]["statistics"]["viewCount"] + print(f" | | Views: {YT_viewCount}") - YT_videoCount = response["items"][0]["statistics"]["videoCount"] - print("Videos: ", YT_videoCount) + YT_subsCount = youtube_json["items"][0]["statistics"]["subscriberCount"] + print(f" | | Subscribers: {YT_subsCount}") - YT_viewCount = response["items"][0]["statistics"]["viewCount"] - print("Views: ", YT_viewCount) + if "kind" in youtube_json: + YT_response_kind = youtube_json["kind"] + print(f" | | Response Kind: {YT_response_kind}") - YT_subsCount = response["items"][0]["statistics"]["subscriberCount"] - print("Subscribers: ", YT_subsCount) - print("Monotonic: ", time.monotonic()) + youtube_response.close() + print("✂️ Disconnected from YouTube API") print("\nFinished!") - print("Next Update in %s %s" % (int(sleep_int), sleep_time_conversion)) + print(f"Board Uptime: {time_calc(time.monotonic())}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") print("===============================") - gc.collect() except (ValueError, RuntimeError) as e: - print("Failed to get data, retrying\n", e) + print(f"Failed to get data, retrying\n {e}") time.sleep(60) - continue - time.sleep(sleep_time) + break + time.sleep(SLEEP_TIME) From fdc0da738f777754a4bc8721c6ab76048bbc6a5b Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 24 Mar 2024 00:51:30 -0400 Subject: [PATCH 236/305] added developer dashboard url comment --- examples/wifi/expanded/requests_wifi_api_youtube.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/wifi/expanded/requests_wifi_api_youtube.py b/examples/wifi/expanded/requests_wifi_api_youtube.py index 5215b10..85bda0d 100644 --- a/examples/wifi/expanded/requests_wifi_api_youtube.py +++ b/examples/wifi/expanded/requests_wifi_api_youtube.py @@ -32,7 +32,8 @@ # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") -# Requires Steam Developer API key +# Requires YouTube/Google API key +# https://console.cloud.google.com/apis/dashboard YT_USERNAME = os.getenv("YOUTUBE_USERNAME") YT_TOKEN = os.getenv("YOUTUBE_TOKEN") From 69406bb10947852370255b03b95d9a7b56123093 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sun, 24 Mar 2024 07:43:15 -0700 Subject: [PATCH 237/305] Update README badge --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index a04f450..4e73438 100755 --- a/README.rst +++ b/README.rst @@ -13,9 +13,9 @@ Introduction :target: https://github.com/adafruit/Adafruit_CircuitPython_Requests/actions/ :alt: Build Status -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/psf/black - :alt: Code Style: Black +.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json + :target: https://github.com/astral-sh/ruff + :alt: Code Style: Ruff A requests-like library for HTTP commands. From 750317a6fbb0113731e5d7389b7a0a5e44f85bdf Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 25 Mar 2024 02:27:05 -0400 Subject: [PATCH 238/305] minor updates Using newer time_calc function, relabeled some variables, fixed os.getenv defaults for CIRCUITPY_ variables. made sleep_time shouty. board uptime displays time_calc human readable instead of monotonic seconds. --- .../requests_wifi_api_rocketlaunch_live.py | 66 +++++++++++-------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py b/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py index dc497a4..7cf9d61 100644 --- a/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py +++ b/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py @@ -1,6 +1,6 @@ # SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.2.x +# Coded for Circuit Python 9.0 """RocketLaunch.Live API Example""" import os @@ -13,31 +13,22 @@ # Time between API refreshes # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour -sleep_time = 43200 +SLEEP_TIME = 43200 # Get WiFi details, ensure these are setup in settings.toml -ssid = os.getenv("WIFI_SSID") -password = os.getenv("WIFI_PASSWORD") +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") -# Converts seconds in minutes/hours/days def time_calc(input_time): + """Converts seconds to minutes/hours/days""" if input_time < 60: - sleep_int = input_time - time_output = f"{sleep_int:.0f} seconds" - elif 60 <= input_time < 3600: - sleep_int = input_time / 60 - time_output = f"{sleep_int:.0f} minutes" - elif 3600 <= input_time < 86400: - sleep_int = input_time / 60 / 60 - time_output = f"{sleep_int:.0f} hours" - elif 86400 <= input_time < 432000: - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.1f} days" - else: # if > 5 days convert float to int & display whole days - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.0f} days" - return time_output + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" # Publicly available data no header required @@ -81,7 +72,7 @@ def time_calc(input_time): # JSON Endpoints RLFN = str(rocketlaunch_json["result"][0]["name"]) RLWO = str(rocketlaunch_json["result"][0]["win_open"]) - TMINUS = str(rocketlaunch_json["result"][0]["t0"]) + TZERO = str(rocketlaunch_json["result"][0]["t0"]) RLWC = str(rocketlaunch_json["result"][0]["win_close"]) RLP = str(rocketlaunch_json["result"][0]["provider"]["name"]) RLVN = str(rocketlaunch_json["result"][0]["vehicle"]["name"]) @@ -100,10 +91,26 @@ def time_calc(input_time): print(f" | | Provider: {RLP}") if RLVN != "None": print(f" | | Vehicle: {RLVN}") - if RLWO != "None": - print(f" | | Window: {RLWO} to {RLWC}") - elif TMINUS != "None": - print(f" | | Window: {TMINUS} to {RLWC}") + + # Launch time can sometimes be Window Open to Close, T-Zero, or weird combination. + # Should obviously be standardized but they're not input that way. + # Have to account for every combination of 3 conditions. + # T-Zero Launch Time Conditionals + if RLWO == "None" and TZERO != "None" and RLWC != "None": + print(f" | | Window: {TZERO} | {RLWC}") + elif RLWO != "None" and TZERO != "None" and RLWC == "None": + print(f" | | Window: {RLWO} | {TZERO}") + elif RLWO != "None" and TZERO == "None" and RLWC != "None": + print(f" | | Window: {RLWO} | {RLWC}") + elif RLWO != "None" and TZERO != "None" and RLWC != "None": + print(f" | | Window: {RLWO} | {TZERO} | {RLWC}") + elif RLWO == "None" and TZERO != "None" and RLWC == "None": + print(f" | | Window: {TZERO}") + elif RLWO != "None" and TZERO == "None" and RLWC == "None": + print(f" | | Window: {RLWO}") + elif RLWO == "None" and TZERO == "None" and RLWC != "None": + print(f" | | Window: {RLWC}") + if RLLS != "None": print(f" | | Site: {RLLS}") if RLPN != "None": @@ -113,13 +120,16 @@ def time_calc(input_time): if RLM != "None": print(f" | | Mission: {RLM}") + rocketlaunch_response.close() + print("✂️ Disconnected from RocketLaunch.Live API") + print("\nFinished!") - print("Board Uptime: ", time.monotonic()) - print("Next Update in: ", time_calc(sleep_time)) + print(f"Board Uptime: {time_calc(time.monotonic())}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") print("===============================") except (ValueError, RuntimeError) as e: print("Failed to get data, retrying\n", e) time.sleep(60) break - time.sleep(sleep_time) + time.sleep(SLEEP_TIME) From 08422e855ff910d5cfdddc2e3101f223a49b7e0c Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 25 Mar 2024 03:23:43 -0400 Subject: [PATCH 239/305] Update multiple cookies example Example for requesting cookies in a GET request. Updated to Connection Manager for 9.0 --- .../requests_wifi_multiple_cookies.py | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_multiple_cookies.py b/examples/wifi/expanded/requests_wifi_multiple_cookies.py index 36e4616..1e3b64d 100644 --- a/examples/wifi/expanded/requests_wifi_multiple_cookies.py +++ b/examples/wifi/expanded/requests_wifi_multiple_cookies.py @@ -1,36 +1,38 @@ # SPDX-FileCopyrightText: 2022 Alec Delaney # SPDX-License-Identifier: MIT - -""" -This example was written for the MagTag; changes may be needed -for connecting to the internet depending on your device. -""" +# Coded for Circuit Python 9.0 +""" Multiple Cookies Example written for MagTag """ +# pylint: disable=import-error import os -import ssl -import socketpool +import adafruit_connection_manager import wifi import adafruit_requests -COOKIE_TEST_URL = "https://www.adafruit.com" - # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") -# Connect to the Wi-Fi network -print("Connecting to %s" % ssid) -wifi.radio.connect(ssid, password) +COOKIE_TEST_URL = "https://www.adafruit.com" -# Set up the requests library -pool = socketpool.SocketPool(wifi.radio) -requests = adafruit_requests.Session(pool, ssl.create_default_context()) +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) -# GET from the URL -print("Fetching multiple cookies from", COOKIE_TEST_URL) -response = requests.get(COOKIE_TEST_URL) +print(f"\nConnecting to {ssid}...") +try: + # Connect to the Wi-Fi network + wifi.radio.connect(ssid, password) + # URL GET Request + response = requests.get(COOKIE_TEST_URL) +except OSError as e: + print(f"❌ OSError: {e}") +print("✅ Wifi!") + +print(f" | Fetching Cookies: {COOKIE_TEST_URL}") # Spilt up the cookies by ", " elements = response.headers["set-cookie"].split(", ") @@ -49,10 +51,13 @@ cookie_list.append(element) # Pring the information about the cookies -print("Number of cookies:", len(cookie_list)) -print("") -print("Cookies received:") -print("-" * 40) +print(f" | Total Cookies: {len(cookie_list)}") +print("-" * 80) + for cookie in cookie_list: - print(cookie) - print("-" * 40) + print(f" | 🍪 {cookie}") + print("-" * 80) + +response.close() +print(f"✂️ Disconnected from {COOKIE_TEST_URL}") +print("Finished!") From 1a275c7baabc2e2a7aab4886c9a5fbdaad66ea43 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 25 Mar 2024 04:55:13 -0400 Subject: [PATCH 240/305] update wifi_simpletest with Connection Manager --- examples/wifi/requests_wifi_simpletest.py | 81 +++++++++++------------ 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/examples/wifi/requests_wifi_simpletest.py b/examples/wifi/requests_wifi_simpletest.py index 35b835a..3c2d66b 100644 --- a/examples/wifi/requests_wifi_simpletest.py +++ b/examples/wifi/requests_wifi_simpletest.py @@ -1,10 +1,12 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +# Updated for Circuit Python 9.0 +""" WiFi Simpletest """ +# pylint: disable=import-error import os -import ssl -import socketpool +import adafruit_connection_manager import wifi import adafruit_requests @@ -13,60 +15,57 @@ ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") -# Initialize WiFi Pool (There can be only 1 pool & top of script) -radio = wifi.radio -pool = socketpool.SocketPool(radio) - -print("Connecting to AP...") -while not wifi.radio.ipv4_address: - try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("could not connect to AP, retrying: ", e) -print("Connected to", str(radio.ap_info.ssid, "utf-8"), "\tRSSI:", radio.ap_info.rssi) - -# Initialize a requests session -ssl_context = ssl.create_default_context() -requests = adafruit_requests.Session(pool, ssl_context) - TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" JSON_GET_URL = "https://httpbin.org/get" JSON_POST_URL = "https://httpbin.org/post" -print("Fetching text from %s" % TEXT_URL) +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) +rssi = wifi.radio.ap_info.rssi + +print(f"\nConnecting to {ssid}...") +print(f"Signal Strength: {rssi}") +try: + # Connect to the Wi-Fi network + wifi.radio.connect(ssid, password) +except OSError as e: + print(f"❌ OSError: {e}") +print("✅ Wifi!") + +print(f" | GET Text Test: {TEXT_URL}") response = requests.get(TEXT_URL) -print("-" * 40) - -print("Text Response: ", response.text) -print("-" * 40) +print(f" | ✅ GET Response: {response.text}") response.close() +print(f" | ✂️ Disconnected from {TEXT_URL}") +print("-" * 80) -print("Fetching JSON data from %s" % JSON_GET_URL) +print(f" | GET Full Response Test: {JSON_GET_URL}") response = requests.get(JSON_GET_URL) -print("-" * 40) - -print("JSON Response: ", response.json()) -print("-" * 40) +print(f" | ✅ Unparsed Full JSON Response: {response.json()}") response.close() +print(f" | ✂️ Disconnected from {JSON_GET_URL}") +print("-" * 80) -data = "31F" -print("POSTing data to {0}: {1}".format(JSON_POST_URL, data)) -response = requests.post(JSON_POST_URL, data=data) -print("-" * 40) - +DATA = "This is an example of a JSON value" +print(f" | ✅ JSON 'value' POST Test: {JSON_POST_URL} {DATA}") +response = requests.post(JSON_POST_URL, data=DATA) json_resp = response.json() # Parse out the 'data' key from json_resp dict. -print("Data received from server:", json_resp["data"]) -print("-" * 40) +print(f" | ✅ JSON 'value' Response: {json_resp['data']}") response.close() +print(f" | ✂️ Disconnected from {JSON_POST_URL}") +print("-" * 80) -json_data = {"Date": "July 25, 2019"} -print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data)) +json_data = {"Date": "January 1, 1970"} +print(f" | ✅ JSON 'key':'value' POST Test: {JSON_POST_URL} {json_data}") response = requests.post(JSON_POST_URL, json=json_data) -print("-" * 40) - json_resp = response.json() # Parse out the 'json' key from json_resp dict. -print("JSON Data received from server:", json_resp["json"]) -print("-" * 40) +print(f" | ✅ JSON 'key':'value' Response: {json_resp['json']}") response.close() +print(f" | ✂️ Disconnected from {JSON_POST_URL}") +print("-" * 80) + +print("Finished!") From 211c2d882ffaf0af94dafe6c0e6a60cae1f2b5b7 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 25 Mar 2024 05:17:06 -0400 Subject: [PATCH 241/305] update requests_wifi_advanced to 9.0 with Connection Manager --- examples/wifi/requests_wifi_advanced.py | 46 ++++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/examples/wifi/requests_wifi_advanced.py b/examples/wifi/requests_wifi_advanced.py index 3bb0976..f59d510 100644 --- a/examples/wifi/requests_wifi_advanced.py +++ b/examples/wifi/requests_wifi_advanced.py @@ -1,10 +1,12 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT +# Updated for Circuit Python 9.0 +""" WiFi Advanced Example """ +# pylint: disable=import-error import os -import ssl -import socketpool +import adafruit_connection_manager import wifi import adafruit_requests @@ -13,39 +15,41 @@ ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") -# Initialize WiFi Pool (There can be only 1 pool & top of script) -radio = wifi.radio -pool = socketpool.SocketPool(radio) - -print("Connecting to AP...") -while not wifi.radio.ipv4_address: - try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("could not connect to AP, retrying: ", e) -print("Connected to", str(radio.ap_info.ssid, "utf-8"), "\tRSSI:", radio.ap_info.rssi) +TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" +JSON_GET_URL = "https://httpbin.org/get" +JSON_POST_URL = "https://httpbin.org/post" -# Initialize a requests session -ssl_context = ssl.create_default_context() +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) requests = adafruit_requests.Session(pool, ssl_context) +rssi = wifi.radio.ap_info.rssi + +print(f"\nConnecting to {ssid}...") +print(f"Signal Strength: {rssi}") +try: + # Connect to the Wi-Fi network + wifi.radio.connect(ssid, password) +except OSError as e: + print(f"❌ OSError: {e}") +print("✅ Wifi!") JSON_GET_URL = "https://httpbin.org/get" # Define a custom header as a dict. headers = {"user-agent": "blinka/1.0.0"} -print("Fetching JSON data from %s..." % JSON_GET_URL) +print(f" | Fetching JSON: {JSON_GET_URL}") response = requests.get(JSON_GET_URL, headers=headers) -print("-" * 60) json_data = response.json() headers = json_data["headers"] -print("Response's Custom User-Agent Header: {0}".format(headers["User-Agent"])) -print("-" * 60) +print(f" | ✅ Response Custom User-Agent Header: {headers['User-Agent']}") # Read Response's HTTP status code -print("Response HTTP Status Code: ", response.status_code) -print("-" * 60) +print(f" | ✅ Response HTTP Status Code: {response.status_code}") # Close, delete and collect the response data response.close() +print(f" | ✂️ Disconnected from {JSON_GET_URL}") +print("Finished!") From 7c2cb6c293f0e7b6c2162a9ad8cc6b2e52abee89 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 26 Mar 2024 02:19:03 -0400 Subject: [PATCH 242/305] update with HTTP status code tester httpbin has pages to test all HTTP status codes --- examples/wifi/requests_wifi_advanced.py | 121 +++++++++++++++++++++--- 1 file changed, 109 insertions(+), 12 deletions(-) diff --git a/examples/wifi/requests_wifi_advanced.py b/examples/wifi/requests_wifi_advanced.py index f59d510..00cdf25 100644 --- a/examples/wifi/requests_wifi_advanced.py +++ b/examples/wifi/requests_wifi_advanced.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT # Updated for Circuit Python 9.0 -""" WiFi Advanced Example """ +""" WiFi Advanced (User-Agent & Status Codes) Example """ # pylint: disable=import-error import os @@ -15,9 +15,8 @@ ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") -TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" JSON_GET_URL = "https://httpbin.org/get" -JSON_POST_URL = "https://httpbin.org/post" +STATUS_TEST = "https://httpbin.org/status/" # Initalize Wifi, Socket Pool, Request Session pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) @@ -25,6 +24,87 @@ requests = adafruit_requests.Session(pool, ssl_context) rssi = wifi.radio.ap_info.rssi + +def print_http_status(code, description): + """Returns HTTP status code and description""" + if "100" <= code <= "103": + print(f" | ✅ Status Test: {code} - {description}") + elif "200" <= code <= "299": + print(f" | ✅ Status Test: {code} - {description}") + elif "300" <= code <= "600": + print(f" | ❌ Status Test: {code} - {description}") + else: + print(f" | Unknown Response Status: {code} - {description}") + + +# All HTTP Status Codes +http_status_codes = { + "100": "Continue", + "101": "Switching Protocols", + "102": "Processing", + "103": "Early Hints", + "200": "OK", + "201": "Created", + "202": "Accepted", + "203": "Non-Authoritative Information", + "204": "No Content", + "205": "Reset Content", + "206": "Partial Content", + "207": "Multi-Status", + "208": "Already Reported", + "226": "IM Used", + "300": "Multiple Choices", + "301": "Moved Permanently", + "302": "Found", + "303": "See Other", + "304": "Not Modified", + "305": "Use Proxy", + "306": "Unused", + "307": "Temporary Redirect", + "308": "Permanent Redirect", + "400": "Bad Request", + "401": "Unauthorized", + "402": "Payment Required", + "403": "Forbidden", + "404": "Not Found", + "405": "Method Not Allowed", + "406": "Not Acceptable", + "407": "Proxy Authentication Required", + "408": "Request Timeout", + "409": "Conflict", + "410": "Gone", + "411": "Length Required", + "412": "Precondition Failed", + "413": "Payload Too Large", + "414": "URI Too Long", + "415": "Unsupported Media Type", + "416": "Range Not Satisfiable", + "417": "Expectation Failed", + "418": "I'm a teapot", + "421": "Misdirected Request", + "422": "Unprocessable Entity", + "423": "Locked", + "424": "Failed Dependency", + "425": "Too Early", + "426": "Upgrade Required", + "428": "Precondition Required", + "429": "Too Many Requests", + "431": "Request Header Fields Too Large", + "451": "Unavailable For Legal Reasons", + "500": "Internal Server Error", + "501": "Not Implemented", + "502": "Bad Gateway", + "503": "Service Unavailable", + "504": "Gateway Timeout", + "505": "HTTP Version Not Supported", + "506": "Variant Also Negotiates", + "507": "Insufficient Storage", + "508": "Loop Detected", + "510": "Not Extended", + "511": "Network Authentication Required", +} + + print(f"\nConnecting to {ssid}...") print(f"Signal Strength: {rssi}") try: @@ -37,19 +117,36 @@ JSON_GET_URL = "https://httpbin.org/get" # Define a custom header as a dict. -headers = {"user-agent": "blinka/1.0.0"} +HEADERS = {"user-agent": "blinka/1.0.0"} -print(f" | Fetching JSON: {JSON_GET_URL}") -response = requests.get(JSON_GET_URL, headers=headers) +print(f" | GET JSON: {JSON_GET_URL}") +response = requests.get(JSON_GET_URL, headers=HEADERS) json_data = response.json() -headers = json_data["headers"] -print(f" | ✅ Response Custom User-Agent Header: {headers['User-Agent']}") +HEADERS = json_data["headers"] +print(f" | User-Agent: {HEADERS['User-Agent']}") -# Read Response's HTTP status code -print(f" | ✅ Response HTTP Status Code: {response.status_code}") - -# Close, delete and collect the response data +# HTTP STATUS CODE TESTING +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Status +STATUS_CODE = str(response.status_code) +STATUS_DESCRIPTION = http_status_codes.get(STATUS_CODE, "Unknown Status Code") +print_http_status(STATUS_CODE, STATUS_DESCRIPTION) response.close() print(f" | ✂️ Disconnected from {JSON_GET_URL}") +print(" | ") +print(f" | Status Code Test: {JSON_GET_URL}") + +# Some return errors then confirm the error (that's a good thing) +# Demonstrates not all errors have the same behavior +# 300, 304, and 306 in particular +for codes in sorted(http_status_codes.keys(), key=int): + status_test_url = STATUS_TEST + codes + response = requests.get(status_test_url, headers=HEADERS) + SORT_STATUS_CODE = str(response.status_code) + SORT_STATUS_DESC = http_status_codes.get(SORT_STATUS_CODE, "Unknown Status Code") + print_http_status(SORT_STATUS_CODE, SORT_STATUS_DESC) + response.close() + +print(f" | ✂️ Disconnected from {JSON_GET_URL}") + print("Finished!") From 24e197d9d291ef5adab89807b9233c3c43199b5d Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 26 Mar 2024 02:30:31 -0400 Subject: [PATCH 243/305] fix duplicate variable for url, correct status url --- examples/wifi/requests_wifi_advanced.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/wifi/requests_wifi_advanced.py b/examples/wifi/requests_wifi_advanced.py index 00cdf25..1040d06 100644 --- a/examples/wifi/requests_wifi_advanced.py +++ b/examples/wifi/requests_wifi_advanced.py @@ -15,9 +15,6 @@ ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") -JSON_GET_URL = "https://httpbin.org/get" -STATUS_TEST = "https://httpbin.org/status/" - # Initalize Wifi, Socket Pool, Request Session pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) @@ -104,6 +101,8 @@ def print_http_status(code, description): "511": "Network Authentication Required", } +JSON_GET_URL = "https://httpbin.org/get" +STATUS_TEST = "https://httpbin.org/status/" print(f"\nConnecting to {ssid}...") print(f"Signal Strength: {rssi}") @@ -114,8 +113,6 @@ def print_http_status(code, description): print(f"❌ OSError: {e}") print("✅ Wifi!") -JSON_GET_URL = "https://httpbin.org/get" - # Define a custom header as a dict. HEADERS = {"user-agent": "blinka/1.0.0"} @@ -134,8 +131,8 @@ def print_http_status(code, description): response.close() print(f" | ✂️ Disconnected from {JSON_GET_URL}") print(" | ") -print(f" | Status Code Test: {JSON_GET_URL}") +print(f" | Status Code Test: {STATUS_TEST}") # Some return errors then confirm the error (that's a good thing) # Demonstrates not all errors have the same behavior # 300, 304, and 306 in particular From c7ff5ccbde4ba67b779d309031396d0d32f01988 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 26 Mar 2024 02:47:00 -0400 Subject: [PATCH 244/305] added ChatGPT attribution --- examples/wifi/requests_wifi_advanced.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/wifi/requests_wifi_advanced.py b/examples/wifi/requests_wifi_advanced.py index 1040d06..b12fa1d 100644 --- a/examples/wifi/requests_wifi_advanced.py +++ b/examples/wifi/requests_wifi_advanced.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT # Updated for Circuit Python 9.0 +# https://help.openai.com/en/articles/6825453-chatgpt-release-notes +# https://chat.openai.com/share/32ef0c5f-ac92-4d36-9d1e-0f91e0c4c574 """ WiFi Advanced (User-Agent & Status Codes) Example """ # pylint: disable=import-error From 408e1bf9ca1eba8300a3cf061651c086d8538804 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 1 Apr 2024 14:24:44 -0500 Subject: [PATCH 245/305] remove unneeded pylint disable in examples --- examples/wifi/expanded/requests_wifi_api_youtube.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/wifi/expanded/requests_wifi_api_youtube.py b/examples/wifi/expanded/requests_wifi_api_youtube.py index 85bda0d..c69c146 100644 --- a/examples/wifi/expanded/requests_wifi_api_youtube.py +++ b/examples/wifi/expanded/requests_wifi_api_youtube.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """YouTube API Subscriber Count Example""" -# pylint: disable=import-error import os import time From 94db22c36fd3a45b2d165a00787958a6a9250160 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 1 Apr 2024 14:24:56 -0500 Subject: [PATCH 246/305] remove unneeded pylint disable in examples --- .../expanded/requests_wifi_adafruit_discord_active_online.py | 1 - examples/wifi/expanded/requests_wifi_api_discord.py | 1 - examples/wifi/expanded/requests_wifi_api_fitbit.py | 2 +- examples/wifi/expanded/requests_wifi_api_github.py | 1 - examples/wifi/expanded/requests_wifi_api_mastodon.py | 1 - examples/wifi/expanded/requests_wifi_api_premiereleague.py | 1 - examples/wifi/expanded/requests_wifi_api_steam.py | 1 - examples/wifi/expanded/requests_wifi_api_twitch.py | 1 - 8 files changed, 1 insertion(+), 8 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py b/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py index dec0e6e..3ebcaf4 100644 --- a/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py +++ b/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """Discord Active Online Shields.IO Example""" -# pylint: disable=import-error import os import time diff --git a/examples/wifi/expanded/requests_wifi_api_discord.py b/examples/wifi/expanded/requests_wifi_api_discord.py index 83b8f8f..08ad16c 100644 --- a/examples/wifi/expanded/requests_wifi_api_discord.py +++ b/examples/wifi/expanded/requests_wifi_api_discord.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """Discord Web Scrape Example""" -# pylint: disable=import-error import os import time diff --git a/examples/wifi/expanded/requests_wifi_api_fitbit.py b/examples/wifi/expanded/requests_wifi_api_fitbit.py index 802995c..206a56d 100644 --- a/examples/wifi/expanded/requests_wifi_api_fitbit.py +++ b/examples/wifi/expanded/requests_wifi_api_fitbit.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """Fitbit API Example""" -# pylint: disable=import-error, disable=no-member +# pylint: disable=no-member import os import time diff --git a/examples/wifi/expanded/requests_wifi_api_github.py b/examples/wifi/expanded/requests_wifi_api_github.py index 6fbe8fe..e045299 100644 --- a/examples/wifi/expanded/requests_wifi_api_github.py +++ b/examples/wifi/expanded/requests_wifi_api_github.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """Github API Example""" -# pylint: disable=import-error import os import time diff --git a/examples/wifi/expanded/requests_wifi_api_mastodon.py b/examples/wifi/expanded/requests_wifi_api_mastodon.py index f6bce9f..fd4266e 100644 --- a/examples/wifi/expanded/requests_wifi_api_mastodon.py +++ b/examples/wifi/expanded/requests_wifi_api_mastodon.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """Mastodon API Example""" -# pylint: disable=import-error import os import time diff --git a/examples/wifi/expanded/requests_wifi_api_premiereleague.py b/examples/wifi/expanded/requests_wifi_api_premiereleague.py index 0a2e607..88524e9 100644 --- a/examples/wifi/expanded/requests_wifi_api_premiereleague.py +++ b/examples/wifi/expanded/requests_wifi_api_premiereleague.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """Premiere League Total Players API Example""" -# pylint: disable=import-error import os import time diff --git a/examples/wifi/expanded/requests_wifi_api_steam.py b/examples/wifi/expanded/requests_wifi_api_steam.py index a05d6c7..fb1f7bd 100644 --- a/examples/wifi/expanded/requests_wifi_api_steam.py +++ b/examples/wifi/expanded/requests_wifi_api_steam.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """Steam API Get Owned Games Example""" -# pylint: disable=import-error import os import time diff --git a/examples/wifi/expanded/requests_wifi_api_twitch.py b/examples/wifi/expanded/requests_wifi_api_twitch.py index 8e15098..1f02e5c 100644 --- a/examples/wifi/expanded/requests_wifi_api_twitch.py +++ b/examples/wifi/expanded/requests_wifi_api_twitch.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """Twitch API Example""" -# pylint: disable=import-error import os import time From 6d627d966d32fc5d76f894e9c111a7b9c97c1bf8 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 2 Apr 2024 02:10:43 -0400 Subject: [PATCH 247/305] update with requested changes --- .../requests_wifi_api_openskynetwork_private.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py index 39dc17a..4a30b57 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """OpenSky-Network.org Single Flight Private API Example""" -# pylint: disable=import-error import binascii import os @@ -18,7 +17,7 @@ # All active flights JSON: https://opensky-network.org/api/states/all # PICK ONE! :) # JSON order: transponder, callsign, country # ACTIVE transpondes only, for multiple "c822af&icao24=cb3993&icao24=c63923" -TRANSPONDER = "ad4f1c" +TRANSPONDER = "4b1812" # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") @@ -44,18 +43,17 @@ # -- Base64 Conversion -- OSN_CREDENTIALS = str(osnusername) + ":" + str(osnpassword) # base64 encode and strip appended \n from bytearray -OSN_CREDENTIALS_B = binascii.b2a_base64(b"" + str(OSN_CREDENTIALS)).strip() -BASE64_STRING = str(OSN_CREDENTIALS_B) # bytearray -SLICED_BASE64_STRING = BASE64_STRING[2:-1] # slice bytearray head/tail +OSN_CREDENTIALS_B = binascii.b2a_base64(OSN_CREDENTIALS.encode()).strip() +BASE64_STRING = OSN_CREDENTIALS_B.decode() # bytearray + if DEBUG: print("Base64 ByteArray: ", BASE64_STRING) - print(f"Base64 Sliced String: {SLICED_BASE64_STRING}") # Requests URL - icao24 is their endpoint required for a transponder # example https://opensky-network.org/api/states/all?icao24=a808c5 # OSN private: requires your website username:password to be base64 encoded -OPENSKY_HEADER = {"Authorization": "Basic " + str(SLICED_BASE64_STRING)} +OPENSKY_HEADER = {"Authorization": "Basic " + BASE64_STRING} OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + TRANSPONDER From d55203af84757dabb5c31d03f3ad3ea7063d07c4 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 2 Apr 2024 02:25:25 -0400 Subject: [PATCH 248/305] update with requested changes --- .../requests_wifi_api_openskynetwork_private_area.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py index ca3ea7e..1652a73 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """OpenSky-Network.org Private Area API Example""" -# pylint: disable=import-error import binascii import os @@ -47,16 +46,14 @@ # -- Base64 Conversion -- OSN_CREDENTIALS = str(osnusername) + ":" + str(osnpassword) # base64 encode and strip appended \n from bytearray -OSN_CREDENTIALS_B = binascii.b2a_base64(b"" + str(OSN_CREDENTIALS)).strip() -BASE64_STRING = str(OSN_CREDENTIALS_B) # bytearray -SLICED_BASE64_STRING = BASE64_STRING[2:-1] # slice bytearray head/tail +OSN_CREDENTIALS_B = binascii.b2a_base64(OSN_CREDENTIALS.encode()).strip() +BASE64_STRING = OSN_CREDENTIALS_B.decode() # bytearray if DEBUG: print("Base64 ByteArray: ", BASE64_STRING) - print(f"Base64 Sliced String: {SLICED_BASE64_STRING}") # Area requires OpenSky-Network.org username:password to be base64 encoded -OSN_HEADER = {"Authorization": "Basic " + str(SLICED_BASE64_STRING)} +OSN_HEADER = {"Authorization": "Basic " + BASE64_STRING} # Example request of all traffic over Florida. # Geographic areas calls cost less against the limit. From 240a8588dc69c4dfe39e6aca94377e210774e4e2 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 2 Apr 2024 02:29:45 -0400 Subject: [PATCH 249/305] removed pylint:disable=import-error yeah these will pass adafruit actions pylint but not my own. will just ignore them on my end from now on. --- examples/wifi/expanded/requests_wifi_multiple_cookies.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/wifi/expanded/requests_wifi_multiple_cookies.py b/examples/wifi/expanded/requests_wifi_multiple_cookies.py index 1e3b64d..9237903 100644 --- a/examples/wifi/expanded/requests_wifi_multiple_cookies.py +++ b/examples/wifi/expanded/requests_wifi_multiple_cookies.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 9.0 """ Multiple Cookies Example written for MagTag """ -# pylint: disable=import-error import os From f66686f3f8957c8e15548f8780035cc017b968f3 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 2 Apr 2024 02:50:11 -0400 Subject: [PATCH 250/305] removed pylint: disable=import-error This passes on adafruit actions but not my local pylint. Cleaning up the discrepancy. --- examples/wifi/requests_wifi_simpletest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/wifi/requests_wifi_simpletest.py b/examples/wifi/requests_wifi_simpletest.py index 3c2d66b..21492fb 100644 --- a/examples/wifi/requests_wifi_simpletest.py +++ b/examples/wifi/requests_wifi_simpletest.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Updated for Circuit Python 9.0 """ WiFi Simpletest """ -# pylint: disable=import-error import os From 09f8e3751412a3b6dd2f1fcb8fb2e268001a9d70 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 2 Apr 2024 02:52:31 -0400 Subject: [PATCH 251/305] removed pylint: disable=import-error cleaning up discrepancy between my local pylint and adafruit actions. --- examples/wifi/requests_wifi_advanced.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/wifi/requests_wifi_advanced.py b/examples/wifi/requests_wifi_advanced.py index b12fa1d..6d9d24d 100644 --- a/examples/wifi/requests_wifi_advanced.py +++ b/examples/wifi/requests_wifi_advanced.py @@ -4,7 +4,6 @@ # https://help.openai.com/en/articles/6825453-chatgpt-release-notes # https://chat.openai.com/share/32ef0c5f-ac92-4d36-9d1e-0f91e0c4c574 """ WiFi Advanced (User-Agent & Status Codes) Example """ -# pylint: disable=import-error import os From 44f40c3c24885ef84dc25a532e854e9861bd7041 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:48:19 -0400 Subject: [PATCH 252/305] add Queue-Times API Example Used for Disney theme park queue times --- .../expanded/requests_wifi_api_queuetimes.py | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 examples/wifi/expanded/requests_wifi_api_queuetimes.py diff --git a/examples/wifi/expanded/requests_wifi_api_queuetimes.py b/examples/wifi/expanded/requests_wifi_api_queuetimes.py new file mode 100644 index 0000000..92167e5 --- /dev/null +++ b/examples/wifi/expanded/requests_wifi_api_queuetimes.py @@ -0,0 +1,106 @@ +# SPDX-FileCopyrightText: 2024 DJDevon3 +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 9.x +"""Queue-Times.com API w/Display Example""" + +import os +import time + +import adafruit_connection_manager +import wifi + +import adafruit_requests + +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) + +# Time between API refreshes +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +SLEEP_TIME = 300 + +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") + +# Publicly Open API (no credentials required) +QTIMES_SOURCE = "https://queue-times.com/parks/16/queue_times.json" + + +def time_calc(input_time): + """Converts seconds to minutes/hours/days""" + if input_time < 60: + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" + + +qtimes_json = {} +while True: + now = time.monotonic() + # Connect to Wi-Fi + print("\n===============================") + print("Connecting to WiFi...") + while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ WiFi!") + + try: + print(" | Attempting to GET Queue-Times JSON!") + try: + qtimes_response = requests.get(url=QTIMES_SOURCE) + qtimes_json = qtimes_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + print(" | ✅ Queue-Times JSON!") + + DEBUG_QTIMES = False + if DEBUG_QTIMES: + print("Full API GET URL: ", QTIMES_SOURCE) + print(qtimes_json) + qtimes_response.close() + print("✂️ Disconnected from Queue-Times API") + + print("\nFinished!") + print(f"Board Uptime: {time_calc(time.monotonic())}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") + print("===============================") + except (ValueError, RuntimeError) as e: + print("Failed to get data, retrying\n", e) + time.sleep(60) + break + + # Loop infinitely until its time to re-poll + if time.monotonic() - now <= SLEEP_TIME: + for land in qtimes_json["lands"]: + qtimes_lands = str(land["name"]) + print(f" | | Lands: {qtimes_lands}") + time.sleep(1) + + # Loop through each ride in the land + for ride in land["rides"]: + qtimes_rides = str(ride["name"]) + qtimes_queuetime = str(ride["wait_time"]) + qtimes_isopen = str(ride["is_open"]) + + print(f" | | Rides: {qtimes_rides}") + print(f" | | Queue Time: {qtimes_queuetime} Minutes") + if qtimes_isopen == "False": + print(" | | Status: Closed") + elif qtimes_isopen == "True": + print(" | | Status: Open") + else: + print(" | | Status: Unknown") + + time.sleep(1) # delay between list items + else: # When its time to poll, break to top of while True loop. + break From 76eca0b981a8ce844ef5fac876d2282bc666bd43 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Wed, 10 Apr 2024 19:00:26 -0400 Subject: [PATCH 253/305] remove pylint: disable=import-error as requested --- .../wifi/expanded/requests_wifi_api_openskynetwork_public.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py index 09997fa..0eda0b3 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT # Coded for Circuit Python 8.2.x """OpenSky-Network.org Single Flight Public API Example""" -# pylint: disable=import-error import os import time From b227b758c9a9c1036516bab7ca9055a51fd3a55c Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Wed, 10 Apr 2024 19:21:33 -0400 Subject: [PATCH 254/305] remove pylint: disable=import-error (missed one) --- examples/wifi/expanded/requests_wifi_api_github.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_github.py b/examples/wifi/expanded/requests_wifi_api_github.py index 6fbe8fe..ed8aaae 100644 --- a/examples/wifi/expanded/requests_wifi_api_github.py +++ b/examples/wifi/expanded/requests_wifi_api_github.py @@ -1,8 +1,7 @@ # SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.2.x +# Coded for Circuit Python 9.x """Github API Example""" -# pylint: disable=import-error import os import time From ea9acf6265d5c0740367f267e482dad4a1498de8 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Wed, 10 Apr 2024 21:48:15 -0400 Subject: [PATCH 255/305] updating with most recent approved PR This file is now updated with the same version as in the Single Flight Private API Example. This should hopefully fix any conflicts. --- ...equests_wifi_api_openskynetwork_private.py | 270 ++++++++++-------- 1 file changed, 155 insertions(+), 115 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py index 486a4de..1e340f7 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py @@ -1,15 +1,13 @@ -# SPDX-FileCopyrightText: 2023 DJDevon3 +# SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.1 -# DJDevon3 ESP32-S3 OpenSkyNetwork_Private_API_Example +# Coded for Circuit Python 9.x +"""OpenSky-Network.org Single Flight Private API Example""" -import json +import binascii import os -import ssl import time -import circuitpython_base64 as base64 -import socketpool +import adafruit_connection_manager import wifi import adafruit_requests @@ -19,135 +17,177 @@ # All active flights JSON: https://opensky-network.org/api/states/all # PICK ONE! :) # JSON order: transponder, callsign, country # ACTIVE transpondes only, for multiple "c822af&icao24=cb3993&icao24=c63923" -transponder = "7c6b2d" +TRANSPONDER = "4b1812" -# Initialize WiFi Pool (There can be only 1 pool & top of script) -pool = socketpool.SocketPool(wifi.radio) +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") +osnusername = os.getenv("OSN_USERNAME") # Website Credentials +osnpassword = os.getenv("OSN_PASSWORD") # Website Credentials -# Time between API refreshes +# API Polling Rate # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour # OpenSky-Networks IP bans for too many requests, check rate limit. # https://openskynetwork.github.io/opensky-api/rest.html#limitations -sleep_time = 1800 +SLEEP_TIME = 1800 -# Get WiFi details, ensure these are setup in settings.toml -ssid = os.getenv("CIRCUITPY_WIFI_SSID") -password = os.getenv("CIRCUITPY_WIFI_PASSWORD") -osnu = os.getenv("OSN_Username") -osnp = os.getenv("OSN_Password") - -osn_cred = str(osnu) + ":" + str(osnp) -bytes_to_encode = b" " + str(osn_cred) + " " -base64_string = base64.encodebytes(bytes_to_encode) -base64cred = repr(base64_string)[2:-1] - -Debug_Auth = False # STREAMER WARNING this will show your credentials! -if Debug_Auth: - osn_cred = str(osnu) + ":" + str(osnp) - bytes_to_encode = b" " + str(osn_cred) + " " - print(repr(bytes_to_encode)) - base64_string = base64.encodebytes(bytes_to_encode) - print(repr(base64_string)[2:-1]) - base64cred = repr(base64_string)[2:-1] - print("Decoded Bytes:", str(base64cred)) +# Set debug to True for full JSON response. +# WARNING: makes credentials visible +DEBUG = False + +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) + +# -- Base64 Conversion -- +OSN_CREDENTIALS = str(osnusername) + ":" + str(osnpassword) +# base64 encode and strip appended \n from bytearray +OSN_CREDENTIALS_B = binascii.b2a_base64(OSN_CREDENTIALS.encode()).strip() +BASE64_STRING = OSN_CREDENTIALS_B.decode() # bytearray + + +if DEBUG: + print("Base64 ByteArray: ", BASE64_STRING) # Requests URL - icao24 is their endpoint required for a transponder # example https://opensky-network.org/api/states/all?icao24=a808c5 -# OSN private requires your username:password to be base64 encoded -osn_header = {"Authorization": "Basic " + str(base64cred)} -OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + transponder +# OSN private: requires your website username:password to be base64 encoded +OPENSKY_HEADER = {"Authorization": "Basic " + BASE64_STRING} +OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + TRANSPONDER -# Converts seconds to human readable minutes/hours/days -def time_calc(input_time): # input_time in seconds +def time_calc(input_time): + """Converts seconds to minutes/hours/days""" if input_time < 60: - sleep_int = input_time - time_output = f"{sleep_int:.0f} seconds" - elif 60 <= input_time < 3600: - sleep_int = input_time / 60 - time_output = f"{sleep_int:.0f} minutes" - elif 3600 <= input_time < 86400: - sleep_int = input_time / 60 / 60 - time_output = f"{sleep_int:.1f} hours" - else: - sleep_int = input_time / 60 / 60 / 24 - time_output = f"{sleep_int:.1f} days" - return time_output + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" def _format_datetime(datetime): - return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( - datetime.tm_mon, - datetime.tm_mday, - datetime.tm_year, - datetime.tm_hour, - datetime.tm_min, - datetime.tm_sec, + return ( + f"{datetime.tm_mon:02}/" + + f"{datetime.tm_mday:02}/" + + f"{datetime.tm_year:02} " + + f"{datetime.tm_hour:02}:" + + f"{datetime.tm_min:02}:" + + f"{datetime.tm_sec:02}" ) -# Connect to Wi-Fi -print("\n===============================") -print("Connecting to WiFi...") -request = adafruit_requests.Session(pool, ssl.create_default_context()) -while not wifi.radio.ipv4_address: +while True: + # Connect to Wi-Fi + print("\nConnecting to WiFi...") + while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ Wifi!") + try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - time.sleep(10) -print("Connected!\n") + print(" | Attempting to GET OpenSky-Network Single Private Flight JSON!") + print(" | Website Credentials Required! Allows more daily calls than Public.") + try: + opensky_response = requests.get(url=OPENSKY_SOURCE, headers=OPENSKY_HEADER) + opensky_json = opensky_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") -while True: - # STREAMER WARNING this will show your credentials! - debug_request = False # Set True to see full request - if debug_request: - print("Full API HEADER: ", str(osn_header)) - print("Full API GET URL: ", OPENSKY_SOURCE) - print("===============================") + print(" | ✅ OpenSky-Network JSON!") - print("\nAttempting to GET OpenSky-Network Data!") - opensky_response = request.get(url=OPENSKY_SOURCE, headers=osn_header).json() + if DEBUG: + print("Full API GET URL: ", OPENSKY_SOURCE) + print("Full API GET Header: ", OPENSKY_HEADER) + print(opensky_json) - # Print Full JSON to Serial (doesn't show credentials) - debug_response = False # Set True to see full response - if debug_response: - dump_object = json.dumps(opensky_response) - print("JSON Dump: ", dump_object) + # ERROR MESSAGE RESPONSES + if "timestamp" in opensky_json: + osn_timestamp = opensky_json["timestamp"] + print(f"❌ Timestamp: {osn_timestamp}") - # Key:Value Serial Debug (doesn't show credentials) - osn_debug_keys = True # Set True to print Serial data - if osn_debug_keys: - try: - osn_flight = opensky_response["time"] - print("Current Unix Time: ", osn_flight) - - current_struct_time = time.localtime(osn_flight) - current_date = "{}".format(_format_datetime(current_struct_time)) - print(f"Unix to Readable Time: {current_date}") - - # Current flight data for single callsign (right now) - osn_single_flight_data = opensky_response["states"] - - if osn_single_flight_data is not None: - print("Flight Data: ", osn_single_flight_data) - transponder = opensky_response["states"][0][0] - print("Transponder: ", transponder) - callsign = opensky_response["states"][0][1] - print("Callsign: ", callsign) - country = opensky_response["states"][0][2] - print("Flight Country: ", country) + if "message" in opensky_json: + osn_message = opensky_json["message"] + print(f"❌ Message: {osn_message}") + + if "error" in opensky_json: + osn_error = opensky_json["error"] + print(f"❌ Error: {osn_error}") + + if "path" in opensky_json: + osn_path = opensky_json["path"] + print(f"❌ Path: {osn_path}") + + if "status" in opensky_json: + osn_status = opensky_json["status"] + print(f"❌ Status: {osn_status}") + + # Current flight data for single callsign (right now) + osn_single_flight_data = opensky_json["states"] + + if osn_single_flight_data is not None: + if DEBUG: + print(f" | | Single Private Flight Data: {osn_single_flight_data}") + + last_contact = opensky_json["states"][0][4] + # print(f" | | Last Contact Unix Time: {last_contact}") + lc_struct_time = time.localtime(last_contact) + lc_readable_time = f"{_format_datetime(lc_struct_time)}" + print(f" | | Last Contact: {lc_readable_time}") + + flight_transponder = opensky_json["states"][0][0] + print(f" | | Transponder: {flight_transponder}") + + callsign = opensky_json["states"][0][1] + print(f" | | Callsign: {callsign}") + + squawk = opensky_json["states"][0][14] + print(f" | | Squawk: {squawk}") + + country = opensky_json["states"][0][2] + print(f" | | Origin: {country}") + + longitude = opensky_json["states"][0][5] + print(f" | | Longitude: {longitude}") + + latitude = opensky_json["states"][0][6] + print(f" | | Latitude: {latitude}") + + # Return Air Flight data if not on ground + on_ground = opensky_json["states"][0][8] + if on_ground is True: + print(f" | | On Ground: {on_ground}") else: - print("Flight has no active data or you're polling too fast.") - - print("\nFinished!") - print("Board Uptime: ", time_calc(time.monotonic())) - print("Next Update: ", time_calc(sleep_time)) - time.sleep(sleep_time) - print("===============================") - - except (ConnectionError, ValueError, NameError) as e: - print("OSN Connection Error:", e) - print("Next Retry: ", time_calc(sleep_time)) - time.sleep(sleep_time) + altitude = opensky_json["states"][0][7] + print(f" | | Barometric Altitude: {altitude}") + + velocity = opensky_json["states"][0][9] + if velocity != "null": + print(f" | | Velocity: {velocity}") + + vertical_rate = opensky_json["states"][0][11] + if vertical_rate != "null": + print(f" | | Vertical Rate: {vertical_rate}") + + else: + print(" | | ❌ Flight has no active data or you're polling too fast.") + + opensky_response.close() + print("✂️ Disconnected from OpenSky-Network API") + + print("\nFinished!") + print(f"Board Uptime: {time_calc(time.monotonic())}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") + print("===============================") + + except (ValueError, RuntimeError) as e: + print(f"Failed to get data, retrying\n {e}") + time.sleep(60) + break + time.sleep(SLEEP_TIME) From e9ca5b2b651409db39c3ae439bd717526f17663e Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Wed, 10 Apr 2024 21:54:00 -0400 Subject: [PATCH 256/305] latest version of single public example --- .../wifi/expanded/requests_wifi_api_openskynetwork_public.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py index 0eda0b3..9151343 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py @@ -1,6 +1,6 @@ # SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT -# Coded for Circuit Python 8.2.x +# Coded for Circuit Python 9.x """OpenSky-Network.org Single Flight Public API Example""" import os From 4f0c265c0cf643774f75cb2e7eb6bccb834ae02a Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 11 Apr 2024 02:37:04 -0400 Subject: [PATCH 257/305] split advanced and status codes into separate examples --- examples/wifi/requests_wifi_advanced.py | 131 +++-------------- examples/wifi/requests_wifi_status_codes.py | 150 ++++++++++++++++++++ 2 files changed, 170 insertions(+), 111 deletions(-) create mode 100644 examples/wifi/requests_wifi_status_codes.py diff --git a/examples/wifi/requests_wifi_advanced.py b/examples/wifi/requests_wifi_advanced.py index 6d9d24d..86ffc34 100644 --- a/examples/wifi/requests_wifi_advanced.py +++ b/examples/wifi/requests_wifi_advanced.py @@ -1,9 +1,8 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT # Updated for Circuit Python 9.0 -# https://help.openai.com/en/articles/6825453-chatgpt-release-notes -# https://chat.openai.com/share/32ef0c5f-ac92-4d36-9d1e-0f91e0c4c574 -""" WiFi Advanced (User-Agent & Status Codes) Example """ + +""" WiFi Advanced Example """ import os @@ -22,88 +21,7 @@ requests = adafruit_requests.Session(pool, ssl_context) rssi = wifi.radio.ap_info.rssi - -def print_http_status(code, description): - """Returns HTTP status code and description""" - if "100" <= code <= "103": - print(f" | ✅ Status Test: {code} - {description}") - elif "200" <= code <= "299": - print(f" | ✅ Status Test: {code} - {description}") - elif "300" <= code <= "600": - print(f" | ❌ Status Test: {code} - {description}") - else: - print(f" | Unknown Response Status: {code} - {description}") - - -# All HTTP Status Codes -http_status_codes = { - "100": "Continue", - "101": "Switching Protocols", - "102": "Processing", - "103": "Early Hints", - "200": "OK", - "201": "Created", - "202": "Accepted", - "203": "Non-Authoritative Information", - "204": "No Content", - "205": "Reset Content", - "206": "Partial Content", - "207": "Multi-Status", - "208": "Already Reported", - "226": "IM Used", - "300": "Multiple Choices", - "301": "Moved Permanently", - "302": "Found", - "303": "See Other", - "304": "Not Modified", - "305": "Use Proxy", - "306": "Unused", - "307": "Temporary Redirect", - "308": "Permanent Redirect", - "400": "Bad Request", - "401": "Unauthorized", - "402": "Payment Required", - "403": "Forbidden", - "404": "Not Found", - "405": "Method Not Allowed", - "406": "Not Acceptable", - "407": "Proxy Authentication Required", - "408": "Request Timeout", - "409": "Conflict", - "410": "Gone", - "411": "Length Required", - "412": "Precondition Failed", - "413": "Payload Too Large", - "414": "URI Too Long", - "415": "Unsupported Media Type", - "416": "Range Not Satisfiable", - "417": "Expectation Failed", - "418": "I'm a teapot", - "421": "Misdirected Request", - "422": "Unprocessable Entity", - "423": "Locked", - "424": "Failed Dependency", - "425": "Too Early", - "426": "Upgrade Required", - "428": "Precondition Required", - "429": "Too Many Requests", - "431": "Request Header Fields Too Large", - "451": "Unavailable For Legal Reasons", - "500": "Internal Server Error", - "501": "Not Implemented", - "502": "Bad Gateway", - "503": "Service Unavailable", - "504": "Gateway Timeout", - "505": "HTTP Version Not Supported", - "506": "Variant Also Negotiates", - "507": "Insufficient Storage", - "508": "Loop Detected", - "510": "Not Extended", - "511": "Network Authentication Required", -} - JSON_GET_URL = "https://httpbin.org/get" -STATUS_TEST = "https://httpbin.org/status/" print(f"\nConnecting to {ssid}...") print(f"Signal Strength: {rssi}") @@ -115,36 +33,27 @@ def print_http_status(code, description): print("✅ Wifi!") # Define a custom header as a dict. -HEADERS = {"user-agent": "blinka/1.0.0"} +headers = {"user-agent": "blinka/1.0.0"} +print(f" | Fetching JSON: {JSON_GET_URL}") -print(f" | GET JSON: {JSON_GET_URL}") -response = requests.get(JSON_GET_URL, headers=HEADERS) +# GET JSON +response = requests.get(JSON_GET_URL, headers=headers) +content_type = response.headers.get("content-type", "") +date = response.headers.get("date", "") json_data = response.json() -HEADERS = json_data["headers"] -print(f" | User-Agent: {HEADERS['User-Agent']}") - -# HTTP STATUS CODE TESTING -# https://developer.mozilla.org/en-US/docs/Web/HTTP/Status -STATUS_CODE = str(response.status_code) -STATUS_DESCRIPTION = http_status_codes.get(STATUS_CODE, "Unknown Status Code") -print_http_status(STATUS_CODE, STATUS_DESCRIPTION) +headers = json_data["headers"] + +# JSON Response +if response.status_code == 200: + print(f" | 🆗 Status Code: {response.status_code}") +else: + print(f" | | Status Code: {response.status_code}") +print(f" | ✅ Custom User-Agent Header: {headers['User-Agent']}") +print(f" | ✅ Content-Type: {content_type}") +print(f" | ✅ Response Timestamp: {date}") + +# Close, delete and collect the response data response.close() -print(f" | ✂️ Disconnected from {JSON_GET_URL}") -print(" | ") - -print(f" | Status Code Test: {STATUS_TEST}") -# Some return errors then confirm the error (that's a good thing) -# Demonstrates not all errors have the same behavior -# 300, 304, and 306 in particular -for codes in sorted(http_status_codes.keys(), key=int): - status_test_url = STATUS_TEST + codes - response = requests.get(status_test_url, headers=HEADERS) - SORT_STATUS_CODE = str(response.status_code) - SORT_STATUS_DESC = http_status_codes.get(SORT_STATUS_CODE, "Unknown Status Code") - print_http_status(SORT_STATUS_CODE, SORT_STATUS_DESC) - response.close() print(f" | ✂️ Disconnected from {JSON_GET_URL}") - -print("Finished!") diff --git a/examples/wifi/requests_wifi_status_codes.py b/examples/wifi/requests_wifi_status_codes.py new file mode 100644 index 0000000..10e84d8 --- /dev/null +++ b/examples/wifi/requests_wifi_status_codes.py @@ -0,0 +1,150 @@ +# SPDX-FileCopyrightText: 2024 DJDevon3 +# SPDX-License-Identifier: MIT +# Updated for Circuit Python 9.0 +# https://help.openai.com/en/articles/6825453-chatgpt-release-notes +# https://chat.openai.com/share/32ef0c5f-ac92-4d36-9d1e-0f91e0c4c574 +""" WiFi Advanced (User-Agent & Status Codes) Example """ + +import os + +import adafruit_connection_manager +import wifi + +import adafruit_requests + +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") + +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) +rssi = wifi.radio.ap_info.rssi + + +def print_http_status(code, description): + """Returns HTTP status code and description""" + if "100" <= code <= "103": + print(f" | ✅ Status Test: {code} - {description}") + elif "200" <= code <= "299": + print(f" | ✅ Status Test: {code} - {description}") + elif "300" <= code <= "600": + print(f" | ❌ Status Test: {code} - {description}") + else: + print(f" | Unknown Response Status: {code} - {description}") + + +# All HTTP Status Codes +http_status_codes = { + "100": "Continue", + "101": "Switching Protocols", + "102": "Processing", + "103": "Early Hints", + "200": "OK", + "201": "Created", + "202": "Accepted", + "203": "Non-Authoritative Information", + "204": "No Content", + "205": "Reset Content", + "206": "Partial Content", + "207": "Multi-Status", + "208": "Already Reported", + "226": "IM Used", + "300": "Multiple Choices", + "301": "Moved Permanently", + "302": "Found", + "303": "See Other", + "304": "Not Modified", + "305": "Use Proxy", + "306": "Unused", + "307": "Temporary Redirect", + "308": "Permanent Redirect", + "400": "Bad Request", + "401": "Unauthorized", + "402": "Payment Required", + "403": "Forbidden", + "404": "Not Found", + "405": "Method Not Allowed", + "406": "Not Acceptable", + "407": "Proxy Authentication Required", + "408": "Request Timeout", + "409": "Conflict", + "410": "Gone", + "411": "Length Required", + "412": "Precondition Failed", + "413": "Payload Too Large", + "414": "URI Too Long", + "415": "Unsupported Media Type", + "416": "Range Not Satisfiable", + "417": "Expectation Failed", + "418": "I'm a teapot", + "421": "Misdirected Request", + "422": "Unprocessable Entity", + "423": "Locked", + "424": "Failed Dependency", + "425": "Too Early", + "426": "Upgrade Required", + "428": "Precondition Required", + "429": "Too Many Requests", + "431": "Request Header Fields Too Large", + "451": "Unavailable For Legal Reasons", + "500": "Internal Server Error", + "501": "Not Implemented", + "502": "Bad Gateway", + "503": "Service Unavailable", + "504": "Gateway Timeout", + "505": "HTTP Version Not Supported", + "506": "Variant Also Negotiates", + "507": "Insufficient Storage", + "508": "Loop Detected", + "510": "Not Extended", + "511": "Network Authentication Required", +} + +JSON_GET_URL = "https://httpbin.org/get" +STATUS_TEST = "https://httpbin.org/status/" + +print(f"\nConnecting to {ssid}...") +print(f"Signal Strength: {rssi}") +try: + # Connect to the Wi-Fi network + wifi.radio.connect(ssid, password) +except OSError as e: + print(f"❌ OSError: {e}") +print("✅ Wifi!") + +# Define a custom header as a dict. +HEADERS = {"user-agent": "blinka/1.0.0"} + +print(f" | GET JSON: {JSON_GET_URL}") +response = requests.get(JSON_GET_URL, headers=HEADERS) + +json_data = response.json() +HEADERS = json_data["headers"] +print(f" | User-Agent: {HEADERS['User-Agent']}") + +# HTTP STATUS CODE TESTING +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Status +STATUS_CODE = str(response.status_code) +STATUS_DESCRIPTION = http_status_codes.get(STATUS_CODE, "Unknown Status Code") +print_http_status(STATUS_CODE, STATUS_DESCRIPTION) +response.close() +print(f" | ✂️ Disconnected from {JSON_GET_URL}") +print(" | ") + +print(f" | Status Code Test: {STATUS_TEST}") +# Some return errors then confirm the error (that's a good thing) +# Demonstrates not all errors have the same behavior +# 300, 304, and 306 in particular +for codes in sorted(http_status_codes.keys(), key=int): + status_test_url = STATUS_TEST + codes + response = requests.get(status_test_url, headers=HEADERS) + SORT_STATUS_CODE = str(response.status_code) + SORT_STATUS_DESC = http_status_codes.get(SORT_STATUS_CODE, "Unknown Status Code") + print_http_status(SORT_STATUS_CODE, SORT_STATUS_DESC) + response.close() + +print(f" | ✂️ Disconnected from {JSON_GET_URL}") + +print("Finished!") From 908077dee0cb6649d76048a298ed314b237a0214 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 11 Apr 2024 09:24:47 -0400 Subject: [PATCH 258/305] requested changes added additional http headers, removed most status codes for separate example --- examples/wifi/requests_wifi_advanced.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/wifi/requests_wifi_advanced.py b/examples/wifi/requests_wifi_advanced.py index 86ffc34..225e914 100644 --- a/examples/wifi/requests_wifi_advanced.py +++ b/examples/wifi/requests_wifi_advanced.py @@ -49,9 +49,9 @@ print(f" | 🆗 Status Code: {response.status_code}") else: print(f" | | Status Code: {response.status_code}") -print(f" | ✅ Custom User-Agent Header: {headers['User-Agent']}") -print(f" | ✅ Content-Type: {content_type}") -print(f" | ✅ Response Timestamp: {date}") +print(f" | | Custom User-Agent Header: {headers['User-Agent']}") +print(f" | | Content-Type: {content_type}") +print(f" | | Response Timestamp: {date}") # Close, delete and collect the response data response.close() From e341d972a8a865d20548e2918fff1141e9021862 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 11 Apr 2024 09:50:05 -0400 Subject: [PATCH 259/305] updated docstring description --- examples/wifi/requests_wifi_status_codes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wifi/requests_wifi_status_codes.py b/examples/wifi/requests_wifi_status_codes.py index 10e84d8..2214e92 100644 --- a/examples/wifi/requests_wifi_status_codes.py +++ b/examples/wifi/requests_wifi_status_codes.py @@ -3,7 +3,7 @@ # Updated for Circuit Python 9.0 # https://help.openai.com/en/articles/6825453-chatgpt-release-notes # https://chat.openai.com/share/32ef0c5f-ac92-4d36-9d1e-0f91e0c4c574 -""" WiFi Advanced (User-Agent & Status Codes) Example """ +""" WiFi Status Codes Example """ import os From 9b1917835c059618b52b93bce41b21512bde7050 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 15 Apr 2024 12:59:03 -0400 Subject: [PATCH 260/305] changed to with statement and removed additional url This is more like the original advanced example. --- examples/wifi/requests_wifi_advanced.py | 41 +++++++++++-------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/examples/wifi/requests_wifi_advanced.py b/examples/wifi/requests_wifi_advanced.py index 225e914..473078a 100644 --- a/examples/wifi/requests_wifi_advanced.py +++ b/examples/wifi/requests_wifi_advanced.py @@ -21,7 +21,10 @@ requests = adafruit_requests.Session(pool, ssl_context) rssi = wifi.radio.ap_info.rssi +# URL for GET request JSON_GET_URL = "https://httpbin.org/get" +# Define a custom header as a dict. +headers = {"user-agent": "blinka/1.0.0"} print(f"\nConnecting to {ssid}...") print(f"Signal Strength: {rssi}") @@ -34,26 +37,18 @@ # Define a custom header as a dict. headers = {"user-agent": "blinka/1.0.0"} -print(f" | Fetching JSON: {JSON_GET_URL}") - -# GET JSON -response = requests.get(JSON_GET_URL, headers=headers) -content_type = response.headers.get("content-type", "") -date = response.headers.get("date", "") - -json_data = response.json() -headers = json_data["headers"] - -# JSON Response -if response.status_code == 200: - print(f" | 🆗 Status Code: {response.status_code}") -else: - print(f" | | Status Code: {response.status_code}") -print(f" | | Custom User-Agent Header: {headers['User-Agent']}") -print(f" | | Content-Type: {content_type}") -print(f" | | Response Timestamp: {date}") - -# Close, delete and collect the response data -response.close() - -print(f" | ✂️ Disconnected from {JSON_GET_URL}") +print(" | Fetching JSON data from %s..." % JSON_GET_URL) + +# Use with statement for retreiving GET request data +with requests.get(JSON_GET_URL, headers=headers) as response: + json_data = response.json() + headers = json_data["headers"] + content_type = response.headers.get("content-type", "") + date = response.headers.get("date", "") + if response.status_code == 200: + print(f" | 🆗 Status Code: {response.status_code}") + else: + print(f" | | Status Code: {response.status_code}") + print(f" | | Custom User-Agent Header: {headers['User-Agent']}") + print(f" | | Content-Type: {content_type}") + print(f" | | Response Timestamp: {date}") From 7cecdd55b1f6bc7eb1741afc291605ef2e06729c Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 15 Apr 2024 13:16:13 -0400 Subject: [PATCH 261/305] added x emoji if response not ok --- examples/wifi/requests_wifi_advanced.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wifi/requests_wifi_advanced.py b/examples/wifi/requests_wifi_advanced.py index 473078a..7afc00f 100644 --- a/examples/wifi/requests_wifi_advanced.py +++ b/examples/wifi/requests_wifi_advanced.py @@ -48,7 +48,7 @@ if response.status_code == 200: print(f" | 🆗 Status Code: {response.status_code}") else: - print(f" | | Status Code: {response.status_code}") + print(f" | ❌ Status Code: {response.status_code}") print(f" | | Custom User-Agent Header: {headers['User-Agent']}") print(f" | | Content-Type: {content_type}") print(f" | | Response Timestamp: {date}") From 7e30fb634adbc05b75dc0a92d9aaa7a3b60bb038 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 15 Apr 2024 13:19:29 -0400 Subject: [PATCH 262/305] change fetching json data to fetching URL --- examples/wifi/requests_wifi_advanced.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wifi/requests_wifi_advanced.py b/examples/wifi/requests_wifi_advanced.py index 7afc00f..bfad6af 100644 --- a/examples/wifi/requests_wifi_advanced.py +++ b/examples/wifi/requests_wifi_advanced.py @@ -37,7 +37,7 @@ # Define a custom header as a dict. headers = {"user-agent": "blinka/1.0.0"} -print(" | Fetching JSON data from %s..." % JSON_GET_URL) +print(f" | Fetching URL {JSON_GET_URL}") # Use with statement for retreiving GET request data with requests.get(JSON_GET_URL, headers=headers) as response: From 5b86eb1f453954acb85922fbf244b57943be4839 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 15 Apr 2024 13:27:29 -0400 Subject: [PATCH 263/305] change Rides to Ride and update main docstring --- examples/wifi/expanded/requests_wifi_api_queuetimes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_queuetimes.py b/examples/wifi/expanded/requests_wifi_api_queuetimes.py index 92167e5..2104c7e 100644 --- a/examples/wifi/expanded/requests_wifi_api_queuetimes.py +++ b/examples/wifi/expanded/requests_wifi_api_queuetimes.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2024 DJDevon3 # SPDX-License-Identifier: MIT # Coded for Circuit Python 9.x -"""Queue-Times.com API w/Display Example""" +"""Queue-Times.com API Example""" import os import time @@ -92,7 +92,7 @@ def time_calc(input_time): qtimes_queuetime = str(ride["wait_time"]) qtimes_isopen = str(ride["is_open"]) - print(f" | | Rides: {qtimes_rides}") + print(f" | | Ride: {qtimes_rides}") print(f" | | Queue Time: {qtimes_queuetime} Minutes") if qtimes_isopen == "False": print(" | | Status: Closed") From 77f8b953b93f4e6bc26fd9f37f55cc9c2736cd3e Mon Sep 17 00:00:00 2001 From: foamyguy Date: Sat, 20 Apr 2024 11:33:03 -0500 Subject: [PATCH 264/305] starting files arg for uploading --- adafruit_requests.py | 72 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 9f38502..5a387bf 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -41,11 +41,13 @@ import errno import json as json_module +import random import sys from adafruit_connection_manager import get_connection_manager if not sys.implementation.name == "circuitpython": + from io import FileIO from types import TracebackType from typing import Any, Dict, Optional, Type @@ -394,6 +396,13 @@ def _send(socket: SocketType, data: bytes): def _send_as_bytes(self, socket: SocketType, data: str): return self._send(socket, bytes(data, "utf-8")) + def _generate_boundary_str(self): + hex_characters = "0123456789abcdef" + _boundary = "" + for i in range(32): + _boundary += random.choice(hex_characters) + return _boundary + def _send_header(self, socket, header, value): if value is None: return @@ -415,6 +424,7 @@ def _send_request( headers: Dict[str, str], data: Any, json: Any, + files: Optional[Dict[str, tuple]], ): # Check headers self._check_headers(headers) @@ -425,6 +435,7 @@ def _send_request( # If json is sent, set content type header and convert to string if json is not None: assert data is None + assert files is None content_type_header = "application/json" data = json_module.dumps(json) @@ -441,6 +452,9 @@ def _send_request( if data and isinstance(data, str): data = bytes(data, "utf-8") + if data is None: + data = b"" + self._send_as_bytes(socket, method) self._send(socket, b" /") self._send_as_bytes(socket, path) @@ -448,6 +462,59 @@ def _send_request( # create lower-case supplied header list supplied_headers = {header.lower() for header in headers} + boundary_str = None + + if files is not None and isinstance(files, dict): + boundary_str = self._generate_boundary_str() + content_type_header = f"multipart/form-data; boundary={boundary_str}" + + for fieldname in files.keys(): + if not fieldname.endswith("-name"): + if files[fieldname][0] is not None: + file_content = files[fieldname][1].read() + + data += b"--" + boundary_str.encode() + b"\r\n" + data += ( + b'Content-Disposition: form-data; name="' + + fieldname.encode() + + b'"; filename="' + + files[fieldname][0].encode() + + b'"\r\n' + ) + if len(files[fieldname]) >= 3: + data += ( + b"Content-Type: " + + files[fieldname][2].encode() + + b"\r\n" + ) + if len(files[fieldname]) >= 4: + for custom_header_key in files[fieldname][3].keys(): + data += ( + custom_header_key.encode() + + b": " + + files[fieldname][3][custom_header_key].encode() + + b"\r\n" + ) + data += b"\r\n" + data += file_content + b"\r\n" + else: + # filename is None + data += b"--" + boundary_str.encode() + b"\r\n" + data += ( + b'Content-Disposition: form-data; name="' + + fieldname.encode() + + b'"; \r\n' + ) + if len(files[fieldname]) >= 3: + data += ( + b"Content-Type: " + + files[fieldname][2].encode() + + b"\r\n" + ) + data += b"\r\n" + data += files[fieldname][1].encode() + b"\r\n" + + data += b"--" + boundary_str.encode() + b"--" # Send headers if not "host" in supplied_headers: @@ -478,6 +545,7 @@ def request( stream: bool = False, timeout: float = 60, allow_redirects: bool = True, + files: Optional[Dict[str, tuple]] = None, ) -> Response: """Perform an HTTP request to the given url which we will parse to determine whether to use SSL ('https://') or not. We can also send some provided 'data' @@ -526,7 +594,9 @@ def request( ) ok = True try: - self._send_request(socket, host, method, path, headers, data, json) + self._send_request( + socket, host, method, path, headers, data, json, files + ) except OSError as exc: last_exc = exc ok = False From d4d03ebb189bb88d429c64fff1f81f8734723ec4 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 20 Apr 2024 14:36:01 -0700 Subject: [PATCH 265/305] Use with in examples, remove close in json() --- adafruit_requests.py | 2 +- examples/cpython/requests_cpython_advanced.py | 24 ++++----- .../cpython/requests_cpython_simpletest.py | 48 +++++++---------- .../esp32spi/requests_esp32spi_advanced.py | 21 ++++---- .../esp32spi/requests_esp32spi_simpletest.py | 48 +++++++---------- examples/fona/requests_fona_advanced.py | 21 ++++---- examples/fona/requests_fona_simpletest.py | 48 +++++++---------- ...sts_wifi_adafruit_discord_active_online.py | 4 +- .../expanded/requests_wifi_api_discord.py | 6 ++- .../wifi/expanded/requests_wifi_api_fitbit.py | 15 +++--- .../wifi/expanded/requests_wifi_api_github.py | 9 ++-- .../expanded/requests_wifi_api_mastodon.py | 4 +- ...equests_wifi_api_openskynetwork_private.py | 9 ++-- ...ts_wifi_api_openskynetwork_private_area.py | 9 ++-- ...requests_wifi_api_openskynetwork_public.py | 7 +-- .../requests_wifi_api_premiereleague.py | 6 +-- .../requests_wifi_api_rocketlaunch_live.py | 7 +-- .../wifi/expanded/requests_wifi_api_steam.py | 7 +-- .../wifi/expanded/requests_wifi_api_twitch.py | 23 ++++---- .../expanded/requests_wifi_api_youtube.py | 7 +-- .../requests_wifi_multiple_cookies.py | 54 +++++++++---------- examples/wifi/requests_wifi_advanced.py | 21 ++++---- examples/wifi/requests_wifi_simpletest.py | 32 +++++------ .../wiznet5k/requests_wiznet5k_advanced.py | 21 ++++---- .../wiznet5k/requests_wiznet5k_simpletest.py | 48 +++++++---------- 25 files changed, 213 insertions(+), 288 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 9f38502..b6cd54c 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -321,7 +321,7 @@ def json(self) -> Any: obj = json_module.load(self._raw) if not self._cached: self._cached = obj - self.close() + return obj def iter_content(self, chunk_size: int = 1, decode_unicode: bool = False) -> bytes: diff --git a/examples/cpython/requests_cpython_advanced.py b/examples/cpython/requests_cpython_advanced.py index 89d715b..b19b37b 100644 --- a/examples/cpython/requests_cpython_advanced.py +++ b/examples/cpython/requests_cpython_advanced.py @@ -15,17 +15,13 @@ headers = {"user-agent": "blinka/1.0.0"} print("Fetching JSON data from %s..." % JSON_GET_URL) -response = requests.get(JSON_GET_URL, headers=headers) -print("-" * 60) - -json_data = response.json() -headers = json_data["headers"] -print("Response's Custom User-Agent Header: {0}".format(headers["User-Agent"])) -print("-" * 60) - -# Read Response's HTTP status code -print("Response HTTP Status Code: ", response.status_code) -print("-" * 60) - -# Close, delete and collect the response data -response.close() +with requests.get(JSON_GET_URL, headers=headers) as response: + print("-" * 60) + json_data = response.json() + headers = json_data["headers"] + print("Response's Custom User-Agent Header: {0}".format(headers["User-Agent"])) + print("-" * 60) + + # Read Response's HTTP status code + print("Response HTTP Status Code: ", response.status_code) + print("-" * 60) diff --git a/examples/cpython/requests_cpython_simpletest.py b/examples/cpython/requests_cpython_simpletest.py index 70bdfde..a3e10e6 100644 --- a/examples/cpython/requests_cpython_simpletest.py +++ b/examples/cpython/requests_cpython_simpletest.py @@ -14,39 +14,31 @@ JSON_POST_URL = "https://httpbin.org/post" print("Fetching text from %s" % TEXT_URL) -response = requests.get(TEXT_URL) -print("-" * 40) - -print("Text Response: ", response.text) -print("-" * 40) -response.close() +with requests.get(TEXT_URL) as response: + print("-" * 40) + print("Text Response: ", response.text) + print("-" * 40) print("Fetching JSON data from %s" % JSON_GET_URL) -response = requests.get(JSON_GET_URL) -print("-" * 40) - -print("JSON Response: ", response.json()) -print("-" * 40) -response.close() +with requests.get(JSON_GET_URL) as response: + print("-" * 40) + print("JSON Response: ", response.json()) + print("-" * 40) data = "31F" print("POSTing data to {0}: {1}".format(JSON_POST_URL, data)) -response = requests.post(JSON_POST_URL, data=data) -print("-" * 40) - -json_resp = response.json() -# Parse out the 'data' key from json_resp dict. -print("Data received from server:", json_resp["data"]) -print("-" * 40) -response.close() +with requests.post(JSON_POST_URL, data=data) as response: + print("-" * 40) + json_resp = response.json() + # Parse out the 'data' key from json_resp dict. + print("Data received from server:", json_resp["data"]) + print("-" * 40) json_data = {"Date": "July 25, 2019"} print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data)) -response = requests.post(JSON_POST_URL, json=json_data) -print("-" * 40) - -json_resp = response.json() -# Parse out the 'json' key from json_resp dict. -print("JSON Data received from server:", json_resp["json"]) -print("-" * 40) -response.close() +with requests.post(JSON_POST_URL, json=json_data) as response: + print("-" * 40) + json_resp = response.json() + # Parse out the 'json' key from json_resp dict. + print("JSON Data received from server:", json_resp["json"]) + print("-" * 40) diff --git a/examples/esp32spi/requests_esp32spi_advanced.py b/examples/esp32spi/requests_esp32spi_advanced.py index 66d7b5c..6754dbf 100644 --- a/examples/esp32spi/requests_esp32spi_advanced.py +++ b/examples/esp32spi/requests_esp32spi_advanced.py @@ -53,17 +53,14 @@ headers = {"user-agent": "blinka/1.0.0"} print("Fetching JSON data from %s..." % JSON_GET_URL) -response = requests.get(JSON_GET_URL, headers=headers) -print("-" * 60) +with requests.get(JSON_GET_URL, headers=headers) as response: + print("-" * 60) -json_data = response.json() -headers = json_data["headers"] -print("Response's Custom User-Agent Header: {0}".format(headers["User-Agent"])) -print("-" * 60) + json_data = response.json() + headers = json_data["headers"] + print("Response's Custom User-Agent Header: {0}".format(headers["User-Agent"])) + print("-" * 60) -# Read Response's HTTP status code -print("Response HTTP Status Code: ", response.status_code) -print("-" * 60) - -# Close, delete and collect the response data -response.close() + # Read Response's HTTP status code + print("Response HTTP Status Code: ", response.status_code) + print("-" * 60) diff --git a/examples/esp32spi/requests_esp32spi_simpletest.py b/examples/esp32spi/requests_esp32spi_simpletest.py index 3ace026..8394df4 100644 --- a/examples/esp32spi/requests_esp32spi_simpletest.py +++ b/examples/esp32spi/requests_esp32spi_simpletest.py @@ -52,39 +52,31 @@ JSON_POST_URL = "https://httpbin.org/post" print("Fetching text from %s" % TEXT_URL) -response = requests.get(TEXT_URL) -print("-" * 40) - -print("Text Response: ", response.text) -print("-" * 40) -response.close() +with requests.get(TEXT_URL) as response: + print("-" * 40) + print("Text Response: ", response.text) + print("-" * 40) print("Fetching JSON data from %s" % JSON_GET_URL) -response = requests.get(JSON_GET_URL) -print("-" * 40) - -print("JSON Response: ", response.json()) -print("-" * 40) -response.close() +with requests.get(JSON_GET_URL) as response: + print("-" * 40) + print("JSON Response: ", response.json()) + print("-" * 40) data = "31F" print("POSTing data to {0}: {1}".format(JSON_POST_URL, data)) -response = requests.post(JSON_POST_URL, data=data) -print("-" * 40) - -json_resp = response.json() -# Parse out the 'data' key from json_resp dict. -print("Data received from server:", json_resp["data"]) -print("-" * 40) -response.close() +with requests.post(JSON_POST_URL, data=data) as response: + print("-" * 40) + json_resp = response.json() + # Parse out the 'data' key from json_resp dict. + print("Data received from server:", json_resp["data"]) + print("-" * 40) json_data = {"Date": "July 25, 2019"} print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data)) -response = requests.post(JSON_POST_URL, json=json_data) -print("-" * 40) - -json_resp = response.json() -# Parse out the 'json' key from json_resp dict. -print("JSON Data received from server:", json_resp["json"]) -print("-" * 40) -response.close() +with requests.post(JSON_POST_URL, json=json_data) as response: + print("-" * 40) + json_resp = response.json() + # Parse out the 'json' key from json_resp dict. + print("JSON Data received from server:", json_resp["json"]) + print("-" * 40) diff --git a/examples/fona/requests_fona_advanced.py b/examples/fona/requests_fona_advanced.py index 33bc6f0..5af1436 100644 --- a/examples/fona/requests_fona_advanced.py +++ b/examples/fona/requests_fona_advanced.py @@ -54,17 +54,14 @@ headers = {"user-agent": "blinka/1.0.0"} print("Fetching JSON data from %s..." % JSON_GET_URL) -response = requests.get(JSON_GET_URL, headers=headers) -print("-" * 60) +with requests.get(JSON_GET_URL, headers=headers) as response: + print("-" * 60) -json_data = response.json() -headers = json_data["headers"] -print("Response's Custom User-Agent Header: {0}".format(headers["User-Agent"])) -print("-" * 60) + json_data = response.json() + headers = json_data["headers"] + print("Response's Custom User-Agent Header: {0}".format(headers["User-Agent"])) + print("-" * 60) -# Read Response's HTTP status code -print("Response HTTP Status Code: ", response.status_code) -print("-" * 60) - -# Close, delete and collect the response data -response.close() + # Read Response's HTTP status code + print("Response HTTP Status Code: ", response.status_code) + print("-" * 60) diff --git a/examples/fona/requests_fona_simpletest.py b/examples/fona/requests_fona_simpletest.py index 8841d3d..11ce3a0 100644 --- a/examples/fona/requests_fona_simpletest.py +++ b/examples/fona/requests_fona_simpletest.py @@ -53,39 +53,31 @@ JSON_POST_URL = "http://httpbin.org/post" print("Fetching text from %s" % TEXT_URL) -response = requests.get(TEXT_URL) -print("-" * 40) - -print("Text Response: ", response.text) -print("-" * 40) -response.close() +with requests.get(TEXT_URL) as response: + print("-" * 40) + print("Text Response: ", response.text) + print("-" * 40) print("Fetching JSON data from %s" % JSON_GET_URL) -response = requests.get(JSON_GET_URL) -print("-" * 40) - -print("JSON Response: ", response.json()) -print("-" * 40) -response.close() +with requests.get(JSON_GET_URL) as response: + print("-" * 40) + print("JSON Response: ", response.json()) + print("-" * 40) data = "31F" print("POSTing data to {0}: {1}".format(JSON_POST_URL, data)) -response = requests.post(JSON_POST_URL, data=data) -print("-" * 40) - -json_resp = response.json() -# Parse out the 'data' key from json_resp dict. -print("Data received from server:", json_resp["data"]) -print("-" * 40) -response.close() +with requests.post(JSON_POST_URL, data=data) as response: + print("-" * 40) + json_resp = response.json() + # Parse out the 'data' key from json_resp dict. + print("Data received from server:", json_resp["data"]) + print("-" * 40) json_data = {"Date": "July 25, 2019"} print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data)) -response = requests.post(JSON_POST_URL, json=json_data) -print("-" * 40) - -json_resp = response.json() -# Parse out the 'json' key from json_resp dict. -print("JSON Data received from server:", json_resp["json"]) -print("-" * 40) -response.close() +with requests.post(JSON_POST_URL, json=json_data) as response: + print("-" * 40) + json_resp = response.json() + # Parse out the 'json' key from json_resp dict. + print("JSON Data received from server:", json_resp["json"]) + print("-" * 40) diff --git a/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py b/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py index 3ebcaf4..8f6917c 100644 --- a/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py +++ b/examples/wifi/expanded/requests_wifi_adafruit_discord_active_online.py @@ -60,8 +60,8 @@ def time_calc(input_time): DEBUG_RESPONSE = True try: - shieldsio_response = requests.get(url=ADA_DISCORD_JSON) - shieldsio_json = shieldsio_response.json() + with requests.get(url=ADA_DISCORD_JSON) as shieldsio_response: + shieldsio_json = shieldsio_response.json() except ConnectionError as e: print(f"Connection Error: {e}") print("Retrying in 10 seconds") diff --git a/examples/wifi/expanded/requests_wifi_api_discord.py b/examples/wifi/expanded/requests_wifi_api_discord.py index 08ad16c..19c2522 100644 --- a/examples/wifi/expanded/requests_wifi_api_discord.py +++ b/examples/wifi/expanded/requests_wifi_api_discord.py @@ -69,8 +69,10 @@ def time_calc(input_time): DEBUG_RESPONSE = False try: - discord_response = requests.get(url=DISCORD_SOURCE, headers=DISCORD_HEADER) - discord_json = discord_response.json() + with requests.get( + url=DISCORD_SOURCE, headers=DISCORD_HEADER + ) as discord_response: + discord_json = discord_response.json() except ConnectionError as e: print(f"Connection Error: {e}") print("Retrying in 10 seconds") diff --git a/examples/wifi/expanded/requests_wifi_api_fitbit.py b/examples/wifi/expanded/requests_wifi_api_fitbit.py index 206a56d..35f3ce2 100644 --- a/examples/wifi/expanded/requests_wifi_api_fitbit.py +++ b/examples/wifi/expanded/requests_wifi_api_fitbit.py @@ -174,17 +174,16 @@ def time_calc(input_time): print(f"Current Refresh Token: {Refresh_Token}") # TOKEN REFRESH POST try: - fitbit_oauth_refresh_POST = requests.post( + with requests.post( url=FITBIT_OAUTH_TOKEN, data=FITBIT_OAUTH_REFRESH_TOKEN, headers=FITBIT_OAUTH_HEADER, - ) + ) as fitbit_oauth_refresh_POST: + fitbit_refresh_oauth_json = fitbit_oauth_refresh_POST.json() except adafruit_requests.OutOfRetries as ex: print(f"OutOfRetries: {ex}") break try: - fitbit_refresh_oauth_json = fitbit_oauth_refresh_POST.json() - fitbit_new_token = fitbit_refresh_oauth_json["access_token"] if DEBUG: print("Your Private SHA-256 Token: ", fitbit_new_token) @@ -252,9 +251,9 @@ def time_calc(input_time): print(" | Attempting to GET Fitbit JSON!") FBIS = FITBIT_INTRADAY_SOURCE FBH = fitbit_header - fitbit_get_response = requests.get(url=FBIS, headers=FBH) try: - fitbit_json = fitbit_get_response.json() + with requests.get(url=FBIS, headers=FBH) as fitbit_get_response: + fitbit_json = fitbit_get_response.json() except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") @@ -318,9 +317,9 @@ def time_calc(input_time): print(" | Attempting to GET Device JSON!") FBDS = FITBIT_DEVICE_SOURCE FBH = fitbit_header - fitbit_get_device_response = requests.get(url=FBDS, headers=FBH) try: - fitbit_device_json = fitbit_get_device_response.json() + with requests.get(url=FBDS, headers=FBH) as fitbit_get_device_response: + fitbit_device_json = fitbit_get_device_response.json() except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") diff --git a/examples/wifi/expanded/requests_wifi_api_github.py b/examples/wifi/expanded/requests_wifi_api_github.py index ed8aaae..d25b15d 100644 --- a/examples/wifi/expanded/requests_wifi_api_github.py +++ b/examples/wifi/expanded/requests_wifi_api_github.py @@ -61,8 +61,10 @@ def time_calc(input_time): try: print(" | Attempting to GET Github JSON!") try: - github_response = requests.get(url=GITHUB_SOURCE, headers=GITHUB_HEADER) - github_json = github_response.json() + with requests.get( + url=GITHUB_SOURCE, headers=GITHUB_HEADER + ) as github_response: + github_json = github_response.json() except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") @@ -92,9 +94,6 @@ def time_calc(input_time): print("Full API GET URL: ", GITHUB_SOURCE) print(github_json) - github_response.close() - print("✂️ Disconnected from Github API") - print("\nFinished!") print(f"Board Uptime: {time_calc(time.monotonic())}") print(f"Next Update: {time_calc(SLEEP_TIME)}") diff --git a/examples/wifi/expanded/requests_wifi_api_mastodon.py b/examples/wifi/expanded/requests_wifi_api_mastodon.py index fd4266e..92455e2 100644 --- a/examples/wifi/expanded/requests_wifi_api_mastodon.py +++ b/examples/wifi/expanded/requests_wifi_api_mastodon.py @@ -75,8 +75,8 @@ def time_calc(input_time): DEBUG_RESPONSE = False try: - mastodon_response = requests.get(url=MAST_SOURCE) - mastodon_json = mastodon_response.json() + with requests.get(url=MAST_SOURCE) as mastodon_response: + mastodon_json = mastodon_response.json() except ConnectionError as e: print(f"Connection Error: {e}") print("Retrying in 10 seconds") diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py index 1e340f7..0f024bf 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py @@ -94,8 +94,10 @@ def _format_datetime(datetime): print(" | Attempting to GET OpenSky-Network Single Private Flight JSON!") print(" | Website Credentials Required! Allows more daily calls than Public.") try: - opensky_response = requests.get(url=OPENSKY_SOURCE, headers=OPENSKY_HEADER) - opensky_json = opensky_response.json() + with requests.get( + url=OPENSKY_SOURCE, headers=OPENSKY_HEADER + ) as opensky_response: + opensky_json = opensky_response.json() except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") @@ -178,9 +180,6 @@ def _format_datetime(datetime): else: print(" | | ❌ Flight has no active data or you're polling too fast.") - opensky_response.close() - print("✂️ Disconnected from OpenSky-Network API") - print("\nFinished!") print(f"Board Uptime: {time_calc(time.monotonic())}") print(f"Next Update: {time_calc(SLEEP_TIME)}") diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py index 1652a73..322115f 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py @@ -108,8 +108,10 @@ def _format_datetime(datetime): try: print(" | Attempting to GET OpenSky-Network Area Flights JSON!") try: - opensky_response = requests.get(url=OPENSKY_SOURCE, headers=OSN_HEADER) - opensky_json = opensky_response.json() + with requests.get( + url=OPENSKY_SOURCE, headers=OSN_HEADER + ) as opensky_response: + opensky_json = opensky_response.json() except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") @@ -184,9 +186,6 @@ def _format_datetime(datetime): else: print(" | | ❌ Area has no active data or you're polling too fast.") - opensky_response.close() - print("✂️ Disconnected from OpenSky-Network API") - print("\nFinished!") print(f"Board Uptime: {time_calc(time.monotonic())}") print(f"Next Update: {time_calc(SLEEP_TIME)}") diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py index 9151343..a2eb873 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py @@ -80,8 +80,8 @@ def _format_datetime(datetime): print(" | Attempting to GET OpenSky-Network Single Public Flight JSON!") print(" | Website Credentials NOT Required! Less daily calls than Private.") try: - opensky_response = requests.get(url=OPENSKY_SOURCE) - opensky_json = opensky_response.json() + with requests.get(url=OPENSKY_SOURCE) as opensky_response: + opensky_json = opensky_response.json() except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") @@ -163,9 +163,6 @@ def _format_datetime(datetime): print("This flight has no active data or you're polling too fast.") print("Public Limits: 10 second max poll & 400 weighted calls daily") - opensky_response.close() - print("✂️ Disconnected from OpenSky-Network API") - print("\nFinished!") print(f"Board Uptime: {time_calc(time.monotonic())}") print(f"Next Update: {time_calc(SLEEP_TIME)}") diff --git a/examples/wifi/expanded/requests_wifi_api_premiereleague.py b/examples/wifi/expanded/requests_wifi_api_premiereleague.py index 88524e9..5f61e1a 100644 --- a/examples/wifi/expanded/requests_wifi_api_premiereleague.py +++ b/examples/wifi/expanded/requests_wifi_api_premiereleague.py @@ -62,16 +62,14 @@ def time_calc(input_time): DEBUG_RESPONSE = False try: - PREMIERE_LEAGUE_RESPONSE = requests.get(url=PREMIERE_LEAGUE_SOURCE) - pl_json = json_stream.load(PREMIERE_LEAGUE_RESPONSE.iter_content(32)) + with requests.get(url=PREMIERE_LEAGUE_SOURCE) as PREMIERE_LEAGUE_RESPONSE: + pl_json = json_stream.load(PREMIERE_LEAGUE_RESPONSE.iter_content(32)) except ConnectionError as e: print(f"Connection Error: {e}") print("Retrying in 10 seconds") print(" | ✅ Premiere League JSON!") print(f" | Total Premiere League Players: {pl_json['total_players']}") - PREMIERE_LEAGUE_RESPONSE.close() - print("✂️ Disconnected from Premiere League") print("\nFinished!") print(f"Board Uptime: {time.monotonic()}") diff --git a/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py b/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py index 7cf9d61..67034bb 100644 --- a/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py +++ b/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py @@ -58,8 +58,8 @@ def time_calc(input_time): debug_rocketlaunch_full_response = False try: - rocketlaunch_response = requests.get(url=ROCKETLAUNCH_SOURCE) - rocketlaunch_json = rocketlaunch_response.json() + with requests.get(url=ROCKETLAUNCH_SOURCE) as rocketlaunch_response: + rocketlaunch_json = rocketlaunch_response.json() except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") @@ -120,9 +120,6 @@ def time_calc(input_time): if RLM != "None": print(f" | | Mission: {RLM}") - rocketlaunch_response.close() - print("✂️ Disconnected from RocketLaunch.Live API") - print("\nFinished!") print(f"Board Uptime: {time_calc(time.monotonic())}") print(f"Next Update: {time_calc(SLEEP_TIME)}") diff --git a/examples/wifi/expanded/requests_wifi_api_steam.py b/examples/wifi/expanded/requests_wifi_api_steam.py index fb1f7bd..42d1e31 100644 --- a/examples/wifi/expanded/requests_wifi_api_steam.py +++ b/examples/wifi/expanded/requests_wifi_api_steam.py @@ -89,8 +89,8 @@ def _format_datetime(datetime): try: print(" | Attempting to GET Steam API JSON!") try: - steam_response = requests.get(url=STEAM_SOURCE) - steam_json = steam_response.json() + with requests.get(url=STEAM_SOURCE) as steam_response: + steam_json = steam_response.json() except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") @@ -114,9 +114,6 @@ def _format_datetime(datetime): print(f" | | Total Days: {total_days}") print(f" | | Total Years: {total_years:.2f}") - steam_response.close() - print("✂️ Disconnected from Steam API") - print("\nFinished!") print(f"Board Uptime: {time_calc(time.monotonic())}") print(f"Next Update: {time_calc(SLEEP_TIME)}") diff --git a/examples/wifi/expanded/requests_wifi_api_twitch.py b/examples/wifi/expanded/requests_wifi_api_twitch.py index 1f02e5c..57f9cdd 100644 --- a/examples/wifi/expanded/requests_wifi_api_twitch.py +++ b/examples/wifi/expanded/requests_wifi_api_twitch.py @@ -94,12 +94,14 @@ def _format_datetime(datetime): ) # POST REQUEST - twitch_0auth_response = requests.post( - url=TWITCH_0AUTH_TOKEN, data=twitch_0auth_data, headers=twitch_0auth_header - ) try: - twitch_0auth_json = twitch_0auth_response.json() - twitch_access_token = twitch_0auth_json["access_token"] + with requests.post( + url=TWITCH_0AUTH_TOKEN, + data=twitch_0auth_data, + headers=twitch_0auth_header, + ) as twitch_0auth_response: + twitch_0auth_json = twitch_0auth_response.json() + twitch_access_token = twitch_0auth_json["access_token"] except ConnectionError as e: print(f"Connection Error: {e}") print("Retrying in 10 seconds") @@ -133,11 +135,11 @@ def _format_datetime(datetime): + TWITCH_UID ) print(" | Attempting to GET Twitch JSON!") - twitch_response = requests.get( - url=TWITCH_FOLLOWERS_SOURCE, headers=twitch_header - ) try: - twitch_json = twitch_response.json() + with requests.get( + url=TWITCH_FOLLOWERS_SOURCE, headers=twitch_header + ) as twitch_response: + twitch_json = twitch_response.json() except ConnectionError as e: print(f"Connection Error: {e}") print("Retrying in 10 seconds") @@ -164,9 +166,6 @@ def _format_datetime(datetime): twitch_followers = twitch_json["total"] print(f" | | Followers: {twitch_followers}") - twitch_response.close() - print("✂️ Disconnected from Twitch API") - print("\nFinished!") print(f"Board Uptime: {time_calc(time.monotonic())}") print(f"Next Update: {time_calc(SLEEP_TIME)}") diff --git a/examples/wifi/expanded/requests_wifi_api_youtube.py b/examples/wifi/expanded/requests_wifi_api_youtube.py index c69c146..a616d99 100644 --- a/examples/wifi/expanded/requests_wifi_api_youtube.py +++ b/examples/wifi/expanded/requests_wifi_api_youtube.py @@ -69,8 +69,8 @@ def time_calc(input_time): try: print(" | Attempting to GET YouTube JSON...") try: - youtube_response = requests.get(url=YOUTUBE_SOURCE) - youtube_json = youtube_response.json() + with requests.get(url=YOUTUBE_SOURCE) as youtube_response: + youtube_json = youtube_response.json() except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") @@ -105,9 +105,6 @@ def time_calc(input_time): YT_response_kind = youtube_json["kind"] print(f" | | Response Kind: {YT_response_kind}") - youtube_response.close() - print("✂️ Disconnected from YouTube API") - print("\nFinished!") print(f"Board Uptime: {time_calc(time.monotonic())}") print(f"Next Update: {time_calc(SLEEP_TIME)}") diff --git a/examples/wifi/expanded/requests_wifi_multiple_cookies.py b/examples/wifi/expanded/requests_wifi_multiple_cookies.py index 9237903..cfc0641 100644 --- a/examples/wifi/expanded/requests_wifi_multiple_cookies.py +++ b/examples/wifi/expanded/requests_wifi_multiple_cookies.py @@ -25,38 +25,36 @@ try: # Connect to the Wi-Fi network wifi.radio.connect(ssid, password) - # URL GET Request - response = requests.get(COOKIE_TEST_URL) except OSError as e: print(f"❌ OSError: {e}") print("✅ Wifi!") -print(f" | Fetching Cookies: {COOKIE_TEST_URL}") - -# Spilt up the cookies by ", " -elements = response.headers["set-cookie"].split(", ") - -# NOTE: Some cookies use ", " when describing dates. This code will iterate through -# the previously split up 'set-cookie' header value and piece back together cookies -# that were accidentally split up for this reason -days_of_week = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat") -elements_iter = iter(elements) -cookie_list = [] -for element in elements_iter: - captured_day = [day for day in days_of_week if element.endswith(day)] - if captured_day: - cookie_list.append(element + ", " + next(elements_iter)) - else: - cookie_list.append(element) - -# Pring the information about the cookies -print(f" | Total Cookies: {len(cookie_list)}") -print("-" * 80) - -for cookie in cookie_list: - print(f" | 🍪 {cookie}") +# URL GET Request +with requests.get(COOKIE_TEST_URL) as response: + print(f" | Fetching Cookies: {COOKIE_TEST_URL}") + + # Spilt up the cookies by ", " + elements = response.headers["set-cookie"].split(", ") + + # NOTE: Some cookies use ", " when describing dates. This code will iterate through + # the previously split up 'set-cookie' header value and piece back together cookies + # that were accidentally split up for this reason + days_of_week = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat") + elements_iter = iter(elements) + cookie_list = [] + for element in elements_iter: + captured_day = [day for day in days_of_week if element.endswith(day)] + if captured_day: + cookie_list.append(element + ", " + next(elements_iter)) + else: + cookie_list.append(element) + + # Pring the information about the cookies + print(f" | Total Cookies: {len(cookie_list)}") print("-" * 80) -response.close() -print(f"✂️ Disconnected from {COOKIE_TEST_URL}") + for cookie in cookie_list: + print(f" | 🍪 {cookie}") + print("-" * 80) + print("Finished!") diff --git a/examples/wifi/requests_wifi_advanced.py b/examples/wifi/requests_wifi_advanced.py index 3bb0976..7e3c8fc 100644 --- a/examples/wifi/requests_wifi_advanced.py +++ b/examples/wifi/requests_wifi_advanced.py @@ -35,17 +35,14 @@ headers = {"user-agent": "blinka/1.0.0"} print("Fetching JSON data from %s..." % JSON_GET_URL) -response = requests.get(JSON_GET_URL, headers=headers) -print("-" * 60) +with requests.get(JSON_GET_URL, headers=headers) as response: + print("-" * 60) -json_data = response.json() -headers = json_data["headers"] -print("Response's Custom User-Agent Header: {0}".format(headers["User-Agent"])) -print("-" * 60) + json_data = response.json() + headers = json_data["headers"] + print("Response's Custom User-Agent Header: {0}".format(headers["User-Agent"])) + print("-" * 60) -# Read Response's HTTP status code -print("Response HTTP Status Code: ", response.status_code) -print("-" * 60) - -# Close, delete and collect the response data -response.close() + # Read Response's HTTP status code + print("Response HTTP Status Code: ", response.status_code) + print("-" * 60) diff --git a/examples/wifi/requests_wifi_simpletest.py b/examples/wifi/requests_wifi_simpletest.py index 21492fb..6bce412 100644 --- a/examples/wifi/requests_wifi_simpletest.py +++ b/examples/wifi/requests_wifi_simpletest.py @@ -34,37 +34,29 @@ print("✅ Wifi!") print(f" | GET Text Test: {TEXT_URL}") -response = requests.get(TEXT_URL) -print(f" | ✅ GET Response: {response.text}") -response.close() -print(f" | ✂️ Disconnected from {TEXT_URL}") +with requests.get(TEXT_URL) as response: + print(f" | ✅ GET Response: {response.text}") print("-" * 80) print(f" | GET Full Response Test: {JSON_GET_URL}") -response = requests.get(JSON_GET_URL) -print(f" | ✅ Unparsed Full JSON Response: {response.json()}") -response.close() -print(f" | ✂️ Disconnected from {JSON_GET_URL}") +with requests.get(JSON_GET_URL) as response: + print(f" | ✅ Unparsed Full JSON Response: {response.json()}") print("-" * 80) DATA = "This is an example of a JSON value" print(f" | ✅ JSON 'value' POST Test: {JSON_POST_URL} {DATA}") -response = requests.post(JSON_POST_URL, data=DATA) -json_resp = response.json() -# Parse out the 'data' key from json_resp dict. -print(f" | ✅ JSON 'value' Response: {json_resp['data']}") -response.close() -print(f" | ✂️ Disconnected from {JSON_POST_URL}") +with requests.post(JSON_POST_URL, data=DATA) as response: + json_resp = response.json() + # Parse out the 'data' key from json_resp dict. + print(f" | ✅ JSON 'value' Response: {json_resp['data']}") print("-" * 80) json_data = {"Date": "January 1, 1970"} print(f" | ✅ JSON 'key':'value' POST Test: {JSON_POST_URL} {json_data}") -response = requests.post(JSON_POST_URL, json=json_data) -json_resp = response.json() -# Parse out the 'json' key from json_resp dict. -print(f" | ✅ JSON 'key':'value' Response: {json_resp['json']}") -response.close() -print(f" | ✂️ Disconnected from {JSON_POST_URL}") +with requests.post(JSON_POST_URL, json=json_data) as response: + json_resp = response.json() + # Parse out the 'json' key from json_resp dict. + print(f" | ✅ JSON 'key':'value' Response: {json_resp['json']}") print("-" * 80) print("Finished!") diff --git a/examples/wiznet5k/requests_wiznet5k_advanced.py b/examples/wiznet5k/requests_wiznet5k_advanced.py index e98e728..1f068ee 100644 --- a/examples/wiznet5k/requests_wiznet5k_advanced.py +++ b/examples/wiznet5k/requests_wiznet5k_advanced.py @@ -26,17 +26,14 @@ headers = {"user-agent": "blinka/1.0.0"} print("Fetching JSON data from %s..." % JSON_GET_URL) -response = requests.get(JSON_GET_URL, headers=headers) -print("-" * 60) +with requests.get(JSON_GET_URL, headers=headers) as response: + print("-" * 60) -json_data = response.json() -headers = json_data["headers"] -print("Response's Custom User-Agent Header: {0}".format(headers["User-Agent"])) -print("-" * 60) + json_data = response.json() + headers = json_data["headers"] + print("Response's Custom User-Agent Header: {0}".format(headers["User-Agent"])) + print("-" * 60) -# Read Response's HTTP status code -print("Response HTTP Status Code: ", response.status_code) -print("-" * 60) - -# Close, delete and collect the response data -response.close() + # Read Response's HTTP status code + print("Response HTTP Status Code: ", response.status_code) + print("-" * 60) diff --git a/examples/wiznet5k/requests_wiznet5k_simpletest.py b/examples/wiznet5k/requests_wiznet5k_simpletest.py index e35b5ab..74e060b 100644 --- a/examples/wiznet5k/requests_wiznet5k_simpletest.py +++ b/examples/wiznet5k/requests_wiznet5k_simpletest.py @@ -25,39 +25,31 @@ JSON_POST_URL = "http://httpbin.org/post" print("Fetching text from %s" % TEXT_URL) -response = requests.get(TEXT_URL) -print("-" * 40) - -print("Text Response: ", response.text) -print("-" * 40) -response.close() +with requests.get(TEXT_URL) as response: + print("-" * 40) + print("Text Response: ", response.text) + print("-" * 40) print("Fetching JSON data from %s" % JSON_GET_URL) -response = requests.get(JSON_GET_URL) -print("-" * 40) - -print("JSON Response: ", response.json()) -print("-" * 40) -response.close() +with requests.get(JSON_GET_URL) as response: + print("-" * 40) + print("JSON Response: ", response.json()) + print("-" * 40) data = "31F" print("POSTing data to {0}: {1}".format(JSON_POST_URL, data)) -response = requests.post(JSON_POST_URL, data=data) -print("-" * 40) - -json_resp = response.json() -# Parse out the 'data' key from json_resp dict. -print("Data received from server:", json_resp["data"]) -print("-" * 40) -response.close() +with requests.post(JSON_POST_URL, data=data) as response: + print("-" * 40) + json_resp = response.json() + # Parse out the 'data' key from json_resp dict. + print("Data received from server:", json_resp["data"]) + print("-" * 40) json_data = {"Date": "July 25, 2019"} print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data)) -response = requests.post(JSON_POST_URL, json=json_data) -print("-" * 40) - -json_resp = response.json() -# Parse out the 'json' key from json_resp dict. -print("JSON Data received from server:", json_resp["json"]) -print("-" * 40) -response.close() +with requests.post(JSON_POST_URL, json=json_data) as response: + print("-" * 40) + json_resp = response.json() + # Parse out the 'json' key from json_resp dict. + print("JSON Data received from server:", json_resp["json"]) + print("-" * 40) From e4646a5fb3983159fc94627f9a1184ebb152f49e Mon Sep 17 00:00:00 2001 From: foamyguy Date: Sun, 21 Apr 2024 10:00:39 -0500 Subject: [PATCH 266/305] cleanup, lint, format --- adafruit_requests.py | 24 ++++++++--------- .../expanded/requests_wifi_file_upload.py | 27 +++++++++++++++++++ 2 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 examples/wifi/expanded/requests_wifi_file_upload.py diff --git a/adafruit_requests.py b/adafruit_requests.py index 5a387bf..a756d5e 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -47,7 +47,6 @@ from adafruit_connection_manager import get_connection_manager if not sys.implementation.name == "circuitpython": - from io import FileIO from types import TracebackType from typing import Any, Dict, Optional, Type @@ -345,6 +344,14 @@ def iter_content(self, chunk_size: int = 1, decode_unicode: bool = False) -> byt self.close() +def _generate_boundary_str(): + hex_characters = "0123456789abcdef" + _boundary = "" + for _ in range(32): + _boundary += random.choice(hex_characters) + return _boundary + + class Session: """HTTP session that shares sockets and ssl context.""" @@ -396,13 +403,6 @@ def _send(socket: SocketType, data: bytes): def _send_as_bytes(self, socket: SocketType, data: str): return self._send(socket, bytes(data, "utf-8")) - def _generate_boundary_str(self): - hex_characters = "0123456789abcdef" - _boundary = "" - for i in range(32): - _boundary += random.choice(hex_characters) - return _boundary - def _send_header(self, socket, header, value): if value is None: return @@ -414,8 +414,7 @@ def _send_header(self, socket, header, value): self._send_as_bytes(socket, value) self._send(socket, b"\r\n") - # pylint: disable=too-many-arguments - def _send_request( + def _send_request( # pylint: disable=too-many-arguments self, socket: SocketType, host: str, @@ -425,7 +424,7 @@ def _send_request( data: Any, json: Any, files: Optional[Dict[str, tuple]], - ): + ): # pylint: disable=too-many-branches,too-many-locals,too-many-statements # Check headers self._check_headers(headers) @@ -464,8 +463,9 @@ def _send_request( supplied_headers = {header.lower() for header in headers} boundary_str = None + # pylint: disable=too-many-nested-blocks if files is not None and isinstance(files, dict): - boundary_str = self._generate_boundary_str() + boundary_str = _generate_boundary_str() content_type_header = f"multipart/form-data; boundary={boundary_str}" for fieldname in files.keys(): diff --git a/examples/wifi/expanded/requests_wifi_file_upload.py b/examples/wifi/expanded/requests_wifi_file_upload.py new file mode 100644 index 0000000..3ceaef0 --- /dev/null +++ b/examples/wifi/expanded/requests_wifi_file_upload.py @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries +# SPDX-License-Identifier: MIT + +import adafruit_connection_manager +import wifi + +import adafruit_requests + +URL = "https://httpbin.org/post" + +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) + +with open("raspi_snip.png", "rb") as file_handle: + files = { + "file": ( + "raspi_snip.png", + file_handle, + "image/png", + {"CustomHeader": "BlinkaRocks"}, + ), + "othervalue": (None, "HelloWorld"), + } + + with requests.post(URL, files=files) as resp: + print(resp.content) From ad7aaca81848829806b96592d1191e8fd2ab9407 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Sun, 21 Apr 2024 10:09:06 -0500 Subject: [PATCH 267/305] example file for upload --- .../expanded/requests_wifi_file_upload_image.png | Bin 0 -> 7615 bytes .../requests_wifi_file_upload_image.png.license | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 examples/wifi/expanded/requests_wifi_file_upload_image.png create mode 100644 examples/wifi/expanded/requests_wifi_file_upload_image.png.license diff --git a/examples/wifi/expanded/requests_wifi_file_upload_image.png b/examples/wifi/expanded/requests_wifi_file_upload_image.png new file mode 100644 index 0000000000000000000000000000000000000000..f08a15472271eba34ce6044e276bf3c86887609a GIT binary patch literal 7615 zcmV;w9YErVP)EX>4Tx04R}tkv&MmKpe$i(~2S$hZa$B$WWbH5EXIMDionYs1;guFuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|?BJy6A|?K>DYS_3;J6>}?mh0_0YbgZG%GL;Xu55t z5^*t;T@|}u5kLq7h+;@)mN6$uNqCO0d-(Wz7vovp=l&dhYR+PSPb8jYhG`RT5KnK~ z2Iqa^Fe}O`@i}qSqze*1a$RxxjdP*N0?!Pa>C`-Nm{=@yu+qV-Xlle$#1U1~DPPFA zta9Gstd*;*bx;1nU`}6I<~q$0B(R7jND!f*iW17O5u;Tn#X^eq;~xIure7kLLaq`R zITlcX2D#}6|AXJ%TKUNdHz^ngx?UXTV-)D#1sXNS`95}>#tGnm2CnqBzfuQgK1r{& zwa5|BzYSbmw>4!CxZD8-pA6ZQo06ZVkk13}XY@^3Aao1#uDQLn_Hp_Eq^Yaq4RCM> zj1?$*-Q(RooxS~grq$mMktcGqI`Y^~00006VoOIv00000008+zyMF)x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru=mZlMEdaT0=+giI02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{02`M{L_t(&-YuHRkDb?<-hXHKru%(&s9SYw9!OSk6e*FK zEZd6Jvfb`<(g~36AVCo1A4t~O2E9pu>;h!bK?g_>1V}FeG=hdZX}b-(ZOc+ivLsRz zXBJr`t5~f0j^BLF$)b9D76&-Pi}!t==O;e=^`E%DshHFyz7#m;5JDiOL`n$&)?$po zQ$Ef)oUvGIQ5J*{C=|wnR6;E+28UlcIt6$JDg{w3*pM;*`XBytR%mkfaEpKci~mi& z+~F`AU_B3~eB`8Jwh+wD^msJfr}8AVtI?(5eAMBeUVoo&hYLhf^3l_~{KZ$l;UIF1 z6G_#Hd3Ahh&&Hm&Bt0hI2V}>bl1?@`m?X#D;6i(AB zz)GN-uvrZGX!8ya4|edqh_}vN;q8_4gf;k1G1@<2x;ce*O4992k>v%-6G#LCu*Twy zMF4@)SkI%v;sB>5Ag~Uw4rdL{8H5xVVR2GooWTkK5=sT0gVl3qX?q!Y)VdSL9QIaI%H{3rNJvLDz70ck;V}P0nQl+Bt|(hSCWc~IuKMojBHLOYYJ^K zzCboSh%~i!SmnSI>>a!UEd73;$z+S3;u=NUTJryByO${>uvLlSC_Ps< zX^NKw6lsB#;7f@oB(k())hhvOkGonlENBxC&#pe$Azg`wg=A{mL<(jo5MjLPb!Xd&4iS>sLQ$KRc?Ou z9OnfHEzr4^l){0q#9@HVGRmT&s%s(?fmBSzO_UO3bxCMygb;X2Ba50k&4>`pdokhWf8h561jL96|DHCIyOSP&A2Lsbi&=y_aD;~9-$5y4#sG0LEF`CNz-Z8 zXBWA$dX9yu9sv7{c~)+-KOQg~kI2dbKZux$5-v>5Bb{VHCCtYiR*cV+VNDeYnqELz z7X)>U%xXHBC2$bU_K1^+BU97%eCqvU7D~wvU%SS7YliIykLkbMWu_D29Q6D)I;sHY z07ujaF!=;EF6rrnOYzBE* zbe417MY<%^RE*68t2}C#Vvxilq7z2=z+f`qsDH%st!Ipe#{`ptX)Vz*pc0OuDHzvd zY#i{_FL3?!8}w9r;&acpvJ`O`(7o?P&WMhs-116IRX<0BXb4Ir0 z$Q4Y+8TMq%YSiJw^=n*NJ%{u?CZ?qH1^s-C7x=`&C$1c!=QApE03xYzS&4MeNMZ_S zxVgW@$HNyKi;Asp?$e24blp&-b%~$X{Qi}1^Wmkd6f$7b?(*f{7Qg)FCa;Elk|aUn zXv7{p8xrV%g-#F8NIaz(ounixq!YB5ZueMe8;mjdP7?P9`r3J>lW7L&5Qj(9 zZBv&pt_{P&U_9`ZcUp%NgO^t%Iv~#`IIW4=Jq{^&IXt8hhD1rskcvjDMM6M6%1P@Q ztt^4_2m_B=f>4HhFyi)ud)(Z8#DSS$b%)ZF#C}Lt7HBod==m}4UAs&Y&+?QJpKLwg z_KPht?U?c#L`LBsS6rFf;DguSW@Bm%L&Y%}yVZdHc#oaEJ&MxOFH-ulMkNu`tqyBR zhu0cC&UIS^HJDN`$|f9DB|o|M1vg(lW~?l&cAImv%d7=$UQZU8Q4!5JCO}YBCAB9R zVQ>nh?aq!hU~@jz%Tty0g47vqDM6?%_Ve;~_B~0fL4LcztD^ zD@$ispIIWnXF|@v6g=49=2y4A?`94`?7}fn^4tG{d8U?$EM0yTSVT4W^Sevwloq1@*Wj zNMasjd;IkF=X|lV$xt~|;xo$n_+dmPBsK~#zF?eZ?2Y!>pY(Zn^n(4Y&v(vVWI3Kf zlr=$G)0R+#im@k1m1Ufz433|0WK)i1!JBhuxDZaEoF(!!BT=za9`p2gmo$+S($NTf zVy(IU{Xb+__tEcNy2jh9mk4S|vk_M=TxI>@H9%0gl6p9zXM8*%aY?|J2haGcyPvRW zNAwfPahc*{X)kseP6~2UA?kv_3urYO)Pcs7hP^W9%l#*uU0UL7bCJcF87?iaa7DF|}|6(=A3`&aLr2qem5i(wtxJ;wA=9gVc_v+gtp_CqL)NuTia#R@@*TPf#ej ztuFfdrOUKrlW|$$S_wyWM!!gDH6r3#5#Zq&g{>;eK$1zz&ZN&~am1u4nO>g54pR>H zUg7m-5T(Hz)p%)%uQg~vAsv;qJUZCs>ERAnrsjwcTt0t+?RvnoK|yY7TwYK~sM|h8 zD7bTcz|!FsAIzR(+6V&U(NGHMpujLz7VB9GSu&70X;BhnIeJ!3vv2!2U-0>ZFL?0$ zDY>b5|HfOqIk!QWXh^{rLpILn_xq$(f!}N}(G@4-Av!B*Br!*mA(0o-j6(dP!Wx6E z1=15ZDe&R|BA-e)0_(WgTV-f6jyG!_p1h*#H`yPID4e6wX)!z*@%(U)<7~`qIF0Z$ z&ZJ1|Xg6Zg#Bpd-PRg1p0Nw6zFzBOmYQ8>t&M$6#!PaO%>77dX7q@;x|Fx12mM-E; zMuYOFwK|le zlxEN%whA@Mxwf#zhu7ZW;P{Xy`3XfNnf1G9A6`Cx!QkKsr4_#NXyD-+h5qW!EpDA` zQ6wRrbu>`a#!(ann=fB*d?h3G0;0+iX`c(dC1?al>qvH=a8l=3C&-+k^iO4frabDR zBq$u++T*owfg3Zce7N*FZ6eCN#s!+H)h5SqOhrFU*;qNpVsC-8tT^tU;A@X=t4)KD z7uxaXpMFGps>2_?`CYCrT_!>C=;apAs{xN*y}$vbQ0SebeJW2t=uwSoLN7o`iPoAl zO>uycf{Yqb8&u(#k6L{1{B>r#vpn2+%J%*%`stXlDJT?p4UbN%#ZqsIjm9+J?yhlt zW|jJANZ6H3q~!kIbM77Pa#D=J3QSQl+nVD1@)|4MdCsaiXNYP;>@@4G8G0@t08o-w=@8urDLui&l+@PYX@!#k zc08fkXwVEI{6>VYHLj@8PT+Z(R10>B5kG$TCBNKxz@9A8vn^!gvwt#V;0#fqsYV0F zkH2Cf4ZnZ+9cJ2XTx}4gWjq`srepe(lqZ8De$YrL>XQD+ArD@?AWb4f6rnw>@$-@- z4%m$pWmysn$E*r@Z}u!_Y=q2fCe##($5$uYe7tp!ub*#nl#YnwnEB~B=Hi66&t76R zp5l_8CIZsQn8G=RNx*?D`O}Yn%>B_m<4%By8dOqHW*KRj({8spu1Z?#bKE{0(0_KD zwR7jW(QF`(Q^cg8>qShIX6P#J9B#8iMn_9*g=1x8SWgw-onGaS z-ur-V(%_)%vm537x6l5PTjeVbB7W@D5BXo8{gQ7cM?B94 z3^>(AcOE|^lQk}sq*eBJcD10K!Ay0*@{>zCOGyR4TXf3S9ye|+s-7QF@yKfwBu-#&f7 z*ZW%xLdPV6IuKat5K^L)!U#xh#n2gwrq8je`F~&hnz6DJ7_wrFDoP?J@vTE!ODH9= z_6emzREBaqA@l=0ar#@BWX#1)nn8e)0_7}S>CwNp#aX|}`x{qjqp8c1m!o5jWksn4 zMoV-z=16(B8eD#c{FSj^%p-r3?%oUFAAg8XuMUqN8v;$8{nkvAAA-mZSVI6Zaq^Sf8 zb8~1R@r`56YwJva?hgk zl4MY_m}_32Ugpy3I?YBCd`-LE=IxEkTwFYZaxk~k>+aObp5{sU0 zPGelj{p~IGJP6|kU3}J>vzWTTs}--!o#CCO4ektIQ56Mw0AA#wJ&m;%XX;Zgz7&K; zpvsDRRPxsQ-$ND^Ra&t+x5)eNd<$>yG2@q8)RQrO5Fi2%BP7azcan0cwZQLQyuszA zGYEX{kM}q{+Gn-XV>ND46*>JfCOp>JY04hh{6?<@2ov{PfXHY~~MYtUK~w_EI&JB-=^wR1#H@r#qE zy!d#;2bW*x?X@ekUCAH4_q$v>IM0)pFE}nn9F7JMN|G>PDV}GwGsoH90t8Un@m;1~ppKD8J@c^Mco*e8_dV;Dlgi_Ozior>rvM!1I z0R75MI1|LYy>@|@kH_2|@3L1zmd7&FNUog|h4T1#?a34P6l#Z|5?tZ~+Fvk*u^Cn-zE&u)Fj z=J=4Pl^~5mJv(73_xRDd4L;~@0FFaS{&w>&hq@;0Oq~W0gOtU%L+FKcIvv!FfAvrP zcy4N%)z%CY1uCs524m{FMg$%@43M5i)xgP^^_fNHM1)HVthF3xL%un9%BN2s^7-?} z+#T%mxH#se$QWqH*oTQUj9kS?Sq>%xwh#8$e6mUZ;E-OYL+JTDe!0cRoA>D3;aOiqBp=<-MkNC;sFL_y|495j& z=wq55I`R=njB%8uqcWDtRdl*dbmbWApOA`zXgXn3W_{UT@gth;7W#2|M7{YnS&{Oi3$L-X zyv*G43Kx!cxU;{*t8B=?6jVjY;#|U%pJ37o=X*@bf(I|2b8qK4bvGtVA_89%1qvrl z*$ZbKRw}eqM9L?fjF4H)N;BbHZ;mED4d3I#5B?tIRExT@%&P`dGC)*@Lz6L9HP3>Q z|NfKzL*0#;nrn0O;XUT(=UH7?MjnhA^#^p@9a_^ws=_P)2;W{^_R|qOYVG1fwV{{XVM~oz+W{-@mdca4|@AC0CU(skZ$g_;H%$c3( zvAnp*=AHW#yGOkH=35+1Mtpkn3&gcw{TEl%2JI|Smb2)GTu$b>(p%=t)I8m|Ll6Y` zLJ^{nML|?L;wWT7$$`kYJ9x!UzWkV1qa!K>wNeNn&_dyyz}iznMoY!(XV-aqX@m9l z3_TG-U6BimQxQ+IBTDP2#~IHKUvYc?Igckt^cxNphcGGVhA}Z3Z7sKM-RAQ74K|j~ z^6@91aGZ@f|Jr55jlcdAcM=M;)^y5>FgrzNyxI{;O~VU`gOFJHTsyPDa%Y<5W|yY) z@sapKQewzV&dY;s4v!BwK0YQ*bE={Q1dSx7*=}>@!UpZ6Lr6%0p`vC`Pq_c=2{&In zWV7f~2MV$hEfqpZCV7UCk}wSEG!mqBymK$WV$!Q!pbVg!vW%h zU;bCOuN3(MUWrJ{~oOv70c?GroR!k9$v_ zGNDE!Avu{ZibNuZ`zoDus-g@gT_K%PF?aeRg z&h|L7u}*(D;Ki$#gh>-2CE|xa{tqshn&#!)pU}@Hgsm0}vvaik zfF}>`6M7+@l60F>I9s!|xy5w1L%TJ_(`Qe}s+yUF9$wVoxPQbrFYp4NMyG|70`YHu z`#;=J7@FN4qhXG9dqrC>l8)C z!^cmUWMj^*Z{Re1efJ)5v%$*RS#}R!;l&X(4)O2)=1<*6cW%*|o2M`WPe+7dNECzw zL4ej8E#+x_X^kI-NGU0*5+}e51B4J1RZie($~;Gy(^OqKsPY2oAoK$UgAr0GDr?B= zQyRu8No^fjSrA1LjYfl$lM^03dc@kfvvj&seERw46sDp#KS#UMAu9{U>4YE*u>y>< hM9qY8m13pf{{w4`M Date: Sun, 21 Apr 2024 15:38:34 -0700 Subject: [PATCH 268/305] Update files to chunk --- adafruit_requests.py | 160 ++++++++++-------- .../expanded/requests_wifi_file_upload.py | 4 +- 2 files changed, 96 insertions(+), 68 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index a756d5e..9a69428 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -46,6 +46,8 @@ from adafruit_connection_manager import get_connection_manager +SEEK_END = 2 + if not sys.implementation.name == "circuitpython": from types import TracebackType from typing import Any, Dict, Optional, Type @@ -344,14 +346,6 @@ def iter_content(self, chunk_size: int = 1, decode_unicode: bool = False) -> byt self.close() -def _generate_boundary_str(): - hex_characters = "0123456789abcdef" - _boundary = "" - for _ in range(32): - _boundary += random.choice(hex_characters) - return _boundary - - class Session: """HTTP session that shares sockets and ssl context.""" @@ -366,6 +360,60 @@ def __init__( self._session_id = session_id self._last_response = None + def _build_boundary_data(self, files: dict): + boundary_string = self._build_boundary_string() + content_length = 0 + boundary_objects = [] + + for field_name, field_values in files.items(): + file_name = field_values[0] + file_data = field_values[1] + + boundary_data = f"--{boundary_string}\r\n" + boundary_data += f'Content-Disposition: form-data; name="{field_name}"; ' + if file_name is not None: + boundary_data += f'filename="{file_name}"' + boundary_data += "\r\n" + if len(field_values) >= 3: + file_content_type = field_values[2] + boundary_data += f"Content-Type: {file_content_type}\r\n" + if len(field_values) >= 4: + file_headers = field_values[3] + for file_header_key, file_header_value in file_headers.items(): + boundary_data += f"{file_header_key}: {file_header_value}\r\n" + boundary_data += "\r\n" + + content_length += len(boundary_data) + boundary_objects.append(boundary_data) + + if file_name is not None: + file_data.seek(0, SEEK_END) + content_length += file_data.tell() + file_data.seek(0) + boundary_objects.append(file_data) + boundary_data = "" + else: + boundary_data = file_data + + boundary_data += "\r\n" + content_length += len(boundary_data) + boundary_objects.append(boundary_data) + + boundary_data = f"--{boundary_string}--" + + content_length += len(boundary_data) + boundary_objects.append(boundary_data) + + return boundary_string, content_length, boundary_objects + + @staticmethod + def _build_boundary_string(): + hex_characters = "0123456789abcdef" + _boundary = "" + for _ in range(32): + _boundary += random.choice(hex_characters) + return _boundary + @staticmethod def _check_headers(headers: Dict[str, str]): if not isinstance(headers, dict): @@ -399,10 +447,31 @@ def _send(socket: SocketType, data: bytes): # Not EAGAIN; that was already handled. raise OSError(errno.EIO) total_sent += sent + return total_sent def _send_as_bytes(self, socket: SocketType, data: str): return self._send(socket, bytes(data, "utf-8")) + def _send_boundary_objects(self, socket: SocketType, boundary_objects: Any): + for boundary_object in boundary_objects: + if isinstance(boundary_object, str): + self._send_as_bytes(socket, boundary_object) + else: + chunk_size = 32 + if hasattr(boundary_object, "readinto"): + b = bytearray(chunk_size) + while True: + size = boundary_object.readinto(b) + if size == 0: + break + self._send(socket, b[:size]) + else: + while True: + b = boundary_object.read(chunk_size) + if len(b) == 0: + break + self._send(socket, b) + def _send_header(self, socket, header, value): if value is None: return @@ -440,6 +509,7 @@ def _send_request( # pylint: disable=too-many-arguments # If data is sent and it's a dict, set content type header and convert to string if data and isinstance(data, dict): + assert files is None content_type_header = "application/x-www-form-urlencoded" _post_data = "" for k in data: @@ -451,8 +521,18 @@ def _send_request( # pylint: disable=too-many-arguments if data and isinstance(data, str): data = bytes(data, "utf-8") - if data is None: - data = b"" + # If files are send, build data to send and calculate length + content_length = 0 + boundary_objects = None + if files and isinstance(files, dict): + boundary_string, content_length, boundary_objects = ( + self._build_boundary_data(files) + ) + content_type_header = f"multipart/form-data; boundary={boundary_string}" + else: + if data is None: + data = b"" + content_length = len(data) self._send_as_bytes(socket, method) self._send(socket, b" /") @@ -461,60 +541,6 @@ def _send_request( # pylint: disable=too-many-arguments # create lower-case supplied header list supplied_headers = {header.lower() for header in headers} - boundary_str = None - - # pylint: disable=too-many-nested-blocks - if files is not None and isinstance(files, dict): - boundary_str = _generate_boundary_str() - content_type_header = f"multipart/form-data; boundary={boundary_str}" - - for fieldname in files.keys(): - if not fieldname.endswith("-name"): - if files[fieldname][0] is not None: - file_content = files[fieldname][1].read() - - data += b"--" + boundary_str.encode() + b"\r\n" - data += ( - b'Content-Disposition: form-data; name="' - + fieldname.encode() - + b'"; filename="' - + files[fieldname][0].encode() - + b'"\r\n' - ) - if len(files[fieldname]) >= 3: - data += ( - b"Content-Type: " - + files[fieldname][2].encode() - + b"\r\n" - ) - if len(files[fieldname]) >= 4: - for custom_header_key in files[fieldname][3].keys(): - data += ( - custom_header_key.encode() - + b": " - + files[fieldname][3][custom_header_key].encode() - + b"\r\n" - ) - data += b"\r\n" - data += file_content + b"\r\n" - else: - # filename is None - data += b"--" + boundary_str.encode() + b"\r\n" - data += ( - b'Content-Disposition: form-data; name="' - + fieldname.encode() - + b'"; \r\n' - ) - if len(files[fieldname]) >= 3: - data += ( - b"Content-Type: " - + files[fieldname][2].encode() - + b"\r\n" - ) - data += b"\r\n" - data += files[fieldname][1].encode() + b"\r\n" - - data += b"--" + boundary_str.encode() + b"--" # Send headers if not "host" in supplied_headers: @@ -523,8 +549,8 @@ def _send_request( # pylint: disable=too-many-arguments self._send_header(socket, "User-Agent", "Adafruit CircuitPython") if content_type_header and not "content-type" in supplied_headers: self._send_header(socket, "Content-Type", content_type_header) - if data and not "content-length" in supplied_headers: - self._send_header(socket, "Content-Length", str(len(data))) + if (data or files) and not "content-length" in supplied_headers: + self._send_header(socket, "Content-Length", str(content_length)) # Iterate over keys to avoid tuple alloc for header in headers: self._send_header(socket, header, headers[header]) @@ -533,6 +559,8 @@ def _send_request( # pylint: disable=too-many-arguments # Send data if data: self._send(socket, bytes(data)) + elif boundary_objects: + self._send_boundary_objects(socket, boundary_objects) # pylint: disable=too-many-branches, too-many-statements, unused-argument, too-many-arguments, too-many-locals def request( diff --git a/examples/wifi/expanded/requests_wifi_file_upload.py b/examples/wifi/expanded/requests_wifi_file_upload.py index 3ceaef0..962f493 100644 --- a/examples/wifi/expanded/requests_wifi_file_upload.py +++ b/examples/wifi/expanded/requests_wifi_file_upload.py @@ -12,10 +12,10 @@ ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) requests = adafruit_requests.Session(pool, ssl_context) -with open("raspi_snip.png", "rb") as file_handle: +with open("requests_wifi_file_upload_image.png", "rb") as file_handle: files = { "file": ( - "raspi_snip.png", + "requests_wifi_file_upload_image.png", file_handle, "image/png", {"CustomHeader": "BlinkaRocks"}, From b175c9b290425091cb0bf4bb5d6e0c260ef0b868 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 21 Apr 2024 23:47:58 -0400 Subject: [PATCH 269/305] added with statement, changed status_test to status_test_url The variable is actually an http header status test so keeping it as url instead of json_url for that one is good. --- examples/wifi/requests_wifi_status_codes.py | 64 +++++++++++---------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/examples/wifi/requests_wifi_status_codes.py b/examples/wifi/requests_wifi_status_codes.py index 2214e92..a7d84d4 100644 --- a/examples/wifi/requests_wifi_status_codes.py +++ b/examples/wifi/requests_wifi_status_codes.py @@ -27,7 +27,9 @@ def print_http_status(code, description): """Returns HTTP status code and description""" if "100" <= code <= "103": print(f" | ✅ Status Test: {code} - {description}") - elif "200" <= code <= "299": + elif "200" == code: + print(f" | 🆗 Status Test: {code} - {description}") + elif "201" <= code <= "299": print(f" | ✅ Status Test: {code} - {description}") elif "300" <= code <= "600": print(f" | ❌ Status Test: {code} - {description}") @@ -103,7 +105,7 @@ def print_http_status(code, description): } JSON_GET_URL = "https://httpbin.org/get" -STATUS_TEST = "https://httpbin.org/status/" +STATUS_TEST_URL = "https://httpbin.org/status/" print(f"\nConnecting to {ssid}...") print(f"Signal Strength: {rssi}") @@ -118,33 +120,33 @@ def print_http_status(code, description): HEADERS = {"user-agent": "blinka/1.0.0"} print(f" | GET JSON: {JSON_GET_URL}") -response = requests.get(JSON_GET_URL, headers=HEADERS) - -json_data = response.json() -HEADERS = json_data["headers"] -print(f" | User-Agent: {HEADERS['User-Agent']}") - -# HTTP STATUS CODE TESTING -# https://developer.mozilla.org/en-US/docs/Web/HTTP/Status -STATUS_CODE = str(response.status_code) -STATUS_DESCRIPTION = http_status_codes.get(STATUS_CODE, "Unknown Status Code") -print_http_status(STATUS_CODE, STATUS_DESCRIPTION) -response.close() -print(f" | ✂️ Disconnected from {JSON_GET_URL}") -print(" | ") - -print(f" | Status Code Test: {STATUS_TEST}") -# Some return errors then confirm the error (that's a good thing) -# Demonstrates not all errors have the same behavior -# 300, 304, and 306 in particular -for codes in sorted(http_status_codes.keys(), key=int): - status_test_url = STATUS_TEST + codes - response = requests.get(status_test_url, headers=HEADERS) - SORT_STATUS_CODE = str(response.status_code) - SORT_STATUS_DESC = http_status_codes.get(SORT_STATUS_CODE, "Unknown Status Code") - print_http_status(SORT_STATUS_CODE, SORT_STATUS_DESC) +with requests.get(JSON_GET_URL, headers=HEADERS) as response: + json_data = response.json() + HEADERS = json_data["headers"] + print(f" | User-Agent: {HEADERS['User-Agent']}") + + # HTTP STATUS CODE TESTING + # https://developer.mozilla.org/en-US/docs/Web/HTTP/Status + STATUS_CODE = str(response.status_code) + STATUS_DESCRIPTION = http_status_codes.get(STATUS_CODE, "Unknown Status Code") + print_http_status(STATUS_CODE, STATUS_DESCRIPTION) response.close() - -print(f" | ✂️ Disconnected from {JSON_GET_URL}") - -print("Finished!") + print(f" | ✂️ Disconnected from {JSON_GET_URL}") + print(" | ") + + print(f" | Status Code Test: {STATUS_TEST_URL}") + # Some return errors then confirm the error (that's a good thing) + # Demonstrates not all errors have the same behavior + # 300, 304, and 306 in particular + for codes in sorted(http_status_codes.keys(), key=int): + header_status_test_url = STATUS_TEST_URL + codes + response = requests.get(header_status_test_url, headers=HEADERS) + SORT_STATUS_CODE = str(response.status_code) + SORT_STATUS_DESC = http_status_codes.get( + SORT_STATUS_CODE, "Unknown Status Code" + ) + print_http_status(SORT_STATUS_CODE, SORT_STATUS_DESC) + + print(f" | ✂️ Disconnected from {JSON_GET_URL}") + + print("Finished!") From 8a14e8dd353ba8eb1b3731c62351a5a71f77a5a2 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Mon, 22 Apr 2024 10:09:55 -0700 Subject: [PATCH 270/305] Add tests --- adafruit_requests.py | 54 +++---- tests/files/green_red.png | Bin 0 -> 125 bytes tests/files/green_red.png.license | 2 + tests/files/red_green.png | Bin 0 -> 123 bytes tests/files/red_green.png.license | 2 + tests/header_test.py | 2 +- tests/method_files.py | 238 ++++++++++++++++++++++++++++++ tests/method_test.py | 10 +- 8 files changed, 279 insertions(+), 29 deletions(-) create mode 100644 tests/files/green_red.png create mode 100644 tests/files/green_red.png.license create mode 100644 tests/files/red_green.png create mode 100644 tests/files/red_green.png.license create mode 100644 tests/method_files.py diff --git a/adafruit_requests.py b/adafruit_requests.py index 9a69428..2edce6e 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -360,19 +360,19 @@ def __init__( self._session_id = session_id self._last_response = None - def _build_boundary_data(self, files: dict): + def _build_boundary_data(self, files: dict): # pylint: disable=too-many-locals boundary_string = self._build_boundary_string() content_length = 0 boundary_objects = [] for field_name, field_values in files.items(): file_name = field_values[0] - file_data = field_values[1] + file_handle = field_values[1] boundary_data = f"--{boundary_string}\r\n" - boundary_data += f'Content-Disposition: form-data; name="{field_name}"; ' + boundary_data += f'Content-Disposition: form-data; name="{field_name}"' if file_name is not None: - boundary_data += f'filename="{file_name}"' + boundary_data += f'; filename="{file_name}"' boundary_data += "\r\n" if len(field_values) >= 3: file_content_type = field_values[2] @@ -386,20 +386,30 @@ def _build_boundary_data(self, files: dict): content_length += len(boundary_data) boundary_objects.append(boundary_data) - if file_name is not None: - file_data.seek(0, SEEK_END) - content_length += file_data.tell() - file_data.seek(0) - boundary_objects.append(file_data) + if hasattr(file_handle, "read"): + is_binary = False + try: + content = file_handle.read(1) + is_binary = isinstance(content, bytes) + except UnicodeError: + is_binary = False + + if not is_binary: + raise AttributeError("Files must be opened in binary mode") + + file_handle.seek(0, SEEK_END) + content_length += file_handle.tell() + file_handle.seek(0) + boundary_objects.append(file_handle) boundary_data = "" else: - boundary_data = file_data + boundary_data = file_handle boundary_data += "\r\n" content_length += len(boundary_data) boundary_objects.append(boundary_data) - boundary_data = f"--{boundary_string}--" + boundary_data = f"--{boundary_string}--\r\n" content_length += len(boundary_data) boundary_objects.append(boundary_data) @@ -417,7 +427,7 @@ def _build_boundary_string(): @staticmethod def _check_headers(headers: Dict[str, str]): if not isinstance(headers, dict): - raise AttributeError("headers must be in dict format") + raise AttributeError("Headers must be in dict format") for key, value in headers.items(): if isinstance(value, (str, bytes)) or value is None: @@ -447,7 +457,6 @@ def _send(socket: SocketType, data: bytes): # Not EAGAIN; that was already handled. raise OSError(errno.EIO) total_sent += sent - return total_sent def _send_as_bytes(self, socket: SocketType, data: str): return self._send(socket, bytes(data, "utf-8")) @@ -458,19 +467,12 @@ def _send_boundary_objects(self, socket: SocketType, boundary_objects: Any): self._send_as_bytes(socket, boundary_object) else: chunk_size = 32 - if hasattr(boundary_object, "readinto"): - b = bytearray(chunk_size) - while True: - size = boundary_object.readinto(b) - if size == 0: - break - self._send(socket, b[:size]) - else: - while True: - b = boundary_object.read(chunk_size) - if len(b) == 0: - break - self._send(socket, b) + b = bytearray(chunk_size) + while True: + size = boundary_object.readinto(b) + if size == 0: + break + self._send(socket, b[:size]) def _send_header(self, socket, header, value): if value is None: diff --git a/tests/files/green_red.png b/tests/files/green_red.png new file mode 100644 index 0000000000000000000000000000000000000000..7d8ddb37c20bcff4cbb43154844f21966c74bc44 GIT binary patch literal 125 zcmeAS@N?(olHy`uVBq!ia0vp^Od!kwBL7~QRScvUi-X*q7}lMWc?smOq&xaLGB9lH z=l+w(3gmMZctipf@f`+X#^d=bQhmdKI;Vst0G}=&`v3p{ literal 0 HcmV?d00001 diff --git a/tests/files/red_green.png.license b/tests/files/red_green.png.license new file mode 100644 index 0000000..d41b03e --- /dev/null +++ b/tests/files/red_green.png.license @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2024 Justin Myers +# SPDX-License-Identifier: Unlicense diff --git a/tests/header_test.py b/tests/header_test.py index 8bcb354..ddfd61a 100644 --- a/tests/header_test.py +++ b/tests/header_test.py @@ -11,7 +11,7 @@ def test_check_headers_not_dict(requests): with pytest.raises(AttributeError) as context: requests._check_headers("") - assert "headers must be in dict format" in str(context) + assert "Headers must be in dict format" in str(context) def test_check_headers_not_valid(requests): diff --git a/tests/method_files.py b/tests/method_files.py new file mode 100644 index 0000000..1e28242 --- /dev/null +++ b/tests/method_files.py @@ -0,0 +1,238 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +""" Post Tests """ +# pylint: disable=line-too-long + +from unittest import mock + +import mocket +import pytest + +""" +For building tests, you can use CPython requests with logging to see what should actuall get sent. + +import logging +import http.client +import requests + +def httpclient_logging_patch(level=logging.DEBUG): + logging.basicConfig(level=level) + + httpclient_logger = logging.getLogger("http.client") + + def httpclient_log(*args): + httpclient_logger.log(level, " ".join(args)) + + http.client.print = httpclient_log + http.client.HTTPConnection.debuglevel = 1 + +httpclient_logging_patch() + +URL = "https://httpbin.org/post" + +with open("tests/files/red_green.png", "rb") as file_1: + file_data = { + "file_1": ( + "red_green.png", + file_1, + "image/png", + { + "Key_1": "Value 1", + "Key_2": "Value 2", + "Key_3": "Value 3", + }, + ), + } + + print(requests.post(URL, files=file_data).json()) +""" + + +def test_post_files_text(sock, requests): + file_data = { + "key_4": (None, "Value 5"), + } + + requests._build_boundary_string = mock.Mock( + return_value="8cd45159712eeb914c049c717d3f4d75" + ) + requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data) + + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + sock.send.assert_has_calls( + [ + mock.call(b"Content-Type"), + mock.call(b": "), + mock.call( + b"multipart/form-data; boundary=8cd45159712eeb914c049c717d3f4d75" + ), + mock.call(b"\r\n"), + ] + ) + sock.send.assert_has_calls( + [ + mock.call( + b'--8cd45159712eeb914c049c717d3f4d75\r\nContent-Disposition: form-data; name="key_4"\r\n\r\n' + ), + mock.call(b"Value 5\r\n"), + mock.call(b"--8cd45159712eeb914c049c717d3f4d75--\r\n"), + ] + ) + + +def test_post_files_file(sock, requests): + with open("tests/files/red_green.png", "rb") as file_1: + file_data = { + "file_1": ( + "red_green.png", + file_1, + "image/png", + { + "Key_1": "Value 1", + "Key_2": "Value 2", + "Key_3": "Value 3", + }, + ), + } + + requests._build_boundary_string = mock.Mock( + return_value="e663061c5bfcc53139c8f68d016cbef3" + ) + requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data) + + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + sock.send.assert_has_calls( + [ + mock.call(b"Content-Type"), + mock.call(b": "), + mock.call( + b"multipart/form-data; boundary=e663061c5bfcc53139c8f68d016cbef3" + ), + mock.call(b"\r\n"), + ] + ) + sock.send.assert_has_calls( + [ + mock.call( + b'--e663061c5bfcc53139c8f68d016cbef3\r\nContent-Disposition: form-data; name="file_1"; filename="red_green.png"\r\nContent-Type: image/png\r\nKey_1: Value 1\r\nKey_2: Value 2\r\nKey_3: Value 3\r\n\r\n' + ), + mock.call( + b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x02\x00\x00\x00\x02\x08\x02\x00\x00\x00\xfd\xd4\x9a" + ), + mock.call( + b"s\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\x00\x00" + ), + mock.call( + b"\x00\tpHYs\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7o\xa8d\x00\x00\x00\x10IDAT\x18Wc\xf8\xcf" + ), + mock.call( + b"\xc0\x00\xc5\xff\x19\x18\x00\x1d\xf0\x03\xfd\x8fk\x13|\x00\x00\x00\x00IEND\xaeB`\x82" + ), + mock.call(b"\r\n"), + mock.call(b"--e663061c5bfcc53139c8f68d016cbef3--\r\n"), + ] + ) + + +def test_post_files_complex(sock, requests): + with open("tests/files/red_green.png", "rb") as file_1, open( + "tests/files/green_red.png", "rb" + ) as file_2: + file_data = { + "file_1": ( + "red_green.png", + file_1, + "image/png", + { + "Key_1": "Value 1", + "Key_2": "Value 2", + "Key_3": "Value 3", + }, + ), + "key_4": (None, "Value 5"), + "file_2": ( + "green_red.png", + file_2, + "image/png", + ), + "key_6": (None, "Value 6"), + } + + requests._build_boundary_string = mock.Mock( + return_value="e663061c5bfcc53139c8f68d016cbef3" + ) + requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data) + + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + sock.send.assert_has_calls( + [ + mock.call(b"Content-Type"), + mock.call(b": "), + mock.call( + b"multipart/form-data; boundary=e663061c5bfcc53139c8f68d016cbef3" + ), + mock.call(b"\r\n"), + ] + ) + sock.send.assert_has_calls( + [ + mock.call( + b'--e663061c5bfcc53139c8f68d016cbef3\r\nContent-Disposition: form-data; name="file_1"; filename="red_green.png"\r\nContent-Type: image/png\r\nKey_1: Value 1\r\nKey_2: Value 2\r\nKey_3: Value 3\r\n\r\n' + ), + mock.call( + b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x02\x00\x00\x00\x02\x08\x02\x00\x00\x00\xfd\xd4\x9a" + ), + mock.call( + b"s\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\x00\x00" + ), + mock.call( + b"\x00\tpHYs\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7o\xa8d\x00\x00\x00\x10IDAT\x18Wc\xf8\xcf" + ), + mock.call( + b"\xc0\x00\xc5\xff\x19\x18\x00\x1d\xf0\x03\xfd\x8fk\x13|\x00\x00\x00\x00IEND\xaeB`\x82" + ), + mock.call(b"\r\n"), + mock.call( + b'--e663061c5bfcc53139c8f68d016cbef3\r\nContent-Disposition: form-data; name="key_4"\r\n\r\n' + ), + mock.call(b"Value 5\r\n"), + mock.call( + b'--e663061c5bfcc53139c8f68d016cbef3\r\nContent-Disposition: form-data; name="file_2"; filename="green_red.png"\r\nContent-Type: image/png\r\n\r\n' + ), + mock.call( + b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x02\x00\x00\x00\x02\x08\x02\x00\x00\x00\xfd\xd4\x9a" + ), + mock.call( + b"s\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\x00\x00" + ), + mock.call( + b"\x00\tpHYs\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7o\xa8d\x00\x00\x00\x12IDAT\x18Wc`\xf8" + ), + mock.call( + b'\x0f\x84 \x92\x81\xe1?\x03\x00\x1d\xf0\x03\xfd\x88"uS\x00\x00\x00\x00IEND\xaeB`\x82' + ), + mock.call(b"\r\n"), + mock.call( + b'--e663061c5bfcc53139c8f68d016cbef3\r\nContent-Disposition: form-data; name="key_6"\r\n\r\n' + ), + mock.call(b"Value 6\r\n"), + mock.call(b"--e663061c5bfcc53139c8f68d016cbef3--\r\n"), + ] + ) + + +def test_post_files_not_binary(requests): + with open("tests/files/red_green.png", "r") as file_1: + file_data = { + "file_1": ( + "red_green.png", + file_1, + "image/png", + ), + } + + with pytest.raises(AttributeError) as context: + requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data) + assert "Files must be opened in binary mode" in str(context) diff --git a/tests/method_test.py b/tests/method_test.py index d75e754..1cda6c2 100644 --- a/tests/method_test.py +++ b/tests/method_test.py @@ -52,7 +52,10 @@ def test_post_string(sock, requests): def test_post_form(sock, requests): - data = {"Date": "July 25, 2019", "Time": "12:00"} + data = { + "Date": "July 25, 2019", + "Time": "12:00", + } requests.post("http://" + mocket.MOCK_HOST_1 + "/post", data=data) sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) sock.send.assert_has_calls( @@ -67,7 +70,10 @@ def test_post_form(sock, requests): def test_post_json(sock, requests): - json_data = {"Date": "July 25, 2019", "Time": "12:00"} + json_data = { + "Date": "July 25, 2019", + "Time": "12:00", + } requests.post("http://" + mocket.MOCK_HOST_1 + "/post", json=json_data) sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) sock.send.assert_has_calls( From d5614b2d32dfdd3774aaf10e44b372a68744d026 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Mon, 22 Apr 2024 10:25:34 -0700 Subject: [PATCH 271/305] Fix test file name --- tests/{method_files.py => files_test.py} | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) rename tests/{method_files.py => files_test.py} (93%) diff --git a/tests/method_files.py b/tests/files_test.py similarity index 93% rename from tests/method_files.py rename to tests/files_test.py index 1e28242..4985cac 100644 --- a/tests/method_files.py +++ b/tests/files_test.py @@ -71,6 +71,14 @@ def test_post_files_text(sock, requests): mock.call(b"\r\n"), ] ) + sock.send.assert_has_calls( + [ + mock.call(b"Content-Length"), + mock.call(b": "), + mock.call(b"131"), + mock.call(b"\r\n"), + ] + ) sock.send.assert_has_calls( [ mock.call( @@ -113,6 +121,14 @@ def test_post_files_file(sock, requests): mock.call(b"\r\n"), ] ) + sock.send.assert_has_calls( + [ + mock.call(b"Content-Length"), + mock.call(b": "), + mock.call(b"347"), + mock.call(b"\r\n"), + ] + ) sock.send.assert_has_calls( [ mock.call( @@ -176,6 +192,14 @@ def test_post_files_complex(sock, requests): mock.call(b"\r\n"), ] ) + sock.send.assert_has_calls( + [ + mock.call(b"Content-Length"), + mock.call(b": "), + mock.call(b"796"), + mock.call(b"\r\n"), + ] + ) sock.send.assert_has_calls( [ mock.call( From 0a772cf27286e981a30baa2342c827c53fd45030 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 23 Apr 2024 19:52:02 -0400 Subject: [PATCH 272/305] remove infinite loop, clean up serial output Using Justins suggested changes --- .../expanded/requests_wifi_api_queuetimes.py | 106 ++++++++---------- 1 file changed, 44 insertions(+), 62 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_queuetimes.py b/examples/wifi/expanded/requests_wifi_api_queuetimes.py index 2104c7e..599ddfa 100644 --- a/examples/wifi/expanded/requests_wifi_api_queuetimes.py +++ b/examples/wifi/expanded/requests_wifi_api_queuetimes.py @@ -40,67 +40,49 @@ def time_calc(input_time): qtimes_json = {} -while True: - now = time.monotonic() - # Connect to Wi-Fi - print("\n===============================") - print("Connecting to WiFi...") - while not wifi.radio.ipv4_address: - try: - wifi.radio.connect(ssid, password) - except ConnectionError as e: - print("❌ Connection Error:", e) - print("Retrying in 10 seconds") - print("✅ WiFi!") +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +while not wifi.radio.ipv4_address: try: - print(" | Attempting to GET Queue-Times JSON!") - try: - qtimes_response = requests.get(url=QTIMES_SOURCE) - qtimes_json = qtimes_response.json() - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - print(" | ✅ Queue-Times JSON!") - - DEBUG_QTIMES = False - if DEBUG_QTIMES: - print("Full API GET URL: ", QTIMES_SOURCE) - print(qtimes_json) - qtimes_response.close() - print("✂️ Disconnected from Queue-Times API") - - print("\nFinished!") - print(f"Board Uptime: {time_calc(time.monotonic())}") - print(f"Next Update: {time_calc(SLEEP_TIME)}") - print("===============================") - except (ValueError, RuntimeError) as e: - print("Failed to get data, retrying\n", e) - time.sleep(60) - break - - # Loop infinitely until its time to re-poll - if time.monotonic() - now <= SLEEP_TIME: - for land in qtimes_json["lands"]: - qtimes_lands = str(land["name"]) - print(f" | | Lands: {qtimes_lands}") - time.sleep(1) - - # Loop through each ride in the land - for ride in land["rides"]: - qtimes_rides = str(ride["name"]) - qtimes_queuetime = str(ride["wait_time"]) - qtimes_isopen = str(ride["is_open"]) - - print(f" | | Ride: {qtimes_rides}") - print(f" | | Queue Time: {qtimes_queuetime} Minutes") - if qtimes_isopen == "False": - print(" | | Status: Closed") - elif qtimes_isopen == "True": - print(" | | Status: Open") - else: - print(" | | Status: Unknown") - - time.sleep(1) # delay between list items - else: # When its time to poll, break to top of while True loop. - break + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") +print("✅ WiFi!") + +try: + with requests.get(url=QTIMES_SOURCE) as qtimes_response: + qtimes_json = qtimes_response.json() +except ConnectionError as e: + print("Connection Error:", e) +print(" | ✅ Queue-Times JSON\n") +DEBUG_QTIMES = False +if DEBUG_QTIMES: + print("Full API GET URL: ", QTIMES_SOURCE) + print(qtimes_json) +qtimes_response.close() + +# Poll Once and end script +for land in qtimes_json["lands"]: + qtimes_lands = str(land["name"]) + print(f" | Land: {qtimes_lands}") + time.sleep(1) + + # Loop through each ride in the land + for ride in land["rides"]: + qtimes_rides = str(ride["name"]) + qtimes_queuetime = str(ride["wait_time"]) + qtimes_isopen = str(ride["is_open"]) + + print(f" | | Ride: {qtimes_rides}") + print(f" | | Queue Time: {qtimes_queuetime} Minutes") + if qtimes_isopen == "False": + print(" | | Status: Closed\n") + elif qtimes_isopen == "True": + print(" | | Status: Open\n") + else: + print(" | | Status: Unknown\n") + + time.sleep(1) # delay between list items From a2b43acbff84adb4bd3731e86ae3e5f55b7993d2 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Wed, 24 Apr 2024 21:44:25 -0700 Subject: [PATCH 273/305] Update example to match others --- examples/wifi/expanded/requests_wifi_file_upload.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_file_upload.py b/examples/wifi/expanded/requests_wifi_file_upload.py index 962f493..bd9ac2a 100644 --- a/examples/wifi/expanded/requests_wifi_file_upload.py +++ b/examples/wifi/expanded/requests_wifi_file_upload.py @@ -23,5 +23,5 @@ "othervalue": (None, "HelloWorld"), } - with requests.post(URL, files=files) as resp: - print(resp.content) + with requests.post(URL, files=files) as response: + print(response.content) From 913c4c856f859d678856ec5a7bfc4b643d43f786 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 26 Apr 2024 20:54:02 -0500 Subject: [PATCH 274/305] remove files test for now --- tests/files/green_red.png | Bin 125 -> 0 bytes tests/files/green_red.png.license | 2 - tests/files/red_green.png | Bin 123 -> 0 bytes tests/files/red_green.png.license | 2 - tests/files_test.py | 262 ------------------------------ 5 files changed, 266 deletions(-) delete mode 100644 tests/files/green_red.png delete mode 100644 tests/files/green_red.png.license delete mode 100644 tests/files/red_green.png delete mode 100644 tests/files/red_green.png.license delete mode 100644 tests/files_test.py diff --git a/tests/files/green_red.png b/tests/files/green_red.png deleted file mode 100644 index 7d8ddb37c20bcff4cbb43154844f21966c74bc44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 125 zcmeAS@N?(olHy`uVBq!ia0vp^Od!kwBL7~QRScvUi-X*q7}lMWc?smOq&xaLGB9lH z=l+w(3gmMZctipf@f`+X#^d=bQhmdKI;Vst0G}=&`v3p{ diff --git a/tests/files/red_green.png.license b/tests/files/red_green.png.license deleted file mode 100644 index d41b03e..0000000 --- a/tests/files/red_green.png.license +++ /dev/null @@ -1,2 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Justin Myers -# SPDX-License-Identifier: Unlicense diff --git a/tests/files_test.py b/tests/files_test.py deleted file mode 100644 index 4985cac..0000000 --- a/tests/files_test.py +++ /dev/null @@ -1,262 +0,0 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -""" Post Tests """ -# pylint: disable=line-too-long - -from unittest import mock - -import mocket -import pytest - -""" -For building tests, you can use CPython requests with logging to see what should actuall get sent. - -import logging -import http.client -import requests - -def httpclient_logging_patch(level=logging.DEBUG): - logging.basicConfig(level=level) - - httpclient_logger = logging.getLogger("http.client") - - def httpclient_log(*args): - httpclient_logger.log(level, " ".join(args)) - - http.client.print = httpclient_log - http.client.HTTPConnection.debuglevel = 1 - -httpclient_logging_patch() - -URL = "https://httpbin.org/post" - -with open("tests/files/red_green.png", "rb") as file_1: - file_data = { - "file_1": ( - "red_green.png", - file_1, - "image/png", - { - "Key_1": "Value 1", - "Key_2": "Value 2", - "Key_3": "Value 3", - }, - ), - } - - print(requests.post(URL, files=file_data).json()) -""" - - -def test_post_files_text(sock, requests): - file_data = { - "key_4": (None, "Value 5"), - } - - requests._build_boundary_string = mock.Mock( - return_value="8cd45159712eeb914c049c717d3f4d75" - ) - requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data) - - sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) - sock.send.assert_has_calls( - [ - mock.call(b"Content-Type"), - mock.call(b": "), - mock.call( - b"multipart/form-data; boundary=8cd45159712eeb914c049c717d3f4d75" - ), - mock.call(b"\r\n"), - ] - ) - sock.send.assert_has_calls( - [ - mock.call(b"Content-Length"), - mock.call(b": "), - mock.call(b"131"), - mock.call(b"\r\n"), - ] - ) - sock.send.assert_has_calls( - [ - mock.call( - b'--8cd45159712eeb914c049c717d3f4d75\r\nContent-Disposition: form-data; name="key_4"\r\n\r\n' - ), - mock.call(b"Value 5\r\n"), - mock.call(b"--8cd45159712eeb914c049c717d3f4d75--\r\n"), - ] - ) - - -def test_post_files_file(sock, requests): - with open("tests/files/red_green.png", "rb") as file_1: - file_data = { - "file_1": ( - "red_green.png", - file_1, - "image/png", - { - "Key_1": "Value 1", - "Key_2": "Value 2", - "Key_3": "Value 3", - }, - ), - } - - requests._build_boundary_string = mock.Mock( - return_value="e663061c5bfcc53139c8f68d016cbef3" - ) - requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data) - - sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) - sock.send.assert_has_calls( - [ - mock.call(b"Content-Type"), - mock.call(b": "), - mock.call( - b"multipart/form-data; boundary=e663061c5bfcc53139c8f68d016cbef3" - ), - mock.call(b"\r\n"), - ] - ) - sock.send.assert_has_calls( - [ - mock.call(b"Content-Length"), - mock.call(b": "), - mock.call(b"347"), - mock.call(b"\r\n"), - ] - ) - sock.send.assert_has_calls( - [ - mock.call( - b'--e663061c5bfcc53139c8f68d016cbef3\r\nContent-Disposition: form-data; name="file_1"; filename="red_green.png"\r\nContent-Type: image/png\r\nKey_1: Value 1\r\nKey_2: Value 2\r\nKey_3: Value 3\r\n\r\n' - ), - mock.call( - b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x02\x00\x00\x00\x02\x08\x02\x00\x00\x00\xfd\xd4\x9a" - ), - mock.call( - b"s\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\x00\x00" - ), - mock.call( - b"\x00\tpHYs\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7o\xa8d\x00\x00\x00\x10IDAT\x18Wc\xf8\xcf" - ), - mock.call( - b"\xc0\x00\xc5\xff\x19\x18\x00\x1d\xf0\x03\xfd\x8fk\x13|\x00\x00\x00\x00IEND\xaeB`\x82" - ), - mock.call(b"\r\n"), - mock.call(b"--e663061c5bfcc53139c8f68d016cbef3--\r\n"), - ] - ) - - -def test_post_files_complex(sock, requests): - with open("tests/files/red_green.png", "rb") as file_1, open( - "tests/files/green_red.png", "rb" - ) as file_2: - file_data = { - "file_1": ( - "red_green.png", - file_1, - "image/png", - { - "Key_1": "Value 1", - "Key_2": "Value 2", - "Key_3": "Value 3", - }, - ), - "key_4": (None, "Value 5"), - "file_2": ( - "green_red.png", - file_2, - "image/png", - ), - "key_6": (None, "Value 6"), - } - - requests._build_boundary_string = mock.Mock( - return_value="e663061c5bfcc53139c8f68d016cbef3" - ) - requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data) - - sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) - sock.send.assert_has_calls( - [ - mock.call(b"Content-Type"), - mock.call(b": "), - mock.call( - b"multipart/form-data; boundary=e663061c5bfcc53139c8f68d016cbef3" - ), - mock.call(b"\r\n"), - ] - ) - sock.send.assert_has_calls( - [ - mock.call(b"Content-Length"), - mock.call(b": "), - mock.call(b"796"), - mock.call(b"\r\n"), - ] - ) - sock.send.assert_has_calls( - [ - mock.call( - b'--e663061c5bfcc53139c8f68d016cbef3\r\nContent-Disposition: form-data; name="file_1"; filename="red_green.png"\r\nContent-Type: image/png\r\nKey_1: Value 1\r\nKey_2: Value 2\r\nKey_3: Value 3\r\n\r\n' - ), - mock.call( - b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x02\x00\x00\x00\x02\x08\x02\x00\x00\x00\xfd\xd4\x9a" - ), - mock.call( - b"s\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\x00\x00" - ), - mock.call( - b"\x00\tpHYs\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7o\xa8d\x00\x00\x00\x10IDAT\x18Wc\xf8\xcf" - ), - mock.call( - b"\xc0\x00\xc5\xff\x19\x18\x00\x1d\xf0\x03\xfd\x8fk\x13|\x00\x00\x00\x00IEND\xaeB`\x82" - ), - mock.call(b"\r\n"), - mock.call( - b'--e663061c5bfcc53139c8f68d016cbef3\r\nContent-Disposition: form-data; name="key_4"\r\n\r\n' - ), - mock.call(b"Value 5\r\n"), - mock.call( - b'--e663061c5bfcc53139c8f68d016cbef3\r\nContent-Disposition: form-data; name="file_2"; filename="green_red.png"\r\nContent-Type: image/png\r\n\r\n' - ), - mock.call( - b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x02\x00\x00\x00\x02\x08\x02\x00\x00\x00\xfd\xd4\x9a" - ), - mock.call( - b"s\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\x00\x00" - ), - mock.call( - b"\x00\tpHYs\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7o\xa8d\x00\x00\x00\x12IDAT\x18Wc`\xf8" - ), - mock.call( - b'\x0f\x84 \x92\x81\xe1?\x03\x00\x1d\xf0\x03\xfd\x88"uS\x00\x00\x00\x00IEND\xaeB`\x82' - ), - mock.call(b"\r\n"), - mock.call( - b'--e663061c5bfcc53139c8f68d016cbef3\r\nContent-Disposition: form-data; name="key_6"\r\n\r\n' - ), - mock.call(b"Value 6\r\n"), - mock.call(b"--e663061c5bfcc53139c8f68d016cbef3--\r\n"), - ] - ) - - -def test_post_files_not_binary(requests): - with open("tests/files/red_green.png", "r") as file_1: - file_data = { - "file_1": ( - "red_green.png", - file_1, - "image/png", - ), - } - - with pytest.raises(AttributeError) as context: - requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data) - assert "Files must be opened in binary mode" in str(context) From 7c45cdde710e01c3f8a5192cffdce2d6793f1253 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Sat, 27 Apr 2024 09:34:35 -0500 Subject: [PATCH 275/305] merge main. remove single request before status codes loop. Use with context inside status codes loop. Add expected code to print out. --- .../requests_wifi_status_codes.py | 78 +++++++++---------- 1 file changed, 37 insertions(+), 41 deletions(-) rename examples/wifi/{ => expanded}/requests_wifi_status_codes.py (62%) diff --git a/examples/wifi/requests_wifi_status_codes.py b/examples/wifi/expanded/requests_wifi_status_codes.py similarity index 62% rename from examples/wifi/requests_wifi_status_codes.py rename to examples/wifi/expanded/requests_wifi_status_codes.py index a7d84d4..036b7bb 100644 --- a/examples/wifi/requests_wifi_status_codes.py +++ b/examples/wifi/expanded/requests_wifi_status_codes.py @@ -6,6 +6,7 @@ """ WiFi Status Codes Example """ import os +import time import adafruit_connection_manager import wifi @@ -23,18 +24,29 @@ rssi = wifi.radio.ap_info.rssi -def print_http_status(code, description): +def print_http_status(expected_code, actual_code, description): """Returns HTTP status code and description""" - if "100" <= code <= "103": - print(f" | ✅ Status Test: {code} - {description}") - elif "200" == code: - print(f" | 🆗 Status Test: {code} - {description}") - elif "201" <= code <= "299": - print(f" | ✅ Status Test: {code} - {description}") - elif "300" <= code <= "600": - print(f" | ❌ Status Test: {code} - {description}") + if "100" <= actual_code <= "103": + print( + f" | ✅ Status Test Expected: {expected_code} Actual: {actual_code} - {description}" + ) + elif "200" == actual_code: + print( + f" | 🆗 Status Test Expected: {expected_code} Actual: {actual_code} - {description}" + ) + elif "201" <= actual_code <= "299": + print( + f" | ✅ Status Test Expected: {expected_code} Actual: {actual_code} - {description}" + ) + elif "300" <= actual_code <= "600": + print( + f" | ❌ Status Test Expected: {expected_code} Actual: {actual_code} - {description}" + ) else: - print(f" | Unknown Response Status: {code} - {description}") + print( + f" | Unknown Response Status Expected: {expected_code} " + + f"Actual: {actual_code} - {description}" + ) # All HTTP Status Codes @@ -104,7 +116,6 @@ def print_http_status(code, description): "511": "Network Authentication Required", } -JSON_GET_URL = "https://httpbin.org/get" STATUS_TEST_URL = "https://httpbin.org/status/" print(f"\nConnecting to {ssid}...") @@ -116,37 +127,22 @@ def print_http_status(code, description): print(f"❌ OSError: {e}") print("✅ Wifi!") -# Define a custom header as a dict. -HEADERS = {"user-agent": "blinka/1.0.0"} - -print(f" | GET JSON: {JSON_GET_URL}") -with requests.get(JSON_GET_URL, headers=HEADERS) as response: - json_data = response.json() - HEADERS = json_data["headers"] - print(f" | User-Agent: {HEADERS['User-Agent']}") - # HTTP STATUS CODE TESTING - # https://developer.mozilla.org/en-US/docs/Web/HTTP/Status - STATUS_CODE = str(response.status_code) - STATUS_DESCRIPTION = http_status_codes.get(STATUS_CODE, "Unknown Status Code") - print_http_status(STATUS_CODE, STATUS_DESCRIPTION) - response.close() - print(f" | ✂️ Disconnected from {JSON_GET_URL}") - print(" | ") - - print(f" | Status Code Test: {STATUS_TEST_URL}") - # Some return errors then confirm the error (that's a good thing) - # Demonstrates not all errors have the same behavior - # 300, 304, and 306 in particular - for codes in sorted(http_status_codes.keys(), key=int): - header_status_test_url = STATUS_TEST_URL + codes - response = requests.get(header_status_test_url, headers=HEADERS) - SORT_STATUS_CODE = str(response.status_code) +print(f" | Status Code Test: {STATUS_TEST_URL}") +# Some return errors then confirm the error (that's a good thing) +# Demonstrates not all errors have the same behavior +# Some 300 level responses contain redirects that requests automatically follows +# By default the response object will contain the status code from the final +# response after all redirect, so it can differ from the expected status code. +for current_code in sorted(http_status_codes.keys(), key=int): + header_status_test_url = STATUS_TEST_URL + current_code + with requests.get(header_status_test_url) as response: + response_status_code = str(response.status_code) SORT_STATUS_DESC = http_status_codes.get( - SORT_STATUS_CODE, "Unknown Status Code" + response_status_code, "Unknown Status Code" ) - print_http_status(SORT_STATUS_CODE, SORT_STATUS_DESC) - - print(f" | ✂️ Disconnected from {JSON_GET_URL}") + print_http_status(current_code, response_status_code, SORT_STATUS_DESC) - print("Finished!") + # Rate limit ourselves a little to avoid strain on server + time.sleep(0.5) +print("Finished!") From 7c8d2b241e34dd15b7f9ae4065268a0d36bf17fa Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 27 Apr 2024 11:23:46 -0700 Subject: [PATCH 276/305] Add tests --- requirements.txt | 1 + tests/files/green_red.png | Bin 0 -> 125 bytes tests/files/green_red.png.license | 2 + tests/files/red_green.png | Bin 0 -> 123 bytes tests/files/red_green.png.license | 2 + tests/files_test.py | 205 ++++++++++++++++++++++++++++++ 6 files changed, 210 insertions(+) create mode 100644 tests/files/green_red.png create mode 100644 tests/files/green_red.png.license create mode 100644 tests/files/red_green.png create mode 100644 tests/files/red_green.png.license create mode 100644 tests/files_test.py diff --git a/requirements.txt b/requirements.txt index 2505288..ae35f68 100755 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ Adafruit-Blinka Adafruit-Circuitpython-ConnectionManager +requests diff --git a/tests/files/green_red.png b/tests/files/green_red.png new file mode 100644 index 0000000000000000000000000000000000000000..7d8ddb37c20bcff4cbb43154844f21966c74bc44 GIT binary patch literal 125 zcmeAS@N?(olHy`uVBq!ia0vp^Od!kwBL7~QRScvUi-X*q7}lMWc?smOq&xaLGB9lH z=l+w(3gmMZctipf@f`+X#^d=bQhmdKI;Vst0G}=&`v3p{ literal 0 HcmV?d00001 diff --git a/tests/files/red_green.png.license b/tests/files/red_green.png.license new file mode 100644 index 0000000..d41b03e --- /dev/null +++ b/tests/files/red_green.png.license @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2024 Justin Myers +# SPDX-License-Identifier: Unlicense diff --git a/tests/files_test.py b/tests/files_test.py new file mode 100644 index 0000000..32f69ae --- /dev/null +++ b/tests/files_test.py @@ -0,0 +1,205 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +""" Post Tests """ +# pylint: disable=line-too-long + +import re +from unittest import mock + +import mocket +import pytest +import requests as python_requests + + +@pytest.fixture +def log_stream(): + return [] + + +@pytest.fixture +def post_url(): + return "https://httpbin.org/post" + + +@pytest.fixture +def request_logging(log_stream): + """Reset the ConnectionManager, since it's a singlton and will hold data""" + import http.client # pylint: disable=import-outside-toplevel + + def httpclient_log(*args): + log_stream.append(args) + + http.client.print = httpclient_log + http.client.HTTPConnection.debuglevel = 1 + + +def get_actual_request_data(log_stream): + boundary_pattern = r"(?<=boundary=)(.\w*)" + + boundary = "" + actual_request_post = "" + for log in log_stream: + for log_arg in log: + boundary_search = re.findall(boundary_pattern, log_arg) + if boundary_search: + boundary = boundary_search[0] + elif "Content-Disposition" in log_arg: + # this will look like: + # b\'{content}\' + # and escapped characters look like: + # \\r + post_data = log_arg[2:-1] + post_bytes = post_data.encode("utf-8") + post_unescaped = post_bytes.decode("unicode_escape") + actual_request_post = post_unescaped.encode("latin1") + + return boundary, actual_request_post + + +def test_post_files_text( # pylint: disable=unused-argument + sock, requests, log_stream, post_url, request_logging +): + file_data = { + "key_4": (None, "Value 5"), + } + + python_requests.post(post_url, files=file_data) + boundary, actual_request_post = get_actual_request_data(log_stream) + + requests._build_boundary_string = mock.Mock(return_value=boundary) + requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data) + + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + sock.send.assert_has_calls( + [ + mock.call(b"Content-Type"), + mock.call(b": "), + mock.call(f"multipart/form-data; boundary={boundary}".encode()), + mock.call(b"\r\n"), + ] + ) + sock.send.assert_has_calls( + [ + mock.call(b"Content-Length"), + mock.call(b": "), + mock.call(b"131"), + mock.call(b"\r\n"), + ] + ) + + sent = b"".join(sock.sent_data) + assert sent.endswith(actual_request_post) + + +def test_post_files_file( # pylint: disable=unused-argument + sock, requests, log_stream, post_url, request_logging +): + with open("tests/files/red_green.png", "rb") as file_1: + file_data = { + "file_1": ( + "red_green.png", + file_1, + "image/png", + { + "Key_1": "Value 1", + "Key_2": "Value 2", + "Key_3": "Value 3", + }, + ), + } + + python_requests.post(post_url, files=file_data) + boundary, actual_request_post = get_actual_request_data(log_stream) + + requests._build_boundary_string = mock.Mock(return_value=boundary) + requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data) + + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + sock.send.assert_has_calls( + [ + mock.call(b"Content-Type"), + mock.call(b": "), + mock.call(f"multipart/form-data; boundary={boundary}".encode()), + mock.call(b"\r\n"), + ] + ) + sock.send.assert_has_calls( + [ + mock.call(b"Content-Length"), + mock.call(b": "), + mock.call(b"347"), + mock.call(b"\r\n"), + ] + ) + sent = b"".join(sock.sent_data) + assert sent.endswith(actual_request_post) + + +def test_post_files_complex( # pylint: disable=unused-argument + sock, requests, log_stream, post_url, request_logging +): + with open("tests/files/red_green.png", "rb") as file_1, open( + "tests/files/green_red.png", "rb" + ) as file_2: + file_data = { + "file_1": ( + "red_green.png", + file_1, + "image/png", + { + "Key_1": "Value 1", + "Key_2": "Value 2", + "Key_3": "Value 3", + }, + ), + "key_4": (None, "Value 5"), + "file_2": ( + "green_red.png", + file_2, + "image/png", + ), + "key_6": (None, "Value 6"), + } + + python_requests.post(post_url, files=file_data) + boundary, actual_request_post = get_actual_request_data(log_stream) + + requests._build_boundary_string = mock.Mock(return_value=boundary) + requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data) + + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + sock.send.assert_has_calls( + [ + mock.call(b"Content-Type"), + mock.call(b": "), + mock.call(f"multipart/form-data; boundary={boundary}".encode()), + mock.call(b"\r\n"), + ] + ) + sock.send.assert_has_calls( + [ + mock.call(b"Content-Length"), + mock.call(b": "), + mock.call(b"796"), + mock.call(b"\r\n"), + ] + ) + sent = b"".join(sock.sent_data) + assert sent.endswith(actual_request_post) + + +def test_post_files_not_binary(requests): + with open("tests/files/red_green.png", "r") as file_1: + file_data = { + "file_1": ( + "red_green.png", + file_1, + "image/png", + ), + } + + with pytest.raises(AttributeError) as context: + requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data) + assert "Files must be opened in binary mode" in str(context) From 1eaa1a816e568573578197e3f89f153c1318e1fb Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 27 Apr 2024 11:38:26 -0700 Subject: [PATCH 277/305] Use actual content length too --- tests/files_test.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/tests/files_test.py b/tests/files_test.py index 32f69ae..fe6e77c 100644 --- a/tests/files_test.py +++ b/tests/files_test.py @@ -37,15 +37,20 @@ def httpclient_log(*args): def get_actual_request_data(log_stream): boundary_pattern = r"(?<=boundary=)(.\w*)" + content_length_pattern = r"(?<=Content-Length: )(.\d*)" boundary = "" actual_request_post = "" + content_length = "" for log in log_stream: for log_arg in log: boundary_search = re.findall(boundary_pattern, log_arg) + content_length_search = re.findall(content_length_pattern, log_arg) if boundary_search: boundary = boundary_search[0] - elif "Content-Disposition" in log_arg: + if content_length_search: + content_length = content_length_search[0] + if "Content-Disposition" in log_arg: # this will look like: # b\'{content}\' # and escapped characters look like: @@ -55,7 +60,7 @@ def get_actual_request_data(log_stream): post_unescaped = post_bytes.decode("unicode_escape") actual_request_post = post_unescaped.encode("latin1") - return boundary, actual_request_post + return boundary, content_length, actual_request_post def test_post_files_text( # pylint: disable=unused-argument @@ -66,7 +71,7 @@ def test_post_files_text( # pylint: disable=unused-argument } python_requests.post(post_url, files=file_data) - boundary, actual_request_post = get_actual_request_data(log_stream) + boundary, content_length, actual_request_post = get_actual_request_data(log_stream) requests._build_boundary_string = mock.Mock(return_value=boundary) requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data) @@ -84,7 +89,7 @@ def test_post_files_text( # pylint: disable=unused-argument [ mock.call(b"Content-Length"), mock.call(b": "), - mock.call(b"131"), + mock.call(content_length.encode()), mock.call(b"\r\n"), ] ) @@ -111,7 +116,9 @@ def test_post_files_file( # pylint: disable=unused-argument } python_requests.post(post_url, files=file_data) - boundary, actual_request_post = get_actual_request_data(log_stream) + boundary, content_length, actual_request_post = get_actual_request_data( + log_stream + ) requests._build_boundary_string = mock.Mock(return_value=boundary) requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data) @@ -129,7 +136,7 @@ def test_post_files_file( # pylint: disable=unused-argument [ mock.call(b"Content-Length"), mock.call(b": "), - mock.call(b"347"), + mock.call(content_length.encode()), mock.call(b"\r\n"), ] ) @@ -164,7 +171,9 @@ def test_post_files_complex( # pylint: disable=unused-argument } python_requests.post(post_url, files=file_data) - boundary, actual_request_post = get_actual_request_data(log_stream) + boundary, content_length, actual_request_post = get_actual_request_data( + log_stream + ) requests._build_boundary_string = mock.Mock(return_value=boundary) requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data) @@ -182,7 +191,7 @@ def test_post_files_complex( # pylint: disable=unused-argument [ mock.call(b"Content-Length"), mock.call(b": "), - mock.call(b"796"), + mock.call(content_length.encode()), mock.call(b"\r\n"), ] ) From c32d58255cf304ceb3390d813e031360c3735aeb Mon Sep 17 00:00:00 2001 From: foamyguy Date: Sun, 28 Apr 2024 16:46:48 -0500 Subject: [PATCH 278/305] move printout inside try block. remove sleeps. remove close(). --- .../expanded/requests_wifi_api_queuetimes.py | 58 +++++++++---------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_api_queuetimes.py b/examples/wifi/expanded/requests_wifi_api_queuetimes.py index 599ddfa..b6fecaf 100644 --- a/examples/wifi/expanded/requests_wifi_api_queuetimes.py +++ b/examples/wifi/expanded/requests_wifi_api_queuetimes.py @@ -4,7 +4,6 @@ """Queue-Times.com API Example""" import os -import time import adafruit_connection_manager import wifi @@ -55,34 +54,33 @@ def time_calc(input_time): try: with requests.get(url=QTIMES_SOURCE) as qtimes_response: qtimes_json = qtimes_response.json() + + print(" | ✅ Queue-Times JSON\n") + DEBUG_QTIMES = False + if DEBUG_QTIMES: + print("Full API GET URL: ", QTIMES_SOURCE) + print(qtimes_json) + + # Poll Once and end script + for land in qtimes_json["lands"]: + qtimes_lands = str(land["name"]) + print(f" | Land: {qtimes_lands}") + + # Loop through each ride in the land + for ride in land["rides"]: + qtimes_rides = str(ride["name"]) + qtimes_queuetime = str(ride["wait_time"]) + qtimes_isopen = str(ride["is_open"]) + + print(f" | | Ride: {qtimes_rides}") + print(f" | | Queue Time: {qtimes_queuetime} Minutes") + if qtimes_isopen == "False": + print(" | | Status: Closed\n") + elif qtimes_isopen == "True": + print(" | | Status: Open\n") + else: + print(" | | Status: Unknown\n") + + except ConnectionError as e: print("Connection Error:", e) -print(" | ✅ Queue-Times JSON\n") -DEBUG_QTIMES = False -if DEBUG_QTIMES: - print("Full API GET URL: ", QTIMES_SOURCE) - print(qtimes_json) -qtimes_response.close() - -# Poll Once and end script -for land in qtimes_json["lands"]: - qtimes_lands = str(land["name"]) - print(f" | Land: {qtimes_lands}") - time.sleep(1) - - # Loop through each ride in the land - for ride in land["rides"]: - qtimes_rides = str(ride["name"]) - qtimes_queuetime = str(ride["wait_time"]) - qtimes_isopen = str(ride["is_open"]) - - print(f" | | Ride: {qtimes_rides}") - print(f" | | Queue Time: {qtimes_queuetime} Minutes") - if qtimes_isopen == "False": - print(" | | Status: Closed\n") - elif qtimes_isopen == "True": - print(" | | Status: Open\n") - else: - print(" | | Status: Unknown\n") - - time.sleep(1) # delay between list items From 9b9f7bcd6f04baec298bd3b676914f22afb08ac7 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 29 Apr 2024 08:31:22 -0500 Subject: [PATCH 279/305] move requests to optional. change copyright name in new test file. add timeouts to python_requests.post. --- optional_requirements.txt | 2 ++ requirements.txt | 1 - tests/files_test.py | 12 ++++++------ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/optional_requirements.txt b/optional_requirements.txt index d4e27c4..38e5c0c 100644 --- a/optional_requirements.txt +++ b/optional_requirements.txt @@ -1,3 +1,5 @@ # SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries # # SPDX-License-Identifier: Unlicense + +requests diff --git a/requirements.txt b/requirements.txt index ae35f68..2505288 100755 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,3 @@ Adafruit-Blinka Adafruit-Circuitpython-ConnectionManager -requests diff --git a/tests/files_test.py b/tests/files_test.py index fe6e77c..8299b1b 100644 --- a/tests/files_test.py +++ b/tests/files_test.py @@ -1,8 +1,8 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2024 Justin Myers # # SPDX-License-Identifier: Unlicense -""" Post Tests """ +""" Post Files Tests """ # pylint: disable=line-too-long import re @@ -53,7 +53,7 @@ def get_actual_request_data(log_stream): if "Content-Disposition" in log_arg: # this will look like: # b\'{content}\' - # and escapped characters look like: + # and escaped characters look like: # \\r post_data = log_arg[2:-1] post_bytes = post_data.encode("utf-8") @@ -70,7 +70,7 @@ def test_post_files_text( # pylint: disable=unused-argument "key_4": (None, "Value 5"), } - python_requests.post(post_url, files=file_data) + python_requests.post(post_url, files=file_data, timeout=30) boundary, content_length, actual_request_post = get_actual_request_data(log_stream) requests._build_boundary_string = mock.Mock(return_value=boundary) @@ -115,7 +115,7 @@ def test_post_files_file( # pylint: disable=unused-argument ), } - python_requests.post(post_url, files=file_data) + python_requests.post(post_url, files=file_data, timeout=30) boundary, content_length, actual_request_post = get_actual_request_data( log_stream ) @@ -170,7 +170,7 @@ def test_post_files_complex( # pylint: disable=unused-argument "key_6": (None, "Value 6"), } - python_requests.post(post_url, files=file_data) + python_requests.post(post_url, files=file_data, timeout=30) boundary, content_length, actual_request_post = get_actual_request_data( log_stream ) From 5a2934e48517bbb926d41e9fd29018323115a9e7 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Wed, 1 May 2024 20:19:03 -0500 Subject: [PATCH 280/305] use os.urandom() --- adafruit_requests.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 2edce6e..16666cc 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -41,7 +41,7 @@ import errno import json as json_module -import random +import os import sys from adafruit_connection_manager import get_connection_manager @@ -418,11 +418,7 @@ def _build_boundary_data(self, files: dict): # pylint: disable=too-many-locals @staticmethod def _build_boundary_string(): - hex_characters = "0123456789abcdef" - _boundary = "" - for _ in range(32): - _boundary += random.choice(hex_characters) - return _boundary + return os.urandom(16).hex() @staticmethod def _check_headers(headers: Dict[str, str]): From 0f9b71fa42808af5b30a6d60068447b69a077316 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Fri, 3 May 2024 13:15:47 -0700 Subject: [PATCH 281/305] Remove str concatenation --- adafruit_requests.py | 36 ++++++++++++++++-------------------- tox.ini | 2 ++ 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 16666cc..7dd3462 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -369,22 +369,22 @@ def _build_boundary_data(self, files: dict): # pylint: disable=too-many-locals file_name = field_values[0] file_handle = field_values[1] - boundary_data = f"--{boundary_string}\r\n" - boundary_data += f'Content-Disposition: form-data; name="{field_name}"' + boundary_objects.append( + f'--{boundary_string}\r\nContent-Disposition: form-data; name="{field_name}"' + ) if file_name is not None: - boundary_data += f'; filename="{file_name}"' - boundary_data += "\r\n" + boundary_objects.append(f'; filename="{file_name}"') + boundary_objects.append("\r\n") if len(field_values) >= 3: file_content_type = field_values[2] - boundary_data += f"Content-Type: {file_content_type}\r\n" + boundary_objects.append(f"Content-Type: {file_content_type}\r\n") if len(field_values) >= 4: file_headers = field_values[3] for file_header_key, file_header_value in file_headers.items(): - boundary_data += f"{file_header_key}: {file_header_value}\r\n" - boundary_data += "\r\n" - - content_length += len(boundary_data) - boundary_objects.append(boundary_data) + boundary_objects.append( + f"{file_header_key}: {file_header_value}\r\n" + ) + boundary_objects.append("\r\n") if hasattr(file_handle, "read"): is_binary = False @@ -400,19 +400,15 @@ def _build_boundary_data(self, files: dict): # pylint: disable=too-many-locals file_handle.seek(0, SEEK_END) content_length += file_handle.tell() file_handle.seek(0) - boundary_objects.append(file_handle) - boundary_data = "" - else: - boundary_data = file_handle - boundary_data += "\r\n" - content_length += len(boundary_data) - boundary_objects.append(boundary_data) + boundary_objects.append(file_handle) + boundary_objects.append("\r\n") - boundary_data = f"--{boundary_string}--\r\n" + boundary_objects.append(f"--{boundary_string}--\r\n") - content_length += len(boundary_data) - boundary_objects.append(boundary_data) + for boundary_object in boundary_objects: + if isinstance(boundary_object, str): + content_length += len(boundary_object) return boundary_string, content_length, boundary_objects diff --git a/tox.ini b/tox.ini index 85530c9..099a9b7 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,7 @@ envlist = py311 description = run tests deps = pytest==7.4.3 + requests commands = pytest [testenv:coverage] @@ -17,6 +18,7 @@ description = run coverage deps = pytest==7.4.3 pytest-cov==4.1.0 + requests package = editable commands = coverage run --source=. --omit=tests/* --branch {posargs} -m pytest From c45bb144d7d12b1e3792bf706212ef432429e549 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 11 May 2024 09:19:51 -0700 Subject: [PATCH 282/305] Update test exceptions --- tests/protocol_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/protocol_test.py b/tests/protocol_test.py index 4fd7770..333bc31 100644 --- a/tests/protocol_test.py +++ b/tests/protocol_test.py @@ -11,7 +11,7 @@ def test_get_https_no_ssl(requests): - with pytest.raises(AttributeError): + with pytest.raises(ValueError): requests.get("https://" + mocket.MOCK_ENDPOINT_1) From 0c88f54157a1a3b519be170db4f2c499dd2ff52c Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Fri, 17 May 2024 18:54:18 -0700 Subject: [PATCH 283/305] Update Exceptions --- adafruit_requests.py | 6 +++--- tests/files_test.py | 2 +- tests/header_test.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 93fa943..8b0539e 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -395,7 +395,7 @@ def _build_boundary_data(self, files: dict): # pylint: disable=too-many-locals is_binary = False if not is_binary: - raise AttributeError("Files must be opened in binary mode") + raise ValueError("Files must be opened in binary mode") file_handle.seek(0, SEEK_END) content_length += file_handle.tell() @@ -419,12 +419,12 @@ def _build_boundary_string(): @staticmethod def _check_headers(headers: Dict[str, str]): if not isinstance(headers, dict): - raise AttributeError("Headers must be in dict format") + raise TypeError("Headers must be in dict format") for key, value in headers.items(): if isinstance(value, (str, bytes)) or value is None: continue - raise AttributeError( + raise TypeError( f"Header part ({value}) from {key} must be of type str or bytes, not {type(value)}" ) diff --git a/tests/files_test.py b/tests/files_test.py index 8299b1b..8cac77c 100644 --- a/tests/files_test.py +++ b/tests/files_test.py @@ -209,6 +209,6 @@ def test_post_files_not_binary(requests): ), } - with pytest.raises(AttributeError) as context: + with pytest.raises(ValueError) as context: requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data) assert "Files must be opened in binary mode" in str(context) diff --git a/tests/header_test.py b/tests/header_test.py index ddfd61a..196deb6 100644 --- a/tests/header_test.py +++ b/tests/header_test.py @@ -9,13 +9,13 @@ def test_check_headers_not_dict(requests): - with pytest.raises(AttributeError) as context: + with pytest.raises(TypeError) as context: requests._check_headers("") assert "Headers must be in dict format" in str(context) def test_check_headers_not_valid(requests): - with pytest.raises(AttributeError) as context: + with pytest.raises(TypeError) as context: requests._check_headers( {"Good1": "a", "Good2": b"b", "Good3": None, "Bad1": True} ) From 29ada91c80dfcbd25a13397d8a59d5ecd6b034fb Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Wed, 19 Jun 2024 12:50:31 -0700 Subject: [PATCH 284/305] Add ability to post a file via the data param --- adafruit_requests.py | 60 ++++++++++++++++++++++++++++---------------- tests/files_test.py | 24 +++++++++++++++++- 2 files changed, 61 insertions(+), 23 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 8b0539e..6231185 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -50,7 +50,7 @@ if not sys.implementation.name == "circuitpython": from types import TracebackType - from typing import Any, Dict, Optional, Type + from typing import IO, Any, Dict, Optional, Type from circuitpython_typing.socket import ( SocketpoolModuleType, @@ -387,19 +387,7 @@ def _build_boundary_data(self, files: dict): # pylint: disable=too-many-locals boundary_objects.append("\r\n") if hasattr(file_handle, "read"): - is_binary = False - try: - content = file_handle.read(1) - is_binary = isinstance(content, bytes) - except UnicodeError: - is_binary = False - - if not is_binary: - raise ValueError("Files must be opened in binary mode") - - file_handle.seek(0, SEEK_END) - content_length += file_handle.tell() - file_handle.seek(0) + content_length += self._get_file_length(file_handle) boundary_objects.append(file_handle) boundary_objects.append("\r\n") @@ -428,6 +416,25 @@ def _check_headers(headers: Dict[str, str]): f"Header part ({value}) from {key} must be of type str or bytes, not {type(value)}" ) + @staticmethod + def _get_file_length(file_handle: IO): + is_binary = False + try: + file_handle.seek(0) + # read at least 4 bytes incase we are reading a b64 stream + content = file_handle.read(4) + is_binary = isinstance(content, bytes) + except UnicodeError: + is_binary = False + + if not is_binary: + raise ValueError("Files must be opened in binary mode") + + file_handle.seek(0, SEEK_END) + content_length = file_handle.tell() + file_handle.seek(0) + return content_length + @staticmethod def _send(socket: SocketType, data: bytes): total_sent = 0 @@ -458,13 +465,16 @@ def _send_boundary_objects(self, socket: SocketType, boundary_objects: Any): if isinstance(boundary_object, str): self._send_as_bytes(socket, boundary_object) else: - chunk_size = 32 - b = bytearray(chunk_size) - while True: - size = boundary_object.readinto(b) - if size == 0: - break - self._send(socket, b[:size]) + self._send_file(socket, boundary_object) + + def _send_file(self, socket: SocketType, file_handle: IO): + chunk_size = 36 + b = bytearray(chunk_size) + while True: + size = file_handle.readinto(b) + if size == 0: + break + self._send(socket, b[:size]) def _send_header(self, socket, header, value): if value is None: @@ -517,12 +527,16 @@ def _send_request( # pylint: disable=too-many-arguments # If files are send, build data to send and calculate length content_length = 0 + data_is_file = False boundary_objects = None if files and isinstance(files, dict): boundary_string, content_length, boundary_objects = ( self._build_boundary_data(files) ) content_type_header = f"multipart/form-data; boundary={boundary_string}" + elif data and hasattr(data, "read"): + data_is_file = True + content_length = self._get_file_length(data) else: if data is None: data = b"" @@ -551,7 +565,9 @@ def _send_request( # pylint: disable=too-many-arguments self._send(socket, b"\r\n") # Send data - if data: + if data_is_file: + self._send_file(socket, data) + elif data: self._send(socket, bytes(data)) elif boundary_objects: self._send_boundary_objects(socket, boundary_objects) diff --git a/tests/files_test.py b/tests/files_test.py index 8cac77c..4026b9d 100644 --- a/tests/files_test.py +++ b/tests/files_test.py @@ -50,7 +50,7 @@ def get_actual_request_data(log_stream): boundary = boundary_search[0] if content_length_search: content_length = content_length_search[0] - if "Content-Disposition" in log_arg: + if "Content-Disposition" in log_arg or "\\x" in log_arg: # this will look like: # b\'{content}\' # and escaped characters look like: @@ -63,6 +63,28 @@ def get_actual_request_data(log_stream): return boundary, content_length, actual_request_post +def test_post_file_as_data( # pylint: disable=unused-argument + requests, sock, log_stream, post_url, request_logging +): + with open("tests/files/red_green.png", "rb") as file_1: + python_requests.post(post_url, data=file_1, timeout=30) + __, content_length, actual_request_post = get_actual_request_data(log_stream) + + requests.post("http://" + mocket.MOCK_HOST_1 + "/post", data=file_1) + + sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80)) + sock.send.assert_has_calls( + [ + mock.call(b"Content-Length"), + mock.call(b": "), + mock.call(content_length.encode()), + mock.call(b"\r\n"), + ] + ) + sent = b"".join(sock.sent_data) + assert sent.endswith(actual_request_post) + + def test_post_files_text( # pylint: disable=unused-argument sock, requests, log_stream, post_url, request_logging ): From dca4c8ab3638c6af715da096396cfd0e3d0aa030 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 23 Jun 2024 07:52:00 -0400 Subject: [PATCH 285/305] Add Rachio Irrigation API Example Example pulls data from your Rachio Irrigation Timer. This is just a basic example. More can be done with zone scheduling, actually running the irrigation system, etc.. will work on an advanced example in the future. This should be good enough for very basic communication with the API and timer. --- .../requests_wifi_rachio_irrigation.py | 224 ++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 examples/wifi/expanded/requests_wifi_rachio_irrigation.py diff --git a/examples/wifi/expanded/requests_wifi_rachio_irrigation.py b/examples/wifi/expanded/requests_wifi_rachio_irrigation.py new file mode 100644 index 0000000..83f7208 --- /dev/null +++ b/examples/wifi/expanded/requests_wifi_rachio_irrigation.py @@ -0,0 +1,224 @@ +# SPDX-FileCopyrightText: 2024 DJDevon3 +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 9.x +"""Rachio Irrigation Timer API Example""" + +import os +import time + +import adafruit_connection_manager +import wifi + +import adafruit_requests + +# Rachio API Key required (comes with purchase of a device) +# API is rate limited to 1700 calls per day. +# https://support.rachio.com/en_us/public-api-documentation-S1UydL1Fv +# https://rachio.readme.io/reference/getting-started +RACHIO_KEY = os.getenv("RACHIO_APIKEY") + +# Get WiFi details, ensure these are setup in settings.toml +ssid = os.getenv("CIRCUITPY_WIFI_SSID") +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") + +# API Polling Rate +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +SLEEP_TIME = 900 + +# Set debug to True for full JSON response. +# WARNING: absolutely shows extremely sensitive personal information & credentials +# Including your real name, latitude, longitude, account id, mac address, etc... +DEBUG = False + +# Initalize Wifi, Socket Pool, Request Session +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) +requests = adafruit_requests.Session(pool, ssl_context) + +RACHIO_HEADER = {"Authorization": " Bearer " + RACHIO_KEY} +RACHIO_SOURCE = "https://api.rach.io/1/public/person/info/" +RACHIO_PERSON_SOURCE = "https://api.rach.io/1/public/person/" + + +def obfuscating_asterix(obfuscate_object, direction, characters=2): + """ + Obfuscates a string with asterisks except for a specified number of characters. + param object: str The string to obfuscate with asterisks + param direction: str Option either 'prepend', 'append', or 'all' direction + param characters: int The number of characters to keep unobfuscated (default is 2) + """ + object_len = len(obfuscate_object) + if direction not in {"prepend", "append", "all"}: + raise ValueError("Invalid direction. Use 'prepend', 'append', or 'all'.") + if characters >= object_len and direction != "all": + # If characters greater than or equal to string length, + # return the original string as it can't be obfuscated. + return obfuscate_object + asterix_replace = "*" * object_len + if direction == "append": + asterix_final = obfuscate_object[:characters] + "*" * (object_len - characters) + elif direction == "prepend": + asterix_final = "*" * (object_len - characters) + obfuscate_object[-characters:] + elif direction == "all": + # Replace all characters with asterisks + asterix_final = asterix_replace + + return asterix_final + + +def time_calc(input_time): + """Converts seconds to minutes/hours/days""" + if input_time < 60: + return f"{input_time:.0f} seconds" + if input_time < 3600: + return f"{input_time / 60:.0f} minutes" + if input_time < 86400: + return f"{input_time / 60 / 60:.0f} hours" + return f"{input_time / 60 / 60 / 24:.1f} days" + + +def _format_datetime(datetime): + """F-String formatted struct time conversion""" + return ( + f"{datetime.tm_mon:02}/" + + f"{datetime.tm_mday:02}/" + + f"{datetime.tm_year:02} " + + f"{datetime.tm_hour:02}:" + + f"{datetime.tm_min:02}:" + + f"{datetime.tm_sec:02}" + ) + + +while True: + # Connect to Wi-Fi + print("\nConnecting to WiFi...") + while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(ssid, password) + except ConnectionError as e: + print("❌ Connection Error:", e) + print("Retrying in 10 seconds") + print("✅ Wifi!") + + try: + print(" | Attempting to GET Rachio Authorization") + try: + with requests.get( + url=RACHIO_SOURCE, headers=RACHIO_HEADER + ) as rachio_response: + rachio_json = rachio_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + print(" | ✅ Authorized") + + rachio_id = rachio_json["id"] + if DEBUG: + print(" | | Person ID: ", rachio_id) + print(" | | This ID will be used for subsequent calls") + print("\nFull API GET URL: ", RACHIO_SOURCE) + print(rachio_json) + + except (ValueError, RuntimeError) as e: + print(f"Failed to get data, retrying\n {e}") + time.sleep(60) + break + + try: + print(" | Attempting to GET Rachio JSON") + try: + with requests.get( + url=RACHIO_PERSON_SOURCE + rachio_id, headers=RACHIO_HEADER + ) as rachio_response: + rachio_json = rachio_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + print(" | ✅ Rachio JSON") + + rachio_id = rachio_json["id"] + rachio_id_ast = obfuscating_asterix(rachio_id, "append", 3) + print(" | | UserID: ", rachio_id_ast) + + rachio_username = rachio_json["username"] + rachio_username_ast = obfuscating_asterix(rachio_username, "append", 3) + print(" | | Username: ", rachio_username_ast) + + rachio_name = rachio_json["fullName"] + rachio_name_ast = obfuscating_asterix(rachio_name, "append", 3) + print(" | | Full Name: ", rachio_name_ast) + + rachio_deleted = rachio_json["deleted"] + if not rachio_deleted: + print(" | | Account Status: Active") + else: + print(" | | Account Status?: Deleted!") + + rachio_createdate = rachio_json["createDate"] + rachio_timezone_offset = rachio_json["devices"][0]["utcOffset"] + # Rachio Unix time is in milliseconds, convert to seconds + rachio_createdate_seconds = rachio_createdate // 1000 + rachio_timezone_offset_seconds = rachio_timezone_offset // 1000 + # Apply timezone offset in seconds + local_unix_time = rachio_createdate_seconds + rachio_timezone_offset_seconds + if DEBUG: + print(f" | | Unix Registration Date: {rachio_createdate}") + print(f" | | Unix Timezone Offset: {rachio_timezone_offset}") + current_struct_time = time.localtime(local_unix_time) + final_timestamp = "{}".format(_format_datetime(current_struct_time)) + print(f" | | Registration Date: {final_timestamp}") + + rachio_devices = rachio_json["devices"][0]["name"] + print(" | | Device: ", rachio_devices) + + rachio_model = rachio_json["devices"][0]["model"] + print(" | | | Model: ", rachio_model) + + rachio_serial = rachio_json["devices"][0]["serialNumber"] + rachio_serial_ast = obfuscating_asterix(rachio_serial, "append") + print(" | | | Serial Number: ", rachio_serial_ast) + + rachio_mac = rachio_json["devices"][0]["macAddress"] + rachio_mac_ast = obfuscating_asterix(rachio_mac, "append") + print(" | | | MAC Address: ", rachio_mac_ast) + + rachio_status = rachio_json["devices"][0]["status"] + print(" | | | Device Status: ", rachio_status) + + rachio_timezone = rachio_json["devices"][0]["timeZone"] + print(" | | | Time Zone: ", rachio_timezone) + + # Latitude & Longtitude are used for smart watering & rain delays + rachio_latitude = str(rachio_json["devices"][0]["latitude"]) + rachio_lat_ast = obfuscating_asterix(rachio_latitude, "all") + print(" | | | Latitude: ", rachio_lat_ast) + + rachio_longitude = str(rachio_json["devices"][0]["longitude"]) + rachio_long_ast = obfuscating_asterix(rachio_longitude, "all") + print(" | | | Latitude: ", rachio_long_ast) + + rachio_rainsensor = rachio_json["devices"][0]["rainSensorTripped"] + print(" | | | Rain Sensor: ", rachio_rainsensor) + + rachio_zone0 = rachio_json["devices"][0]["zones"][0]["name"] + rachio_zone1 = rachio_json["devices"][0]["zones"][1]["name"] + rachio_zone2 = rachio_json["devices"][0]["zones"][2]["name"] + rachio_zone3 = rachio_json["devices"][0]["zones"][3]["name"] + zones = f"{rachio_zone0}, {rachio_zone1}, {rachio_zone2}, {rachio_zone3}" + print(f" | | | Zones: {zones}") + + if DEBUG: + print(f"\nFull API GET URL: {RACHIO_PERSON_SOURCE+rachio_id}") + print(rachio_json) + + print("\nFinished!") + print(f"Board Uptime: {time_calc(time.monotonic())}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") + print("===============================") + + except (ValueError, RuntimeError) as e: + print(f"Failed to get data, retrying\n {e}") + time.sleep(60) + break + + time.sleep(SLEEP_TIME) From 0d9d6dc0655015b27380eec0f14903066f81aacb Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Sun, 23 Jun 2024 08:29:55 -0400 Subject: [PATCH 286/305] mixed up a variable name for printing quick simple fix for a misspelling --- examples/wifi/expanded/requests_wifi_rachio_irrigation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wifi/expanded/requests_wifi_rachio_irrigation.py b/examples/wifi/expanded/requests_wifi_rachio_irrigation.py index 83f7208..f2394e1 100644 --- a/examples/wifi/expanded/requests_wifi_rachio_irrigation.py +++ b/examples/wifi/expanded/requests_wifi_rachio_irrigation.py @@ -195,7 +195,7 @@ def _format_datetime(datetime): rachio_longitude = str(rachio_json["devices"][0]["longitude"]) rachio_long_ast = obfuscating_asterix(rachio_longitude, "all") - print(" | | | Latitude: ", rachio_long_ast) + print(" | | | Longitude: ", rachio_long_ast) rachio_rainsensor = rachio_json["devices"][0]["rainSensorTripped"] print(" | | | Rain Sensor: ", rachio_rainsensor) From 0244f02d97f9d1161efe1985cd2bcc6a3db6a252 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Fri, 28 Jun 2024 03:49:06 -0400 Subject: [PATCH 287/305] Updated with response headers with rate limits Because I was making 2 calls per request it cut down the total daily calls from 1700 to 850. By forcing a user to first get the PERSONID and paste it into settings.toml that allows for a true 1700 limit by making 1 request per run instead of 2. --- .../requests_wifi_rachio_irrigation.py | 262 ++++++++++-------- 1 file changed, 144 insertions(+), 118 deletions(-) diff --git a/examples/wifi/expanded/requests_wifi_rachio_irrigation.py b/examples/wifi/expanded/requests_wifi_rachio_irrigation.py index f2394e1..2b8650d 100644 --- a/examples/wifi/expanded/requests_wifi_rachio_irrigation.py +++ b/examples/wifi/expanded/requests_wifi_rachio_irrigation.py @@ -16,6 +16,7 @@ # https://support.rachio.com/en_us/public-api-documentation-S1UydL1Fv # https://rachio.readme.io/reference/getting-started RACHIO_KEY = os.getenv("RACHIO_APIKEY") +RACHIO_PERSONID = os.getenv("RACHIO_PERSONID") # Get WiFi details, ensure these are setup in settings.toml ssid = os.getenv("CIRCUITPY_WIFI_SSID") @@ -100,125 +101,150 @@ def _format_datetime(datetime): print("Retrying in 10 seconds") print("✅ Wifi!") - try: - print(" | Attempting to GET Rachio Authorization") + # RETREIVE PERSONID AND PASTE IT TO SETTINGS.TOML + if RACHIO_PERSONID is None or RACHIO_PERSONID == "": try: - with requests.get( - url=RACHIO_SOURCE, headers=RACHIO_HEADER - ) as rachio_response: - rachio_json = rachio_response.json() - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - print(" | ✅ Authorized") - - rachio_id = rachio_json["id"] - if DEBUG: - print(" | | Person ID: ", rachio_id) - print(" | | This ID will be used for subsequent calls") - print("\nFull API GET URL: ", RACHIO_SOURCE) - print(rachio_json) - - except (ValueError, RuntimeError) as e: - print(f"Failed to get data, retrying\n {e}") - time.sleep(60) - break - - try: - print(" | Attempting to GET Rachio JSON") - try: - with requests.get( - url=RACHIO_PERSON_SOURCE + rachio_id, headers=RACHIO_HEADER - ) as rachio_response: - rachio_json = rachio_response.json() - except ConnectionError as e: - print("Connection Error:", e) - print("Retrying in 10 seconds") - print(" | ✅ Rachio JSON") - - rachio_id = rachio_json["id"] - rachio_id_ast = obfuscating_asterix(rachio_id, "append", 3) - print(" | | UserID: ", rachio_id_ast) - - rachio_username = rachio_json["username"] - rachio_username_ast = obfuscating_asterix(rachio_username, "append", 3) - print(" | | Username: ", rachio_username_ast) - - rachio_name = rachio_json["fullName"] - rachio_name_ast = obfuscating_asterix(rachio_name, "append", 3) - print(" | | Full Name: ", rachio_name_ast) - - rachio_deleted = rachio_json["deleted"] - if not rachio_deleted: - print(" | | Account Status: Active") - else: - print(" | | Account Status?: Deleted!") - - rachio_createdate = rachio_json["createDate"] - rachio_timezone_offset = rachio_json["devices"][0]["utcOffset"] - # Rachio Unix time is in milliseconds, convert to seconds - rachio_createdate_seconds = rachio_createdate // 1000 - rachio_timezone_offset_seconds = rachio_timezone_offset // 1000 - # Apply timezone offset in seconds - local_unix_time = rachio_createdate_seconds + rachio_timezone_offset_seconds - if DEBUG: - print(f" | | Unix Registration Date: {rachio_createdate}") - print(f" | | Unix Timezone Offset: {rachio_timezone_offset}") - current_struct_time = time.localtime(local_unix_time) - final_timestamp = "{}".format(_format_datetime(current_struct_time)) - print(f" | | Registration Date: {final_timestamp}") - - rachio_devices = rachio_json["devices"][0]["name"] - print(" | | Device: ", rachio_devices) - - rachio_model = rachio_json["devices"][0]["model"] - print(" | | | Model: ", rachio_model) - - rachio_serial = rachio_json["devices"][0]["serialNumber"] - rachio_serial_ast = obfuscating_asterix(rachio_serial, "append") - print(" | | | Serial Number: ", rachio_serial_ast) - - rachio_mac = rachio_json["devices"][0]["macAddress"] - rachio_mac_ast = obfuscating_asterix(rachio_mac, "append") - print(" | | | MAC Address: ", rachio_mac_ast) - - rachio_status = rachio_json["devices"][0]["status"] - print(" | | | Device Status: ", rachio_status) - - rachio_timezone = rachio_json["devices"][0]["timeZone"] - print(" | | | Time Zone: ", rachio_timezone) - - # Latitude & Longtitude are used for smart watering & rain delays - rachio_latitude = str(rachio_json["devices"][0]["latitude"]) - rachio_lat_ast = obfuscating_asterix(rachio_latitude, "all") - print(" | | | Latitude: ", rachio_lat_ast) - - rachio_longitude = str(rachio_json["devices"][0]["longitude"]) - rachio_long_ast = obfuscating_asterix(rachio_longitude, "all") - print(" | | | Longitude: ", rachio_long_ast) - - rachio_rainsensor = rachio_json["devices"][0]["rainSensorTripped"] - print(" | | | Rain Sensor: ", rachio_rainsensor) - - rachio_zone0 = rachio_json["devices"][0]["zones"][0]["name"] - rachio_zone1 = rachio_json["devices"][0]["zones"][1]["name"] - rachio_zone2 = rachio_json["devices"][0]["zones"][2]["name"] - rachio_zone3 = rachio_json["devices"][0]["zones"][3]["name"] - zones = f"{rachio_zone0}, {rachio_zone1}, {rachio_zone2}, {rachio_zone3}" - print(f" | | | Zones: {zones}") - - if DEBUG: - print(f"\nFull API GET URL: {RACHIO_PERSON_SOURCE+rachio_id}") - print(rachio_json) - + print(" | Attempting to GET Rachio Authorization") + try: + with requests.get( + url=RACHIO_SOURCE, headers=RACHIO_HEADER + ) as rachio_response: + rachio_json = rachio_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + print(" | ✅ Authorized") + + rachio_id = rachio_json["id"] + print("\nADD THIS 🔑 TO YOUR SETTINGS.TOML FILE!") + print(f'RACHIO_PERSONID = "{rachio_id}"') + + if DEBUG: + print("\nFull API GET URL: ", RACHIO_SOURCE) + print(rachio_json) + + except (ValueError, RuntimeError) as e: + print(f"Failed to GET data: {e}") + time.sleep(60) + break + print( + "\nThis script can only continue when a proper APIKey & PersonID is used." + ) print("\nFinished!") - print(f"Board Uptime: {time_calc(time.monotonic())}") - print(f"Next Update: {time_calc(SLEEP_TIME)}") print("===============================") + time.sleep(SLEEP_TIME) - except (ValueError, RuntimeError) as e: - print(f"Failed to get data, retrying\n {e}") - time.sleep(60) - break - - time.sleep(SLEEP_TIME) + # Main Script + if RACHIO_PERSONID is not None and RACHIO_PERSONID != "": + try: + print(" | Attempting to GET Rachio JSON") + try: + with requests.get( + url=RACHIO_PERSON_SOURCE + RACHIO_PERSONID, headers=RACHIO_HEADER + ) as rachio_response: + rachio_json = rachio_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + print(" | ✅ Rachio JSON") + response_headers = rachio_response.headers + if DEBUG: + print(f"Response Headers: {response_headers}") + call_limit = int(response_headers["x-ratelimit-limit"]) + calls_remaining = int(response_headers["x-ratelimit-remaining"]) + calls_made_today = call_limit - calls_remaining + + print(" | | Headers:") + print(f" | | | Date: {response_headers['date']}") + print(f" | | | Maximum Daily Requests: {call_limit}") + print(f" | | | Today's Requests: {calls_made_today}") + print(f" | | | Remaining Requests: {calls_remaining}") + print(f" | | | Limit Reset: {response_headers['x-ratelimit-reset']}") + print(f" | | | Content Type: {response_headers['content-type']}") + + rachio_id = rachio_json["id"] + rachio_id_ast = obfuscating_asterix(rachio_id, "append", 3) + print(" | | PersonID: ", rachio_id_ast) + + rachio_username = rachio_json["username"] + rachio_username_ast = obfuscating_asterix(rachio_username, "append", 3) + print(" | | Username: ", rachio_username_ast) + + rachio_name = rachio_json["fullName"] + rachio_name_ast = obfuscating_asterix(rachio_name, "append", 3) + print(" | | Full Name: ", rachio_name_ast) + + rachio_deleted = rachio_json["deleted"] + if not rachio_deleted: + print(" | | Account Status: Active") + else: + print(" | | Account Status?: Deleted!") + + rachio_createdate = rachio_json["createDate"] + rachio_timezone_offset = rachio_json["devices"][0]["utcOffset"] + # Rachio Unix time is in milliseconds, convert to seconds + rachio_createdate_seconds = rachio_createdate // 1000 + rachio_timezone_offset_seconds = rachio_timezone_offset // 1000 + # Apply timezone offset in seconds + local_unix_time = rachio_createdate_seconds + rachio_timezone_offset_seconds + if DEBUG: + print(f" | | Unix Registration Date: {rachio_createdate}") + print(f" | | Unix Timezone Offset: {rachio_timezone_offset}") + current_struct_time = time.localtime(local_unix_time) + final_timestamp = "{}".format(_format_datetime(current_struct_time)) + print(f" | | Registration Date: {final_timestamp}") + + rachio_devices = rachio_json["devices"][0]["name"] + print(" | | Device: ", rachio_devices) + + rachio_model = rachio_json["devices"][0]["model"] + print(" | | | Model: ", rachio_model) + + rachio_serial = rachio_json["devices"][0]["serialNumber"] + rachio_serial_ast = obfuscating_asterix(rachio_serial, "append") + print(" | | | Serial Number: ", rachio_serial_ast) + + rachio_mac = rachio_json["devices"][0]["macAddress"] + rachio_mac_ast = obfuscating_asterix(rachio_mac, "append") + print(" | | | MAC Address: ", rachio_mac_ast) + + rachio_status = rachio_json["devices"][0]["status"] + print(" | | | Device Status: ", rachio_status) + + rachio_timezone = rachio_json["devices"][0]["timeZone"] + print(" | | | Time Zone: ", rachio_timezone) + + # Latitude & Longtitude are used for smart watering & rain delays + rachio_latitude = str(rachio_json["devices"][0]["latitude"]) + rachio_lat_ast = obfuscating_asterix(rachio_latitude, "all") + print(" | | | Latitude: ", rachio_lat_ast) + + rachio_longitude = str(rachio_json["devices"][0]["longitude"]) + rachio_long_ast = obfuscating_asterix(rachio_longitude, "all") + print(" | | | Longitude: ", rachio_long_ast) + + rachio_rainsensor = rachio_json["devices"][0]["rainSensorTripped"] + print(" | | | Rain Sensor: ", rachio_rainsensor) + + rachio_zone0 = rachio_json["devices"][0]["zones"][0]["name"] + rachio_zone1 = rachio_json["devices"][0]["zones"][1]["name"] + rachio_zone2 = rachio_json["devices"][0]["zones"][2]["name"] + rachio_zone3 = rachio_json["devices"][0]["zones"][3]["name"] + zones = f"{rachio_zone0}, {rachio_zone1}, {rachio_zone2}, {rachio_zone3}" + print(f" | | | Zones: {zones}") + + if DEBUG: + print(f"\nFull API GET URL: {RACHIO_PERSON_SOURCE+rachio_id}") + print(rachio_json) + + print("\nFinished!") + print(f"Board Uptime: {time_calc(time.monotonic())}") + print(f"Next Update: {time_calc(SLEEP_TIME)}") + print("===============================") + + except (ValueError, RuntimeError) as e: + print(f"Failed to get data, retrying\n {e}") + time.sleep(60) + break + + time.sleep(SLEEP_TIME) From 376fff2e4e6c640a616b5f4609b4162178e47f2a Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 1 Jul 2024 10:32:30 -0500 Subject: [PATCH 288/305] Document `socket` property of Response objects --- adafruit_requests.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/adafruit_requests.py b/adafruit_requests.py index 6231185..4b90736 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -88,6 +88,21 @@ class Response: # pylint: disable=too-many-instance-attributes encoding = None + socket: SocketType + """The underlying socket object (CircuitPython extension, not in standard requests) + + Under the following circumstances, calling code may directly access the underlying + socket object: + + * The request was made with ``stream=True`` + * The request headers included ``{'connection': 'close'}`` + * No methods or properties on the Response object that access the response content + may be used + + Methods and properties that access response headers may be accessed. + + It is still necessary to ``close`` the response object for correct management of + sockets, including doing so implicitly via ``with requests.get(...) as response``.""" def __init__(self, sock: SocketType, session: "Session") -> None: self.socket = sock From 9543728198760e9f2c207517aaecf7e348c2e7e1 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 3 Jul 2024 16:16:07 -0500 Subject: [PATCH 289/305] handle servers that omit a space after a colon in response headers --- adafruit_requests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 6231185..89e2209 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -251,7 +251,8 @@ def _parse_headers(self) -> None: header = self._readto(b"\r\n") if not header: break - title, content = bytes(header).split(b": ", 1) + title, content = bytes(header).split(b":", 1) + content = content.strip() if title and content: # enforce that all headers are lowercase title = str(title, "utf-8").lower() From edef59ac296804240a35ff551763ad601865c2c1 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 29 Jul 2024 09:26:58 -0500 Subject: [PATCH 290/305] merge main, resolve conflicts --- adafruit_requests.py | 24 ++--- docs/conf.py | 4 +- .../expanded/requests_wifi_api_discord.py | 4 +- .../wifi/expanded/requests_wifi_api_fitbit.py | 21 ++-- .../wifi/expanded/requests_wifi_api_github.py | 4 +- .../expanded/requests_wifi_api_mastodon.py | 6 +- ...equests_wifi_api_openskynetwork_private.py | 9 +- ...ts_wifi_api_openskynetwork_private_area.py | 9 +- ...requests_wifi_api_openskynetwork_public.py | 5 +- .../wifi/expanded/requests_wifi_api_twitch.py | 5 +- .../requests_wifi_file_upload_image.png | Bin 7615 -> 7614 bytes .../requests_wifi_multiple_cookies.py | 2 +- .../requests_wifi_rachio_irrigation.py | 10 +- .../expanded/requests_wifi_status_codes.py | 22 ++-- examples/wifi/requests_wifi_advanced.py | 2 +- examples/wifi/requests_wifi_simpletest.py | 2 +- ruff.toml | 99 ++++++++++++++++++ tests/chunk_test.py | 6 +- tests/chunked_redirect_test.py | 36 ++----- tests/conftest.py | 4 +- tests/files/green_red.png | Bin 125 -> 124 bytes tests/files/red_green.png | Bin 123 -> 122 bytes tests/files_test.py | 12 +-- tests/header_test.py | 4 +- tests/mocket.py | 4 +- tests/reuse_test.py | 2 +- 26 files changed, 162 insertions(+), 134 deletions(-) create mode 100644 ruff.toml diff --git a/adafruit_requests.py b/adafruit_requests.py index 032d69e..f0c9e65 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -191,9 +191,7 @@ def _read_from_buffer( def _readinto(self, buf: bytearray) -> int: if not self.socket: - raise RuntimeError( - "Newer Response closed this one. Use Responses immediately." - ) + raise RuntimeError("Newer Response closed this one. Use Responses immediately.") if not self._remaining: # Consume the chunk header if need be. @@ -280,10 +278,7 @@ def _parse_headers(self) -> None: def _validate_not_gzip(self) -> None: """gzip encoding is not supported. Raise an exception if found.""" - if ( - "content-encoding" in self.headers - and self.headers["content-encoding"] == "gzip" - ): + if "content-encoding" in self.headers and self.headers["content-encoding"] == "gzip": raise ValueError( "Content-encoding is gzip, data cannot be accessed as json or text. " "Use content property to access raw bytes." @@ -394,9 +389,7 @@ def _build_boundary_data(self, files: dict): # pylint: disable=too-many-locals if len(field_values) >= 4: file_headers = field_values[3] for file_header_key, file_header_value in file_headers.items(): - boundary_objects.append( - f"{file_header_key}: {file_header_value}\r\n" - ) + boundary_objects.append(f"{file_header_key}: {file_header_value}\r\n") boundary_objects.append("\r\n") if hasattr(file_handle, "read"): @@ -500,7 +493,8 @@ def _send_header(self, socket, header, value): self._send_as_bytes(socket, value) self._send(socket, b"\r\n") - def _send_request( # noqa: PLR0913 Too many arguments in function definition + # noqa: PLR0912 Too many branches + def _send_request( # noqa: PLR0913,PLR0912 Too many arguments in function definition,Too many branches self, socket: SocketType, host: str, @@ -543,9 +537,7 @@ def _send_request( # noqa: PLR0913 Too many arguments in function definition data_is_file = False boundary_objects = None if files and isinstance(files, dict): - boundary_string, content_length, boundary_objects = ( - self._build_boundary_data(files) - ) + boundary_string, content_length, boundary_objects = self._build_boundary_data(files) content_type_header = f"multipart/form-data; boundary={boundary_string}" elif data and hasattr(data, "read"): data_is_file = True @@ -644,9 +636,7 @@ def request( # noqa: PLR0912,PLR0913,PLR0915 Too many branches,Too many argumen ) ok = True try: - self._send_request( - socket, host, method, path, headers, data, json, files - ) + self._send_request(socket, host, method, path, headers, data, json, files) except OSError as exc: last_exc = exc ok = False diff --git a/docs/conf.py b/docs/conf.py index c9c7548..f3a8700 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -46,9 +46,7 @@ creation_year = "2019" current_year = str(datetime.datetime.now().year) year_duration = ( - current_year - if current_year == creation_year - else creation_year + " - " + current_year + current_year if current_year == creation_year else creation_year + " - " + current_year ) copyright = year_duration + " ladyada" author = "ladyada" diff --git a/examples/wifi/expanded/requests_wifi_api_discord.py b/examples/wifi/expanded/requests_wifi_api_discord.py index 19c2522..f1b50bc 100644 --- a/examples/wifi/expanded/requests_wifi_api_discord.py +++ b/examples/wifi/expanded/requests_wifi_api_discord.py @@ -69,9 +69,7 @@ def time_calc(input_time): DEBUG_RESPONSE = False try: - with requests.get( - url=DISCORD_SOURCE, headers=DISCORD_HEADER - ) as discord_response: + with requests.get(url=DISCORD_SOURCE, headers=DISCORD_HEADER) as discord_response: discord_json = discord_response.json() except ConnectionError as e: print(f"Connection Error: {e}") diff --git a/examples/wifi/expanded/requests_wifi_api_fitbit.py b/examples/wifi/expanded/requests_wifi_api_fitbit.py index 193fe62..ee26a0a 100644 --- a/examples/wifi/expanded/requests_wifi_api_fitbit.py +++ b/examples/wifi/expanded/requests_wifi_api_fitbit.py @@ -30,9 +30,7 @@ password = os.getenv("CIRCUITPY_WIFI_PASSWORD") Fitbit_ClientID = os.getenv("FITBIT_CLIENTID") Fitbit_Token = os.getenv("FITBIT_ACCESS_TOKEN") -Fitbit_First_Refresh_Token = os.getenv( - "FITBIT_FIRST_REFRESH_TOKEN" -) # overides nvm first run only +Fitbit_First_Refresh_Token = os.getenv("FITBIT_FIRST_REFRESH_TOKEN") # overides nvm first run only Fitbit_UserID = os.getenv("FITBIT_USERID") # Set debug to True for full INTRADAY JSON response. @@ -167,8 +165,7 @@ def time_calc(input_time): print(" | Requesting authorization for next token") if DEBUG: print( - "FULL REFRESH TOKEN POST:" - + f"{FITBIT_OAUTH_TOKEN}{FITBIT_OAUTH_REFRESH_TOKEN}" + "FULL REFRESH TOKEN POST:" + f"{FITBIT_OAUTH_TOKEN}{FITBIT_OAUTH_REFRESH_TOKEN}" ) print(f"Current Refresh Token: {Refresh_Token}") # TOKEN REFRESH POST @@ -270,24 +267,20 @@ def time_calc(input_time): try: # Fitbit's sync to mobile device & server every 15 minutes in chunks. # Pointless to poll their API faster than 15 minute intervals. - activities_heart_value = fitbit_json["activities-heart-intraday"][ - "dataset" - ] + activities_heart_value = fitbit_json["activities-heart-intraday"]["dataset"] if MIDNIGHT_DEBUG: RESPONSE_LENGTH = 0 else: RESPONSE_LENGTH = len(activities_heart_value) if RESPONSE_LENGTH >= 15: - activities_timestamp = fitbit_json["activities-heart"][0][ - "dateTime" - ] + activities_timestamp = fitbit_json["activities-heart"][0]["dateTime"] print(f" | | Fitbit Date: {activities_timestamp}") if MIDNIGHT_DEBUG: ACTIVITIES_LATEST_HEART_TIME = "00:05:00" else: - ACTIVITIES_LATEST_HEART_TIME = fitbit_json[ - "activities-heart-intraday" - ]["dataset"][RESPONSE_LENGTH - 1]["time"] + ACTIVITIES_LATEST_HEART_TIME = fitbit_json["activities-heart-intraday"][ + "dataset" + ][RESPONSE_LENGTH - 1]["time"] print(f" | | Fitbit Time: {ACTIVITIES_LATEST_HEART_TIME[0:-3]}") print(f" | | Today's Logged Pulses: {RESPONSE_LENGTH}") diff --git a/examples/wifi/expanded/requests_wifi_api_github.py b/examples/wifi/expanded/requests_wifi_api_github.py index d25b15d..9d6770b 100644 --- a/examples/wifi/expanded/requests_wifi_api_github.py +++ b/examples/wifi/expanded/requests_wifi_api_github.py @@ -61,9 +61,7 @@ def time_calc(input_time): try: print(" | Attempting to GET Github JSON!") try: - with requests.get( - url=GITHUB_SOURCE, headers=GITHUB_HEADER - ) as github_response: + with requests.get(url=GITHUB_SOURCE, headers=GITHUB_HEADER) as github_response: github_json = github_response.json() except ConnectionError as e: print("Connection Error:", e) diff --git a/examples/wifi/expanded/requests_wifi_api_mastodon.py b/examples/wifi/expanded/requests_wifi_api_mastodon.py index 92455e2..bcbf211 100644 --- a/examples/wifi/expanded/requests_wifi_api_mastodon.py +++ b/examples/wifi/expanded/requests_wifi_api_mastodon.py @@ -48,11 +48,7 @@ def time_calc(input_time): # Publicly available data no header required MAST_SOURCE = ( - "https://" - + MASTODON_SERVER - + "/api/v1/accounts/" - + MASTODON_USERID - + "/statuses?limit=1" + "https://" + MASTODON_SERVER + "/api/v1/accounts/" + MASTODON_USERID + "/statuses?limit=1" ) while True: diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py index c8d54b6..0608b30 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private.py @@ -69,7 +69,10 @@ def time_calc(input_time): def _format_datetime(datetime): - return f"{datetime.tm_mon:02}/{datetime.tm_mday:02}/{datetime.tm_year} {datetime.tm_hour:02}:{datetime.tm_min:02}:{datetime.tm_sec:02}" + return ( + f"{datetime.tm_mon:02}/{datetime.tm_mday:02}/{datetime.tm_year} " + f"{datetime.tm_hour:02}:{datetime.tm_min:02}:{datetime.tm_sec:02}" + ) while True: @@ -87,9 +90,7 @@ def _format_datetime(datetime): print(" | Attempting to GET OpenSky-Network Single Private Flight JSON!") print(" | Website Credentials Required! Allows more daily calls than Public.") try: - with requests.get( - url=OPENSKY_SOURCE, headers=OPENSKY_HEADER - ) as opensky_response: + with requests.get(url=OPENSKY_SOURCE, headers=OPENSKY_HEADER) as opensky_response: opensky_json = opensky_response.json() except ConnectionError as e: print("Connection Error:", e) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py index 8f2b28f..243c859 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_private_area.py @@ -83,7 +83,10 @@ def time_calc(input_time): def _format_datetime(datetime): - return f"{datetime.tm_mon:02}/{datetime.tm_mday:02}/{datetime.tm_year} {datetime.tm_hour:02}:{datetime.tm_min:02}:{datetime.tm_sec:02}" + return ( + f"{datetime.tm_mon:02}/{datetime.tm_mday:02}/{datetime.tm_year} " + f"{datetime.tm_hour:02}:{datetime.tm_min:02}:{datetime.tm_sec:02}" + ) while True: @@ -100,9 +103,7 @@ def _format_datetime(datetime): try: print(" | Attempting to GET OpenSky-Network Area Flights JSON!") try: - with requests.get( - url=OPENSKY_SOURCE, headers=OSN_HEADER - ) as opensky_response: + with requests.get(url=OPENSKY_SOURCE, headers=OSN_HEADER) as opensky_response: opensky_json = opensky_response.json() except ConnectionError as e: print("Connection Error:", e) diff --git a/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py index b07b476..f89f88d 100644 --- a/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py +++ b/examples/wifi/expanded/requests_wifi_api_openskynetwork_public.py @@ -54,7 +54,10 @@ def time_calc(input_time): def _format_datetime(datetime): - return f"{datetime.tm_mon:02}/{datetime.tm_mday:02}/{datetime.tm_year} {datetime.tm_hour:02}:{datetime.tm_min:02}:{datetime.tm_sec:02}" + return ( + f"{datetime.tm_mon:02}/{datetime.tm_mday:02}/{datetime.tm_year} " + f"{datetime.tm_hour:02}:{datetime.tm_min:02}:{datetime.tm_sec:02}" + ) while True: diff --git a/examples/wifi/expanded/requests_wifi_api_twitch.py b/examples/wifi/expanded/requests_wifi_api_twitch.py index 57f9cdd..15cbcc0 100644 --- a/examples/wifi/expanded/requests_wifi_api_twitch.py +++ b/examples/wifi/expanded/requests_wifi_api_twitch.py @@ -129,10 +129,7 @@ def _format_datetime(datetime): "Client-Id": "" + TWITCH_CID + "", } TWITCH_FOLLOWERS_SOURCE = ( - "https://api.twitch.tv/helix/channels" - + "/followers?" - + "broadcaster_id=" - + TWITCH_UID + "https://api.twitch.tv/helix/channels" + "/followers?" + "broadcaster_id=" + TWITCH_UID ) print(" | Attempting to GET Twitch JSON!") try: diff --git a/examples/wifi/expanded/requests_wifi_file_upload_image.png b/examples/wifi/expanded/requests_wifi_file_upload_image.png index f08a15472271eba34ce6044e276bf3c86887609a..da2d2194b4dc5b413271e5e21462075f1725632b 100644 GIT binary patch delta 13 UcmdmQz0aDZGr-S%BkL|%03|a7^8f$< delta 14 VcmdmIz2BO(Gr-TCcO%O#SpX>C1poj5 diff --git a/examples/wifi/expanded/requests_wifi_multiple_cookies.py b/examples/wifi/expanded/requests_wifi_multiple_cookies.py index cfc0641..21fe9ba 100644 --- a/examples/wifi/expanded/requests_wifi_multiple_cookies.py +++ b/examples/wifi/expanded/requests_wifi_multiple_cookies.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2022 Alec Delaney # SPDX-License-Identifier: MIT # Coded for Circuit Python 9.0 -""" Multiple Cookies Example written for MagTag """ +"""Multiple Cookies Example written for MagTag""" import os diff --git a/examples/wifi/expanded/requests_wifi_rachio_irrigation.py b/examples/wifi/expanded/requests_wifi_rachio_irrigation.py index 2b8650d..55a46a1 100644 --- a/examples/wifi/expanded/requests_wifi_rachio_irrigation.py +++ b/examples/wifi/expanded/requests_wifi_rachio_irrigation.py @@ -106,9 +106,7 @@ def _format_datetime(datetime): try: print(" | Attempting to GET Rachio Authorization") try: - with requests.get( - url=RACHIO_SOURCE, headers=RACHIO_HEADER - ) as rachio_response: + with requests.get(url=RACHIO_SOURCE, headers=RACHIO_HEADER) as rachio_response: rachio_json = rachio_response.json() except ConnectionError as e: print("Connection Error:", e) @@ -127,9 +125,7 @@ def _format_datetime(datetime): print(f"Failed to GET data: {e}") time.sleep(60) break - print( - "\nThis script can only continue when a proper APIKey & PersonID is used." - ) + print("\nThis script can only continue when a proper APIKey & PersonID is used.") print("\nFinished!") print("===============================") time.sleep(SLEEP_TIME) @@ -191,7 +187,7 @@ def _format_datetime(datetime): print(f" | | Unix Registration Date: {rachio_createdate}") print(f" | | Unix Timezone Offset: {rachio_timezone_offset}") current_struct_time = time.localtime(local_unix_time) - final_timestamp = "{}".format(_format_datetime(current_struct_time)) + final_timestamp = f"{_format_datetime(current_struct_time)}" print(f" | | Registration Date: {final_timestamp}") rachio_devices = rachio_json["devices"][0]["name"] diff --git a/examples/wifi/expanded/requests_wifi_status_codes.py b/examples/wifi/expanded/requests_wifi_status_codes.py index 036b7bb..a933c28 100644 --- a/examples/wifi/expanded/requests_wifi_status_codes.py +++ b/examples/wifi/expanded/requests_wifi_status_codes.py @@ -3,7 +3,7 @@ # Updated for Circuit Python 9.0 # https://help.openai.com/en/articles/6825453-chatgpt-release-notes # https://chat.openai.com/share/32ef0c5f-ac92-4d36-9d1e-0f91e0c4c574 -""" WiFi Status Codes Example """ +"""WiFi Status Codes Example""" import os import time @@ -27,21 +27,13 @@ def print_http_status(expected_code, actual_code, description): """Returns HTTP status code and description""" if "100" <= actual_code <= "103": - print( - f" | ✅ Status Test Expected: {expected_code} Actual: {actual_code} - {description}" - ) + print(f" | ✅ Status Test Expected: {expected_code} Actual: {actual_code} - {description}") elif "200" == actual_code: - print( - f" | 🆗 Status Test Expected: {expected_code} Actual: {actual_code} - {description}" - ) + print(f" | 🆗 Status Test Expected: {expected_code} Actual: {actual_code} - {description}") elif "201" <= actual_code <= "299": - print( - f" | ✅ Status Test Expected: {expected_code} Actual: {actual_code} - {description}" - ) + print(f" | ✅ Status Test Expected: {expected_code} Actual: {actual_code} - {description}") elif "300" <= actual_code <= "600": - print( - f" | ❌ Status Test Expected: {expected_code} Actual: {actual_code} - {description}" - ) + print(f" | ❌ Status Test Expected: {expected_code} Actual: {actual_code} - {description}") else: print( f" | Unknown Response Status Expected: {expected_code} " @@ -138,9 +130,7 @@ def print_http_status(expected_code, actual_code, description): header_status_test_url = STATUS_TEST_URL + current_code with requests.get(header_status_test_url) as response: response_status_code = str(response.status_code) - SORT_STATUS_DESC = http_status_codes.get( - response_status_code, "Unknown Status Code" - ) + SORT_STATUS_DESC = http_status_codes.get(response_status_code, "Unknown Status Code") print_http_status(current_code, response_status_code, SORT_STATUS_DESC) # Rate limit ourselves a little to avoid strain on server diff --git a/examples/wifi/requests_wifi_advanced.py b/examples/wifi/requests_wifi_advanced.py index bfad6af..bc29df7 100644 --- a/examples/wifi/requests_wifi_advanced.py +++ b/examples/wifi/requests_wifi_advanced.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: MIT # Updated for Circuit Python 9.0 -""" WiFi Advanced Example """ +"""WiFi Advanced Example""" import os diff --git a/examples/wifi/requests_wifi_simpletest.py b/examples/wifi/requests_wifi_simpletest.py index 6bce412..ddc70a2 100644 --- a/examples/wifi/requests_wifi_simpletest.py +++ b/examples/wifi/requests_wifi_simpletest.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT # Updated for Circuit Python 9.0 -""" WiFi Simpletest """ +"""WiFi Simpletest""" import os diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..db37c83 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,99 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +target-version = "py38" +line-length = 100 + +[lint] +select = ["I", "PL", "UP"] + +extend-select = [ + "D419", # empty-docstring + "E501", # line-too-long + "W291", # trailing-whitespace + "PLC0414", # useless-import-alias + "PLC2401", # non-ascii-name + "PLC2801", # unnecessary-dunder-call + "PLC3002", # unnecessary-direct-lambda-call + "E999", # syntax-error + "PLE0101", # return-in-init + "F706", # return-outside-function + "F704", # yield-outside-function + "PLE0116", # continue-in-finally + "PLE0117", # nonlocal-without-binding + "PLE0241", # duplicate-bases + "PLE0302", # unexpected-special-method-signature + "PLE0604", # invalid-all-object + "PLE0605", # invalid-all-format + "PLE0643", # potential-index-error + "PLE0704", # misplaced-bare-raise + "PLE1141", # dict-iter-missing-items + "PLE1142", # await-outside-async + "PLE1205", # logging-too-many-args + "PLE1206", # logging-too-few-args + "PLE1307", # bad-string-format-type + "PLE1310", # bad-str-strip-call + "PLE1507", # invalid-envvar-value + "PLE2502", # bidirectional-unicode + "PLE2510", # invalid-character-backspace + "PLE2512", # invalid-character-sub + "PLE2513", # invalid-character-esc + "PLE2514", # invalid-character-nul + "PLE2515", # invalid-character-zero-width-space + "PLR0124", # comparison-with-itself + "PLR0202", # no-classmethod-decorator + "PLR0203", # no-staticmethod-decorator + "UP004", # useless-object-inheritance + "PLR0206", # property-with-parameters + "PLR0904", # too-many-public-methods + "PLR0911", # too-many-return-statements + "PLR0912", # too-many-branches + "PLR0913", # too-many-arguments + "PLR0914", # too-many-locals + "PLR0915", # too-many-statements + "PLR0916", # too-many-boolean-expressions + "PLR1702", # too-many-nested-blocks + "PLR1704", # redefined-argument-from-local + "PLR1711", # useless-return + "C416", # unnecessary-comprehension + "PLR1733", # unnecessary-dict-index-lookup + "PLR1736", # unnecessary-list-index-lookup + + # ruff reports this rule is unstable + #"PLR6301", # no-self-use + + "PLW0108", # unnecessary-lambda + "PLW0120", # useless-else-on-loop + "PLW0127", # self-assigning-variable + "PLW0129", # assert-on-string-literal + "B033", # duplicate-value + "PLW0131", # named-expr-without-context + "PLW0245", # super-without-brackets + "PLW0406", # import-self + "PLW0602", # global-variable-not-assigned + "PLW0603", # global-statement + "PLW0604", # global-at-module-level + + # fails on the try: import typing used by libraries + #"F401", # unused-import + + "F841", # unused-variable + "E722", # bare-except + "PLW0711", # binary-op-exception + "PLW1501", # bad-open-mode + "PLW1508", # invalid-envvar-default + "PLW1509", # subprocess-popen-preexec-fn + "PLW2101", # useless-with-lock + "PLW3301", # nested-min-max +] + +ignore = [ + "PLR2004", # magic-value-comparison + "UP030", # format literals + "PLW1514", # unspecified-encoding + +] + +[format] +line-ending = "lf" diff --git a/tests/chunk_test.py b/tests/chunk_test.py index f67e44a..4be0e05 100644 --- a/tests/chunk_test.py +++ b/tests/chunk_test.py @@ -28,11 +28,7 @@ def _chunk(response, split, extra=b""): chunk_size = remaining new_i = i + chunk_size chunked += ( - hex(chunk_size)[2:].encode("ascii") - + extra - + b"\r\n" - + response[i:new_i] - + b"\r\n" + hex(chunk_size)[2:].encode("ascii") + extra + b"\r\n" + response[i:new_i] + b"\r\n" ) i = new_i # The final chunk is zero length. diff --git a/tests/chunked_redirect_test.py b/tests/chunked_redirect_test.py index 0990acb..871dc21 100644 --- a/tests/chunked_redirect_test.py +++ b/tests/chunked_redirect_test.py @@ -27,9 +27,7 @@ b"370cmver1f290kjsnpar5ku2h9g/3llvt5u8njbvat22m9l19db1h4/1656191325000/109226138307867586192/*/" + FILE_REDIRECT ) -RELATIVE_ABSOLUTE_REDIRECT = ( - b"/pub/70cmver1f290kjsnpar5ku2h9g/" + RELATIVE_RELATIVE_REDIRECT -) +RELATIVE_ABSOLUTE_REDIRECT = b"/pub/70cmver1f290kjsnpar5ku2h9g/" + RELATIVE_RELATIVE_REDIRECT ABSOLUTE_ABSOLUTE_REDIRECT = ( b"https://doc-14-2g-sheets.googleusercontent.com" + RELATIVE_ABSOLUTE_REDIRECT ) @@ -137,9 +135,7 @@ def test_chunked_absolute_absolute_redirect(): chunk = _chunk(BODY_REDIRECT, len(BODY_REDIRECT)) chunk2 = _chunk(BODY, len(BODY)) - redirect = HEADERS_REDIRECT.replace( - b"NEW_LOCATION_HERE", ABSOLUTE_ABSOLUTE_REDIRECT - ) + redirect = HEADERS_REDIRECT.replace(b"NEW_LOCATION_HERE", ABSOLUTE_ABSOLUTE_REDIRECT) sock1 = MocketRecvInto(redirect + chunk) sock2 = mocket.Mocket(HEADERS + chunk2) pool.socket.side_effect = (sock1, sock2) @@ -148,9 +144,7 @@ def test_chunked_absolute_absolute_redirect(): response = requests_session.get("https://" + HOST + PATH) sock1.connect.assert_called_once_with((HOST, 443)) - sock2.connect.assert_called_once_with( - ("doc-14-2g-sheets.googleusercontent.com", 443) - ) + sock2.connect.assert_called_once_with(("doc-14-2g-sheets.googleusercontent.com", 443)) sock2.send.assert_has_calls([mock.call(RELATIVE_ABSOLUTE_REDIRECT[1:])]) assert response.text == str(BODY, "utf-8") @@ -162,9 +156,7 @@ def test_chunked_relative_absolute_redirect(): chunk = _chunk(BODY_REDIRECT, len(BODY_REDIRECT)) chunk2 = _chunk(BODY, len(BODY)) - redirect = HEADERS_REDIRECT.replace( - b"NEW_LOCATION_HERE", RELATIVE_ABSOLUTE_REDIRECT - ) + redirect = HEADERS_REDIRECT.replace(b"NEW_LOCATION_HERE", RELATIVE_ABSOLUTE_REDIRECT) sock1 = MocketRecvInto(redirect + chunk) sock2 = mocket.Mocket(HEADERS + chunk2) pool.socket.side_effect = (sock1, sock2) @@ -185,9 +177,7 @@ def test_chunked_relative_relative_redirect(): chunk = _chunk(BODY_REDIRECT, len(BODY_REDIRECT)) chunk2 = _chunk(BODY, len(BODY)) - redirect = HEADERS_REDIRECT.replace( - b"NEW_LOCATION_HERE", RELATIVE_RELATIVE_REDIRECT - ) + redirect = HEADERS_REDIRECT.replace(b"NEW_LOCATION_HERE", RELATIVE_RELATIVE_REDIRECT) sock1 = MocketRecvInto(redirect + chunk) sock2 = mocket.Mocket(HEADERS + chunk2) pool.socket.side_effect = (sock1, sock2) @@ -214,13 +204,9 @@ def test_chunked_relative_relative_redirect_backstep(): backstep = b"../" * remove_paths path_base_parts = PATH_BASE.split("/") # PATH_BASE starts with "/" so skip it and also remove from the count - path_base = ( - "/".join(path_base_parts[1 : len(path_base_parts) - remove_paths - 1]) + "/" - ) + path_base = "/".join(path_base_parts[1 : len(path_base_parts) - remove_paths - 1]) + "/" - redirect = HEADERS_REDIRECT.replace( - b"NEW_LOCATION_HERE", backstep + RELATIVE_RELATIVE_REDIRECT - ) + redirect = HEADERS_REDIRECT.replace(b"NEW_LOCATION_HERE", backstep + RELATIVE_RELATIVE_REDIRECT) sock1 = MocketRecvInto(redirect + chunk) sock2 = mocket.Mocket(HEADERS + chunk2) pool.socket.side_effect = (sock1, sock2) @@ -230,9 +216,7 @@ def test_chunked_relative_relative_redirect_backstep(): sock1.connect.assert_called_once_with((HOST, 443)) sock2.connect.assert_called_once_with(("docs.google.com", 443)) - sock2.send.assert_has_calls( - [mock.call(bytes(path_base, "utf-8") + RELATIVE_RELATIVE_REDIRECT)] - ) + sock2.send.assert_has_calls([mock.call(bytes(path_base, "utf-8") + RELATIVE_RELATIVE_REDIRECT)]) assert response.text == str(BODY, "utf-8") @@ -243,9 +227,7 @@ def test_chunked_allow_redirects_false(): chunk = _chunk(BODY_REDIRECT, len(BODY_REDIRECT)) chunk2 = _chunk(BODY, len(BODY)) - redirect = HEADERS_REDIRECT.replace( - b"NEW_LOCATION_HERE", ABSOLUTE_ABSOLUTE_REDIRECT - ) + redirect = HEADERS_REDIRECT.replace(b"NEW_LOCATION_HERE", ABSOLUTE_ABSOLUTE_REDIRECT) sock1 = MocketRecvInto(redirect + chunk) sock2 = mocket.Mocket(HEADERS + chunk2) pool.socket.side_effect = (sock1, sock2) diff --git a/tests/conftest.py b/tests/conftest.py index 25bac4b..02aa1a6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,9 +28,7 @@ def sock(): @pytest.fixture def pool(sock): pool = mocket.MocketPool() - pool.getaddrinfo.return_value = ( - (None, None, None, None, (mocket.MOCK_POOL_IP, 80)), - ) + pool.getaddrinfo.return_value = ((None, None, None, None, (mocket.MOCK_POOL_IP, 80)),) pool.socket.return_value = sock return pool diff --git a/tests/files/green_red.png b/tests/files/green_red.png index 7d8ddb37c20bcff4cbb43154844f21966c74bc44..532c956355bccd356a67877a8e89e6fa76676280 100644 GIT binary patch delta 10 Rcmb=eVd)I;bDzjs0RR);19|`e delta 11 Scmb=aW$g^`bLXANQUL%Kgad~F diff --git a/tests/files/red_green.png b/tests/files/red_green.png index 6b4fc3052e22c6f90c25bcc4385ac3fa9b36d2ba..9d3bdb6f2b567ef4e3c5d98e98dc8814a7392491 100644 GIT binary patch delta 10 Rcmb=fV(AR Date: Mon, 29 Jul 2024 11:20:46 -0500 Subject: [PATCH 291/305] precommit copyright --- .pre-commit-config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ff19dde..f27b786 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,5 @@ -# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries +# SPDX-FileCopyrightText: 2020 Diego Elio Pettenò +# SPDX-FileCopyrightText: 2024 Justin Myers # # SPDX-License-Identifier: Unlicense From 211e7cf9f60ce7b46416dc1fc5d3bb2075053788 Mon Sep 17 00:00:00 2001 From: aggieNick02 Date: Fri, 27 Sep 2024 14:53:58 -0500 Subject: [PATCH 292/305] Fix cases in Session.request method where an exception could lead to exiting the method without the created socket being owned by a response or closed. This would then cause future calls to get a socket with the same parameters to fail because one already existed and hadn't been closed. The PR https://github.com/adafruit/Adafruit_CircuitPython_Requests/pull/72 fixed this issue for one case where an exception could be raised. This generalizes that in an attempt to fix it in other cases that could still be hit. --- adafruit_requests.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index f0c9e65..427b9e1 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -622,6 +622,10 @@ def request( # noqa: PLR0912,PLR0913,PLR0915 Too many branches,Too many argumen # We may fail to send the request if the socket we got is closed already. So, try a second # time in that case. + # Note that the loop below actually tries a second time in other failure cases too, + # namely timeout and no data from socket. This was not covered in the stated intent of the + # commit that introduced the loop, but removing the retry from those cases could prove + # problematic to callers that now depend on that resiliency. retry_count = 0 last_exc = None while retry_count < 2: @@ -643,17 +647,23 @@ def request( # noqa: PLR0912,PLR0913,PLR0915 Too many branches,Too many argumen if ok: # Read the H of "HTTP/1.1" to make sure the socket is alive. send can appear to work # even when the socket is closed. - if hasattr(socket, "recv"): - result = socket.recv(1) - else: - result = bytearray(1) - try: + # Both recv/recv_into can raise OSError; when that happens, we need to call + # _connection_manager.close_socket(socket) or future calls to _connection_manager.get_socket() + # for the same parameter set will fail + try: + if hasattr(socket, "recv"): + result = socket.recv(1) + else: + result = bytearray(1) socket.recv_into(result) - except OSError: - pass - if result == b"H": - # Things seem to be ok so break with socket set. - break + if result == b"H": + # Things seem to be ok so break with socket set. + break + else: + raise RuntimeError("no data from socket") + except (OSError, RuntimeError) as exc: + last_exc = exc + pass self._connection_manager.close_socket(socket) socket = None From 4c9372bb32598eaf1427ba31f63b459b866fb946 Mon Sep 17 00:00:00 2001 From: aggieNick02 Date: Fri, 27 Sep 2024 14:59:58 -0500 Subject: [PATCH 293/305] fix line length in comment --- adafruit_requests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 427b9e1..5bcf0e4 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -648,8 +648,8 @@ def request( # noqa: PLR0912,PLR0913,PLR0915 Too many branches,Too many argumen # Read the H of "HTTP/1.1" to make sure the socket is alive. send can appear to work # even when the socket is closed. # Both recv/recv_into can raise OSError; when that happens, we need to call - # _connection_manager.close_socket(socket) or future calls to _connection_manager.get_socket() - # for the same parameter set will fail + # _connection_manager.close_socket(socket) or future calls to + # _connection_manager.get_socket() for the same parameter set will fail try: if hasattr(socket, "recv"): result = socket.recv(1) From b1741b3d617203fc3932ad5dab0f75c9e1daac67 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 7 Oct 2024 09:24:05 -0500 Subject: [PATCH 294/305] remove deprecated get_html_theme_path() call Signed-off-by: foamyguy --- docs/conf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index f3a8700..689c1af 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -100,7 +100,6 @@ import sphinx_rtd_theme html_theme = "sphinx_rtd_theme" -html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, From 5edee7367cd049c694e2d9c90989d09376055276 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 14 Jan 2025 11:32:34 -0600 Subject: [PATCH 295/305] add sphinx configuration to rtd.yaml Signed-off-by: foamyguy --- .readthedocs.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 33c2a61..88bca9f 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,6 +8,9 @@ # Required version: 2 +sphinx: + configuration: docs/conf.py + build: os: ubuntu-20.04 tools: From bbb24801ecbf0e4453f809a15c851200fd4fb764 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Wed, 26 Feb 2025 14:52:15 -0500 Subject: [PATCH 296/305] update examples to use ap_info --- examples/esp32spi/requests_esp32spi_advanced.py | 2 +- examples/esp32spi/requests_esp32spi_simpletest.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/esp32spi/requests_esp32spi_advanced.py b/examples/esp32spi/requests_esp32spi_advanced.py index 6754dbf..170589f 100644 --- a/examples/esp32spi/requests_esp32spi_advanced.py +++ b/examples/esp32spi/requests_esp32spi_advanced.py @@ -40,7 +40,7 @@ except RuntimeError as e: print("could not connect to AP, retrying: ", e) continue -print("Connected to", str(radio.ssid, "utf-8"), "\tRSSI:", radio.rssi) +print("Connected to", str(radio.ap_info.ssid, "utf-8"), "\tRSSI:", radio.ap_info.rssi) # Initialize a requests session pool = adafruit_connection_manager.get_radio_socketpool(radio) diff --git a/examples/esp32spi/requests_esp32spi_simpletest.py b/examples/esp32spi/requests_esp32spi_simpletest.py index 8a61209..3e856c0 100644 --- a/examples/esp32spi/requests_esp32spi_simpletest.py +++ b/examples/esp32spi/requests_esp32spi_simpletest.py @@ -40,7 +40,7 @@ except RuntimeError as e: print("could not connect to AP, retrying: ", e) continue -print("Connected to", str(radio.ssid, "utf-8"), "\tRSSI:", radio.rssi) +print("Connected to", str(radio.ap_info.ssid, "utf-8"), "\tRSSI:", radio.ap_info.rssi) # Initialize a requests session pool = adafruit_connection_manager.get_radio_socketpool(radio) From d4acdd019bc3c0e4ee0e5b78209cf15bb615103a Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Sat, 24 May 2025 16:09:51 -0700 Subject: [PATCH 297/305] 204 Fix --- adafruit_requests.py | 14 ++++++++++++-- tests/real_call_test.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 tests/real_call_test.py diff --git a/adafruit_requests.py b/adafruit_requests.py index 5bcf0e4..d15db69 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -102,11 +102,12 @@ class Response: It is still necessary to ``close`` the response object for correct management of sockets, including doing so implicitly via ``with requests.get(...) as response``.""" - def __init__(self, sock: SocketType, session: "Session") -> None: + def __init__(self, sock: SocketType, session: "Session", method: str) -> None: self.socket = sock self.encoding = "utf-8" self._cached = None self._headers = {} + self._method = method # _start_index and _receive_buffer are used when parsing headers. # _receive_buffer will grow by 32 bytes everytime it is too small. @@ -276,6 +277,15 @@ def _parse_headers(self) -> None: else: self._headers[title] = content + # does the body have a fixed length? (of zero) + if ( + self.status_code == 204 + or self.status_code == 304 + or 100 <= self.status_code < 200 # 1xx codes + or self._method == "HEAD" + ): + self._remaining = 0 + def _validate_not_gzip(self) -> None: """gzip encoding is not supported. Raise an exception if found.""" if "content-encoding" in self.headers and self.headers["content-encoding"] == "gzip": @@ -670,7 +680,7 @@ def request( # noqa: PLR0912,PLR0913,PLR0915 Too many branches,Too many argumen if not socket: raise OutOfRetries("Repeated socket failures") from last_exc - resp = Response(socket, self) # our response + resp = Response(socket, self, method) # our response if allow_redirects: if "location" in resp.headers and 300 <= resp.status_code <= 399: # a naive handler for redirects diff --git a/tests/real_call_test.py b/tests/real_call_test.py new file mode 100644 index 0000000..3ef3627 --- /dev/null +++ b/tests/real_call_test.py @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: 2024 Justin Myers +# +# SPDX-License-Identifier: Unlicense + +"""Real call Tests""" + +import socket +import ssl + +import adafruit_connection_manager +import pytest + +import adafruit_requests + + +@pytest.mark.parametrize( + ("path", "status_code", "text_result", "json_keys"), + ( + ("get", 200, None, {"url": "https://httpbin.org/get"}), + ("status/200", 200, "", None), + ("status/204", 204, "", None), + ), +) +def test_gets(path, status_code, text_result, json_keys): + requests = adafruit_requests.Session(socket, ssl.create_default_context()) + with requests.get(f"https://httpbin.org/{path}") as response: + assert response.status_code == status_code + if text_result is not None: + assert response.text == text_result + if json_keys is not None: + for key, value in json_keys.items(): + assert response.json()[key] == value + + adafruit_connection_manager.connection_manager_close_all(release_references=True) From d08344e0a29177dfbfc0795c99f6d8ab0cae09c3 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Sat, 31 May 2025 10:11:12 -0500 Subject: [PATCH 298/305] use a local test server instead of httpbin --- tests/local_test_server.py | 38 +++++++++++++++++++++++++++ tests/real_call_test.py | 53 ++++++++++++++++++++++++++------------ 2 files changed, 74 insertions(+), 17 deletions(-) create mode 100644 tests/local_test_server.py diff --git a/tests/local_test_server.py b/tests/local_test_server.py new file mode 100644 index 0000000..73b52b3 --- /dev/null +++ b/tests/local_test_server.py @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2025 Tim Cocks +# +# SPDX-License-Identifier: MIT +import json +from http.server import SimpleHTTPRequestHandler + + +class LocalTestServerHandler(SimpleHTTPRequestHandler): + def do_GET(self): + if self.path == "/get": + resp_body = json.dumps({"url": "http://localhost:5000/get"}).encode("utf-8") + self.send_response(200) + self.send_header("Content-type", "application/json") + self.send_header("Content-Length", str(len(resp_body))) + self.end_headers() + self.wfile.write(resp_body) + if self.path.startswith("/status"): + try: + requested_status = int(self.path.split("/")[2]) + except ValueError: + resp_body = json.dumps({"error": "requested status code must be int"}).encode( + "utf-8" + ) + self.send_response(400) + self.send_header("Content-type", "application/json") + self.send_header("Content-Length", str(len(resp_body))) + self.end_headers() + self.wfile.write(resp_body) + return + + if requested_status != 204: + self.send_response(requested_status) + self.send_header("Content-type", "text/html") + self.send_header("Content-Length", "0") + else: + self.send_response(requested_status) + self.send_header("Content-type", "text/html") + self.end_headers() diff --git a/tests/real_call_test.py b/tests/real_call_test.py index 3ef3627..7876a90 100644 --- a/tests/real_call_test.py +++ b/tests/real_call_test.py @@ -5,30 +5,49 @@ """Real call Tests""" import socket +import socketserver import ssl +import threading +import time import adafruit_connection_manager import pytest +from local_test_server import LocalTestServerHandler import adafruit_requests -@pytest.mark.parametrize( - ("path", "status_code", "text_result", "json_keys"), - ( - ("get", 200, None, {"url": "https://httpbin.org/get"}), +def test_gets(): + path_index = 0 + status_code_index = 1 + text_result_index = 2 + json_keys_index = 3 + cases = [ + ("get", 200, None, {"url": "http://localhost:5000/get"}), ("status/200", 200, "", None), ("status/204", 204, "", None), - ), -) -def test_gets(path, status_code, text_result, json_keys): - requests = adafruit_requests.Session(socket, ssl.create_default_context()) - with requests.get(f"https://httpbin.org/{path}") as response: - assert response.status_code == status_code - if text_result is not None: - assert response.text == text_result - if json_keys is not None: - for key, value in json_keys.items(): - assert response.json()[key] == value - - adafruit_connection_manager.connection_manager_close_all(release_references=True) + ] + + with socketserver.TCPServer(("", 5000), LocalTestServerHandler) as server: + server_thread = threading.Thread(target=server.serve_forever) + server_thread.daemon = True + server_thread.start() + + time.sleep(2) # Give the server some time to start + + for case in cases: + requests = adafruit_requests.Session(socket, ssl.create_default_context()) + with requests.get(f"http://localhost:5000/{case[path_index]}") as response: + assert response.status_code == case[status_code_index] + if case[text_result_index] is not None: + assert response.text == case[text_result_index] + if case[json_keys_index] is not None: + for key, value in case[json_keys_index].items(): + assert response.json()[key] == value + + adafruit_connection_manager.connection_manager_close_all(release_references=True) + + server.shutdown() + server.server_close() + time.sleep(2) + print("Server stopped.") From 9a4ae29a8e486f1a529fc077bc64887a952ad3d9 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Sat, 31 May 2025 10:26:56 -0500 Subject: [PATCH 299/305] try 127.0.0.1 --- tests/real_call_test.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/real_call_test.py b/tests/real_call_test.py index 7876a90..b982f00 100644 --- a/tests/real_call_test.py +++ b/tests/real_call_test.py @@ -28,7 +28,7 @@ def test_gets(): ("status/204", 204, "", None), ] - with socketserver.TCPServer(("", 5000), LocalTestServerHandler) as server: + with socketserver.TCPServer(("127.0.0.1", 5000), LocalTestServerHandler) as server: server_thread = threading.Thread(target=server.serve_forever) server_thread.daemon = True server_thread.start() @@ -37,7 +37,7 @@ def test_gets(): for case in cases: requests = adafruit_requests.Session(socket, ssl.create_default_context()) - with requests.get(f"http://localhost:5000/{case[path_index]}") as response: + with requests.get(f"http://127.0.0.1:5000/{case[path_index]}") as response: assert response.status_code == case[status_code_index] if case[text_result_index] is not None: assert response.text == case[text_result_index] @@ -50,4 +50,3 @@ def test_gets(): server.shutdown() server.server_close() time.sleep(2) - print("Server stopped.") From 5e646b244cf36f879f15aaf77a270e4c7e6e8336 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Wed, 4 Jun 2025 10:00:20 -0500 Subject: [PATCH 300/305] update rtd.yml file Signed-off-by: foamyguy --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 88bca9f..255dafd 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -12,7 +12,7 @@ sphinx: configuration: docs/conf.py build: - os: ubuntu-20.04 + os: ubuntu-lts-latest tools: python: "3" From f2e4ed161fbff59ff8da41f680ef2d8595abd0a4 Mon Sep 17 00:00:00 2001 From: Ed Baker Date: Fri, 3 Oct 2025 16:48:07 +0100 Subject: [PATCH 301/305] LICENSE non-exec. --- LICENSE | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 LICENSE diff --git a/LICENSE b/LICENSE old mode 100755 new mode 100644 From f3610eafc01030ae12392c2d81a293b30776b18e Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 10 Oct 2025 16:18:40 -0500 Subject: [PATCH 302/305] remove deprecated ruff rule, workaround RTD theme property inline issue. Signed-off-by: foamyguy --- docs/_static/custom.css | 8 ++++++++ docs/conf.py | 3 +++ ruff.toml | 1 - 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 docs/_static/custom.css diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..d60cf4b --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,8 @@ +/* SPDX-FileCopyrightText: 2025 Sam Blenny + * SPDX-License-Identifier: MIT + */ + +/* Monkey patch the rtd theme to prevent horizontal stacking of short items + * see https://github.com/readthedocs/sphinx_rtd_theme/issues/1301 + */ +.py.property{display: block !important;} diff --git a/docs/conf.py b/docs/conf.py index 689c1af..132c2eb 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -106,6 +106,9 @@ # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] +# Include extra css to work around rtd theme glitches +html_css_files = ["custom.css"] + # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. diff --git a/ruff.toml b/ruff.toml index db37c83..a3b4844 100644 --- a/ruff.toml +++ b/ruff.toml @@ -16,7 +16,6 @@ extend-select = [ "PLC2401", # non-ascii-name "PLC2801", # unnecessary-dunder-call "PLC3002", # unnecessary-direct-lambda-call - "E999", # syntax-error "PLE0101", # return-in-init "F706", # return-outside-function "F704", # yield-outside-function From 9a33321438e6dbb76f0b4624387493373a36d806 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 24 Oct 2025 17:10:46 -0500 Subject: [PATCH 303/305] use local server on more tests --- tests/files_test.py | 30 +++++++++++++++++++++++++++++- tests/local_test_server.py | 9 +++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/files_test.py b/tests/files_test.py index c263cd3..449d768 100644 --- a/tests/files_test.py +++ b/tests/files_test.py @@ -5,12 +5,36 @@ """Post Files Tests""" # pylint: disable=line-too-long +import functools import re +import socketserver +import threading +import time from unittest import mock import mocket import pytest import requests as python_requests +from local_test_server import LocalTestServerHandler + + +def uses_local_server(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + with socketserver.TCPServer(("127.0.0.1", 5000), LocalTestServerHandler) as server: + server_thread = threading.Thread(target=server.serve_forever) + server_thread.daemon = True + server_thread.start() + time.sleep(2) # Give the server some time to start + + result = func(*args, **kwargs) + + server.shutdown() + server.server_close() + time.sleep(2) + return result + + return wrapper @pytest.fixture @@ -20,7 +44,8 @@ def log_stream(): @pytest.fixture def post_url(): - return "https://httpbin.org/post" + # return "https://httpbin.org/post" + return "http://127.0.0.1:5000/post" @pytest.fixture @@ -63,6 +88,7 @@ def get_actual_request_data(log_stream): return boundary, content_length, actual_request_post +@uses_local_server def test_post_file_as_data( # pylint: disable=unused-argument requests, sock, log_stream, post_url, request_logging ): @@ -85,6 +111,7 @@ def test_post_file_as_data( # pylint: disable=unused-argument assert sent.endswith(actual_request_post) +@uses_local_server def test_post_files_text( # pylint: disable=unused-argument sock, requests, log_stream, post_url, request_logging ): @@ -120,6 +147,7 @@ def test_post_files_text( # pylint: disable=unused-argument assert sent.endswith(actual_request_post) +@uses_local_server def test_post_files_file( # pylint: disable=unused-argument sock, requests, log_stream, post_url, request_logging ): diff --git a/tests/local_test_server.py b/tests/local_test_server.py index 73b52b3..b5b2be1 100644 --- a/tests/local_test_server.py +++ b/tests/local_test_server.py @@ -6,6 +6,15 @@ class LocalTestServerHandler(SimpleHTTPRequestHandler): + def do_POST(self): + if self.path == "/post": + resp_body = json.dumps({"url": "http://localhost:5000/post"}).encode("utf-8") + self.send_response(200) + self.send_header("Content-type", "application/json") + self.send_header("Content-Length", str(len(resp_body))) + self.end_headers() + self.wfile.write(resp_body) + def do_GET(self): if self.path == "/get": resp_body = json.dumps({"url": "http://localhost:5000/get"}).encode("utf-8") From 21d788c02e4296c0d12e7da30f54846c2fc47a34 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Sat, 1 Nov 2025 10:40:16 -0500 Subject: [PATCH 304/305] move decorator to local_test_server.py. Use decorator in real_call_test. Add tests for https & redirect. --- tests/files_test.py | 27 ++---------------------- tests/local_test_server.py | 27 ++++++++++++++++++++++++ tests/real_call_test.py | 42 +++++++++++++++++++++----------------- 3 files changed, 52 insertions(+), 44 deletions(-) diff --git a/tests/files_test.py b/tests/files_test.py index 449d768..5a81877 100644 --- a/tests/files_test.py +++ b/tests/files_test.py @@ -5,36 +5,13 @@ """Post Files Tests""" # pylint: disable=line-too-long -import functools import re -import socketserver -import threading -import time from unittest import mock import mocket import pytest import requests as python_requests -from local_test_server import LocalTestServerHandler - - -def uses_local_server(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - with socketserver.TCPServer(("127.0.0.1", 5000), LocalTestServerHandler) as server: - server_thread = threading.Thread(target=server.serve_forever) - server_thread.daemon = True - server_thread.start() - time.sleep(2) # Give the server some time to start - - result = func(*args, **kwargs) - - server.shutdown() - server.server_close() - time.sleep(2) - return result - - return wrapper +from local_test_server import uses_local_server @pytest.fixture @@ -44,7 +21,6 @@ def log_stream(): @pytest.fixture def post_url(): - # return "https://httpbin.org/post" return "http://127.0.0.1:5000/post" @@ -192,6 +168,7 @@ def test_post_files_file( # pylint: disable=unused-argument assert sent.endswith(actual_request_post) +@uses_local_server def test_post_files_complex( # pylint: disable=unused-argument sock, requests, log_stream, post_url, request_logging ): diff --git a/tests/local_test_server.py b/tests/local_test_server.py index b5b2be1..04f47ce 100644 --- a/tests/local_test_server.py +++ b/tests/local_test_server.py @@ -1,10 +1,37 @@ # SPDX-FileCopyrightText: 2025 Tim Cocks # # SPDX-License-Identifier: MIT +import functools import json +import socketserver +import threading +import time from http.server import SimpleHTTPRequestHandler +def uses_local_server(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + with ReusableAddressTCPServer(("127.0.0.1", 5000), LocalTestServerHandler) as server: + server_thread = threading.Thread(target=server.serve_forever) + server_thread.daemon = True + server_thread.start() + time.sleep(2) # Give the server some time to start + + result = func(*args, **kwargs) + + server.shutdown() + server.server_close() + return result + + return wrapper + + +class ReusableAddressTCPServer(socketserver.TCPServer): + # Enable SO_REUSEADDR + allow_reuse_address = True + + class LocalTestServerHandler(SimpleHTTPRequestHandler): def do_POST(self): if self.path == "/post": diff --git a/tests/real_call_test.py b/tests/real_call_test.py index b982f00..60a3450 100644 --- a/tests/real_call_test.py +++ b/tests/real_call_test.py @@ -12,11 +12,12 @@ import adafruit_connection_manager import pytest -from local_test_server import LocalTestServerHandler +from local_test_server import uses_local_server import adafruit_requests +@uses_local_server def test_gets(): path_index = 0 status_code_index = 1 @@ -28,25 +29,28 @@ def test_gets(): ("status/204", 204, "", None), ] - with socketserver.TCPServer(("127.0.0.1", 5000), LocalTestServerHandler) as server: - server_thread = threading.Thread(target=server.serve_forever) - server_thread.daemon = True - server_thread.start() + for case in cases: + requests = adafruit_requests.Session(socket, ssl.create_default_context()) + with requests.get(f"http://127.0.0.1:5000/{case[path_index]}") as response: + assert response.status_code == case[status_code_index] + if case[text_result_index] is not None: + assert response.text == case[text_result_index] + if case[json_keys_index] is not None: + for key, value in case[json_keys_index].items(): + assert response.json()[key] == value - time.sleep(2) # Give the server some time to start + adafruit_connection_manager.connection_manager_close_all(release_references=True) - for case in cases: - requests = adafruit_requests.Session(socket, ssl.create_default_context()) - with requests.get(f"http://127.0.0.1:5000/{case[path_index]}") as response: - assert response.status_code == case[status_code_index] - if case[text_result_index] is not None: - assert response.text == case[text_result_index] - if case[json_keys_index] is not None: - for key, value in case[json_keys_index].items(): - assert response.json()[key] == value - adafruit_connection_manager.connection_manager_close_all(release_references=True) +def test_http_to_https_redirect(): + url = "http://www.adafruit.com/api/quotes.php" + requests = adafruit_requests.Session(socket, ssl.create_default_context()) + with requests.get(url) as response: + assert response.status_code == 200 - server.shutdown() - server.server_close() - time.sleep(2) + +def test_https_direct(): + url = "https://www.adafruit.com/api/quotes.php" + requests = adafruit_requests.Session(socket, ssl.create_default_context()) + with requests.get(url) as response: + assert response.status_code == 200 From a7ec3bae806b0de97275d65902189ed43f950b39 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 3 Nov 2025 12:42:53 -0600 Subject: [PATCH 305/305] test allow_redirects --- tests/real_call_test.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/real_call_test.py b/tests/real_call_test.py index 60a3450..fad0178 100644 --- a/tests/real_call_test.py +++ b/tests/real_call_test.py @@ -42,11 +42,18 @@ def test_gets(): adafruit_connection_manager.connection_manager_close_all(release_references=True) -def test_http_to_https_redirect(): +@pytest.mark.parametrize( + ("allow_redirects", "status_code"), + ( + (True, 200), + (False, 301), + ), +) +def test_http_to_https_redirect(allow_redirects, status_code): url = "http://www.adafruit.com/api/quotes.php" requests = adafruit_requests.Session(socket, ssl.create_default_context()) - with requests.get(url) as response: - assert response.status_code == 200 + with requests.get(url, allow_redirects=allow_redirects) as response: + assert response.status_code == status_code def test_https_direct():