6 Commits
3.2.0 ... 3.3.0

Author SHA1 Message Date
deesiigneer
931b1a8621 update poetry 2026-01-17 19:39:52 +00:00
deesiigneer
e9765e8b6a refactor: update Python publish workflow to use Poetry for dependency management 2026-01-17 19:30:51 +00:00
deesiigneer
047dbb38d0 chore: update Python version to 3.12 in GitHub Actions workflow 2026-01-17 19:02:52 +00:00
deesiigneer
6da906e0d1 feat(docs): localize documentation to Russian and update Makefile for Sphinx 2026-01-17 19:02:11 +00:00
deesiigneer
4fc530caeb refactor: improve code structure and add proxy support in APISession and SPAPI 2026-01-17 18:59:58 +00:00
deesiigneer
6e77bac3ba feat: migrate to poetry for dependency management and project configuration
- Added pyproject.toml for project metadata and dependencies.
- Removed requirements.txt as dependencies are now managed by poetry.
- Deleted setup.py as it is no longer needed with poetry.
2026-01-17 18:59:20 +00:00
17 changed files with 2045 additions and 216 deletions

View File

@@ -17,20 +17,26 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: [ 3.9 ] python-version: [3.12]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install Poetry
run: |
curl -sSL https://install.python-poetry.org | python3 -
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Install dependencies - name: Install dependencies
run: | run: poetry install --no-interaction
python -m pip install --upgrade pip
pip install twine - name: Build package
- name: Compile package run: poetry build
run: |
python3 setup.py sdist
- name: Publish package - name: Publish package
uses: pypa/gh-action-pypi-publish@release/v1 uses: pypa/gh-action-pypi-publish@release/v1
with: with:

View File

@@ -1,15 +1,13 @@
version: 2 version: 2
formats: []
build: build:
os: ubuntu-lts-latest os: ubuntu-lts-latest
tools: tools:
python: '3.8' python: '3.12'
sphinx: sphinx:
configuration: docs/conf.py configuration: docs/conf.py
fail_on_warning: false
builder: html builder: html
python: python:
@@ -18,4 +16,3 @@ python:
path: . path: .
extra_requirements: extra_requirements:
- docs - docs
- requirements: docs/requirements.txt

View File

@@ -1,3 +0,0 @@
include README.rst
include LICENSE
include requirements.txt

View File

@@ -5,8 +5,8 @@
# from the environment for the first two. # from the environment for the first two.
SPHINXOPTS ?= SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build SPHINXBUILD ?= sphinx-build
SOURCEDIR = source SOURCEDIR = .
BUILDDIR = build BUILDDIR = _build
# Put it first so that "make" without argument is like "make help". # Put it first so that "make" without argument is like "make help".
help: help:

View File

@@ -1,25 +1,25 @@
.. currentmodule:: pyspapi .. currentmodule:: pyspapi
API Reference Справочник API
=============== ===============
The following section outlines the API of pyspapi. В следующем разделе описывается API pyspapi.
Version Info Информация о версии
--------------------- ---------------------
There are two main ways to query version information. Существует два основных способа запроса информации о версии.
.. data:: version_info .. data:: version_info
A named tuple that is similar to :obj:`py:sys.version_info`. Именованный кортеж, аналогичный :obj:`py:sys.version_info`.
Just like :obj:`py:sys.version_info` the valid values for ``releaselevel`` are Как и в :obj:`py:sys.version_info`, допустимые значения для ``releaselevel`` это
'alpha', 'beta', 'candidate' and 'final'. 'alpha', 'beta', 'candidate' и 'final'.
.. data:: __version__ .. data:: __version__
A string representation of the version. Строковое представление версии.
``pyspapi`` ``pyspapi``
----------- -----------

View File

