Repository: nvaccess/nvda

Issue:
nvwave.playWaveFile/Python 3 syntax: async is no longer accepted as a variable name, throws SyntaxError in Python 3.7
Hi,

Continuing work on researching Python 3 transition barriers:

### Background:
Python 3.5 introduces async and await keywords for working with asynchronous generators and other possibilities. However, this poses a syntax issue for NVDA: nvwave.playWaveFile has "async" keyword, which, when run with Python 3.7, produces SyntaxError.

### Steps to reproduce:
1. Run Python 2.7 and 3.7 interpreters.
2. Run the below code in both interpreters.

Code fragment:

async = True

### Actual behavior:
In 2.7, the code works, but in 3.7, SyntaxError is raised.

### Expected behavior:
Code should work in 3.7.

### System configuration:

#### NVDA Installed/portable/running from source:
Not applicable

#### NVDA version:
Not applicable

#### Windows version:
Windows 10 Version 1803

#### Name and version of other software in use when reproducing the issue:
Python 2.7, 3.7

#### Other information about your system:
N/A

### Other questions:

#### Does the issue still occur after restarting your PC?
Yes

#### Have you tried any other versions of NVDA?
N/A

### Possible solution:
One possible solution is to rename the keyword "async" to "_async" or "asynchronous". I would vote for the latter option as it is a bit clear as to what that keyword does.

### Impact:
Any module (including add-ons) that calls nvwave.playWaveFile with "async" set to True must now use the proposed keyword argument name, breaking compatibility with old NVDA releases.

Thanks.


Discussion:
I vote for "asynchronous"

Resolving patch:
diff --git a/source/core.py b/source/core.py
--- a/source/core.py
+++ b/source/core.py
@@ -274,7 +274,7 @@ def onEndSession(evt):
 		speech.cancelSpeech()
 		if not globalVars.appArgs.minimal and config.conf["general"]["playStartAndExitSounds"]:
 			try:
-				nvwave.playWaveFile("waves\\exit.wav",async=False)
+				nvwave.playWaveFile("waves\\exit.wav",asynchronous=False)
 			except:
 				pass
 		log.info("Windows session ending")
@@ -557,7 +557,7 @@ def run(self):
 
 	if not globalVars.appArgs.minimal and config.conf["general"]["playStartAndExitSounds"]:
 		try:
-			nvwave.playWaveFile("waves\\exit.wav",async=False)
+			nvwave.playWaveFile("waves\\exit.wav",asynchronous=False)
 		except:
 			pass
 	# #5189: Destroy the message window as late as possible
diff --git a/source/nvwave.py b/source/nvwave.py
--- a/source/nvwave.py
+++ b/source/nvwave.py
@@ -383,9 +383,11 @@ def outputDeviceNameToID(name, useDefaultIfInvalid=False):
 
 fileWavePlayer = None
 fileWavePlayerThread=None
-def playWaveFile(fileName, async=True):
+def playWaveFile(fileName, asynchronous=True):
 	"""plays a specified wave file.
-"""
+	@param asynchronous: whether the wave file should be played asynchronously
+	@type asynchronous: bool
+	"""
 	global fileWavePlayer, fileWavePlayerThread
 	f = wave.open(fileName,"r")
 	if f is None: raise RuntimeError("can not open file %s"%fileName)
@@ -393,7 +395,7 @@ def playWaveFile(fileName, async=True):
 		fileWavePlayer.stop()
 	fileWavePlayer = WavePlayer(channels=f.getnchannels(), samplesPerSec=f.getframerate(),bitsPerSample=f.getsampwidth()*8, outputDevice=config.conf["speech"]["outputDevice"],wantDucking=False)
 	fileWavePlayer.feed(f.readframes(f.getnframes()))
-	if async:
+	if asynchronous:
 		if fileWavePlayerThread is not None:
 			fileWavePlayerThread.join()
 		fileWavePlayerThread=threading.Thread(target=fileWavePlayer.idle)
diff --git a/source/speech/commands.py b/source/speech/commands.py
--- a/source/speech/commands.py
+++ b/source/speech/commands.py
@@ -287,7 +287,7 @@ def __init__(self, fileName):
 
 	def run(self):
 		import nvwave
-		nvwave.playWaveFile(self.fileName, async=True)
+		nvwave.playWaveFile(self.fileName, asynchronous=True)
 
 	def __repr__(self):
 		return "WaveFileCommand(%r)" % self.fileName


Test patch: