Giter Site home page Giter Site logo

ooliver1 / mafic Goto Github PK

View Code? Open in Web Editor NEW
54.0 3.0 5.0 751 KB

A properly typehinted lavalink client for discord.py, nextcord, disnake and py-cord.

License: MIT License

Python 99.72% Shell 0.11% Dockerfile 0.17%
discord-py disnake disnake-py lavalink lavalink-client lavalink-music lavalink-wrapper nextcord nextcord-py py-cord

mafic's Introduction

Mafic

MIT License Releases Discord Lint Workflow Status PyPI - Status Open Issues Open PRs Read the Docs

A properly typehinted lavalink client for discord.py, nextcord, disnake and py-cord.

Installation

pip install mafic

Note Use python -m, py -m, python3 -m or similar if that is how you install packages. Generally windows uses py -m pip and linux uses python3 -m pip

Discord Server

Join the Discord Server for support and updates.

Documentation

Read the docs.

Features

  • Fully customisable node balancing.
  • Multi-node support.
  • Filters.
  • Full API coverage.
  • Properly typehinted for Pyright strict.

Usage

Go to the Lavalink Repository to set up a Lavalink node.

import os

import mafic
import nextcord
from nextcord.ext import commands


class MyBot(commands.Bot):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.pool = mafic.NodePool(self)
        self.loop.create_task(self.add_nodes())

    async def add_nodes(self):
        await self.pool.create_node(
            host="127.0.0.1",
            port=2333,
            label="MAIN",
            password="<password>",
        )


bot = MyBot(intents=nextcord.Intents(guilds=True, voice_states=True))


@bot.slash_command(dm_permission=False)
async def play(inter: nextcord.Interaction, query: str):
    if not inter.guild.voice_client:
        player = await inter.user.voice.channel.connect(cls=mafic.Player)
    else:
        player = inter.guild.voice_client

    tracks = await player.fetch_tracks(query)

    if not tracks:
        return await inter.send("No tracks found.")

    track = tracks[0]

    await player.play(track)

    await inter.send(f"Playing {track.title}.")


bot.run(...)

mafic's People

Contributors

dependabot[bot] avatar ooliver1 avatar penggin avatar pre-commit-ci[bot] avatar spifory avatar stefanlight8 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

mafic's Issues

provide default filter implementations

Summary

provide default filter combos for effects

The Problem

Kareoke, nightcore and similar effects are harder to make by yourself and require many elements of filters at once.

The Ideal Solution

Providing default filters which use Filter but provide descriptive arguments to pass.

The Current Solution

Implementing the filters yourself.

Additional Context

No response

HTTPNotFound

Summary

trying to play a song results in a HTTPNotFound error using the simple example

Reproduction Steps

Set up the simple example in a cog based variant using pycord

Minimal Reproducible Code

# DragonBot.py
class DragonBot(commands.Bot):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.first_start = True
        self.pool = mafic.NodePool(self)

    async def on_ready(self) -> None:
        if self.first_start:
            log.info(
                f"Bot started as {self.user.name}#{self.user.discriminator} | {self.user.id}"
            )
            await db.set_up()
            log.debug("Database setup successful")
            await self.pool.create_node(
                host = "localhost", port = 2333, password = "youshallnotpass", label = "Main"
            )
            self.first_start = False
# MusicPlayer.py
from DragonBot import DragonBot
from discord.abc import Connectable
from mafic import Player, Track

class DragonPlayer(Player[DragonBot]):
    def __init__(self, client: DragonBot, channel: Connectable) -> None:
        super().__init__(client, channel)

        # Mafic does not provide a queue system right now, low priority.
        self.queue: list[Track] = []
