2

I'm trying to create a batch file to run a Python script I wrote and ran into some hurdles:

  1. I need to ensure that end user has matplotlib-3.1.1 or later installed so I have a command:
C:\ProgramData\Anaconda3\python.exe -m pip freeze | findstr matplotlib

But this comes up with an output:

WARNING: Could not generate requirement for distribution -atplotlib 2.2.2 (c:\pr
ogramdata\anaconda3\lib\site-packages): Parse error at "'-atplotl'": Expected W:
(abcd...)
matplotlib==3.1.1
matplotlib-venn==0.11.5

So the question is: how can I search for a very specific package rather than anything that roughly matches search key?

  1. If user's version of matplotlib is older than what is required I want to have the latest version installed automatically. How can I send the output of version check (above) to some variable to be used in if clause?

Thanks in advance!

Surya Tej
  • 1,342
  • 2
  • 15
  • 25
NotAName
  • 3,821
  • 2
  • 29
  • 44
  • `package_name in sys.modules` helps in identifying if a package is available or not – Surya Tej Oct 06 '19 at 04:26
  • @SuryaTej the package name would only appear `in sys.modules` if you'd already imported it, and if it's not available then the attempt to import it will `raise ImportError`. – Karl Knechtel Oct 06 '19 at 05:08

2 Answers2

3

how can I search for a very specific package rather than anything that roughly matches search key?

I suspect that you don't really need to do this at all (skip to the last section), and especially not from your batch file.

If you really must do this from batch: the pip list output is probably more convenient for you than pip freeze. It should give you tabular output along the lines of:

Package        Version Location
-------------- ------- --------------------------------------------
package-name   1.0.0   c:\path\to\install\of\package-name

After a bunch of searching and playing around, I eventually came up with:

(for /f "tokens=1,2" %a in ('pip list') do if "%a"=="matplotlib" set MPLVER=%b)

(within a script, you might need to double up the percentage signs)

