From a136c513a4722dfa37f1f739d1e04082593eb4cb Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Wed, 29 Nov 2023 13:42:52 -0500 Subject: [PATCH 1/5] Move to pyproject toml --- .github/workflows/ci-build.yml | 4 +- .github/workflows/pytype.yml | 4 +- pyproject.toml | 69 ++++++++++++++- pytest.ini | 9 -- requirements/optional.txt | 17 ++++ requirements/testing.txt | 19 ++++ scripts/build_pypi_package.sh | 11 +++ scripts/deploy_to_prod_pypi_org.sh | 11 ++- scripts/deploy_to_test_pypi_org.sh | 12 +++ scripts/run_integration_tests.sh | 4 +- scripts/run_unit_tests.sh | 4 +- scripts/run_validation.sh | 4 +- scripts/uninstall_all.sh | 4 + setup.cfg | 2 - setup.py | 138 +---------------------------- 15 files changed, 153 insertions(+), 159 deletions(-) delete mode 100644 pytest.ini create mode 100644 requirements/optional.txt create mode 100644 requirements/testing.txt create mode 100755 scripts/build_pypi_package.sh create mode 100755 scripts/deploy_to_test_pypi_org.sh create mode 100755 scripts/uninstall_all.sh delete mode 100644 setup.cfg diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index fceb760e6..3e8e9fa88 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -26,8 +26,8 @@ jobs: - name: Install dependencies run: | pip install -U pip wheel - pip install -e ".[testing]" - pip install -e ".[optional]" + pip install -r requirements/testing.txt + pip install -r requirements/optional.txt - name: Run validation run: | python setup.py validate diff --git a/.github/workflows/pytype.yml b/.github/workflows/pytype.yml index ada1b1b48..d5782e111 100644 --- a/.github/workflows/pytype.yml +++ b/.github/workflows/pytype.yml @@ -21,8 +21,8 @@ jobs: - name: Install dependencies run: | pip install -U pip - pip install -e ".[testing]" - pip install -e ".[optional]" + pip install -r requirements/testing.txt + pip install -r requirements/optional.txt # As pytype can change its behavior in newer versions, we manually upgrade it # 2023.9.11 fails due to errors that only happen on GH Actions pip install "pytype==2023.5.24" diff --git a/pyproject.toml b/pyproject.toml index 0cda914cd..18d141d15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,67 @@ -# black project prefers pyproject.toml -# that's why we have this file in addition to other setting files +[build-system] +requires = ["setuptools", "pytest-runner", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "slack_sdk" +dynamic = ["version", "readme"] +description = "The Slack API Platform SDK for Python" +license = { text = "MIT" } +authors = [{ name = "Slack Technologies, LLC", email = "opensource@slack.com" }] +requires-python = ">=3.6" +keywords = [ + "slack", + "slack-api", + "web-api", + "slack-rtm", + "websocket", + "chat", + "chatbot", + "chatops", +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Topic :: Communications :: Chat", + "Topic :: System :: Networking", + "Topic :: Office/Business", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] + + +[project.urls] +homepage = "https://github.com/slackapi/python-slack-sdk" + +[tool.setuptools.packages.find] +include = ["slack_bolt*", "slack*"] + +[tool.setuptools.dynamic] +version = { attr = "slack_sdk.version.__version__" } +readme = { file = ["README.md"], content-type = "text/markdown" } + +[tool.distutils.bdist_wheel] +universal = true + [tool.black] -line-length = 125 \ No newline at end of file +line-length = 125 + +[tool.pytest.ini_options] +testpaths = ["tests"] +log_file = "logs/pytest.log" +log_file_level = "DEBUG" +log_format = "%(asctime)s %(levelname)s %(message)s" +log_date_format = "%Y-%m-%d %H:%M:%S" +filterwarnings = [ + "ignore:\"@coroutine\" decorator is deprecated since Python 3.8, use \"async def\" instead:DeprecationWarning", + "ignore:The loop argument is deprecated since Python 3.8, and scheduled for removal in Python 3.10.:DeprecationWarning", +] +asyncio_mode = "auto" diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index c9030f7af..000000000 --- a/pytest.ini +++ /dev/null @@ -1,9 +0,0 @@ -[pytest] -log_file = logs/pytest.log -log_file_level = DEBUG -log_format = %(asctime)s %(levelname)s %(message)s -log_date_format = %Y-%m-%d %H:%M:%S -filterwarnings = - ignore:"@coroutine" decorator is deprecated since Python 3.8, use "async def" instead:DeprecationWarning - ignore:The loop argument is deprecated since Python 3.8, and scheduled for removal in Python 3.10.:DeprecationWarning -asyncio_mode = auto diff --git a/requirements/optional.txt b/requirements/optional.txt new file mode 100644 index 000000000..0f1d6cd58 --- /dev/null +++ b/requirements/optional.txt @@ -0,0 +1,17 @@ +# pip install -r requirements/optional.txt +# async modules depend on aiohttp +aiodns>1.0 +# We recommend using 3.7.1+ for RTMClient +# https://github.com/slackapi/python-slack-sdk/issues/912 +aiohttp>=3.7.3,<4 +# used only under slack_sdk/*_store +boto3<=2 +# InstallationStore/OAuthStateStore +# Since v3.20, we no longer support SQLAlchemy 1.3 or older. +# If you need to use a legacy version, please add our v3.19.5 code to your project. +SQLAlchemy>=1.4,<3 +# Socket Mode +# websockets 9 is not compatible with Python 3.10 +websockets>=9.1,<10; python_version=="3.6" +websockets>=10,<11; python_version>"3.6" +websocket-client>=1,<2 diff --git a/requirements/testing.txt b/requirements/testing.txt new file mode 100644 index 000000000..31f636603 --- /dev/null +++ b/requirements/testing.txt @@ -0,0 +1,19 @@ +# pip install -r requirements/testing.txt +pytest>=6.2.5,<7 +pytest-asyncio<1 # for async +Flask-Sockets>=0.2,<1 +Flask>=1,<2 # TODO: Flask-Sockets is not yet compatible with Flask 2.x +Werkzeug<2 # TODO: Flask-Sockets is not yet compatible with Flask 2.x +itsdangerous==1.1.0 # TODO: Flask-Sockets is not yet compatible with Flask 2.x +Jinja2==3.0.3 # https://github.com/pallets/flask/issues/4494 +pytest-cov>=2,<3 +flake8>=5,<6 +# Don't change this version without running CI builds; +# The latest version may not be available for older Python runtime +black>=22.8.0; python_version=="3.6" +black==22.10.0; python_version>"3.6" +click==8.0.4 # black is affected by https://github.com/pallets/click/issues/2225 +psutil>=5,<6 +# used only under slack_sdk/*_store +boto3<=2 +moto>=3,<4 # For AWS tests diff --git a/scripts/build_pypi_package.sh b/scripts/build_pypi_package.sh new file mode 100755 index 000000000..cc1fe7528 --- /dev/null +++ b/scripts/build_pypi_package.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +script_dir=`dirname $0` +cd ${script_dir}/.. +rm -rf ./slack_bolt.egg-info + +pip install -U pip && \ + pip install twine build && \ + rm -rf dist/ build/ slack_sdk.egg-info/ && \ + python -m build --sdist --wheel && \ + twine check dist/* diff --git a/scripts/deploy_to_prod_pypi_org.sh b/scripts/deploy_to_prod_pypi_org.sh index 4b15fef2c..01863545c 100755 --- a/scripts/deploy_to_prod_pypi_org.sh +++ b/scripts/deploy_to_prod_pypi_org.sh @@ -1,3 +1,12 @@ #!/bin/bash -python setup.py upload +script_dir=`dirname $0` +cd ${script_dir}/.. +rm -rf ./slack_sdk.egg-info + +pip install -U pip && \ + pip install twine build && \ + rm -rf dist/ build/ slack_sdk.egg-info/ && \ + python -m build --sdist --wheel && \ + twine check dist/* && \ + twine upload dist/* diff --git a/scripts/deploy_to_test_pypi_org.sh b/scripts/deploy_to_test_pypi_org.sh new file mode 100755 index 000000000..01f6a9818 --- /dev/null +++ b/scripts/deploy_to_test_pypi_org.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +script_dir=`dirname $0` +cd ${script_dir}/.. +rm -rf ./slack_sdk.egg-info + +pip install -U pip && \ + pip install twine build && \ + rm -rf dist/ build/ slack_sdk.egg-info/ && \ + python -m build --sdist --wheel && \ + twine check dist/* && \ + twine upload --repository testpypi dist/* diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index ee12d155f..c67a4a086 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -8,7 +8,9 @@ cd ${script_dir}/.. test_target="$1" python_version=`python --version | awk '{print $2}'` -pip install -e . +pip install -U pip && \ + pip install -r requirements/testing.txt && \ + pip install -r requirements/optional.txt && \ if [[ $test_target != "" ]] then diff --git a/scripts/run_unit_tests.sh b/scripts/run_unit_tests.sh index 80d4119d0..b9090c019 100755 --- a/scripts/run_unit_tests.sh +++ b/scripts/run_unit_tests.sh @@ -8,7 +8,9 @@ cd ${script_dir}/.. test_target="$1" python_version=`python --version | awk '{print $2}'` -pip install -e . +pip install -U pip && \ + pip install -r requirements/testing.txt && \ + pip install -r requirements/optional.txt && \ if [[ $test_target != "" ]] then diff --git a/scripts/run_validation.sh b/scripts/run_validation.sh index 8e11f0f62..cca0dd8eb 100755 --- a/scripts/run_validation.sh +++ b/scripts/run_validation.sh @@ -4,8 +4,8 @@ script_dir=`dirname $0` cd ${script_dir}/.. pip install -U pip && \ - pip install -e ".[testing]" && \ - pip install -e ".[optional]" && \ + pip install -r requirements/testing.txt && \ + pip install -r requirements/optional.txt && \ black slack_sdk/ slack/ tests/ integration_tests/ && \ python setup.py codegen && \ python setup.py validate diff --git a/scripts/uninstall_all.sh b/scripts/uninstall_all.sh new file mode 100755 index 000000000..1ce28826e --- /dev/null +++ b/scripts/uninstall_all.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +pip uninstall -y slack-sdk && \ + pip freeze | grep -v "^-e" | xargs pip uninstall -y diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index b7e478982..000000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[aliases] -test=pytest diff --git a/setup.py b/setup.py index 253332ae6..3580b3b74 100644 --- a/setup.py +++ b/setup.py @@ -1,50 +1,18 @@ # -*- coding: utf-8 -*- -import codecs import os import subprocess import sys -from shutil import rmtree -from setuptools import setup, find_packages, Command +from setuptools import setup, Command here = os.path.abspath(os.path.dirname(__file__)) -__version__ = None -exec(open(f"{here}/slack_sdk/version.py").read()) - -long_description = "" -with codecs.open(os.path.join(here, "README.md"), encoding="utf-8") as readme: - long_description = readme.read() - -validate_dependencies = [ - "pytest>=6.2.5,<7", - "pytest-asyncio<1", # for async - "Flask-Sockets>=0.2,<1", - "Flask>=1,<2", # TODO: Flask-Sockets is not yet compatible with Flask 2.x - "Werkzeug<2", # TODO: Flask-Sockets is not yet compatible with Flask 2.x - "itsdangerous==1.1.0", # TODO: Flask-Sockets is not yet compatible with Flask 2.x - "Jinja2==3.0.3", # https://github.com/pallets/flask/issues/4494 - "pytest-cov>=2,<3", - "flake8>=5,<6", - # Don't change this version without running CI builds; - # The latest version may not be available for older Python runtime - "black==22.8.0", - "click==8.0.4", # black is affected by https://github.com/pallets/click/issues/2225 - "psutil>=5,<6", - # used only under slack_sdk/*_store - "boto3<=2", - "moto>=3,<4", # For AWS tests -] codegen_dependencies = [ # Don't change this version without running CI builds; # The latest version may not be available for older Python runtime "black==22.10.0", ] -needs_pytest = {"pytest", "test", "ptr"}.intersection(sys.argv) -pytest_runner = ["pytest-runner"] if needs_pytest else [] - - class BaseCommand(Command): user_options = [] @@ -67,37 +35,6 @@ def _run(self, s, command): sys.exit(error.returncode) -class UploadCommand(BaseCommand): - """Support setup.py upload. Thanks @kennethreitz!""" - - description = "Build and publish the package." - - def run(self): - self._run( - "Installing upload dependencies ...", - [sys.executable, "-m", "pip", "install", "wheel"], - ) - try: - self.status("Removing previous builds ...") - rmtree(os.path.join(here, "dist")) - rmtree(os.path.join(here, "build")) - except OSError: - pass - - self._run( - "Building Source and Wheel (universal) distribution ...", - [sys.executable, "setup.py", "sdist", "bdist_wheel", "--universal"], - ) - self._run( - "Installing Twine dependency ...", - [sys.executable, "-m", "pip", "install", "twine"], - ) - self._run( - "Uploading the package to PyPI via Twine ...", - [sys.executable, "-m", "twine", "upload", "dist/*"], - ) - - class CodegenCommand(BaseCommand): def run(self): self._run( @@ -210,7 +147,7 @@ def initialize_options(self): def run(self): self._run( "Installing test dependencies ...", - [sys.executable, "-m", "pip", "install"] + validate_dependencies, + [sys.executable, "-m", "pip", "install", "-r", "requirements/testing.txt"], ) self._run("Running black ...", [sys.executable, "-m", "black", f"{here}/slack"]) self._run("Running black ...", [sys.executable, "-m", "black", f"{here}/slack_sdk"]) @@ -281,78 +218,7 @@ def run(self): setup( - name="slack_sdk", - version=__version__, - description="The Slack API Platform SDK for Python", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/slackapi/python-slack-sdk", - author="Slack Technologies, LLC", - author_email="opensource@slack.com", - python_requires=">=3.6.0", - include_package_data=True, - license="MIT", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "Topic :: Communications :: Chat", - "Topic :: System :: Networking", - "Topic :: Office/Business", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - ], - keywords="slack slack-api web-api slack-rtm websocket chat chatbot chatops", - packages=find_packages( - exclude=[ - "docs", - "docs-src", - "docs-v2", - "docs-src-v2", - "docs-v3", - "docs-src-v3", - "integration_tests", - "integration_tests_legacy", - "tests", - "tests.*", - "tutorial", - ] - ), - install_requires=[], - extras_require={ - # pip install -e ".[testing]" - "testing": validate_dependencies, - # pip install -e ".[optional]" - "optional": [ - # async modules depend on aiohttp - "aiodns>1.0", - # We recommend using 3.7.1+ for RTMClient - # https://github.com/slackapi/python-slack-sdk/issues/912 - "aiohttp>=3.7.3,<4", - # used only under slack_sdk/*_store - "boto3<=2", - # InstallationStore/OAuthStateStore - # Since v3.20, we no longer support SQLAlchemy 1.3 or older. - # If you need to use a legacy version, please add our v3.19.5 code to your project. - "SQLAlchemy>=1.4,<3", - # Socket Mode - # websockets 9 is not compatible with Python 3.10 - "websockets>=10,<11" if sys.version_info.minor > 6 else "websockets>=9.1,<10", - "websocket-client>=1,<2", - ], - }, - setup_requires=pytest_runner, - test_suite="tests", - tests_require=validate_dependencies, cmdclass={ - "upload": UploadCommand, "codegen": CodegenCommand, "validate": ValidateCommand, "unit_tests": UnitTestsCommand, From 1f8ae550a9697299d49227a1422fe209fdc6ec8d Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Thu, 30 Nov 2023 10:23:31 -0500 Subject: [PATCH 2/5] Update pyproject.toml Co-authored-by: Aliaksei Urbanski --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 18d141d15..9b81fbaea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ classifiers = [ homepage = "https://github.com/slackapi/python-slack-sdk" [tool.setuptools.packages.find] -include = ["slack_bolt*", "slack*"] +include = ["slack*"] [tool.setuptools.dynamic] version = { attr = "slack_sdk.version.__version__" } From 8e9fb8ae57cb59d0cc08725648246465bddb3888 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Thu, 30 Nov 2023 10:24:22 -0500 Subject: [PATCH 3/5] Update scripts/build_pypi_package.sh Co-authored-by: Aliaksei Urbanski --- scripts/build_pypi_package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build_pypi_package.sh b/scripts/build_pypi_package.sh index cc1fe7528..e4acb96bb 100755 --- a/scripts/build_pypi_package.sh +++ b/scripts/build_pypi_package.sh @@ -2,7 +2,7 @@ script_dir=`dirname $0` cd ${script_dir}/.. -rm -rf ./slack_bolt.egg-info +rm -rf ./slack_sdk.egg-info pip install -U pip && \ pip install twine build && \ From 0884adf1334f9c7dedb734d657e8db6edbd56257 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Thu, 30 Nov 2023 10:42:23 -0500 Subject: [PATCH 4/5] Merge remote-tracking branch 'upstream/main' into pyproject-toml-configuration --- .github/workflows/ci-build.yml | 16 ++++- pyproject.toml | 5 +- requirements/testing.txt | 6 +- setup.py | 24 +++---- .../oauth/installation_store/internals.py | 65 ++++++++++++------- .../oauth/installation_store/models/bot.py | 26 ++------ .../installation_store/models/installation.py | 34 ++-------- .../installation_store/test_internals.py | 32 ++++++++- .../test_interactions_websockets.py | 4 +- 9 files changed, 119 insertions(+), 93 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 3e8e9fa88..bb88d11fb 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -10,10 +10,19 @@ jobs: build: # Avoiding -latest due to https://github.com/actions/setup-python/issues/162 runs-on: ubuntu-20.04 - timeout-minutes: 10 + timeout-minutes: 15 strategy: + fail-fast: false matrix: - python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: + - '3.12' + - '3.11' + - '3.10' + - '3.9' + - '3.8' + - '3.7' + - '3.6' + - 'pypy3.10' env: PYTHON_SLACK_SDK_MOCK_SERVER_MODE: 'threading' CI_LARGE_SOCKET_MODE_PAYLOAD_TESTING_DISABLED: '1' @@ -23,12 +32,13 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + cache: pip - name: Install dependencies run: | pip install -U pip wheel pip install -r requirements/testing.txt pip install -r requirements/optional.txt - - name: Run validation + - name: Run validation (black/flake8/pytest) run: | python setup.py validate - name: Run tests for SQLAlchemy v1.4 (backward-compatibility) diff --git a/pyproject.toml b/pyproject.toml index 9b81fbaea..618b2078e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ classifiers = [ "Topic :: Office/Business", "License :: OSI Approved :: MIT License", "Programming Language :: Python", - "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", @@ -35,6 +35,8 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", ] @@ -63,5 +65,6 @@ log_date_format = "%Y-%m-%d %H:%M:%S" filterwarnings = [ "ignore:\"@coroutine\" decorator is deprecated since Python 3.8, use \"async def\" instead:DeprecationWarning", "ignore:The loop argument is deprecated since Python 3.8, and scheduled for removal in Python 3.10.:DeprecationWarning", + "ignore:slack.* package is deprecated. Please use slack_sdk.* package instead.*:UserWarning", ] asyncio_mode = "auto" diff --git a/requirements/testing.txt b/requirements/testing.txt index 31f636603..06555a553 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -1,5 +1,5 @@ # pip install -r requirements/testing.txt -pytest>=6.2.5,<7 +pytest>=7.0.1,<8 pytest-asyncio<1 # for async Flask-Sockets>=0.2,<1 Flask>=1,<2 # TODO: Flask-Sockets is not yet compatible with Flask 2.x @@ -7,7 +7,9 @@ Werkzeug<2 # TODO: Flask-Sockets is not yet compatible with Flask 2.x itsdangerous==1.1.0 # TODO: Flask-Sockets is not yet compatible with Flask 2.x Jinja2==3.0.3 # https://github.com/pallets/flask/issues/4494 pytest-cov>=2,<3 -flake8>=5,<6 +# while flake8 5.x have issues with Python 3.12, flake8 6.x requires Python >= 3.8.1, +# so 5.x should be kept in order to stay compatible with Python 3.6/3.7 +flake8>=5.0.4,<7 # Don't change this version without running CI builds; # The latest version may not be available for older Python runtime black>=22.8.0; python_version=="3.6" diff --git a/setup.py b/setup.py index 3580b3b74..222114dbc 100644 --- a/setup.py +++ b/setup.py @@ -60,7 +60,7 @@ def run(self): async_source = header + source async_source = re.sub(" def ", " async def ", async_source) async_source = re.sub("from asyncio import Future\n", "", async_source) - async_source = re.sub("return self.api_call\(", "return await self.api_call(", async_source) + async_source = re.sub(r"return self.api_call\(", "return await self.api_call(", async_source) async_source = re.sub("-> SlackResponse", "-> AsyncSlackResponse", async_source) async_source = re.sub( "from .base_client import BaseClient, SlackResponse", @@ -69,7 +69,7 @@ def run(self): ) # from slack_sdk import WebClient async_source = re.sub( - "class WebClient\(BaseClient\):", + r"class WebClient\(BaseClient\):", "class AsyncWebClient(AsyncBaseClient):", async_source, ) @@ -78,19 +78,19 @@ def run(self): "from slack_sdk.web.async_client import AsyncWebClient", async_source, ) - async_source = re.sub("= WebClient\(", "= AsyncWebClient(", async_source) + async_source = re.sub(r"= WebClient\(", "= AsyncWebClient(", async_source) async_source = re.sub( - " self.files_getUploadURLExternal\(", + r" self.files_getUploadURLExternal\(", " await self.files_getUploadURLExternal(", async_source, ) async_source = re.sub( - " self.files_completeUploadExternal\(", + r" self.files_completeUploadExternal\(", " await self.files_completeUploadExternal(", async_source, ) async_source = re.sub( - " self.files_info\(", + r" self.files_info\(", " await self.files_info(", async_source, ) @@ -100,7 +100,7 @@ def run(self): async_source, ) async_source = re.sub( - " _attach_full_file_metadata_async\(", + r" _attach_full_file_metadata_async\(", " await _attach_full_file_metadata_async(", async_source, ) @@ -115,7 +115,7 @@ def run(self): legacy_source, ) legacy_source = re.sub( - "class WebClient\(BaseClient\):", + r"class WebClient\(BaseClient\):", "class LegacyWebClient(LegacyBaseClient):", legacy_source, ) @@ -124,7 +124,7 @@ def run(self): "from slack_sdk.web.legacy_client import LegacyWebClient", legacy_source, ) - legacy_source = re.sub("= WebClient\(", "= LegacyWebClient(", legacy_source) + legacy_source = re.sub(r"= WebClient\(", "= LegacyWebClient(", legacy_source) with open(f"{here}/slack_sdk/web/legacy_client.py", "w") as output: output.write(legacy_source) @@ -149,8 +149,10 @@ def run(self): "Installing test dependencies ...", [sys.executable, "-m", "pip", "install", "-r", "requirements/testing.txt"], ) - self._run("Running black ...", [sys.executable, "-m", "black", f"{here}/slack"]) - self._run("Running black ...", [sys.executable, "-m", "black", f"{here}/slack_sdk"]) + + self._run("Running black for legacy packages ...", [sys.executable, "-m", "black", f"{here}/slack"]) + self._run("Running black for slack_sdk package ...", [sys.executable, "-m", "black", f"{here}/slack_sdk"]) + self._run("Running flake8 for legacy packages ...", [sys.executable, "-m", "flake8", f"{here}/slack"]) self._run("Running flake8 for slack_sdk package ...", [sys.executable, "-m", "flake8", f"{here}/slack_sdk"]) diff --git a/slack_sdk/oauth/installation_store/internals.py b/slack_sdk/oauth/installation_store/internals.py index f0b31d394..1c3bfccaf 100644 --- a/slack_sdk/oauth/installation_store/internals.py +++ b/slack_sdk/oauth/installation_store/internals.py @@ -1,32 +1,47 @@ -import platform -import datetime - -(major, minor, patch) = platform.python_version_tuple() -is_python_3_6: bool = int(major) == 3 and int(minor) >= 6 - -utc_timezone = datetime.timezone.utc - - -def _from_iso_format_to_datetime(iso_datetime_str: str) -> datetime.datetime: - if is_python_3_6: - elements = iso_datetime_str.split(" ") - ymd = elements[0].split("-") - hms = elements[1].split(":") - return datetime.datetime( - int(ymd[0]), - int(ymd[1]), - int(ymd[2]), - int(hms[0]), - int(hms[1]), - int(hms[2]), - 0, - utc_timezone, - ) +import sys +from datetime import datetime, timezone +from typing import Type, TypeVar, Union + + +def _from_iso_format_to_datetime(iso_datetime_str: str) -> datetime: + if sys.version_info[:2] == (3, 6): + format = "%Y-%m-%d %H:%M:%S" + if "." in iso_datetime_str: + format += ".%f" + return datetime.strptime(iso_datetime_str, format).replace(tzinfo=timezone.utc) else: if "+" not in iso_datetime_str: iso_datetime_str += "+00:00" - return datetime.datetime.fromisoformat(iso_datetime_str) + return datetime.fromisoformat(iso_datetime_str) def _from_iso_format_to_unix_timestamp(iso_datetime_str: str) -> float: return _from_iso_format_to_datetime(iso_datetime_str).timestamp() + + +TimestampType = TypeVar("TimestampType", float, int) + + +def _timestamp_to_type(ts: Union[TimestampType, datetime, str], target_type: Type[TimestampType]) -> TimestampType: + result: TimestampType + + if isinstance(ts, target_type): + # unnecessary type casting makes pytype happy + result = target_type(ts) + + # although a type of the timestamp is just checked, + # pytype doesn't consider the following line valid: + # result = ts + # see https://github.com/google/pytype/issues/1012 + + elif isinstance(ts, datetime): + result = target_type(ts.timestamp()) + elif isinstance(ts, str): + try: + result = target_type(ts) + except ValueError: + result = target_type(_from_iso_format_to_unix_timestamp(ts)) + else: + raise ValueError(f"Unsupported data format for timestamp {ts}") + + return result diff --git a/slack_sdk/oauth/installation_store/models/bot.py b/slack_sdk/oauth/installation_store/models/bot.py index 36be38644..e060b8489 100644 --- a/slack_sdk/oauth/installation_store/models/bot.py +++ b/slack_sdk/oauth/installation_store/models/bot.py @@ -1,11 +1,8 @@ -import re from datetime import datetime # type: ignore from time import time from typing import Optional, Union, Dict, Any, Sequence -from slack_sdk.oauth.installation_store.internals import ( - _from_iso_format_to_unix_timestamp, -) +from slack_sdk.oauth.installation_store.internals import _timestamp_to_type class Bot: @@ -70,30 +67,17 @@ def __init__( else: self.bot_scopes = bot_scopes self.bot_refresh_token = bot_refresh_token + if bot_token_expires_at is not None: - if type(bot_token_expires_at) == datetime: - self.bot_token_expires_at = int(bot_token_expires_at.timestamp()) # type: ignore - elif type(bot_token_expires_at) == str and not re.match("^\\d+$", bot_token_expires_at): - self.bot_token_expires_at = int(_from_iso_format_to_unix_timestamp(bot_token_expires_at)) - else: - self.bot_token_expires_at = int(bot_token_expires_at) + self.bot_token_expires_at = _timestamp_to_type(bot_token_expires_at, int) elif bot_token_expires_in is not None: self.bot_token_expires_at = int(time()) + bot_token_expires_in else: self.bot_token_expires_at = None + self.is_enterprise_install = is_enterprise_install or False - if type(installed_at) == float: - self.installed_at = installed_at # type: ignore - elif type(installed_at) == datetime: - self.installed_at = installed_at.timestamp() # type: ignore - elif type(installed_at) == str: - if re.match("^\\d+.\\d+$", installed_at): - self.installed_at = float(installed_at) - else: - self.installed_at = _from_iso_format_to_unix_timestamp(installed_at) - else: - raise ValueError(f"Unsupported data format for installed_at {installed_at}") + self.installed_at = _timestamp_to_type(installed_at, float) self.custom_values = custom_values if custom_values is not None else {} diff --git a/slack_sdk/oauth/installation_store/models/installation.py b/slack_sdk/oauth/installation_store/models/installation.py index e55cf2b98..09418b36d 100644 --- a/slack_sdk/oauth/installation_store/models/installation.py +++ b/slack_sdk/oauth/installation_store/models/installation.py @@ -1,11 +1,8 @@ -import re from datetime import datetime # type: ignore from time import time from typing import Optional, Union, Dict, Any, Sequence -from slack_sdk.oauth.installation_store.internals import ( - _from_iso_format_to_unix_timestamp, -) +from slack_sdk.oauth.installation_store.internals import _timestamp_to_type from slack_sdk.oauth.installation_store.models.bot import Bot @@ -100,14 +97,9 @@ def __init__( else: self.bot_scopes = bot_scopes self.bot_refresh_token = bot_refresh_token + if bot_token_expires_at is not None: - if type(bot_token_expires_at) == datetime: - ts: float = bot_token_expires_at.timestamp() # type: ignore - self.bot_token_expires_at = int(ts) - elif type(bot_token_expires_at) == str and not re.match("^\\d+$", bot_token_expires_at): - self.bot_token_expires_at = int(_from_iso_format_to_unix_timestamp(bot_token_expires_at)) - else: - self.bot_token_expires_at = bot_token_expires_at # type: ignore + self.bot_token_expires_at = _timestamp_to_type(bot_token_expires_at, int) elif bot_token_expires_in is not None: self.bot_token_expires_at = int(time()) + bot_token_expires_in else: @@ -120,14 +112,9 @@ def __init__( else: self.user_scopes = user_scopes self.user_refresh_token = user_refresh_token + if user_token_expires_at is not None: - if type(user_token_expires_at) == datetime: - ts: float = user_token_expires_at.timestamp() # type: ignore - self.user_token_expires_at = int(ts) - elif type(user_token_expires_at) == str and not re.match("^\\d+$", user_token_expires_at): - self.user_token_expires_at = int(_from_iso_format_to_unix_timestamp(user_token_expires_at)) - else: - self.user_token_expires_at = user_token_expires_at # type: ignore + self.user_token_expires_at = _timestamp_to_type(user_token_expires_at, int) elif user_token_expires_in is not None: self.user_token_expires_at = int(time()) + user_token_expires_in else: @@ -143,17 +130,8 @@ def __init__( if installed_at is None: self.installed_at = datetime.now().timestamp() - elif type(installed_at) == float: - self.installed_at = installed_at # type: ignore - elif type(installed_at) == datetime: - self.installed_at = installed_at.timestamp() # type: ignore - elif type(installed_at) == str: - if re.match("^\\d+.\\d+$", installed_at): - self.installed_at = float(installed_at) - else: - self.installed_at = _from_iso_format_to_unix_timestamp(installed_at) else: - raise ValueError(f"Unsupported data format for installed_at {installed_at}") + self.installed_at = _timestamp_to_type(installed_at, float) self.custom_values = custom_values if custom_values is not None else {} diff --git a/tests/slack_sdk/oauth/installation_store/test_internals.py b/tests/slack_sdk/oauth/installation_store/test_internals.py index 5d0f3d435..948aa8c15 100644 --- a/tests/slack_sdk/oauth/installation_store/test_internals.py +++ b/tests/slack_sdk/oauth/installation_store/test_internals.py @@ -1,7 +1,11 @@ +import sys import unittest +from datetime import datetime, timezone + +import pytest from slack_sdk.oauth.installation_store import Installation, FileInstallationStore -from slack_sdk.oauth.installation_store.internals import _from_iso_format_to_datetime +from slack_sdk.oauth.installation_store.internals import _from_iso_format_to_datetime, _timestamp_to_type class TestFile(unittest.TestCase): @@ -14,3 +18,29 @@ def tearDown(self): def test_iso_format(self): dt = _from_iso_format_to_datetime("2021-07-14 08:00:17") self.assertEqual(dt.timestamp(), 1626249617.0) + + +@pytest.mark.parametrize('ts,target_type,expected_result', [ + (1701209097, int, 1701209097), + (datetime(2023, 11, 28, 22, 9, 7, tzinfo=timezone.utc), int, 1701209347), + ("1701209605", int, 1701209605), + ("2023-11-28 22:11:19", int, 1701209479), + (1701209998.3429494, float, 1701209998.3429494), + (datetime(2023, 11, 28, 22, 20, 25, 262571, tzinfo=timezone.utc), float, 1701210025.262571), + ("1701210054.4672053", float, 1701210054.4672053), + ("2023-11-28 22:21:14.745556", float, 1701210074.745556), +]) +def test_timestamp_to_type(ts, target_type, expected_result): + result = _timestamp_to_type(ts, target_type) + assert result == expected_result + + +def test_timestamp_to_type_invalid_str(): + match = "Invalid isoformat string" if sys.version_info[:2] > (3, 6) else "time data .* does not match format" + with pytest.raises(ValueError, match=match): + _timestamp_to_type('not-a-timestamp', int) + + +def test_timestamp_to_type_unsupported_format(): + with pytest.raises(ValueError, match="Unsupported data format"): + _timestamp_to_type({}, int) diff --git a/tests/slack_sdk_async/socket_mode/test_interactions_websockets.py b/tests/slack_sdk_async/socket_mode/test_interactions_websockets.py index a935c0f3c..b1b7c1652 100644 --- a/tests/slack_sdk_async/socket_mode/test_interactions_websockets.py +++ b/tests/slack_sdk_async/socket_mode/test_interactions_websockets.py @@ -90,7 +90,9 @@ async def socket_mode_listener( expected.sort() count = 0 - while count < 10 and len(received_messages) < len(expected): + while count < 10 and ( + len(received_messages) < len(expected) or len(received_socket_mode_requests) < len(socket_mode_envelopes) + ): await asyncio.sleep(0.2) count += 0.2 From 69b73eb9e31d63a859fec59c28ed9a74ccb1d774 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Fri, 1 Dec 2023 10:46:07 -0500 Subject: [PATCH 5/5] Maintain pip show command --- pyproject.toml | 9 ++++----- setup.cfg | 5 +++++ setup.py | 4 ++++ 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 setup.cfg diff --git a/pyproject.toml b/pyproject.toml index 618b2078e..1976da1ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,12 @@ [build-system] -requires = ["setuptools", "pytest-runner", "wheel"] +requires = ["setuptools", "pytest-runner"] build-backend = "setuptools.build_meta" [project] name = "slack_sdk" -dynamic = ["version", "readme"] +dynamic = ["version", "readme", "authors"] description = "The Slack API Platform SDK for Python" license = { text = "MIT" } -authors = [{ name = "Slack Technologies, LLC", email = "opensource@slack.com" }] requires-python = ">=3.6" keywords = [ "slack", @@ -41,10 +40,10 @@ classifiers = [ [project.urls] -homepage = "https://github.com/slackapi/python-slack-sdk" +Documentation = "https://slack.dev/python-slack-sdk/" [tool.setuptools.packages.find] -include = ["slack*"] +include = ["slack*", "slack_sdk*"] [tool.setuptools.dynamic] version = { attr = "slack_sdk.version.__version__" } diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..384126b63 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +; Legacy package configuration, prefer pyproject.toml over setup.cfg or setup.py +[metadata] +url=https://github.com/slackapi/python-slack-sdk +author=Slack Technologies, LLC +author_email=opensource@slack.com diff --git a/setup.py b/setup.py index 222114dbc..6be1405b9 100644 --- a/setup.py +++ b/setup.py @@ -5,6 +5,10 @@ from setuptools import setup, Command +################################################################################### +# Legacy package configuration, prefer pyproject.toml over setup.cfg or setup.py # +################################################################################### + here = os.path.abspath(os.path.dirname(__file__)) codegen_dependencies = [