A powerful Lavalink library for Discord.py.

Overview

logo.png?raw=true

https://api.codacy.com/project/badge/Grade/d020ed97fd2a46fcb1f42bd3bc397e63

A robust and powerful Lavalink wrapper for Discord.py!

Documentation

Official Documentation.

Support

For support using WaveLink, please join the official support server on Discord.

Discord

Installation

The following commands are currently the valid ways of installing WaveLink.

WaveLink requires Python 3.7+

Windows

py -3.7 -m pip install Wavelink

Linux

python3.7 -m pip install Wavelink

Getting Started

A quick and easy bot example:

import discord
import wavelink
from discord.ext import commands


class Bot(commands.Bot):

    def __init__(self):
        super(Bot, self).__init__(command_prefix=['audio ', 'wave ','aw '])

        self.add_cog(Music(self))

    async def on_ready(self):
        print(f'Logged in as {self.user.name} | {self.user.id}')


class Music(commands.Cog):

    def __init__(self, bot):
        self.bot = bot

        if not hasattr(bot, 'wavelink'):
            self.bot.wavelink = wavelink.Client(bot=self.bot)

        self.bot.loop.create_task(self.start_nodes())

    async def start_nodes(self):
        await self.bot.wait_until_ready()

        # Initiate our nodes. For this example we will use one server.
        # Region should be a discord.py guild.region e.g sydney or us_central (Though this is not technically required)
        await self.bot.wavelink.initiate_node(host='127.0.0.1',
                                              port=2333,
                                              rest_uri='http://127.0.0.1:2333',
                                              password='youshallnotpass',
                                              identifier='TEST',
                                              region='us_central')

    @commands.command(name='connect')
    async def connect_(self, ctx, *, channel: discord.VoiceChannel=None):
        if not channel:
            try:
                channel = ctx.author.voice.channel
            except AttributeError:
                raise discord.DiscordException('No channel to join. Please either specify a valid channel or join one.')

        player = self.bot.wavelink.get_player(ctx.guild.id)
        await ctx.send(f'Connecting to **`{channel.name}`**')
        await player.connect(channel.id)

    @commands.command()
    async def play(self, ctx, *, query: str):
        tracks = await self.bot.wavelink.get_tracks(f'ytsearch:{query}')

        if not tracks:
            return await ctx.send('Could not find any songs with that query.')

        player = self.bot.wavelink.get_player(ctx.guild.id)
        if not player.is_connected:
            await ctx.invoke(self.connect_)

        await ctx.send(f'Added {str(tracks[0])} to the queue.')
        await player.play(tracks[0])