# m_play.py
class MaficMusicEventsCog(commands.Cog):
    def __init__(self, client):
        self.client = client

    @commands.slash_command(name = "join", description = "join's your voice")
    async def join(self, inter: discord.ApplicationContext):
        """Join your voice channel."""

        assert isinstance(inter.user, discord.Member)

        if not inter.user.voice or not inter.user.voice.channel:
            return await inter.response.send_message("You are not in a voice channel.")

        channel = inter.user.voice.channel

        # This apparently **must** only be `Client`.
        await channel.connect(cls = DragonPlayer)  # pyright: ignore[reportGeneralTypeIssues]
        await inter.send(f"Joined {channel.mention}.")

    @commands.slash_command(name = "play", description = "Play a song of your desire")
    async def play(self, inter: discord.ApplicationContext, query: str):
        """Play a song.
        query:
            The song to search or play.
        """

        assert inter.guild is not None

        if not inter.guild.voice_client:
            await self.join(inter)

        player: DragonPlayer = (
            inter.guild.voice_client
        )  # pyright: ignore[reportGeneralTypeIssues]

        tracks = await player.fetch_tracks(query)

        if not tracks:
            return await inter.send("No tracks found.")

        if isinstance(tracks, mafic.Playlist):
            tracks = tracks.tracks
            if len(tracks) > 1:
                player.queue.extend(tracks[1:])

        track = tracks[0]

        await player.play(track)

        await inter.send(f"Playing {track}")

def setup(client: DragonBot):
    client.add_cog(MaficMusicEventsCog(client))

Expected Results

Play the requested song

Actual Results

joins the voice channel and throws this error