@@ -1,59 +1,44 @@
from re import search, MULTILINE from importlib.metadata import version as pkg_version
import os import os
import sys
project = "pyspapi"
author = "deesiigneer"
copyright = "2022, deesiigneer"
project = 'pyspapi' version = pkg_version("pyspapi")
copyright = '2022, deesiigneer'
author = 'deesiigneer'
with open("../pyspapi/__init__.py") as f:
match = search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), MULTILINE)
if not match or match.group(1) is None:
raise RuntimeError("The version could not be resolved")
version = match.group(1)
# The full version, including alpha/beta/rc tags.
release = version release = version
# -- General configuration
sys.path.insert(0, os.path.abspath(".."))
extensions = [ extensions = [
'sphinx.ext.duration', "sphinx.ext.duration",
'sphinx.ext.doctest', "sphinx.ext.doctest",
'sphinx.ext.autodoc', "sphinx.ext.autodoc",
'sphinx.ext.autosummary', "sphinx.ext.autosummary",
'sphinx.ext.intersphinx', "sphinx.ext.intersphinx",
] ]
autodoc_member_order = "bysource" autosummary_generate = True
autodoc_typehinta = "none"
intersphinx_mapping = {
'python': ('https://docs.python.org/3/', None),
'sphinx': ('https://www.sphinx-doc.org/en/master/', None),
}
version_match = os.environ.get("READTHEDOCS_VERSION") version_match = os.environ.get("READTHEDOCS_VERSION")
json_url = f"https://pyspapi.readthedocs.io/ru/{version_match}/_static/switcher.json" json_url = f"https://pyspapi.readthedocs.io/ru/{version_match}/_static/switcher.json"
intersphinx_disabled_domains = ['std']
language = 'en' language = "ru"
locale_dirs = ["locale/"]
exclude_patterns = [] exclude_patterns = []
html_static_path = ["_static"] html_static_path = ["_static"]
html_theme = "pydata_sphinx_theme" html_theme = "pydata_sphinx_theme"
html_logo = "./images/logo.png" html_logo = "./images/logo.png"
html_favicon = "./images/logo.ico" html_favicon = "./images/logo.ico"
html_theme_options = { html_theme_options = {
"external_links": [ "external_links": [
{ {
"url": "https://github.com/deesiigneer/pyspapi/releases", "url": "https://github.com/deesiigneer/pyspapi/releases",
"name": "Changelog", "name": "Changelog",
},
{
"url": "https://github.com/sp-worlds/api-docs/wiki",
"name": "SPWorlds API Docs",
} }
], ],
"icon_links": [ "icon_links": [
@@ -61,24 +46,27 @@ html_theme_options = {
"name": "GitHub", "name": "GitHub",
"url": "https://github.com/deesiigneer/pyspapi", "url": "https://github.com/deesiigneer/pyspapi",
"icon": "fab fa-brands fa-github", "icon": "fab fa-brands fa-github",
"type": "fontawesome" "type": "fontawesome",
}, },
{ {
"name": "Discord", "name": "Discord",
"url": "https://discord.gg/VbyHaKRAaN", "url": "https://discord.gg/VbyHaKRAaN",
"icon": "fab fa-brands fa-discord", "icon": "fab fa-brands fa-discord",
"type": "fontawesome" "type": "fontawesome",
}, },
{ {
"name": "PyPi", "name": "PyPI",
"url": "https://pypi.org/project/pyspapi/", "url": "https://pypi.org/project/pyspapi/",
"icon": "fab fa-brands fa-python", "icon": "fab fa-brands fa-python",
"type": "fontawesome" "type": "fontawesome",
} },
], ],
"header_links_before_dropdown": 4, "header_links_before_dropdown": 4,
"show_toc_level": 1, "show_toc_level": 1,
"navbar_start": ["navbar-logo"], "navbar_start": ["navbar-logo"],
"navigation_with_keys": True, "navigation_with_keys": True,
"switcher": {
"json_url": json_url,
"version_match": version_match,
},
} }
html_css_files = ["custom.css"]

View File