bot = Bot()
bot.run('TOKEN')
Comments
  • TypeError: 'coroutine' object is not callable

    TypeError: 'coroutine' object is not callable

    I'm getting this error when I try and play music on using my discord bot.

    Task exception was never retrieved future: <Task finished name='Task-32' coro=<Websocket.process_data() done, defined at C:\Users\icep2\AppData\Local\Programs\Python\Python39\lib\site-packages\wavelink\websocket.py:137> exception=TypeError("'coroutine' object is not callable")> Traceback (most recent call last): File "C:\Users\icep2\AppData\Local\Programs\Python\Python39\lib\site-packages\wavelink\websocket.py", line 156, in process_data event, payload = await self._get_event_payload(data['type'], data) File "C:\Users\icep2\AppData\Local\Programs\Python\Python39\lib\site-packages\wavelink\websocket.py", line 184, in get_event_payload track = await self.node.build_track(cls=wavelink.Track, identifier=base64) File "C:\Users\icep2\AppData\Local\Programs\Python\Python39\lib\site-packages\wavelink\pool.py", line 294, in build_track return cls(identifier, data) TypeError: 'coroutine' object is not callable Task exception was never retrieved future: <Task finished name='Task-39' coro=<Websocket.process_data() done, defined at C:\Users\icep2\AppData\Local\Programs\Python\Python39\lib\site-packages\wavelink\websocket.py:137> exception=TypeError("'coroutine' object is not callable")> Traceback (most recent call last): File "C:\Users\icep2\AppData\Local\Programs\Python\Python39\lib\site-packages\wavelink\websocket.py", line 156, in process_data event, payload = await self._get_event_payload(data['type'], data) File "C:\Users\icep2\AppData\Local\Programs\Python\Python39\lib\site-packages\wavelink\websocket.py", line 184, in get_event_payload track = await self.node.build_track(cls=wavelink.Track, identifier=base64) File "C:\Users\icep2\AppData\Local\Programs\Python\Python39\lib\site-packages\wavelink\pool.py", line 294, in build_track return cls(identifier, data) TypeError: 'coroutine' object is not callable

    Not a Bug 
    opened by Kaz213 15
  • Player.is_playing always returns False after track replace

    Player.is_playing always returns False after track replace

    Was broken in this commit: https://github.com/PythonistaGuild/Wavelink/commit/f126755662cdf17199b7814dbe4abcb5f038966b After first track has been replaced with Player.play(track), track_end event triggered after Player.play done. So, after first track replaced, Player._source is always None and Player.is_playing() always returns False.

    Consider changing code from commit above to something like this:

    if event == 'track_end' and payload.get('reason') == 'FINISHED':
        player._source = None
    
    opened by wladbelsky 7
  • Add support for

    Add support for "Session resuming" & "session data queueing".

    This PR is Implemented in #66

    Added New attributes

    • resume_session: bool
    • resume_timeout: float
    • resume_key: str (Best left None)
    • payload_timeout: float This is basically how recent requests shall be sent to the server
    • Websocket.reset() method.

    What are the new features?

    Websocket queueing.

    • Websocket._send() method now queues any data that is requested to be sent while the WS is disconnected.
    • New method Websocket.send_queue() which is called when connecting, and sends queue only when a session is resumed and Websocket is connected.
    • New exception NodeSessionClosedError raised when node closes a session or doesn't resume the old session. Handled within _connect method.

    Resuming

    • example of use of new features
    import string
    import secret
    class key:
        def __init__(self, len, first_key="NoSnoopingOnMyLava"):
            self.len = len
            self.persistent = first_key
    
        def __str__(self):
            return self.persistent
    
        def __repr__(self):
            """This should generate a key and shall make it persistent """
            self.persistent = SomeSecurePasswordGenerator(self.len)
            return self.persistent
    
    ---------------
    
     async def start_nodes(self) -> None:
        """Connect and intiate nodes."""
        nodes = {'MAIN': {'host': 'lavaserver.badhost.com',
                          'port': 80,
                          'rest_uri': 'http://lavaserver.badhost.com',
                          'password': "verytrivialpassword",
                          'identifier': 'MAIN',
                          'region': 'us_central',
                          'heartbeat': 40.0, # ping the server every 40s
                          'resume_session': True,
                          'resume_timeout': 90.0,
                          'resume_key': key(10) # or "Astring"
                          'payload_timeout': 40.0
                          }}
    
        for n in nodes.values():
            await self.bot.wavelink.initiate_node(**n)
    
    • or simply by setting 'resume_session': True
     async def start_nodes(self) -> None:
        """Connect and intiate nodes."""
        nodes = {'MAIN': {'host': 'lavaserver.badhost.com',
                          'port': 80,
                          'rest_uri': 'http://lavaserver.badhost.com',
                          'password': "verytrivialpassword",
                          'identifier': 'MAIN',
                          'region': 'us_central',
                          'resume_session': True
                          }}
    
        for n in nodes.values():
            await self.bot.wavelink.initiate_node(**n)
    

    Though the former has more use cases by using a Key Object we can:

    • Make the key persistent across Clusters
    • log the events, since __str__ is called when configuring the server, while __repr__ is called when the Node's session is closed to generate a new key.
    • To Generate a more secure password
    • Increase length of the password (The default is 32 characters)
    • Use

    How does resuming work?

    When the bot disconnects from the node (eg: 1006) then the lavalink server keeps on playing the music until session timeout, this allows the bot to reconnect and take control of the session. This PR implements this lavalink feature. The changes are not breaking, assuming no-one initializes Node instances directly.

    How does Queueing work?

    After the Node disconnects, we try to reconnect twice before timeout. during this time the requests to the node are queued in an asyncio.Queue subclass whose payload expires after some time. This could be useful when you have an higher timeout but want to only send recent requests

    opened by WizzyGeek 7
  • Add /decodetracks endpoint support

    Add /decodetracks endpoint support

    In addition to the Client/Node.build_track method, this adds the corresponding POST to /decodetracks to batch decode track identifiers. Useful if there are a large number of tracks that need to be decoded.

    The definition of the endpoint can be found here: https://github.com/Frederikam/Lavalink/blob/18ee6778674b7f9a817893b676a91a9b96e642f3/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.java#L140

    opened by james7132 6
  • AttributeError: '_MissingSentinel' object has no attribute 'guild'

    AttributeError: '_MissingSentinel' object has no attribute 'guild'

    Hi I am trying to make a setvolume command but I keep getting this error: AttributeError: '_MissingSentinel' object has no attribute 'guild'

    Code:

    @bot.command()
    async def setVolume(ctx, *, volume: float):
        vc = ctx.voice_client
        custom_player = CustomPlayer()
    
        if not vc:
            await ctx.send("I ain't in a vc bro")
        else:
            await custom_player.set_volume(volume=volume)
    opened by Necrosis000 5
  • Is there a better way to play a local file?

    Is there a better way to play a local file?

    I've finally got playing a local file (to lavalink) from the bot. (No more ffmpeg)

    I'm Just throwing this out here as a general question:

    is there a better way to search for a Local Track? I see SearchableTrack, having a return_first=true. Just trying to get my head around the API. Any help is apperciated?

    @slash_command(name="lplay", description="Play some local music or SFX") async def lplay(self, inter: Interaction, search): path = "/media/" + search
        search = wavelink.PartialTrack(query=path, cls=wavelink.LocalTrack)
    
        if inter.user.voice is None:
            return await inter.send(f'You are not connected to any voice channel!')
    
        # If the bot isn't in a voice channel
        if not inter.guild.voice_client:
            vc: wavelink.Player = await inter.user.voice.channel.connect(cls=wavelink.Player)
        else:
          #See if the bot is in another channel
          if inter.guild.voice_client.channel != inter.user.voice.channel:
            await inter.guild.voice_client.move_to(inter.user.voice.channel) 
            await inter.send(f'Connected to {inter.user.voice.channel}.')
            
        vc: wavelink.Player = inter.guild.voice_client
       
        try: 
           await vc.play(search)
           await inter.send(f'Playing {search.title}')
        except:
           await inter.send(f'{path} Not found!') 
    
    opened by Mudpuppy12 4
  • queue error please help

    queue error please help

    @bot.event async def on_wavelink_track_end(player: wavelink.Player, track: wavelink.Track, reason): ctx = player.ctx vc: player = ctx.voice_client if vc.loop: return await vc.play(track) next_song = vc.queue.get() await vc.play(next_song) await ctx.send(f"Now playing {next_song.title}")

    • File "/home/container/discord-bot/main.py", line 118, in on_wavelink_track_end next_song = vc.queue.get() File "/home/container/.local/lib/python3.10/site-packages/wavelink/queue.py", line 212, in get raise QueueEmpty("No items in the queue.") wavelink.errors.QueueEmpty: No items in the queue.
    opened by slefgameRz 4
  • wavelink.Player.connect incompatibility with Discord.py 2.0

    wavelink.Player.connect incompatibility with Discord.py 2.0

    Discord.py 2.0 introduced a new self_deaf and self_mute parameter to the connect method. This causes TypeError: Player.connect() got an unexpected keyword argument 'self_deaf'

    opened by Luc1412 4
  • module 'wavelink' has no attribute 'client'

    module 'wavelink' has no attribute 'client'

    Traceback (most recent call last): File "/Users/admin/Desktop/Cogs/music1.py", line 272, in bot = Bot() File "/Users/admin/Desktop/Cogs/music1.py", line 22, in init self.add_cog(Music(self)) File "/Users/admin/Desktop/Cogs/music1.py", line 69, in init self.bot.wavelink = wavelink.Client(bot=self.bot) AttributeError: module 'wavelink' has no attribute 'Client'

    opened by sanjaysanooj22 4
  • v1.2.0 No Longer Supports Python Version 3.8

    v1.2.0 No Longer Supports Python Version 3.8

    In the newest version (v1.2.0), the implementation of the YouTube Playlist searching has removed support for Python Version 3.8. The use of the method .removeprefix() is a python 3.9+ feature.

    The line in question: https://github.com/PythonistaGuild/Wavelink/blob/8a4fb12404a5e908bd7a22ecba1ace7dc950d73f/wavelink/tracks.py#L187

    PEP 616: String methods to remove prefixes and suffixes https://www.python.org/dev/peps/pep-0616/

    If this is intentional to remove Python 3.8 version, then please can the documentation be updated. Otherwise, an implementation to get around this could be:

    def removePrefix(url, prefix):
        if string.startswith(prefix):
            return string[len(prefix):]
        else:
            return string
    
    opened by TwoSails 4
  • [Suggestion] Advanced exmple

    [Suggestion] Advanced exmple

    Hello, Wavelink is working good but in my opinion there a a few missing things:

    • loop song & queue
    • lyrics command
    • pitch (1.00 standard 5.00 max)
    • option to keep the bot into the voicechannel even if nothing is playing.
    opened by FeelsBadMan1 4
  • Add payload_args optional argument to the play method in player.py

    Add payload_args optional argument to the play method in player.py

    This argument allows the user to pass arguments which will be directly sent to Lavalink in the play method. This gives the ability to interact with Lavalink plugins

    opened by Rajdave69 0
  • getting this error on 2.0

    getting this error on 2.0

    File "/home/subrata/Wavelink/wavelink/node.py", line 106, in <genexpr>
        version_tuple = tuple(int(v) for v in version.split('.'))
    ValueError: invalid literal for int() with base 10: 'c597fb10d9d323a174b69efa66fcebd616fb3f1e-SNAPSHOT_Unofficial'
    
    bug 2.0 
    opened by Subrata2402 0
  • Update pool.py

    Update pool.py

    Fixed bug with 'wavelink.NodePool.get_node(region="rotterdam")' raise ZeroConnectedNodes(f "No Nodes for region <{region}> exist on this pool."), although there is at least one node with this region.

    opened by Marco12223 1
  • Player running endless (without sound)

    Player running endless (without sound)

    I currently play an MP3 on the lavalink server in a loop. The loop has been accomplished by following code.

    @commands.Cog.listener(name="on_wavelink_track_end")
        async def _on_track_end(self, player: RadioPlayer, track: wavelink.Track, reason: str):
            if reason != 'FINISHED':
                return
            await player.play(track)
    

    This transition works fine so far. I also added on_wavelink_track_exception and on_wavelink_track_stuck for debug reasons, but both won't get triggered.

    I experience that the player randomly stops playing. The client is still connected. The player got is_connected() and is_playing() set to True. But when checking the position it got an inappropriate high number set.

    My track is about an hour long, but postion is set to 21h in seconds.

    Pausing and resuming resumes the playback.

    opened by Luc1412 0
  • cant search for youtube playlist

    cant search for youtube playlist

    I try to use wavelink.YouTubePlaylist to search for youtubeplaylist and I get error

    code @commands.command(aliases=['P', 'PLAY', 'Play','p']) async def play(self,ctx: commands.Context, *, search:wavelink.YouTubePlaylist): print(search)

    error Ignoring exception in on_message Traceback (most recent call last): File "C:\Python3.10\lib\site-packages\discord\ext\commands\core.py", line 456, in _actual_conversion ret = await method(ctx, argument) File "C:\Python3.10\lib\site-packages\wavelink\tracks.py", line 218, in convert return results[0] TypeError: 'YouTubePlaylist' object is not subscriptable

    Did i miss something?

    and also sorry for my bad English

    opened by tunwit 2
