Skip to content
This repository has been archived by the owner on Feb 4, 2024. It is now read-only.

Commit

Permalink
Move to mcstatus 11
Browse files Browse the repository at this point in the history
Mostly, for testing py-mine/mcstatus#306.
  • Loading branch information
PerchunPak committed Mar 24, 2023
1 parent 473d356 commit 2db49e7
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 142 deletions.
41 changes: 10 additions & 31 deletions pinger_bot/mc_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,12 @@ async def status(cls, host: str) -> typing.Union["MCServer", "FailedMCServer"]:
*(
await asyncio.wait(
{
asyncio.create_task(cls.handle_java(host), name="MCServer.handle_java"),
asyncio.create_task(cls.handle_bedrock(host), name="MCServer.handle_bedrock"),
asyncio.create_task(
cls.handle_response(host, java=True), name="MCServer.handle_response(java=True)"
),
asyncio.create_task(
cls.handle_response(host, java=False), name="MCServer.handle_response(java=False)"
),
},
return_when=asyncio.FIRST_COMPLETED,
)
Expand Down Expand Up @@ -274,17 +278,18 @@ async def _handle_exceptions( # type: ignore[return]
return task

@classmethod
async def handle_java(cls, host: str) -> "MCServer":
async def handle_response(cls, host: str, *, java: bool) -> "MCServer":
"""Handle java server and transform it to :py:class:`.MCServer` object.
Args:
host: Host where server is, like ``127.0.0.1:25565``, ``hypixel.net`` or alias.
java: If server is Java or Bedrock.
Returns:
Initialised :py:class:`.MCServer` object.
"""
log.debug("MCServer.handle_java", host=host)
address = await Address.resolve(host, java=True)
log.debug("MCServer.handle_response", host=host, java=java)
address = await Address.resolve(host, java=java)
# we access this private attribute, because it's expected behaviour to use
# `mcstatus`' object exactly here. it must not be used anywhere else.
status = await address._server.async_status() # skipcq: PYL-W0212 # accessing private attribute
Expand All @@ -299,32 +304,6 @@ async def handle_java(cls, host: str) -> "MCServer":
latency=status.latency,
)

@classmethod
async def handle_bedrock(cls, host: str) -> "MCServer":
"""Handle bedrock server and transform it to :py:class:`.MCServer` object.
Args:
host: Host where server is, like ``127.0.0.1:25565``.
Returns:
Initialised :py:class:`.MCServer` object.
"""
log.debug("MCServer.handle_bedrock", host=host)
address = await Address.resolve(host, java=False)
# we access this private attribute, because it's expected behaviour to use
# `mcstatus`' object exactly here. it must not be used anywhere else.
status = await address._server.async_status() # skipcq: PYL-W0212 # accessing private attribute
return cls(
address=address,
motd=status.motd,
version=status.version.version,
players=Players(
online=status.players_online,
max=status.players_max,
),
latency=status.latency,
)


@dataclasses.dataclass
class FailedMCServer(BaseMCServer):
Expand Down
40 changes: 28 additions & 12 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ hikari = "~2.0.0-dev"
hikari-lightbulb = "~2.3"
APScheduler = "~3.10"

mcstatus = "~10.0"
mcstatus = "11.0.0a1"
SQLAlchemy = {version = "~1.4", extras = ["asyncio"]}
alembic = "~1.10"
dnspython = "~2.2"
dnspython = "~2.3"
omegaconf = "~2.3"
matplotlib = "~3.7"
aiohttp = "~3.8"
Expand Down Expand Up @@ -78,6 +78,7 @@ pytest-testmon = "~2.0"
pytest-randomly = "~3.12"

Faker = "~18.3"
optional-faker = "^1.0.0.post2"
factory-boy = "~3.2"
freezegun = "~1.2"

Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ select = D
# documentation from __init__ methods doesn't included in result
ignore = D107
docstring-convention = google
per-file-ignores = tests/mcstatus_mocks.py:D
exclude = venv,pinger_bot/migrations


Expand Down
4 changes: 4 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import alembic.config
import faker
import omegaconf
import optional_faker # we need to import it somewhere # skipcq: PY-W2000 # nopycln: import
import pytest
import sqlalchemy
import structlog
Expand All @@ -19,6 +20,9 @@
from tests import (
custom_fakes, # we need to import it somewhere # skipcq: PY-W2000 # nopycln: import
)
from tests import (
mcstatus_mocks, # and this too # skipcq: PY-W2000 # nopycln: import
)
from tests import factories


Expand Down
148 changes: 90 additions & 58 deletions tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@

import factory.fuzzy
import faker as faker_package
import mcstatus.bedrock_status
import mcstatus.pinger
import mcstatus.status_response
from sqlalchemy.ext import asyncio as sqlalchemy_asyncio

from pinger_bot import mc_api, models
Expand Down Expand Up @@ -145,31 +144,23 @@ class Meta: # noqa: D106

@classmethod
def from_mcstatus_status(
cls, status: typing.Union[mcstatus.pinger.PingResponse, mcstatus.bedrock_status.BedrockStatusResponse]
cls,
status: typing.Union[
mcstatus.status_response.JavaStatusResponse, mcstatus.status_response.BedrockStatusResponse
],
) -> mc_api.MCServer:
"""Generate :class:`pinger_bot.mc_api.MCServer` from ``mcstatus``' classes.
Args:
status: ``mcstatus``' status class.
"""
if isinstance(status, mcstatus.pinger.PingResponse):
return cls(
address=AddressFactory(),
motd=status.description,
version=status.version.name,
players=mc_api.Players(
online=status.players.online,
max=status.players.max,
),
latency=status.latency,
)
return cls(
address=AddressFactory(),
motd=status.motd,
version=status.version.version,
version=status.version.name,
players=mc_api.Players(
online=status.players_online,
max=status.players_max,
online=status.players.online,
max=status.players.max,
),
latency=status.latency,
)
Expand Down Expand Up @@ -202,65 +193,106 @@ class Meta: # noqa: D106
address: mc_api.Address = factory.SubFactory(AddressFactory)


class MCStatusJavaResponseFactory(factory.Factory):
"""Factory for :class:`mcstatus.pinger.PingResponse`, its java server's response class."""
# mcstatus part


class MCStatusBasePlayersFactory(MCPlayersFactory): # noqa: D101
class Meta: # noqa: D106
model = mcstatus.pinger.PingResponse
model = mcstatus.status_response.BaseStatusPlayers

players: mc_api.Players = factory.SubFactory(MCPlayersFactory)
version: str = factory.fuzzy.FuzzyAttribute(faker.sem_version)
version_protocol: int = factory.fuzzy.FuzzyAttribute(lambda: faker.pyint(max_value=999))

class MCStatusBaseVersionFactory(factory.Factory): # noqa: D101
class Meta: # noqa: D106
model = mcstatus.status_response.BaseStatusVersion

name: str = factory.fuzzy.FuzzyAttribute(faker.sem_version)
protocol: int = factory.fuzzy.FuzzyAttribute(faker.pyint)


class MCStatusBaseResponseFactory(factory.Factory): # noqa: D101
class Meta: # noqa: D106
model = mcstatus.status_response.BaseStatusResponse

players: mcstatus.status_response.BaseStatusPlayers = factory.SubFactory(MCStatusBasePlayersFactory)
version: mcstatus.status_response.BaseStatusVersion = factory.SubFactory(MCStatusBaseVersionFactory)
motd: str = factory.fuzzy.FuzzyAttribute(lambda: faker.sentence(nb_words=10))
icon: str = factory.fuzzy.FuzzyAttribute(lambda: faker.pystr(max_chars=255))
latency: float = factory.fuzzy.FuzzyAttribute(faker.pyfloat)


class MCStatusJavaPlayerFactory(factory.Factory): # noqa: D101
class Meta: # noqa: D106
model = mcstatus.status_response.JavaStatusPlayer

name: str = factory.fuzzy.FuzzyAttribute(faker.name)
uuid: str = factory.fuzzy.FuzzyAttribute(faker.uuid4)

@staticmethod
def _create(model_class: typing.Type[mcstatus.pinger.PingResponse], **kwargs) -> mcstatus.pinger.PingResponse:
players, version, version_protocol, motd, icon = (
typing.cast(mc_api.Players, kwargs.pop("players")),
typing.cast(str, kwargs.pop("version")),
typing.cast(int, kwargs.pop("version_protocol")),
typing.cast(str, kwargs.pop("motd")),
typing.cast(str, kwargs.pop("icon")),
def _create(
model_class: typing.Type[mcstatus.status_response.JavaStatusPlayer], **kwargs
) -> mcstatus.status_response.JavaStatusPlayer:
name, uuid = (
typing.cast(str, kwargs.pop("name")),
typing.cast(str, kwargs.pop("uuid")),
)

if kwargs != {}:
raise ValueError(f"Unexpected kwargs: {kwargs}")

return mcstatus.pinger.PingResponse(
{
"players": {"online": players.online, "max": players.max},
"version": {"name": version, "protocol": version_protocol},
"description": motd,
"favicon": icon,
}
)

@classmethod
def _build(cls, *args, **kwargs):
return cls._create(*args, **kwargs)

return model_class(name=name, id=uuid)

class MCStatusBedrockResponseFactory(factory.Factory):
"""Factory for :class:`mcstatus.bedrock_status.BedrockStatusResponse`."""

class MCStatusJavaPlayersFactory(MCPlayersFactory): # noqa: D101
class Meta: # noqa: D106
model = mcstatus.bedrock_status.BedrockStatusResponse
model = mcstatus.status_response.JavaStatusPlayers

protocol: int = factory.fuzzy.FuzzyAttribute(lambda: faker.pyint(max_value=999))
brand: str = factory.fuzzy.FuzzyAttribute(faker.word)
version: str = factory.fuzzy.FuzzyAttribute(faker.sem_version)
latency: float = factory.fuzzy.FuzzyAttribute(faker.pyfloat)
players_online: int = None # type: ignore[assignment] # will be set in post hook
players_max: int = factory.fuzzy.FuzzyAttribute(faker.pyint)
motd: str = factory.fuzzy.FuzzyAttribute(lambda: faker.sentence(nb_words=10))
map_: typing.Optional[str] = factory.fuzzy.FuzzyAttribute(faker.word)
gamemode: typing.Optional[str] = factory.fuzzy.FuzzyAttribute(faker.word)
sample: typing.Optional[typing.List[mcstatus.status_response.JavaStatusPlayer]] = None

@factory.post_generation
def _set_default_values(self, *args, **kwargs):
"""Set some additional default values.
``mypy`` thinks that it's unreachable, because of type ignores.
"""
if self.players_online is None:
self.players_online = faker.pyint(max_value=self.players_max) # type: ignore[unreachable]
if self.sample is None:
self.sample = faker.optional(
lambda: list(MCStatusJavaPlayerFactory.create_batch(faker.pyint(max_value=10)))
)


class MCStatusJavaVersionFactory(MCStatusBaseVersionFactory): # noqa: D101
class Meta: # noqa: D106
model = mcstatus.status_response.JavaStatusVersion


class MCStatusJavaResponseFactory(MCStatusBaseResponseFactory): # noqa: D101
"""Factory for :class:`mcstatus.pinger.PingResponse`, its java server's response class."""

class Meta: # noqa: D106
model = mcstatus.status_response.JavaStatusResponse

raw: typing.Dict[str, typing.Any] = factory.fuzzy.FuzzyAttribute(faker.pydict) # type: ignore[misc] # Explicit Any
players: mcstatus.status_response.JavaStatusPlayers = factory.SubFactory(MCStatusJavaPlayersFactory)
version: mcstatus.status_response.JavaStatusVersion = factory.SubFactory(MCStatusJavaVersionFactory)
icon: str = factory.fuzzy.FuzzyAttribute(lambda: faker.pystr(max_chars=255))


class MCStatusBedrockPlayersFactory(MCPlayersFactory): # noqa: D101
class Meta: # noqa: D106
model = mcstatus.status_response.BedrockStatusPlayers


class MCStatusBedrockVersionFactory(MCStatusBaseVersionFactory): # noqa: D101
class Meta: # noqa: D106
model = mcstatus.status_response.BedrockStatusVersion

brand: str = factory.fuzzy.FuzzyAttribute(lambda: faker.random_element(elements=["MCPE", "MCEE"]))


class MCStatusBedrockResponseFactory(MCStatusBaseResponseFactory): # noqa: D101
class Meta: # noqa: D106
model = mcstatus.status_response.BedrockStatusResponse

players: MCStatusBedrockPlayersFactory = factory.SubFactory(MCStatusBedrockPlayersFactory)
version: MCStatusBedrockVersionFactory = factory.SubFactory(MCStatusBedrockVersionFactory)
map_name: typing.Optional[str] = factory.fuzzy.FuzzyAttribute(lambda: faker.optional(faker.word))
gamemode: typing.Optional[str] = factory.fuzzy.FuzzyAttribute(lambda: faker.optional(faker.word))
Loading

0 comments on commit 2db49e7

Please sign in to comment.