feat: Support python 3.12 (#113)

python 3.12 was already release on Oct. 2, 2023, see more in
https://www.python.org/downloads/release/python-3120/
so we need to test and support it.
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 1cf52b0..6a01703 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -76,7 +76,7 @@
       matrix:
         # YAML parse `3.10` to `3.1`, so we have to add quotes for `'3.10'`, see also:
         # https://github.com/actions/setup-python/issues/160#issuecomment-724485470
-        python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11']
+        python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12']
         # FIXME: temp change os to ubuntu-20.04 to fix python can not found error https://github.com/actions/setup-python/issues/162#issuecomment-1325307787
         os: [ubuntu-20.04, macOS-latest, windows-latest]
         # Skip because dependence [py4j](https://pypi.org/project/py4j/) not work on those environments
@@ -85,6 +85,8 @@
             python-version: '3.10'
           - os: windows-latest
             python-version: 3.11
+          - os: windows-latest
+            python-version: 3.12
     steps:
       - uses: actions/checkout@v3
       - name: Set up Python ${{ matrix.python-version }}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2ab225c..7a5dec4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -80,7 +80,7 @@
 ### With GitHub Action
 
 GitHub Action test in various environment for pydolphinscheduler, including different python version in
-`3.6|3.7|3.8|3.9|3.10|3.11` and operating system `linux|macOS|windows`. It will trigger and run automatically when you
+`3.6|3.7|3.8|3.9|3.10|3.11|3.12` and operating system `linux|macOS|windows`. It will trigger and run automatically when you
 submit pull requests to repository `apache/dolphinscheduler-sdk-python`.
 
 ### Automated Testing With tox
@@ -214,7 +214,7 @@
 ## Unit Test
 
 pydolphinscheduler using [pytest][pytest] to test our codebase. GitHub Action will run our test when you create
-pull request or commit to dev branch, with python version `3.6|3.7|3.8|3.9|3.10|3.11` and operating system `linux|macOS|windows`.
+pull request or commit to dev branch, with python version `3.6|3.7|3.8|3.9|3.10|3.11|3.12` and operating system `linux|macOS|windows`.
 
 ### Unit Test Using tox
 
diff --git a/setup.cfg b/setup.cfg
index ad67231..f8f28d7 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -49,6 +49,7 @@
     Programming Language :: Python :: 3.9
     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
     Topic :: Software Development :: User Interfaces
@@ -202,7 +203,7 @@
     code-test
     integrate-test
     local-integrate-test
-    py{36,37,38,39,310,311}
+    py{36,37,38,39,310,311,312}
 
 [testenv]
 allowlist_externals =
diff --git a/src/pydolphinscheduler/utils/versions.py b/src/pydolphinscheduler/utils/versions.py
index 5fc93e5..4c1ea6f 100644
--- a/src/pydolphinscheduler/utils/versions.py
+++ b/src/pydolphinscheduler/utils/versions.py
@@ -19,9 +19,8 @@
 
 from pathlib import Path
 
-from packaging import requirements
+from packaging.requirements import Requirement as packaging_Requirement
 from packaging.version import InvalidVersion
-from pkg_resources import parse_requirements
 
 from pydolphinscheduler.constants import Version
 
@@ -34,13 +33,11 @@
     """
     path = Path(__file__).parent.parent.joinpath(Version.FILE_NAME)
     with path.open() as match:
-        content = match.read()
-        for reqs in parse_requirements(content):
-            if reqs.name == name:
+        for line in match.readlines():
+            req = packaging_Requirement(line)
+            if req.name == name:
                 try:
-                    return requirements.Requirement(str(reqs)).specifier.contains(
-                        version
-                    )
+                    return req.specifier.contains(version)
                 except InvalidVersion:
                     return False
         raise ValueError("%s is not in %s" % (name, Version.FILE_NAME))
diff --git a/tests/utils/test_versions.py b/tests/utils/test_versions.py
index b075e5e..d01e5bf 100644
--- a/tests/utils/test_versions.py
+++ b/tests/utils/test_versions.py
@@ -44,7 +44,9 @@
 @mock.patch("pathlib.Path.open")
 def test_version_match(mock_open, content: str, name: str, version: str, expect: str):
     """Test function version_match."""
-    mock_open.return_value.__enter__.return_value.read.return_value = content
+    mock_open.return_value.__enter__.return_value.readlines.return_value = [
+        f"{content}\n"
+    ]
     assert version_match(name, version) == expect
     assert mock_open.call_count == 1