@@ -1,32 +1,31 @@
:theme_html_remove_secondary_sidebar: :theme_html_remove_secondary_sidebar:
Welcome to pyspapi Добро пожаловать в pyspapi
=================== ===========================
API wrapper for SP servers written in Python. Обертка API для SPWorlds серверов, написанная на Python.
Getting started Начало работы
--------------- ---------------
Is this your first time using the library? This is the place to get started! Вы впервые используете библиотеку? Это место, с которого нужно начать!
- **First steps:** :ref:`Quickstart <quickstart>` - **Первые шаги:** :ref:`Быстрый старт <quickstart>`
- **Examples:** Many examples are available in the `examples directory <https://github.com/deesiigneer/pyspapi/tree/main/examples/>`_. - **Примеры:** Много примеров доступно в `папке примеров <https://github.com/deesiigneer/pyspapi/tree/main/examples/>`_.
Getting help Получение помощи
------------ ------------------
If you're having trouble with something, these resources might help. Если у вас есть проблемы с чем-то, эти ресурсы могут помочь.
- Ask questions in `Discord <https://discord.gg/VbyHaKRAaN>`_ server. - Задавайте вопросы на сервере `Discord <https://discord.gg/VbyHaKRAaN>`_.
- If you're looking for something specific, try the :ref:`searching <search>`. - Если вы ищете что-то конкретное, попробуйте :ref:`поиск <search>`.
- Report bugs in the `issue tracker <https://github.com/deesiigneer/pyspapi/issues>`_. - Сообщайте об ошибках в `трекер проблем <https://github.com/deesiigneer/pyspapi/issues>`_.
- Ask in `GitHub discussions page <https://github.com/deesiigneer/pyspapi/discussions>`_.
Manuals Руководства
------- -----------
These pages go into great detail about everything the API can do. Эти страницы подробно описывают все, что может сделать API.
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1

View File

@@ -4,17 +4,17 @@
.. currentmodule:: pyspapi .. currentmodule:: pyspapi
Quickstart Быстрый старт
========== ==============
This page gives a brief introduction to the library. На этой странице дается краткое введение в библиотеку.
Checking balance Проверка баланса
---------------- -----------------
Let's output the amount of money remaining in the card account to the console. Выведем количество денег, оставшихся на счету карты, на консоль.
It looks something like this: Это выглядит примерно так:
.. code-block:: python .. code-block:: python
@@ -30,7 +30,6 @@ It looks something like this:
loop = get_event_loop() loop = get_event_loop()
loop.run_until_complete(main()) loop.run_until_complete(main())
Make sure not to name it ``pyspapi`` as that'll conflict with the library. Убедитесь, что вы не называете его ``pyspapi``, так как это вызовет конфликт с библиотекой.
Вы можете найти больше примеров в `папке примеров <https://github.com/deesiigneer/pyspapi/tree/main/examples/>`_ на GitHub.
You can find more examples in the `examples directory <https://github.com/deesiigneer/pyspapi/tree/main/examples/>`_ on GitHub.

1826
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

29
pyproject.toml Normal file
View File

@@ -0,0 +1,29 @@
[project]
name = "pyspapi"
version = "3.3.0"
description = "API wrapper for SP servers written in Python."
readme = "README.rst"
license = { text = "MIT" }
authors = [{ name = "deesiigneer", email = "xdeesiigneerx@gmail.com" }]
requires-python = ">=3.12,<3.15"
dependencies = ["aiohttp>=3.9,<4.0"]
[project.urls]
Homepage = "https://pyspapi.deesiigneer.ru"
Documentation = "https://pyspapi.readthedocs.org"
Repository = "https://github.com/deesiigneer/pyspapi"
"Issue Tracker" = "https://github.com/deesiigneer/pyspapi/issues"
Discord = "https://discord.com/invite/VbyHaKRAaN"
[project.optional-dependencies]
docs = [
"sphinx>=7,<9",
"sphinx-autobuild>=2025.8.25,<2026.0.0",
"pydata-sphinx-theme>=0.16.1,<0.17.0",
]
dev = ["ruff>=0.14,<0.15", "toml-sort>=0.24,<0.25"]
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"

View File

@@ -1,9 +1,13 @@
from .api import * """pyspapi - API wrapper for SP servers written in Python
from .spworlds import * TODO: заполнить описание"""
from .types import *
__author__ = 'deesiigneer' import importlib.metadata
__url__ = 'https://github.com/deesiigneer/pyspapi' from .spworlds import SPAPI
__description__ = 'API wrapper for SP servers written in Python.'
__license__ = 'MIT' __all__ = [SPAPI]
__version__ = "3.2.0"
__author__: str = "deesiigneer"
__url__: str = "https://github.com/deesiigneer/pyspapi"
__description__: str = "API wrapper for SP servers written in Python."
__license__: str = "MIT"
__version__: str = importlib.metadata.version("pyspapi")

