From 12b32ed9adad0341f40f64c8b62c7c6832374532 Mon Sep 17 00:00:00 2001 From: snowykami Date: Wed, 2 Oct 2024 08:29:51 +0800 Subject: [PATCH] :sparkles: first comm --- .gitignore | 162 ++++++++++++++++++ README.md | 1 + pdm.lock | 193 ++++++++++++++++++++++ pyproject.toml | 23 +++ server_status/__init__.py | 0 server_status/__main__.py | 37 +++++ server_status/api.py | 247 ++++++++++++++++++++++++++++ server_status/cmd_parser.py | 22 +++ server_status/i18n.py | 5 + src/server_status_cv_py/__init__.py | 0 tests/__init__.py | 0 11 files changed, 690 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 pdm.lock create mode 100644 pyproject.toml create mode 100644 server_status/__init__.py create mode 100644 server_status/__main__.py create mode 100644 server_status/api.py create mode 100644 server_status/cmd_parser.py create mode 100644 server_status/i18n.py create mode 100644 src/server_status_cv_py/__init__.py create mode 100644 tests/__init__.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..29c6762 --- /dev/null +++ b/.gitignore @@ -0,0 +1,162 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm-project.org/#use-with-ide +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..63ab643 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# server-status-cv-py diff --git a/pdm.lock b/pdm.lock new file mode 100644 index 0000000..5dda336 --- /dev/null +++ b/pdm.lock @@ -0,0 +1,193 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[metadata] +groups = ["default"] +strategy = ["inherit_metadata"] +lock_version = "4.5.0" +content_hash = "sha256:35d8de0fef9028880b4f2f9e77f90ea9296a7760777e2fc1851550bd188977b8" + +[[metadata.targets]] +requires_python = ">=3.11" + +[[package]] +name = "arclet-alconna" +version = "1.8.30" +requires_python = ">=3.8" +summary = "A High-performance, Generality, Humane Command Line Arguments Parser Library." +groups = ["default"] +dependencies = [ + "nepattern<1.0.0,>=0.7.6", + "tarina>=0.5.8", + "typing-extensions>=4.5.0", +] +files = [ + {file = "arclet_alconna-1.8.30-py3-none-any.whl", hash = "sha256:835bf4e8d5deeb78ced2687d49e958a28fe19e39d0fb0fc30d767143d3e41329"}, + {file = "arclet_alconna-1.8.30.tar.gz", hash = "sha256:cdc064446c0db31285fd2cd4573d7fabe59b81e00e816b9f20f395f1c214b4ac"}, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +requires_python = ">=3.6" +summary = "Python package for providing Mozilla's CA Bundle." +groups = ["default"] +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +requires_python = ">=3.7.0" +summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +groups = ["default"] +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "idna" +version = "3.10" +requires_python = ">=3.6" +summary = "Internationalized Domain Names in Applications (IDNA)" +groups = ["default"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[[package]] +name = "nepattern" +version = "0.7.6" +requires_python = ">=3.8" +summary = "a complex pattern, support typing" +groups = ["default"] +dependencies = [ + "tarina>=0.5.1", + "typing-extensions>=4.5.0", +] +files = [ + {file = "nepattern-0.7.6-py3-none-any.whl", hash = "sha256:233d0befecc190f228ded3651a85faaf53f1308bba40ab8ddec379d0d3c88051"}, + {file = "nepattern-0.7.6.tar.gz", hash = "sha256:07bd5b2f3b9b9739b703bf723ffd642ca93738a32df7b699d57d6f338d46bad0"}, +] + +[[package]] +name = "psutil" +version = "6.0.0" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +summary = "Cross-platform lib for process and system monitoring in Python." +groups = ["default"] +files = [ + {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, + {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, + {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, + {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, + {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +requires_python = ">=3.8" +summary = "Python HTTP for Humans." +groups = ["default"] +dependencies = [ + "certifi>=2017.4.17", + "charset-normalizer<4,>=2", + "idna<4,>=2.5", + "urllib3<3,>=1.21.1", +] +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[[package]] +name = "tarina" +version = "0.5.8" +requires_python = ">=3.8" +summary = "A collection of common utils for Arclet" +groups = ["default"] +dependencies = [ + "typing-extensions>=4.4.0", +] +files = [ + {file = "tarina-0.5.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9b730d605691c1afc074f684b77c12e921d8a0a278b80b5fc016ab2bf75ee081"}, + {file = "tarina-0.5.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21dfdacf4ca5b46ecfbcd2ea92445abf9aced634aaef285fec8d914163261db8"}, + {file = "tarina-0.5.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3162eace1e5193313f1523a943b5ae14464199782f235e87702da9ee3fb37a6"}, + {file = "tarina-0.5.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:385882a2991046aa05f7b183f386ec2c949076aeacb4acad525ead63342d73f7"}, + {file = "tarina-0.5.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76ee0f135cbe26549592fa12691cb057aa4464d4182c35d7d967361eba52ed95"}, + {file = "tarina-0.5.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2c188c34143ae6bdcee13bac089845f1ca7d32169d85f172091550e0f34fda35"}, + {file = "tarina-0.5.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a45e5f0fccd0267a15582b9d8cfa4b21fca5c1c690ced673f0f58869b98cb178"}, + {file = "tarina-0.5.8-cp311-cp311-win32.whl", hash = "sha256:e554bd8e22a43ffc8f441d771585e81f90150de2f9e9d9a984c7b004bb613c10"}, + {file = "tarina-0.5.8-cp311-cp311-win_amd64.whl", hash = "sha256:51c8b7ad1cc114efde36ab09687b5f93afde27ad082cd38721dc327c7f0d922d"}, + {file = "tarina-0.5.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c95f227e7265cfce8c4fb5eebef2a148934b52b782527ded278a4e0926b90ceb"}, + {file = "tarina-0.5.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a394bd75c92d39c0e4c1ee40404de24316f4263f10e296e8d4e19bd0a3c50e55"}, + {file = "tarina-0.5.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9db70e6fb97ee8a87da52e9ced52ee6df7c468f75b72ef98af5a97929e12bc2d"}, + {file = "tarina-0.5.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b713717dcafcd03a86f41509b6c9ebc2749419c9c8c6d559edd6fdfaca6f354"}, + {file = "tarina-0.5.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccaf87a54e062a2d72a60d699198760684aca231c7de7de11d61c191d1e870bf"}, + {file = "tarina-0.5.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a1dc7e8e84ab4e0d6bfb3e4e9c82c7d8a4c002794b7b44010658f0f81e8b5e52"}, + {file = "tarina-0.5.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:dbc6e78e3ee9b24f9c0feb2c14c17d9696098abf6530ae63d6f4158ab7038c38"}, + {file = "tarina-0.5.8-cp312-cp312-win32.whl", hash = "sha256:4e1a08f1c3d40f935cc8c9507b7ea669b002a53dc7334c9b0ede9f71cf9d1cba"}, + {file = "tarina-0.5.8-cp312-cp312-win_amd64.whl", hash = "sha256:ab90fd830ec05d5f7cd001906fdd1a3e00d8c9fd221772d02bb87a7aec947925"}, + {file = "tarina-0.5.8-py3-none-any.whl", hash = "sha256:90740760e9f516677962eff5242a722c616939b123c566a85d7e009ec9868eb3"}, + {file = "tarina-0.5.8.tar.gz", hash = "sha256:ab5a8b901829242c64a8a0436c7753e894ccae36891ca20a9deda9de6210a0b3"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +requires_python = ">=3.8" +summary = "Backported and Experimental Type Hints for Python 3.8+" +groups = ["default"] +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +requires_python = ">=3.8" +summary = "HTTP library with thread-safe connection pooling, file post, and more." +groups = ["default"] +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e6df650 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[project] +name = "server-status-cv-py" +version = "0.1.0" +description = "Server status client" +authors = [ + {name = "snowykami", email = "snowykami@outlook.com"}, +] +dependencies = [ + "requests>=2.32.3", + "psutil>=6.0.0", + "arclet-alconna>=1.8.30", +] +requires-python = ">=3.11" +readme = "README.md" +license = {text = "MIT"} + +[build-system] +requires = ["pdm-backend"] +build-backend = "pdm.backend" + + +[tool.pdm] +distribution = true diff --git a/server_status/__init__.py b/server_status/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server_status/__main__.py b/server_status/__main__.py new file mode 100644 index 0000000..a4b3ac8 --- /dev/null +++ b/server_status/__main__.py @@ -0,0 +1,37 @@ +import socket +import sys + +from server_status.api import * +from server_status.cmd_parser import server_status_alc + +if __name__ == "__main__": + raw_msg = "server_status " + " ".join(sys.argv[1:]) + arp = server_status_alc.parse(raw_msg) + + if arp.query("run"): + sub_opts = arp.subcommands["run"].options + client = Client( + addr=arp["server"], + token=arp["token"], + client_id=arp["id"], + name=sub_opts["name"].args["name"], + location=sub_opts["location"].args["location"], + labels=sub_opts["labels"].args["labels"], + interval=sub_opts["interval"].args["interval"], + ) + client.start() + + elif arp.query("rm"): + client = Client( + addr=arp["server"], + token=arp["token"], + client_id=arp["id"], + ) + resp = client.remove(arp["id"]) + if resp.status_code == 200: + log("Host removed successfully") + else: + log(f"Failed to remove host: {resp.text}") + + else: + log("Unknown command, use 'server_status --help' for help/未知命令或参数错误,请使用 'server_status --help' 获取帮助") diff --git a/server_status/api.py b/server_status/api.py new file mode 100644 index 0000000..f664785 --- /dev/null +++ b/server_status/api.py @@ -0,0 +1,247 @@ +import platform +import threading +import time +from typing import Any + +import psutil +import requests + + +def log(*args): + # 在输出前加上时间 + print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), *args) + + +def get_network_speed(interval) -> tuple[int, int]: + """ + 获取网络速度,会阻塞interval秒 + Returns: + tuple[int, int]: 上行速度, 下行速度 + """ + net1 = psutil.net_io_counters() + time.sleep(interval) + net2 = psutil.net_io_counters() + return net2.bytes_sent - net1.bytes_sent, net2.bytes_recv - net1.bytes_recv + + +class Hardware: + mem_total: int = psutil.virtual_memory().total + mem_used: int = psutil.virtual_memory().used + + swap_total: int = psutil.swap_memory().total + swap_used: int = psutil.swap_memory().used + + cpu_cores: int = psutil.cpu_count() + cpu_logics: int = psutil.cpu_count(logical=True) + cpu_percent: float = psutil.cpu_percent() + + disks: dict[str, dict[str, int]] = {} + + net_up: int = 0 + net_down: int = 0 + net_type: str = "ethernet" + + +class Api: + def __init__(self, api_root: str, variables: dict[str, str] = None): + """ + 初始化一个API组 + Args: + api_root: API根路径,不要用/结尾,且所有类方法参数中的path都前置/但不后置 + variables: 变量,使用{}填充 + """ + self.variables = variables if variables else {} + self.api_root = api_root.format(self.variables) + self.headers = {} + + def get(self, path: str, *args, **kwargs) -> requests.Response: + """ + 发送一个GET请求, path中的变量会被替换, 其他参数请使用format + Args: + path: + *args: + **kwargs: + + Returns: + + """ + path = path.format(self.variables) + args = self.format(args) + kwargs = self.format(kwargs) + if "headers" not in kwargs: + kwargs["headers"] = self.headers + return requests.get(self.api_root + "/" + path, *args, **kwargs) + + def post(self, path: str, *args, **kwargs) -> requests.Response: + path = path.format(self.variables) + args = self.format(args) + kwargs = self.format(kwargs) + if "headers" not in kwargs: + kwargs["headers"] = self.headers + return requests.post(self.api_root + "/" + path, *args, **kwargs) + + def delete(self, path: str, *args, **kwargs) -> requests.Response: + path = path.format(self.variables) + args = self.format(args) + kwargs = self.format(kwargs) + if "headers" not in kwargs: + kwargs["headers"] = self.headers + return requests.delete(self.api_root + "/" + path, *args, **kwargs) + + def group(self, path: str) -> "Api": + """ + 获取一个子API + Args: + path: 子API路径例如"/user" + Returns: + 子API对象 + """ + return type(self)(self.api_root + path, self.variables) + + def add_headers(self, **headers): + """ + 添加请求头 + Args: + **headers: 请求头 + """ + self.headers.update(self.format(headers)) + + def format(self, obj: str | tuple[str, ...] | dict[str, Any]): + if isinstance(obj, str): + obj = obj.format(**self.variables) + elif isinstance(obj, dict): + for key in obj: + obj[key] = self.format(obj[key]) + elif isinstance(obj, (list, tuple)): + for i in range(len(obj)): + obj[i] = self.format(obj[i]) + else: + pass + return obj + + +class Client: + def __init__(self, addr: str, token: str, client_id: str, name: str = "", location: str = "", labels: list[str] = [], link: str = "", + interval: int = 5): + self.api = Api(addr, {"token": token, "id": client_id}) + self.api = self.api.group("/client") + self.api.add_headers(Authorization="{token}") + + self.addr = addr + self.start_time = None + self.client_id = client_id + self.name = name + self.location = location + self.labels = labels + self.link = link + self.interval = interval + + self.hardware = Hardware() + + log("Client initialized", + f"Name: {self.name}({self.client_id}), Location: {self.location}, Labels: {self.labels}") + + def start(self): + self.start_time = time.time() + self.observe() + + while True: + try: + resp = self.get_ping() + if resp.status_code == 200: + log(f"Connected to server {self.addr}") + break + else: + log(f"Failed to connect to server {self.addr}, retrying in 5 seconds: {resp.text}") + except Exception as e: + log(f"Failed to connect to server {self.addr}, retrying in 5 seconds: {e}") + time.sleep(5) + + while True: + try: + resp = self.post_status() + if resp.status_code == 200: + log("Status updated successfully") + else: + log(f"Failed to post status: {resp.text}") + except Exception as e: + log(f"Failed to post status: {e}") + time.sleep(self.interval) + + def get_ping(self): + return self.api.get("/ping") + + def post_status(self): + status = self.get_device_status() + return self.api.post("/status", json=status) + + def get_device_status(self) -> dict[str, Any]: + return { + "meta": { + "id": self.client_id, + "name": self.name, + "os": { + "name": platform.system(), + "version": platform.version(), + }, + "labels": self.labels, + "location": self.location, + "uptime": int(time.time() - self.start_time), + "link": self.link, + "observed_at": int(time.time()), + }, + "hardware": { + "mem": { + "total": self.hardware.mem_total, + "used": self.hardware.mem_used, + }, + "swap": { + "total": self.hardware.swap_total, + "used": self.hardware.swap_used, + }, + "cpu": { + "cores": self.hardware.cpu_cores, + "logics": self.hardware.cpu_logics, + "percent": self.hardware.cpu_percent, + }, + "disks": self.hardware.disks, + "net": { + "up": self.hardware.net_up, + "down": self.hardware.net_down, + "type": "ethernet", + }, + }, + } + + def observe(self): + """ + 观察硬件状态并更新 + Returns: + + """ + + def _observe(): + while True: + self.hardware.mem_total = psutil.virtual_memory().total + self.hardware.mem_used = psutil.virtual_memory().used + self.hardware.swap_total = psutil.swap_memory().total + self.hardware.swap_used = psutil.swap_memory().used + self.hardware.cpu_cores = psutil.cpu_count() + self.hardware.cpu_logics = psutil.cpu_count(logical=True) + for part in psutil.disk_partitions(): + try: + usage = psutil.disk_usage(part.mountpoint) + self.hardware.disks[part.device] = { + "total": usage.total, + "used": usage.used, + } + except: + pass + self.hardware.cpu_percent = psutil.cpu_percent(1) + self.hardware.net_up, self.hardware.net_down = get_network_speed(1) + log("Observed") + + threading.Thread(target=_observe, daemon=True).start() + + def remove(self, client_id) -> requests.Response: + return self.api.delete("/host", data={"id": client_id}) diff --git a/server_status/cmd_parser.py b/server_status/cmd_parser.py new file mode 100644 index 0000000..bfde61e --- /dev/null +++ b/server_status/cmd_parser.py @@ -0,0 +1,22 @@ +import platform +import socket + +from arclet.alconna import Alconna, Subcommand, Option, Args, MultiVar + +server_status_alc = Alconna( + "server_status", + Args["server", str]["token", str]["id", str], + Subcommand( + "run", + Option("-n|--name", Args["name", str, socket.gethostname()], help_text="Host name/主机名称"), + Option("--location", Args["location", str, "Unknown"], help_text="Host location/主机地理位置"), + Option("--labels", Args["labels", MultiVar(str), [platform.system()]], help_text="Host labels/主机标签"), + Option("--link", Args["link", str, None], help_text="Server address/服务器地址"), + Option("--interval", Args["interval", int, 5], help_text="Interval to send data: 5/发送数据的间隔: 5"), + help_text="Run the client/运行客户端", + ), + Subcommand( + "rm", + help_text="Remove the host/移除主机", + ), +) diff --git a/server_status/i18n.py b/server_status/i18n.py new file mode 100644 index 0000000..d6fb515 --- /dev/null +++ b/server_status/i18n.py @@ -0,0 +1,5 @@ +from locale import locale_alias + +lang_data = { + +} \ No newline at end of file diff --git a/src/server_status_cv_py/__init__.py b/src/server_status_cv_py/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29