Releases(v1.3.4)
  • v1.3.4(Dec 22, 2022)

  • v1.3.3(Oct 4, 2022)

  • v1.3.2(Jul 10, 2022)

  • v1.3.1(Jun 6, 2022)

  • v1.3.0(Jun 6, 2022)

    Implemented Lavalink Filters, some documentation can be found here: https://wavelink.readthedocs.io/en/latest/wavelink.html#filters This is still a WIP.

    Fixed an issue with YouTubePlaylist converter failing. Bumped aiohttp inline with discord.py

    Source code(tar.gz)
    Source code(zip)
  • v1.2.5(Apr 22, 2022)

  • v1.2.4(Mar 21, 2022)

  • v1.2.3(Mar 21, 2022)

  • v1.2.2(Mar 13, 2022)

  • v1.2.1(Mar 10, 2022)

  • v1.2.0(Mar 7, 2022)

  • v1.1.1(Feb 8, 2022)

  • v1.1.0(Feb 4, 2022)

  • v1.0.2(Jan 24, 2022)

  • v1.0.1(Jan 24, 2022)

  • v1.0.0(Jan 24, 2022)

    Wavelink 1.0.0

    This release is a whole rewrite of the library. It supports discord.py 2.0 and some derivatives.

    See the documentaion for more info and examples. Documentation

    If the documentation is still displaying as the old version, please clear your cache.

    Source code(tar.gz)
    Source code(zip)
  • v0.9.10(Jul 13, 2021)

  • v0.9.9(Mar 8, 2021)

  • v0.9.8(Feb 25, 2021)

    Add force keyword argument to Node.destroy() and Player.destroy()/disconnect(). This prevents a bug from occuring when a player is being destroyed, and the bot is no longer apart of the Guild the player was associated with.

    Source code(tar.gz)
    Source code(zip)
  • v0.9.7(Feb 23, 2021)

  • v0.9.6(Sep 9, 2020)

    Fix a bug in player setting current track to None on TrackStuck and TrackException.

    Since Lavalink sends two events on Exceptions, an End and Exception event this causes a possible race condition in the player, setting the current Track to None when one is actually playing.

    Source code(tar.gz)
    Source code(zip)
  • v0.9.5(Sep 5, 2020)

    Added Exponential Backoff attempts to get_tracks. Retrieving tracks will now by default retry on failure up to a maximum of 5 attempts with an Exponential Backoff. You can set this to False with the kwarg retry_on_failure=False, which will only attempt to retrieve tracks once.

    Added support for custom JSON Encoders.

    Changed YouTube's Track Image to hqdefault, in place of maxresdefault.

    Source code(tar.gz)
    Source code(zip)
  • v0.9.4(Aug 5, 2020)

    Changes to Equalizer which allow for construction without the build() classmethod.

    Added name parameters to the build() classmethod and Equalizer constructor to allow setting a custom name.

    Added a name property to Equalizer. Setting the name should be done by constrcutor or the build() classmethod

    Added __str__ and __repr__ to Equalizer.

    Fixed a bug where set_eq was setting a blank Equalizer class on the player.

    Source code(tar.gz)
    Source code(zip)
  • v0.9.3(Jul 30, 2020)

  • v0.9.2(Jun 20, 2020)

    Add heartbeat to node and websocket.

    This stabilizes connection to Nodes hosted on a VPS with a router or any such intermediate process that closes the connection if it remains idle. Which puts the bot in a "reconnection" loop.

    Source code(tar.gz)
    Source code(zip)
  • v0.9.1(Jun 20, 2020)

  • v0.9.0(Jun 8, 2020)

    Added wavelink.WavelinkMixin to be used in conjunction with a discord.py commands.Cog. This class allows for the registration of the new wavelink listeners:

    on_node_ready on_track_start on_track_end on_track_stuck on_track_exception on_websocket_closed

    All listeners must be decorated with the wavelink.WavelinkMixin.listener() decorator. All listeners must be in a wavelink.WavelinkMixin class. Listeners can be stacked.

    Docs

    Event Payloads WavelinkMixin

    Example

    class Music(commands.Cog, wavelink.WavelinkMixin):
        
        @wavelink.WavelinkMixin.listener()
        async def on_node_ready(self, node):
            print(f'Node {node.identifier} is ready!')
    
    Source code(tar.gz)
    Source code(zip)
  • v0.8.0(May 18, 2020)

  • v0.7.2(May 16, 2020)

  • v0.7.1(May 2, 2020)