View File

@@ -1,2 +1,3 @@
from .api import APISession from .api import APISession
__all__ = [APISession]

View File

@@ -8,17 +8,20 @@ import aiohttp
from ..exceptions import ValidationError, SPAPIError from ..exceptions import ValidationError, SPAPIError
log = getLogger('pyspapi') log = getLogger("pyspapi")
class APISession(object): class APISession(object):
def __init__(
def __init__(self, card_id: str, self,
card_id: str,
token: str, token: str,
timeout: int = 5, timeout: int = 5,
sleep_time: float = 0.2, sleep_time: float = 0.2,
retries: int = 0, retries: int = 0,
raise_exception: bool = False): raise_exception: bool = False,
proxy: str = None,
):
self.__url = "https://spworlds.ru/api/public/" self.__url = "https://spworlds.ru/api/public/"
self.__id = card_id self.__id = card_id
self.__token = token self.__token = token
@@ -26,23 +29,29 @@ class APISession(object):
self.__retries = retries self.__retries = retries
self.__timeout = timeout self.__timeout = timeout
self.__raise_exception = raise_exception self.__raise_exception = raise_exception
self.__proxy = proxy
self.session: Optional[aiohttp.ClientSession] = None self.session: Optional[aiohttp.ClientSession] = None
async def __aenter__(self): async def __aenter__(self):
print("proxy=", self.__proxy)
self.session = aiohttp.ClientSession( self.session = aiohttp.ClientSession(
json_serialize=json.dumps, json_serialize=json.dumps,
timeout=aiohttp.ClientTimeout(total=self.__timeout)) timeout=aiohttp.ClientTimeout(total=self.__timeout),
proxy=self.__proxy,
)
return self return self
async def __aexit__(self, *err): async def __aexit__(self, *err):
await self.session.close() await self.session.close()
self.session = None self.session = None
async def request(self, method: str, endpoint: str, data: Optional[Dict] = None) -> Any: async def request(
self, method: str, endpoint: str, data: Optional[Dict] = None
) -> Any:
url = self.__url + endpoint url = self.__url + endpoint
headers = { headers = {
'Authorization': f"Bearer {str(b64encode(str(f'{self.__id}:{self.__token}').encode('utf-8')), 'utf-8')}", "Authorization": f"Bearer {str(b64encode(str(f'{self.__id}:{self.__token}').encode('utf-8')), 'utf-8')}",
'User-Agent': 'https://github.com/deesiigneer/pyspapi', "User-Agent": "https://github.com/deesiigneer/pyspapi",
"Content-Type": "application/json", "Content-Type": "application/json",
} }
@@ -50,9 +59,13 @@ class APISession(object):
while True: while True:
attempt += 1 attempt += 1
if attempt > 1: if attempt > 1:
log.warning(f'[pyspapi] Repeat attempt {attempt}: {method.upper()} {url}') log.warning(
f"[pyspapi] Repeat attempt {attempt}: {method.upper()} {url}"
)
try: try:
async with self.session.request(method, url, json=data, headers=headers) as resp: async with self.session.request(
method, url, json=data, headers=headers
) as resp:
if resp.status == 422: if resp.status == 422:
errors = await resp.json() errors = await resp.json()
log.error(f"[pyspapi] Validation error: {errors}") log.error(f"[pyspapi] Validation error: {errors}")
@@ -69,7 +82,7 @@ class APISession(object):
return await resp.json() return await resp.json()
except (aiohttp.ClientError, asyncio.TimeoutError) as e: except (aiohttp.ClientError, asyncio.TimeoutError) as e:
log.exception(f"[pyspapi] Connection error: {e}") log.exception(f"[pyspapi] Connection error: {e} \n attempt {attempt}")
if attempt > self.__retries: if attempt > self.__retries:
return None return None
await asyncio.sleep(self.__sleep_timeout) await asyncio.sleep(self.__sleep_timeout)