And then you can verify the %MPLVER% in your script (I have no idea how you would compare a version string to check if it's 3.1.1 or later, though).


It would be much easier to do this from within Python:

import pkg_resources

def have_recent_mpl():
    try:
        pkg_resources.require('matplotlib>=3.1.1')
    except pkg_resources.VersionConflict:
        # out of date
        return False
    except pkg_resources.DistributionNotFound:
        # not installed
        return False
    return True

If user's version of matplotlib is older than what is required I want to have the latest version installed automatically.

This is the part where it starts to sound like you're trying to solve the wrong problem. Presumably, you want to make sure that you have an up-to-date matplotlib because something else you're installing requires it, yes? So you should just use the proper packaging tools, and specify the required matplotlib version as a requirement in setup.py.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • Thanks for this detailed response! I've decided to follow your advice and not try to solve problems that may not even exist. I've added the check into the script that warns user that they use an outdated version and included another batch file that simply runs install for latest matplotlib. – NotAName Oct 06 '19 at 05:26
  • Could I ask more about what the script is actually intended to do? – Karl Knechtel Oct 06 '19 at 05:41
  • Oh, it's just meant to create some plots. It works fine with all versions of matplotlib from python console, but if you try to run the script from terminal and matplotlib version is old you get the following error: ```attribute qt::aa_enablehighdpiscaling must be set before qcoreapplication is created.``` Hence there's the requirement to have the latest version where this bug has been fixed. The problem is that we work from under corporate environment and you can't just run pip install because it will be blocked by firewall and I can't expect end users to know how to get around it. – NotAName Oct 06 '19 at 07:17
  • If your users can't just `pip install` things, then how exactly are you expecting them to deal with having an inadequate version of matplotlib? And for that matter, if you're in a corporate environment where the machine setup is dictated by someone else, why would you not have the same version? – Karl Knechtel Oct 06 '19 at 17:25
  • It's possible to ```pip install``` but with a specific ```--trusted-host``` that I know but I simply don't want to make it overly complicated for people using my script. Therefore, it's easier to simply put required install command inside a batch file. – NotAName Oct 06 '19 at 22:11
1

I definitely agree this would be done easier in python, but if you really want to do it with batch script (maybe its your favorite language and you can't do without it ???):

Update: Handles version strings correctly now.

@ECHO OFF
setlocal
setlocal EnableDelayedExpansion 

::  this is the package which you need version of
set packageName=matplotlib

::  this is the minimum version you want
set desiredVersion=3.1.1

::  this will be filled by called function
set versionString=""

call :GetPythonPackageVersionString %packageName% versionString
if %versionString% == "" (
    ECHO PACKAGE %packageName% IS NOT INSTALLED OR COULD NOT BE FOUND!
    exit /b 1
)


::  remove spaces at the start and end
call :TrimLeadingTrailingSpaces %versionString% versionString


set versionOk=""
call :CompareVersionStrings %versionString% %desiredVersion% versionOk
if %versionOk% == "" (
    ECHO PACKAGE VERSION TOO LOW
    ECHO PLEASE UPDATE %packageName% VERSION %desiredVersion%
    exit /b 3
) else (
    ECHO PACKAGE VERSION IS OK
    exit /b 0
)

::  end of main
goto:eof






::
::  helper function for getting version of a python package
::  arg 1:takes package name as string
::  arg 2 (return value): package version as string (eg "3.10.0" without quotes)
::
:GetPythonPackageVersionString
set "command1=pip show %~1"
set "command2=findstr /I version:"

::  check if package exists
set var=""
FOR /F "tokens=* USEBACKQ" %%F IN (`%command1%`) DO (
set var=%%F
)
if %var% == "" (
    set %2=""
    ECHO GetPythonPackageVersionString --- PACKAGE COULD NOT BE FOUND
    goto:eof
)

::  use pip show + findstr to capture version string
FOR /F "tokens=* USEBACKQ" %%F IN (`%command1% ^| %command2%`) DO (
set var=%%F
)
::  check result of findstr
if ERRORLEVEL 1 (
    set %2="" 
    ECHO GetPythonPackageVersionString --- FINDSTR UNABLE TO FIND
    goto:eof
)

::  get right side of the string
::separate by comma
set %2=
FOR /F "tokens=2 delims=:" %%f in ("%var%") DO (
    set %2=%%f
)
goto:eof



::
::  I found this function from
::  https://stackoverflow.com/questions/3001999/how-to-remove-trailing-and-leading-whitespace-for-user-provided-input-in-a-batch
::  answer of Foumpie
::
:TrimLeadingTrailingSpaces
set %2=%1
goto:eof




::
::  Compares to version strings
::  arg 1: version string captured from pip
::  arg 2: desired version string
::  arg 3(return value): empty or y
::
:CompareVersionStrings
::  separate by . character to get version numbers
for /f "tokens=1,2,3 delims=." %%a in ("%1") do (
   set packageVersionNumber[1]=%%a
   set packageVersionNumber[2]=%%b
   set packageVersionNumber[3]=%%c
)
::ECHO MAJOR VERSION:%packageVersionNumber[1]%
::ECHO MINOR VERSION:%packageVersionNumber[2]%
::ECHO SUBMINOR VERSION:%packageVersionNumber[3]%

for /f "tokens=1,2,3 delims=." %%a in ("%2") do (
   set desiredVersionNumber[1]=%%a
   set desiredVersionNumber[2]=%%b
   set desiredVersionNumber[3]=%%c
)
::ECHO MAJOR VERSION:%desiredVersionNumber[1]%
::ECHO MINOR VERSION:%desiredVersionNumber[2]%
::ECHO SUBMINOR VERSION:%desiredVersionNumber[3]%

for /L %%i IN (1 1 3) do (
    if !packageVersionNumber[%%i]! GTR !desiredVersionNumber[%%i]! (
        REM package version is larger than desired one
        set %3="y"
        goto:eof
    ) else if !packageVersionNumber[%%i]! EQU !desiredVersionNumber[%%i]! (
        REM this version number is equal, continue checking with next one
    ) else (
        REM package version is less than desired one
        set %3=""
        goto:eof
    )
)    

::  if we are here, all version numbers are equal
set %3="y"
goto:eof
unlut
  • 3,525
  • 2
  • 14
  • 23
  • 1
    I'm impressed by the level of effort here, but I can assure you that `GEQ` is not sufficient to compare version strings. For example, it does not think that `"10.0.0"` satisfies a requirement for `"9.9.9"` or later. – Karl Knechtel Oct 06 '19 at 17:29
  • @KarlKnechtel Thanks for pointing that out! I actually had a version that parses the version string dot as the delimiter, then perform comparisons on ints, but totally forgot that scenario and put this version because its much simpler. I will update it as soon as possible =) – unlut Oct 06 '19 at 17:34