Owner
Pythonista
Organisation for the Pythonista Guild
Pythonista
DeKrypt 24 Sep 21, 2022
API which uses discord+mojang api to scrape NameMC searches/droptime/dropping status of minecraft names, and texture links

API which uses discord+mojang api to scrape NameMC searches/droptime/dropping status of minecraft names, and texture links

2 Dec 22, 2021
OpenSea Bulk Uploader And Trader 100000 NFTs (MAC WINDOWS ANDROID LINUX) Automatically and massively upload and sell your non-fungible tokens on OpenSea using Python Selenium

OpenSea Bulk Uploader And Trader 100000 NFTs (MAC WINDOWS ANDROID LINUX) Automatically and massively upload and sell your non-fungible tokens on OpenS

ERC-7211 3 Mar 24, 2022
A discord bot that moderates your server!

Staff Bot para Discord O que é? É um bot que modera o seu servidor no Discord, apagando mensagens indesejadas que os usuários mandem! Como usar Primei

Isac Gonçalves Cunha 3 Oct 07, 2021
A simple Discord bot that can fetch definitions and post them in chat.

A simple Discord bot that can fetch definitions and post them in chat. If you are connected to a voice channel, the bot will also read out the definition to you.

Tycho Bellers 4 Sep 29, 2022
Typed interactions with the GitHub API v3