View File

@@ -4,27 +4,31 @@ from hmac import new, compare_digest
from typing import Optional from typing import Optional
from .api import APISession from .api import APISession
from .types import User from pyspapi.types import User
from .types.me import Account from pyspapi.types.me import Account
from .types.payment import Item from pyspapi.types.payment import Item
__all__ = ['SPAPI'] __all__ = ["SPAPI"]
class SPAPI(APISession): class SPAPI(APISession):
""" """
pyspapi — высокоуровневый клиент для взаимодействия с SPWorldsAPI. pyspapi — высокоуровневый клиент для взаимодействия с SPWorlds API.
Предоставляет удобные методы для работы с балансом карты, вебхуками, Предоставляет удобные методы для работы с балансом карты, вебхуками,
информацией о пользователе, транзакциями и платежами, а также верификацией вебхуков. информацией о пользователе, транзакциями и платежами, а также верификацией вебхуков.
""" """
def __init__(self, card_id: str, def __init__(
self,
card_id: str,
token: str, token: str,
timeout: int = 5, timeout: int = 5,
sleep_time: float = 0.2, sleep_time: float = 0.2,
retries: int = 0, retries: int = 0,
raise_exception: bool = False): raise_exception: bool = False,
proxy: str = None,
):
""" """
Инициализирует объект SPAPI. Инициализирует объект SPAPI.
@@ -40,8 +44,12 @@ class SPAPI(APISession):
:type retries: int :type retries: int
:param raise_exception: Поднимать исключения при ошибке, если True. :param raise_exception: Поднимать исключения при ошибке, если True.
:type raise_exception: bool :type raise_exception: bool
:param proxy: Прокся!
:type proxy: str
""" """
super().__init__(card_id, token, timeout, sleep_time, retries, raise_exception) super().__init__(
card_id, token, timeout, sleep_time, retries, raise_exception, proxy
)
self.__card_id = card_id self.__card_id = card_id
self.__token = token self.__token = token
@@ -59,7 +67,7 @@ class SPAPI(APISession):
:return: Текущий баланс карты. :return: Текущий баланс карты.
:rtype: int :rtype: int
""" """
return int((await super().get('card'))['balance']) return int((await super().get("card"))["balance"])
@property @property
async def webhook(self) -> Optional[str]: async def webhook(self) -> Optional[str]:
@@ -69,7 +77,7 @@ class SPAPI(APISession):
:return: URL вебхука. :return: URL вебхука.
:rtype: str :rtype: str
""" """
return str((await super().get('card'))['webhook']) return str((await super().get("card"))["webhook"])
@property @property
async def me(self) -> Optional[Account]: async def me(self) -> Optional[Account]:
@@ -79,16 +87,17 @@ class SPAPI(APISession):
:return: Объект Account, представляющий аккаунт текущего пользователя. :return: Объект Account, представляющий аккаунт текущего пользователя.
:rtype: :class:`Account` :rtype: :class:`Account`
""" """
me = await super().get('accounts/me') me = await super().get("accounts/me")
return Account( return Account(
account_id=me['id'], account_id=me["id"],
username=me['username'], username=me["username"],
minecraftuuid=me['minecraftUUID'], minecraftuuid=me["minecraftUUID"],
status=me['status'], status=me["status"],
roles=me['roles'], roles=me["roles"],
cities=me['cities'], cities=me["cities"],
cards=me['cards'], cards=me["cards"],
created_at=me['createdAt']) created_at=me["createdAt"],
)
async def get_user(self, discord_id: int) -> Optional[User]: async def get_user(self, discord_id: int) -> Optional[User]:
""" """
@@ -100,11 +109,16 @@ class SPAPI(APISession):
:return: Объект User, представляющий пользователя. :return: Объект User, представляющий пользователя.
:rtype: :class:`User` :rtype: :class:`User`
""" """
user = await super().get(f'users/{discord_id}') user = await super().get(f"users/{discord_id}")
if user:
cards = await super().get(f"accounts/{user['username']}/cards") cards = await super().get(f"accounts/{user['username']}/cards")
return User(user['username'], user['uuid'], cards) return User(user["username"], user["uuid"], cards)
else:
return None
async def create_transaction(self, receiver: str, amount: int, comment: str) -> Optional[int]: async def create_transaction(
self, receiver: str, amount: int, comment: str
) -> Optional[int]:
""" """
Создает транзакцию. Создает транзакцию.
@@ -118,15 +132,13 @@ class SPAPI(APISession):
:return: Баланс после транзакции. :return: Баланс после транзакции.
:rtype: int :rtype: int
""" """
data = { data = {"receiver": receiver, "amount": amount, "comment": comment}
'receiver': receiver,
'amount': amount,
'comment': comment
}
return int((await super().post('transactions', data))['balance']) return int((await super().post("transactions", data))["balance"])
async def create_payment(self, webhook_url: str, redirect_url: str, data: str, items: list[Item]) -> Optional[str]: async def create_payment(
self, webhook_url: str, redirect_url: str, data: str, items: list[Item]
) -> Optional[str]:
""" """
Создает платеж. Создает платеж.
@@ -142,13 +154,13 @@ class SPAPI(APISession):
:rtype: str :rtype: str
""" """
data = { data = {
'items': items, "items": items,
'redirectUrl': redirect_url, "redirectUrl": redirect_url,
'webhookUrl': webhook_url, "webhookUrl": webhook_url,
'data': data "data": data,
} }
return str((await super().post('payments', data))['url']) return str((await super().post("payments", data))["url"])
async def update_webhook(self, url: str) -> Optional[dict]: async def update_webhook(self, url: str) -> Optional[dict]:
""" """
@@ -157,9 +169,9 @@ class SPAPI(APISession):
:param url: Новый URL вебхука. :param url: Новый URL вебхука.
:return: Ответ API в виде словаря или None при ошибке. :return: Ответ API в виде словаря или None при ошибке.
""" """
data = {'url': url} data = {"url": url}
return await super().put('card/webhook', data) return await super().put("card/webhook", data)
def webhook_verify(self, data: str, header: str) -> bool: def webhook_verify(self, data: str, header: str) -> bool:
""" """
@@ -172,8 +184,8 @@ class SPAPI(APISession):
:return: True, если заголовок из вебхука достоверен, иначе False. :return: True, если заголовок из вебхука достоверен, иначе False.
:rtype: bool :rtype: bool
""" """
hmac_data = b64encode(new(self.__token.encode('utf-8'), data, sha256).digest()) hmac_data = b64encode(new(self.__token.encode("utf-8"), data, sha256).digest())
return compare_digest(hmac_data, header.encode('utf-8')) return compare_digest(hmac_data, header.encode("utf-8"))
def to_dict(self) -> dict: def to_dict(self) -> dict:
""" """

