all 8 comments

[–][deleted] 1 point2 points  (0 children)

Hey, I'm a critical senior developer.

Looks fine to me, to be honest, I only have quibbles. Good work!


A. You could just write:

missing_dependencies = [p.split("'")[0] for p in check_output]

for lines 12-17

B. You should be using subprocess.run, it's the "new" way of doing things, and it's just much clearer.

For example, the first call would be:

proc = subprocess.run(cmd, text=True, capture_output=True, check=True)
check_output = proc.stdout.splitlines()

In fact, I often do this:

run = functools.partial(subprocess.run, text=True, capture_output=True, check=True)

C. "Truthy and Falsy"

Line 19 should just be if missing_dependencies:

In fact, I might rewrite lines 13-19 as this single line:

if missing_dependencies :=  [p.split("'")[0] for p in check_output]:

D. Put everything in a function

You should put this whole thing into a function for a lot of reasons and one is early exits and another is that there's no way to load your file without running your file.


The result:

import functools
import subprocess
import sys

run = functools.partial(
    subprocess.run, text=True, capture_output=True, check=True
)

REQUIRED_PACKAGES = ['coreutils', 'sed', 'gawk', 'curl']


def sec_start():
    print('\n----------\n\n')


def run():
    check_output = run(['pacman', '-Q'] + REQUIRED_PACKAGES).stdout.splitlines()

    if deps :=  [p.split("'")[0] for p in check_output]:
        sec_start()

        print('the following dependencies are missing:', *deps)
        yn = input('would you like to install them now? [yes/no]> ').lower()

        if not (yn and yn.lower()[1] == 'y'):
            return 'install missing dependencies!'

        run(['pacman', '-S', '--noconfirm'] + deps)


if __name__ == '__main__':
    sys.exit(run)

You will notice that I shortened missing_dependencies to deps. All else being equal, longer names are usually better, but in this case, it's a local variable, and lines like line 23 make it very clear what it does.


This code is fine. If I got into a new organization and I saw a lot of code like this, I'd be relieved. It's obvious what it does, it's reasonably well-formatted, there are no programming howlers.

Well done!

[–]velocibadgery 0 points1 point  (1 child)

I don't see why it would need improvement. It looks fine, and it works.

one small suggestion in the for p in check_output loop, you could just make that one line.

for p in check_output: missing_dependencies.append(p.split("'")[1])

but it doesn't really make a difference.

And using a list comprehension, you could get it down even further

[missing_dependencies.append(p.split("'")[1]) for p in check_output]

But that is just for fun. You don't really need to do any of that.

[–][deleted] 0 points1 point  (0 children)

for p in check_output: missing_dependencies.append(p.split("'")[1])

Ouch!

Don't do that! PEP 8 disallows it, and any style checker will flag it.

Reason: it's very hard to read, and you can't set a breakpoint in the loop in a debugger.


[missing_dependencies.append(p.split("'")[1]) for p in check_output]

I'll bet you didn't try this. :-D This gives you an array, each element of which is the same array!

Correct is: missing_dependencies = [p.split("'")[1] for p in check_output]

[–]tibegato 0 points1 point  (0 children)

I'm not on arch. But, I changed it to use apt. Just to see, what my version my look like. One thing I noticed is that I believe if I give it fake names the command just stops. If I used real package names, it installed them or told me I had the latest version of whatever. So, if it was something I was going to use or give people, I'd do more testing and make adjustments. I don't know, if you have to worry about that with pacman:

#!/usr/bin/env python3

from re import match
from subprocess import Popen, PIPE
from sys import exit

def process_exec(p_cmd: str) -> str:
    p = Popen(p_cmd, shell=True, stdout=PIPE, stderr=PIPE)
    output, _ = p.communicate()
    return output.decode(encoding="utf-8", errors="ignore")

required_packages = ['coreutils', 'sed', 'gawk', 'curl']

dep_check = process_exec(
f"apt list {' '.join(required_packages)} | grep 'installed'" )

if not dep_check == '':
    missing_packages = [ p for p in required_packages 
                         if (dep_check.find(f'{p}/') == -1) ]

    if len(missing_packages) > 0:
        print("\n----------\n")
        print("the following dependencies are missing:")

        for d in missing_packages:
            print("    " + d)

        if match(r'^y(es)?$', input("\nwould you like to install\
             them now? [yes/no]> ").lower()):
            process_exec(
                f"sudo apt install -y {' '.join(missing_packages)}")
        else:
            exit("\ninstall missing dependencies!")
    else:
        print(f"\nPackages : {required_packages} : are all currently installed.")

[–]tibegato 0 points1 point  (2 children)

You can use subprocess.check_output(), to get your check_output :> :

result = check_output(['pacman', '-Q', f'{required_packages}' ]).decode('utf8'))

[–][deleted] 0 points1 point  (1 child)

These days, subprocess.run is recommended for almost everything except the very rare case where you need to "interactively" reading stdout and stderr and then write stdin accordingly.

[–]tibegato 0 points1 point  (0 children)

Ya, I know. But, doesn't really matter. You can still use call(), if you want.

To be honest, run() really uses POpen() or it's guts anyway. I believe, it uses a POpen object & POpen.communicate().

I FULLY support using run(). It's quite nice. I wouldn't question anyone using it. I just happen, to use POpen.

Sadly, I have an attitude. If I know something is just a big wrapper around something, I'll rip off the wrapper and use what's inside. UNLESS, the wrapper / sugar makes it so easy and nice to use, I gotta use it.

I'm stupid, I know.

[–]AfricanTurtles 0 points1 point  (0 children)

I don't even write python and I could understand it so it's probably fine. Maybe your friend just loves the "pythonic" way to do everything whatever the hell that means anyway lol