PyGitHub PyGitHub is a Python library to access the GitHub API v3 and Github Enterprise API v3. This library enables you to manage GitHub resources su

5.7k Jan 06, 2023
A Pythonic client for the official https://data.gov.gr API.

pydatagovgr An unofficial Pythonic client for the official data.gov.gr API. Aims to be an easy, intuitive and out-of-the-box way to: find data publish

Ilias Antonopoulos 40 Nov 10, 2022
✖️ Unofficial API of 1337x.to

✖️ Unofficial Python API Wrapper of 1337x This is the unofficial API of 1337x. It supports all proxies of 1337x and almost all functions of 1337x. You

Hemanta Pokharel 71 Dec 26, 2022
Discord bot code to stop users that are scamming with fake messages of free discord nitro on servers in order to steal users accounts.

AntiScam Discord bot code to stop users that are scamming with fake messages of free discord nitro on servers in order to steal users accounts. How to

H3cJP 94 Dec 15, 2022
A Discord chat bot for the Tardsquad guild (Discord name for server).

Tardsquad Discord Bot A Discord chat bot for the Tardsquad guild (Discord name for server). Resouces Discord Developer Portal A general tutorial for a

Tardsquad Quality Code Inc. 4 Jul 26, 2022
Biblioteca Python que extrai dados de mercado do Bacen (Séries Temporais)