View File

@@ -1,3 +1,5 @@
from .payment import Item
from .users import User
from .me import Account from .me import Account
from .payment import Item
from .users import Cards, User
__all__ = [Account, Item, Cards, User]

View File

@@ -1 +0,0 @@
aiohttp>=3.8.0

View File

@@ -1,43 +0,0 @@
import re
from setuptools import setup, find_packages
requirements = []
with open("requirements.txt") as f:
requirements = f.read().splitlines()
version = ""
with open("pyspapi/__init__.py") as f:
match = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE)
if match is None or match.group(1) is None:
raise RuntimeError('Version is not set')
version = match.group(1)
if not version:
raise RuntimeError("Version is not set")
readme = ""
with open("README.rst") as f:
readme = f.read()
setup(
name='pyspapi',
license='MIT',
author='deesiigneer',
version=version,
url='https://github.com/deesiigneer/pyspapi',
project_urls={
"Documentation": "https://pyspapi.readthedocs.io/ru/latest/",
"GitHub": "https://github.com/deesiigneer/pyspapi",
"Discord": "https://discord.com/invite/VbyHaKRAaN"
},
description='API wrapper for SP servers written in Python',
long_description=readme,
long_description_content_type='text/x-rst',
packages=find_packages(),
package_data={'pyspapi': ['types/*', 'api/*']}, # Включаем дополнительные файлы и папки
include_package_data=True,
install_requires=requirements,
python_requires='>=3.8.0',
)