Ignoring exception in command play:
Traceback (most recent call last):
  File "E:\DragonBot\venv\Lib\site-packages\discord\commands\core.py", line 124, in wrapped
    ret = await coro(arg)
          ^^^^^^^^^^^^^^^
  File "E:\DragonBot\venv\Lib\site-packages\discord\commands\core.py", line 978, in _invoke
    await self.callback(self.cog, ctx, **kwargs)
  File "E:\DragonBot\extensions\mafic\m_play.py", line 57, in play
    await player.play(track)
  File "E:\DragonBot\venv\Lib\site-packages\mafic\player.py", line 553, in play
    return await self.update(
           ^^^^^^^^^^^^^^^^^^
  File "E:\DragonBot\venv\Lib\site-packages\mafic\player.py", line 504, in update
    await self._node.update(
  File "E:\DragonBot\venv\Lib\site-packages\mafic\node.py", line 854, in __request
    raise HTTPNotFound(text)
mafic.errors.HTTPNotFound: An HTTP error occured: {"timestamp":1676481027978,"status":404,"error":"Not Found","message":"Not Found","path":"/sessions/None/players/578446945425555464"} (404)

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "E:\DragonBot\venv\Lib\site-packages\discord\bot.py", line 1114, in invoke_application_command
    await ctx.command.invoke(ctx)
  File "E:\DragonBot\venv\Lib\site-packages\discord\commands\core.py", line 375, in invoke
    await injected(ctx)
  File "E:\DragonBot\venv\Lib\site-packages\discord\commands\core.py", line 132, in wrapped
    raise ApplicationCommandInvokeError(exc) from exc
discord.errors.ApplicationCommandInvokeError: Application Command raised an exception: HTTPNotFound: An HTTP error occured: {"timestamp":1676481027978,"status":404,"error":"Not Found","message":"Not Found","path":"/sessions/None/players/578446945425555464"} (404)

Lavalink Information

    Version:        3.7.4
    Build time:     02.02.2023 18:34:07 UTC
    Branch          HEAD
    Commit:         8ebaba1
    Commit time:    02.02.2023 18:32:05 UTC
    JVM:            17.0.5
    Lavaplayer      1.3.99.2-original

System Information

  • Python v3.11.1-final
  • mafic v1.1.0
  • aiohttp v3.8.3
  • py-cord v2.4.None-final
  • system info: Windows 10 10.0.22621 (still win 11)

Checklist

  • I have searched the open issues for duplicates.
  • I have shown the entire traceback, if possible.

Additional Context

No response

Add is_playing()

Summary

Be able to tell if the bot currently playing a song

The Problem

I really want to be able to see if the bot is playing in the current VC.

The Ideal Solution

if player.is_playing():
print("Bot is currently playing a song!")
do_something()
else:
print("Bot is currently not playing anything:)
do_something_else()

The Current Solution

Not possible as far as i can tell

Could be wrong not sure

Additional Context

No not really

Documentation is misleading to end users

Under the install section on the documentation it states that Mafic is not on Pypi as it is not stable and directs people to do a git based pypi install when it is in fact on pypi. This documentation should be updated to be inline with the current project state

use multiple subclasses for different types of `Track`

Summary

use different subclasses of Track to provide relevant data per source type

The Problem

Track types like youtube do not provide their thumbnail urls to the user and other similar data.

The Ideal Solution

Using a base track class - for unknowns, and a function to construct different classes depending on the data type.

The Current Solution

No response

Additional Context

No response

Play example throws KeyError: 'encoded'

Summary

trying to play a song results in a key error using the simple example

Reproduction Steps

Set up the basic example (in a cog) and use e.g. /play query: Wolke 10 HBz

Minimal Reproducible Code

# DragonBot.py
class DragonBot(commands.Bot):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.first_start = True
        self.pool = mafic.NodePool(self)

    async def on_ready(self) -> None:
        if self.first_start:
            log.info(
                f"Bot started as {self.user.name}#{self.user.discriminator} | {self.user.id}"
            )
            await db.set_up()
            log.debug("Database setup successful")
            await self.pool.create_node(
                host = "localhost", port = 2333, password = "youshallnotpass", label = "Main"
            )
            self.first_start = False
# MusicPlayer.py
from DragonBot import DragonBot
from discord.abc import Connectable
from mafic import Player, Track

class DragonPlayer(Player[DragonBot]):
    def __init__(self, client: DragonBot, channel: Connectable) -> None:
        super().__init__(client, channel)

        # Mafic does not provide a queue system right now, low priority.
        self.queue: list[Track] = []
# m_play.py
class MaficMusicEventsCog(commands.Cog):
    def __init__(self, client):
        self.client = client

    @commands.slash_command(name = "join", description = "join's your voice")
    async def join(self, inter: discord.ApplicationContext):
        """Join your voice channel."""

        assert isinstance(inter.user, discord.Member)

        if not inter.user.voice or not inter.user.voice.channel:
            return await inter.response.send_message("You are not in a voice channel.")

        channel = inter.user.voice.channel

        # This apparently **must** only be `Client`.
        await channel.connect(cls = DragonPlayer)  # pyright: ignore[reportGeneralTypeIssues]
        await inter.send(f"Joined {channel.mention}.")

    @commands.slash_command(name = "play", description = "Play a song of your desire")
    async def play(self, inter: discord.ApplicationContext, query: str):
        """Play a song.
        query:
            The song to search or play.
        """

        assert inter.guild is not None

        if not inter.guild.voice_client:
            await self.join(inter)

        player: DragonPlayer = (
            inter.guild.voice_client
        )  # pyright: ignore[reportGeneralTypeIssues]

        tracks = await player.fetch_tracks(query)

        if not tracks:
            return await inter.send("No tracks found.")

        if isinstance(tracks, mafic.Playlist):
            tracks = tracks.tracks
            if len(tracks) > 1:
                player.queue.extend(tracks[1:])

        track = tracks[0]

        await player.play(track)

        await inter.send(f"Playing {track}")

def setup(client: DragonBot):
    client.add_cog(MaficMusicEventsCog(client))

Expected Results

The bot joins the voice channel and plays that song

Actual Results

The bot joined but didn't play any song

Ignoring exception in command play:
Traceback (most recent call last):
  File "E:\DragonBot\venv\Lib\site-packages\discord\commands\core.py", line 124, in wrapped
    ret = await coro(arg)
          ^^^^^^^^^^^^^^^
  File "E:\DragonBot\venv\Lib\site-packages\discord\commands\core.py", line 978, in _invoke
    await self.callback(self.cog, ctx, **kwargs)
  File "E:\DragonBot\extensions\mafic\m_play.py", line 45, in play
    tracks = await player.fetch_tracks(query)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\DragonBot\venv\Lib\site-packages\mafic\player.py", line 457, in fetch_tracks
    return await node.fetch_tracks(query, search_type=raw_type)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\DragonBot\venv\Lib\site-packages\mafic\node.py", line 902, in fetch_tracks
    return [Track.from_data_with_info(track) for track in data["tracks"]]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\DragonBot\venv\Lib\site-packages\mafic\node.py", line 902, in <listcomp>
    return [Track.from_data_with_info(track) for track in data["tracks"]]
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\DragonBot\venv\Lib\site-packages\mafic\track.py", line 151, in from_data_with_info
    return cls.from_data(track=data["encoded"], info=data["info"])
                               ~~~~^^^^^^^^^^^
KeyError: 'encoded'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "E:\DragonBot\venv\Lib\site-packages\discord\bot.py", line 1114, in invoke_application_command
    await ctx.command.invoke(ctx)
  File "E:\DragonBot\venv\Lib\site-packages\discord\commands\core.py", line 375, in invoke
    await injected(ctx)
  File "E:\DragonBot\venv\Lib\site-packages\discord\commands\core.py", line 132, in wrapped
    raise ApplicationCommandInvokeError(exc) from exc
discord.errors.ApplicationCommandInvokeError: Application Command raised an exception: KeyError: 'encoded'

Lavalink Information

  • Lavalink version 3.6.2

System Information

  • Python v3.11.1-final
  • mafic v1.1.0
  • aiohttp v3.8.3
  • py-cord v2.4.None-final
  • system info: Windows 10 10.0.22621 (actually win 11)

Checklist

  • I have searched the open issues for duplicates.
  • I have shown the entire traceback, if possible.

Additional Context

No response

optimised node selection

Summary

NodePool.get_node should use multiple factors to determine the best node

The Problem

The pool is there to hold nodes and orcestrate usage of them, this request is to add methods of getting the best node for a player.

The Ideal Solution

The following strategies should be available, ideally usable in combination, with a good default combo

  • shard id based off guild - user assigns different nodes different shards, ideal for multi-server splitting
  • endpoint region - get the closest node to the voice server - ideally more accurate than just hte continent
  • node load/memory usage - get the least used node, possibly using a "weight" system to calculate with multiple factors what is the best

The Current Solution

No response

Additional Context

No response

Trying to choose from an empty sequence when only 1 node passed.

Summary

NodePool.get_random_node() will cause an IndexError if contains only one node.

Reproduction Steps

  @slash_command(
      name="play",
      description="Play a music track from name or url",
      dm_permission=False
  )
  @application_checks.guild_only()
  async def play_command_callback(self, interaction: Interaction,  query: str) -> None:
      player = Player(self.bot, interaction.user.voice.channel)
      fetched_tracks = await player.fetch_tracks(query)
      await player.connect(timeout=300.0, reconnect=True)

      try:
          track = fetched_tracks[0]
          print(player.node)
      except Exception as e:
          print(e)

Minimal Reproducible Code

No response

Expected Results

Retrun first node from cls._nodes if IndexError raised.

Actual Results

Traceback (most recent call last):
  File "...\mafic\player.py", line 580, in fetch_tracks
    node = self.node
           ^^^^^^^^^
  File "...\mafic\player.py", line 179, in node
    return NodePool[ClientT].get_random_node()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "...\mafic\pool.py", line 367, in get_random_node
    if node := choice(list(cls._nodes.values())):
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python311\Lib\random.py", line 373, in choice
    raise IndexError('Cannot choose from an empty sequence')
IndexError: Cannot choose from an empty sequence

### Lavalink Information

Problem is not in Lavalink

### System Information

Unnecessary

### Checklist

- [X] I have searched the open issues for duplicates.
- [X] I have shown the entire traceback, if possible.

### Additional Context

_No response_

check lavalink node version

Summary

check the lavalink node version for compatibility

The Problem

Currently, mafic just assumes nodes are using latest lavalink (3.5/6), which may lead to odd errors and other issues.

The Ideal Solution

Use the /version endpoint and check against 3.0.0, erroring. If it is not 3.5+, warn that some features may not be supported by the lavalink node, if 3.7+, warn that mafic may not support all of 's API.

The Current Solution

Hoping everyone uses latest lavalink.

Additional Context

https://github.com/freyacodes/Lavalink/blob/eee27d4170c92abaa502100dc2c7582b168ee082/LavalinkServer/src/main/java/lavalink/server/info/InfoRestHandler.java#L18-L21

handle http errors from lavalink

Summary

handle 4xx and 5xx errors from lavalink

The Problem

REST endpoints currently do not raise errors if an error response is returned.

The Ideal Solution

Different errors are raised depending on the HTTP error code.

The Current Solution

No response

Additional Context

No response

PlayerNotConnected problem

Summary

When the bot is playing some music on a channel and at that time the bot is restarted the bot still plays music but when you use player.stop() it throws an error mafic.errors.PlayerNotConnected: The player is not connected to a voice channel, even though it is on the channel and playing music. In most cases when it throws an error you can just execute player.stop() again and everything works normally

Reproduction Steps

  • Run some song in the bot
  • Restart the bot
  • When the bot won't stop playing music (sometimes it stops) try executing player.stop()

Minimal Reproducible Code

Ignoring exception in command <nextcord.application_command.SlashApplicationSubcommand object at 0x00000155F9F11EA0>:
Traceback (most recent call last):
  File "C:\Users\szymo\PycharmProjects\SmiffyBot\venv\lib\site-packages\nextcord\application_command.py", line 890, in invoke_callback_with_hooks
    await self(interaction, *args, **kwargs)
  File "C:\Users\szymo\PycharmProjects\SmiffyBot\Commands\music\CommandStop.py", line 48, in music_stop
    await player.stop()
  File "C:\Users\szymo\PycharmProjects\SmiffyBot\venv\lib\site-packages\mafic\player.py", line 648, in stop
    await self.update(track=None, replace=True)
  File "C:\Users\szymo\PycharmProjects\SmiffyBot\venv\lib\site-packages\mafic\player.py", line 530, in update
    raise PlayerNotConnected
mafic.errors.PlayerNotConnected: The player is not connected to a voice channel.

Expected Results

When restarting the bot, the bot would exit the channel and stop playing the song

Actual Results

Bugs the bot, you can't stop the song after restarting the bot. (Actually it can, but you have to execute player.stop() 2 times)

Lavalink Information

Version: 3.7.5
Build time: 06.03.2023 12:02:05 UTC
Branch HEAD
Commit: c00286d
Commit time: 06.03.2023 11:59:46 UTC
JVM: 17.0.6
Lavaplayer 1.4.0-original

System Information

  • Python v3.10.6-final
  • mafic v2.0.1
  • aiohttp v3.8.1
  • nextcord v2.4.0-final
  • system info: Linux 5.15.0-69-generic #76-Ubuntu SMP Fri Mar 17 17:19:29 UTC 2023

Checklist

  • I have searched the open issues for duplicates.
  • I have shown the entire traceback, if possible.

Additional Context

So far I have found a solution but sometimes it requires typing the stop command 2 times

try:
    player.loop = False
    await player.set_volume(100)
    await player.stop()
except errors.PlayerNotConnected:
    embed = Embed(
        title="<:error:919648598713573417> Wystąpił błąd.",
        color=Color.red(),
        timestamp=utils.utcnow(),
        description=f"**Use command again.**"
            )
        embed.set_author(name=interaction.user, icon_url=self.avatars.get_user_avatar(interaction.user))
        return await interaction.followup.send(embed=embed)

Implement 3.7.0's new RESTful API

Summary

Implementing the new 3.7.0 lavalink API.

The Problem

When 3.7.0 is released, mafic would be using the old websocket communication.

The Ideal Solution

Implement the new API, using #13 to figure out which to use.

The Current Solution

Using the deprecated API

Additional Context

lavalink-devs/Lavalink#748

A discord server

Summary

Create a discord server (if it's not already there)

The Problem

  • I don't want to spam the issues with problems that could also be my fault.
  • You could just create one discord for all your projects, so it's clean but there is a way to communicate with other users of this (and other libaries) to talk to each other and help finding solutions together.

The Ideal Solution

A small tidy discord server linked under each of your repos

The Current Solution

I don't think there is any

Additional Context

I'd also voulenteer to "maintain"/moderate the server if you want.

nextcord.errors.ApplicationInvokeError: Command raised an exception: IndexError: list index out of range

Summary

look

Reproduction Steps

@bot.slash_command(dm_permission=False)
async def play(inter: nextcord.Interaction, query: str):
    if not inter.guild.voice_client:
        player = await inter.user.voice.channel.connect(cls=mafic.Player)
    else:
        player = inter.guild.voice_client

    tracks = await player.fetch_tracks(query)

    if not tracks:
        return await inter.send("No tracks found.")

    track = tracks[0]

    await player.play(track)

    await inter.send(f"Playing {track.title}.")

Minimal Reproducible Code

tracks = await player.fetch_tracks(query)

    if not tracks:
        return await inter.send("No tracks found.")

    track = tracks[0]

    await player.play(track)

Expected Results

to play music

Actual Results

it gave me a error

Lavalink Information

Version: 3.7.5
Build time: 06.03.2023 12:02:05 UTC
Branch HEAD
Commit: c00286d
Commit time: 06.03.2023 11:59:46 UTC
JVM: 17.0.6
Lavaplayer 1.4.0-original

System Information

  • Python v3.10.11-final
  • mafic v2.4.2
  • aiohttp v3.8.3
  • nextcord v2.4.0-final
  • system info: Windows 10 10.0.22621

Checklist

  • I have searched the open issues for duplicates.
  • I have shown the entire traceback, if possible.

Additional Context

nope

make utils.decode_track support custom fields

Summary

utils.decode_track does not decode custom lavaplayer fields

The Problem

utils.decode_track does not decode custom fields like what the http source provides.

The Ideal Solution

Custom decoders can be provided to decode_tracks to decode these custom fields, #8 may need to be done first to support this.

The Current Solution

No response

Additional Context

https://github.com/disgoorg/disgolink/blob/1ec8fc65d6d173cfab5e13eff14f901f88d7f493/lavalink/audio_track_decode.go#L74

Type Issue in typings/misc.py - Not 3.8 compatible (reported from mafic support discord)

Summary

Exception thrown when running mafic on python 3.8

Reproduction Steps

Import mafic while using Python 3.8

Minimal Reproducible Code

`import mafic`

Expected Results

No errors when running bot that includes Mafic

Actual Results

Traceback (most recent call last):
  File "/usr/local/lib/python3.8/dist-packages/disnake/ext/commands/common_bot_base.py", line 452, in _load_from_module_spec
    spec.loader.exec_module(lib)  # type: ignore
  File "<frozen importlib._bootstrap_external>", line 848, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/home/project/mikis/modules/music/play.py", line 3, in <module>
    import mafic
  File "/usr/local/lib/python3.8/dist-packages/mafic/__init__.py", line 15, in <module>
    from .node import *
  File "/usr/local/lib/python3.8/dist-packages/mafic/node.py", line 32, in <module>
    from .typings import (
  File "/usr/local/lib/python3.8/dist-packages/mafic/typings/__init__.py", line 4, in <module>
    from .common import *
  File "/usr/local/lib/python3.8/dist-packages/mafic/typings/common.py", line 7, in <module>
    from .misc import PayloadWithGuild
  File "/usr/local/lib/python3.8/dist-packages/mafic/typings/misc.py", line 16, in <module>
    Coro = Coroutine[Any, Any, T]
TypeError: 'ABCMeta' object is not subscriptable

Lavalink Information

Not applicable

System Information

Reported in Tooty Town by multiple users. Users were using 3.8 and latest Mafic available on PyPi

Checklist

  • I have searched the open issues for duplicates.
  • I have shown the entire traceback, if possible.

Additional Context

I'm not super familiar with types, but my Google-fu led me to the answer that from collections.abc import Coroutine is not compatible with Python 3.8.

So, pyproject should be updated to show that 3.9 is the minimum Python version that is supported and users having this issue should update or the import statement for Coroutine should be from typing import Coroutine instead to maintain 3.8 compatibility

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.