Pybacen This library was developed for economic analysis in the Brazilian scenario (Investments, micro and macroeconomic indicators) Installation Inst

42 Jan 05, 2023
Bot for Telegram data Analysis

Bot Scraper for telegram This bot use an AI to Work powered by BOG Team you must do the following steps to make the bot functional: Install the requir

8 Nov 28, 2022
A simple python discord bot which give you a yogurt brand name, basing on a large database often updated.

YaourtBot A discord simple bot by Lopinosaurus Before using this code : ・Move env file to .env ・Change the channel ID on line 38 of bot.py to your #pi

The only one bunny who can dev. 0 May 09, 2022
A quick and dirty script to scan the network, find default credentials on services and post a message to a Slack channel with the results.

A quick and dirty script to scan the network, find default credentials on services and post a message to a Slack channel with the results.

Security Weekly 11 Jun 03, 2022
An API-driven solution for Makerspaces, Tinkerers, and Hackers.

Mventory is an API-driven inventory solution for Makers, Makerspaces, Hackspaces, and just about anyone else who needs to keep track of "stuff".

Matthew Macdonald-Wallace 107 Dec 21, 2022
Unlimited Filter Telegram Bot 2

Mother NAther Bot Features Auto Filter Manuel Filter IMDB Admin Commands Broadcast Index IMDB search Inline Search Random pics ids and User info Stats

LɪᴏɴKᴇᴛᴛʏUᴅ 1 Oct 30, 2021
A Discord bot for viewing any currency you want comfortably.

Dost Dost is a Discord bot for viewing currencies. Getting Started These instructions will get you a copy of the project up and running on your local

Baran Gökalp 2 Jan 18, 2022
Coin-based opinion monitoring system

介绍 本仓库提供了基于币安 (Binance) 的二级市场舆情系统,可以根据自己的需求修改代码,设定各类告警提示 代码结构 binance.py - 与币安API交互 data_loader.py - 数据相关的读写 monitor.py - 监控的核心方法实现 analyze.py - 基于历史数

luv_dusk 6 Jun 08, 2022
An all-in-one financial analytics and smart portfolio creator as a Discord bot!

An all-in-one financial analytics bot to help you gain quantitative financial insights. Finn is a Discord Bot that lets you explore the stock market like you've never before!

6 Jan 12, 2022
A Python interface module to the SAS System. It works with Linux, Windows, and mainframe SAS. It supports the sas_kernel project (a Jupyter Notebook kernel for SAS) or can be used on its own.

A Python interface to MVA SAS Overview This module creates a bridge between Python and SAS 9.4. This module enables a Python developer, familiar with

SAS Software 319 Dec 19, 2022