Seite 1 von 3

Eigenes Modul

Verfasst: Freitag 4. Oktober 2019, 16:53
von Fire Spike
Hallo ich habe ein eigenes Modul erstellt.
Das Modul kann auf Linux Betriebsystemen ausgeführt werden die Debian Pakete unterstützen.
Es bietet funktionen zum anzeigen der infos über ein Paket.
Ich bin offen für verbesserungen und neue Ideen.
Ich habe hierhttps://1drv.ms/u/s!AhT7GNW97XSLnnDN9Yq ... d?e=7qfYlt einen Link für das vollständige Paket.
Der Code selber habe ich noch hier runterkopiert.
Liebe Grüsse Fire Spike.

Code: Alles auswählen

import linecache
import json
from pkg_resources import resource_filename

def read_json():
    jsonpath = resource_filename(__name__, "little_RAM_mode.json")
    with open(jsonpath, "r") as jsonfile:
        return json.load(jsonfile)
    
def package_exists(package, line_number=False):
    with open("/var/lib/dpkg/status") as infofile:
        if line_number:
            line_number = 0
            for line in infofile:
                line_number += 1
                if line == 'Package: ' + package + '\n':
                    return [True, line_number]
                
                else:
                    pass
        
            return [False]
        
        else:
            for line in infofile:
                if line == 'Package: ' + package + '\n':
                    return True
                
                else:
                    pass
            
            return False

def package_values(package, value):
    loop = True
    counter = 0        
    OK = package_exists(package, True)
    if OK[0] == True:
        line_number = OK[1]
        if value == "Description" or value == "Conffiles":
            while loop == True:
                counter += 1
                line = linecache.getline("/var/lib/dpkg/status", line_number + counter)
                h = line.find(value)
                if line.find("Package: ") == 0:
                    raise NameError("The value are not exist")
                
                if h == 0:
                    return_value = line.split(": ")
                    while loop == True:
                        counter += 1
                        line = linecache.getline("/var/lib/dpkg/status", line_number + counter)
                        if line.find("Package: ") == 0 or line.find("Homepage: ") == 0 or line.find("Description: ") == 0:
                            return_value[:-2]
                            del return_value[0]
                            a = ""
                            for i in return_value:
                                a += i
                            return_value = a
                            return return_value[:-2]
                        else:
                            return_value.append(line)
                            
        else:
            while loop == True:
                counter += 1
                line = linecache.getline("/var/lib/dpkg/status", line_number + counter)
                h = line.find(value)
                if line.find("Package: ") == 0:
                    raise NameError("The value are not exist")
                
                if h == 0:
                    return_value = line.split(": ")
                    return_value = return_value[1].split("\n")
                    return return_value[0]
        
    else:
        raise NameError("The package are not exists")

def package_list():
    packages_list = []
    with open("/var/lib/dpkg/status") as infofile:
        for line in infofile:
            if line.find("Package: ") == 0:
                akt_package = line.split(": ")[1]
                akt_package = akt_package.split("\n")
                packages_list.append(akt_package[0])
                
            else:
                pass
            
    return packages_list

def count_packages(little_RAM_mode=""):
    if little_RAM_mode == "":
        little_RAM_mode = read_json()
        
    if little_RAM_mode:
        counter_packages = 0
        with open("/var/lib/dpkg/status") as infofile:
            for line in infofile:
                if line.find("Package: ") == 0:
                    counter_packages += 1
                
                else:
                    pass
            return counter_packages

        
    else:
        return(len(package_list()))

def set_little_RAM_mode(value):
    if value == True or False:
        with open("little_RAM_mode.json", "w") as jsonfile:
            jsonfile.write(json.dumps(value, indent=4))
    
    else:
        raise ValueError("enter False or True")

Re: Eigenes Modul

Verfasst: Freitag 4. Oktober 2019, 17:02
von __blackjack__
@Fire Spike: Nur mal so als ein Punkt: Die API/die Rückgabewerte von `package_exists()` sind sehr unübersichtlich. Eine Funktion sollte immer die gleichen Strukturen/Typen zurückgeben, und nicht mal einen Wahrheitswert und mal eine Liste mit Wahrheitswert und einer Zahl und mal eine Liste mit nur einem Wahrheitswert. Man könnte hier Beispielsweise `None` oder eine Zahl zurückgeben.

Die Funktion enthält auch zweimal fast das gleiche was über das Argument `line_number` ausgewählt wird. Auch das ist unübersichtlich. Man kann auch einfach immer davon ausgehen das die Zeilennummer ermittelt werden soll. Der Aufrufer kann die ja ignorieren wenn er sie nicht braucht oder eben nur unterscheiden zwischen `None` oder eben nicht, falls ihn die konkrete Zahl nicht interessiert.

Re: Eigenes Modul

Verfasst: Freitag 4. Oktober 2019, 17:46
von Fire Spike
Das habe ich jetzt verbessert. die Funktion gibt nur noch entweder None oder wenn das Paket existiert die Zeilennummer zurück.
sonst noch Kritik?

Re: Eigenes Modul

Verfasst: Samstag 5. Oktober 2019, 07:03
von Fire Spike
Ich werde das Modul nächstes Wochenende auf Pypi hochladen. :P
Wer noch eine Verbesserung oder Idee hat soll es bis dann hier reinschreiben! :D
Liebe Grüsse Fire Spike. :wink:
Aktueller Code:

Code: Alles auswählen

import linecache
import json
from pkg_resources import resource_filename

def read_json():
    jsonpath = resource_filename(__name__, "little_RAM_mode.json")
    with open(jsonpath, "r") as jsonfile:
        return json.load(jsonfile)
    
def package_exists(package):
    with open("/var/lib/dpkg/status") as infofile:
        line_number = 0
        for line in infofile:
            line_number += 1
            if line == 'Package: ' + package + '\n':
                return line_number
            
            else:
                pass
        
        return None

def package_values(package, value):
    loop = True
    counter = 0        
    OK = package_exists(package)
    if not OK == None:
        line_number = OK
        if value == "Description" or value == "Conffiles":
            while loop == True:
                counter += 1
                line = linecache.getline("/var/lib/dpkg/status", line_number + counter)
                h = line.find(value)
                if line.find("Package: ") == 0:
                    raise NameError("The value are not exist")
                
                if h == 0:
                    return_value = line.split(": ")
                    while loop == True:
                        counter += 1
                        line = linecache.getline("/var/lib/dpkg/status", line_number + counter)
                        if line.find("Package: ") == 0 or line.find("Homepage: ") == 0 or line.find("Description: ") == 0:
                            return_value[:-2]
                            del return_value[0]
                            a = ""
                            for i in return_value:
                                a += i
                            return_value = a
                            return return_value[:-2]
                        else:
                            return_value.append(line)
                            
        else:
            while loop == True:
                counter += 1
                line = linecache.getline("/var/lib/dpkg/status", line_number + counter)
                h = line.find(value)
                if line.find("Package: ") == 0:
                    raise NameError("The value are not exist")
                
                if h == 0:
                    return_value = line.split(": ")
                    return_value = return_value[1].split("\n")
                    return return_value[0]
        
    else:
        raise NameError("The package are not exists")

def package_list():
    packages_list = []
    with open("/var/lib/dpkg/status") as infofile:
        for line in infofile:
            if line.find("Package: ") == 0:
                akt_package = line.split(": ")[1]
                akt_package = akt_package.split("\n")
                packages_list.append(akt_package[0])
                
            else:
                pass
            
    return packages_list

def count_packages(little_RAM_mode=""):
    if little_RAM_mode == "":
        little_RAM_mode = read_json()
        
    if little_RAM_mode:
        counter_packages = 0
        with open("/var/lib/dpkg/status") as infofile:
            for line in infofile:
                if line.find("Package: ") == 0:
                    counter_packages += 1
                
                else:
                    pass
            return counter_packages

        
    else:
        return(len(package_list()))

def set_little_RAM_mode(value):
    if value == True or False:
        with open("little_RAM_mode.json", "w") as jsonfile:
            jsonfile.write(json.dumps(value, indent=4))
    
    else:
        raise ValueError("enter False or True")

Re: Eigenes Modul

Verfasst: Samstag 5. Oktober 2019, 13:28
von __deets__
else: pass ist überflüssig. Einen index/Nummerierung für eine Liste oder allgemein etwas aufzählbares erzeugt man mit enumerate. Nicht von Hand. Auf none prüft man mit is, nicht mit ==. Statt (not a == b) schreibt man (a != b), oder wenn du den Hinweis vorher berücksichtigst “a is not b” bei is. Ob ein Wert eine Reihe von werten annehmen kann prüft man mit “a in (1, 2, 3)” und nicht “a == 1 or a == 2 or....”. while ding == True ist überflüssig, while ding reicht. Oder while not ding. Falls es falsch/False ist. Statt “String.find() == 0” nimmt man String.startswith. Und auch hier verkettet man nicht endlos viele Male den nahezu gleichen Test mit or. Sondern macht zb “if any(s.startswith(begriff) for begriff in lauter_begriffe):”.

Re: Eigenes Modul

Verfasst: Samstag 5. Oktober 2019, 13:46
von Sirius3
Wenn man zusätzlich zu den Elementen eines Iterators auch einen Index braucht, nimmt man ›enumerate‹. Strings stückelt man nicht mit + zusammen, sondern benutzt Format-Strings. Ein else-Block der nur aus ›pass‹ besteht, kann auch gleich ganz weggelassen werden:

Code: Alles auswählen

def package_exists(package):
    search_line = f"Package: {package}\n"
    with open("/var/lib/dpkg/status") as infofile:
        for line_number, line in enumerate(infofile, start=1):
            if line == search_line:
                return line_number
        return None
Bei einer Funktion, die ›package_exists‹ heißt, würde ich als Ergebnis True/False erwarten und nicht Zeilennummer oder None.

Noch verwirrender geht es in ›package_values‹ weiter. Dort wird der Rückgabewert von `package_exists` erst an ›OK‹ gebunden, das dann in line_number umgenannt wird. Wer nur diese Zeile liest, und nicht den kompletten Code im Kopf hat, wundert sich. Und sich wundern ist das schlimmste, was ein Programmierer tun kann, denn dann macht er Fehler.
›loop‹ wird nicht gebraucht, da es immer den Wert ›True‹ hat. ›counter‹ wird viel zu weit weg von der Nutzung definiert. ›not OK == None‹ schreibt man als ›OK is not None‹. Und auf `True` wird nicht explizit geprüft.
Was im if-Block zu `value` steht, ist fast identisch zum else-Block und kann zusammengefasst werden. NameError ist für fehlende Variablennamen reserviert und sollte nicht für irgendwas anderes benutzt werden.
›line.find(abc) == 0‹ ist umständlich geschrieben, was ›line.startswith‹ auch macht.
Du benutzt ›split‹ in der Erwartung, dass das nur einmal in der Zeile vorkommt. Das ist aber nicht garantiert. Daher wäre ›split‹ mit der Anzahl 1 aufzurufen. ›return_value‹ ist dann aber auch ein falscher Variablenname, weil das ja gar nicht zurückgegeben wird, sondern nur ein Teil.

Kommen wir zu

Code: Alles auswählen

                        return_value[:-2]
                        del return_value[0]
                        a = ""
                        for i in return_value:
                            a += i
                        return_value = a
                        return return_value[:-2]
Die erste Zeile ist wirkungslos und kann weg. Dann löschst Du das erste Element der Liste ›return_value‹, die hoffentlich nur aus zwei Elemnten besteht, weil sonst die for-Schleife etwas zusammenbaut, wo ein ": " fehlt. `i` ist ein schlechter Variablenname für einen String, `a` ebenso, und das was Du da tust, ließe sich lesbarer mit ''.join schreiben. Das ganze ist aber gar nicht nötig, wenn man split wie oben beschrieben benutzt.
Die innere while-Schleife verstehe ich nicht, weil weder etwas mit line noch mit counter gemacht wird. Bleibt ungefähr das:

Code: Alles auswählen

def package_values(package, value):
    line_number = package_exists(package)
    if line_number is None:
        raise KeyError("The package does not exists")
    counter = 0
    while loop == True:
        counter += 1
        line = linecache.getline("/var/lib/dpkg/status", line_number + counter)
        key, content = line.split(": ", 1)
        if key == "Package":
            raise ValueError("The value does not exists")
        elif key == value:
            return value.rstrip()
Jetzt ist aber die Benutzung von linecache mit Kanonen auf Spatzen geschossen. Da Du die Datei eh Zeilenweise durchgehst, kann man beide Funktionen zusammenfassen:

Code: Alles auswählen

def package_values(package, value):
    search_line = f"Package: {package}\n"
    with open("/var/lib/dpkg/status") as infofile:
        for line in infofile:
            if line == search_line:
                break
        else:
            raise KeyError("The package does not exists")
        for line in infofile:
            key, content = line.split(": ", 1)
            if key == "Package":
                break
            elif key == value:
                return value.rstrip()
    raise ValueError("The value does not exists")
In ›package_list‹ gilt das oben gesagte:

Code: Alles auswählen

def package_list():
    packages = []
    with open("/var/lib/dpkg/status") as infofile:
        for line in infofile:
            if line.startswith("Package: "):
                _, name = line.split(": ", 1)
                packages.append(name.rstrip())
    return packages
Das `little_RAM_mode`-Argument mit seiner doppelten Code-Variante braucht wahrscheinlich mehr RAM, als dass das Argument sparen würde. Wenn der Code eh schon da ist, warum dann überhaupt noch den huge-RAM-Mode anbieten?

Jetzt bleibt noch das Problem, dass in jeder Funktion im Prinzip das selbe gemacht wird, es wird eine Datei zeilenweise gelesen und nach "Packages:" gesucht.

Das kann man lösen, indem man sich eine Funktion schreibt, die das Lesen der Datei abstrahiert:

Code: Alles auswählen

from more_itertools import split_before
def iter_packages():
    with open("/var/lib/dpkg/status") as infofile:
        items = (line.strip().split(": ", 1) for line in infofile)
        return split_before(items, lambda x:x[0] == "Package")

def package_exists(package):
    for info in iter_packages():
        key, content = next(info)
        if content == package:
            return True
    return False

def package_values(package, value):
    for info in iter_packages():
        key, content = next(info)
        if content == package:
            for key, content in info:
                if key == value;
                    return content
            raise ValueError("The value does not exists")
    raise KeyError("The package does not exists")

def package_list():
    return [
        next(info)[1]
        for info in iter_packages()
    ]

def count_packages():
    count = 0
    for info in iter_packages():
        count += 1
    return count

Re: Eigenes Modul

Verfasst: Samstag 5. Oktober 2019, 20:03
von Fire Spike
Danke euch!

die Abfrage

Code: Alles auswählen

if value == "Description" or value == "Conffiles":
ist aus dem grund das die zwei werte auf mehrere zeilen verteilt ist.
Das ist ein beispiel:

Code: Alles auswählen

Package: adduser
Status: install ok installed
Priority: important
Section: admin
Installed-Size: 849
Maintainer: Debian Adduser Developers <adduser@packages.debian.org>
Architecture: all
Multi-Arch: foreign
Version: 3.118
Depends: passwd, debconf (>= 0.5) | debconf-2.0
Suggests: liblocale-gettext-perl, perl
Conffiles:
 /etc/deluser.conf 773fb95e98a27947de4a95abb3d3f2a2
Description: add and remove users and groups
 This package includes the 'adduser' and 'deluser' commands for creating
 and removing users.
 .
  - 'adduser' creates new users and groups and adds existing users to
    existing groups;
  - 'deluser' removes users and groups and removes users from a given
    group.
 .
 Adding users with 'adduser' is much easier than adding them manually.
 Adduser will choose appropriate UID and GID values, create a home
 directory, copy skeletal user configuration, and automate setting
 initial values for the user's password, real name and so on.
 .
 Deluser can back up and remove users' home directories
 and mail spool or all the files they own on the system.
 .
 A custom script can be executed after each of the commands.
und wieso kommt bei der zeile

Code: Alles auswählen

items = (line.strip().split(": ", 1) for line in infofile)
der Fehler

Code: Alles auswählen

ValueError: I/O operation on closed file.
? :?
Die Datei wird ja geöffnet :|

Re: Eigenes Modul

Verfasst: Samstag 5. Oktober 2019, 20:38
von Sirius3
Stimmt, war mein Denkfehler, split_before wird ja zurückgegeben und damit wird der with-Block wieder verlassen. Deinen else-Block hatte ich übersehen.
Damit wird `iter_packages` komplizierter:

Code: Alles auswählen

def iter_packages(infofile):
        info = []
        for line in infofile:
            if line.startswith(' '):
                # continue content
                info[-1][-1] += '\n' + line[1:]
            else:
                key, content = line.split(":", 1)
                if key == "Package":
                    if info:
                        yield iter(info)
                    info = []
                info.append([key, content.strip()])
        if info:
            yield iter(info)
Aber dafür ist die Funktion ja da, um die komplizierte Arbeit zu machen.

Re: Eigenes Modul

Verfasst: Sonntag 6. Oktober 2019, 09:39
von Fire Spike
tut mir leid wenn ich nerve aber wieso kommt jetzt wieder ein Fehler? :

Code: Alles auswählen

key, content = line.split(":", 1)
ValueError: not enough values to unpack (expected 2, got 1)
eigentlich sollte er ja in diese

Code: Alles auswählen

if line.startswith(' '):
schleife gehen oder? :?: :|

Re: Eigenes Modul

Verfasst: Sonntag 6. Oktober 2019, 10:05
von Sirius3
Da musst du halt nachschauen was für eine Zeile da kommt und warum es den Fall geben kann.

Re: Eigenes Modul

Verfasst: Sonntag 6. Oktober 2019, 10:36
von Fire Spike
Weil

Code: Alles auswählen

 if line.startswith(' '):
den abstand am anfang nicht erkennt.

Re: Eigenes Modul

Verfasst: Sonntag 6. Oktober 2019, 10:56
von Sirius3
Das Prinzip sollte doch jetzt aber klar sein?

Re: Eigenes Modul

Verfasst: Sonntag 6. Oktober 2019, 12:11
von Fire Spike
Ja, aber es geht nicht😥

Re: Eigenes Modul

Verfasst: Sonntag 6. Oktober 2019, 14:52
von sparrow
Häh? Der Fehler hat doch mit der Zeile gar nichts zu tun.
Du willst eine Zeichenkette an einem Doppelpunkt in exakt zwei Teile teilen und da ist halt kein Doppelpunkt enthalten.

Re: Eigenes Modul

Verfasst: Sonntag 6. Oktober 2019, 16:12
von Fire Spike

Code: Alles auswählen

Du willst eine Zeichenkette an einem Doppelpunkt in exakt zwei Teile teilen und da ist halt kein Doppelpunkt enthalten.
Das weiss ich, aber ich weiss nicht wieso alles gut funktioniert, aber das programm die letze zeile versucht zu spalten. :?:
die sollte ja abgefangen werden. :shock:
das ist das problem.
das programm selber verstehe ich

Re: Eigenes Modul

Verfasst: Sonntag 6. Oktober 2019, 16:23
von __deets__
Und bist du mal auf die Idee gekommen dir die letzte Zeile ausgeben zu lassen, am besten mit "print(repr(letzte_zeile))"? Damit du mal siehst WAS genau da kommt?

Re: Eigenes Modul

Verfasst: Sonntag 6. Oktober 2019, 19:20
von Fire Spike
Das

Code: Alles auswählen

 A custom script can be executed after each of the commands.
kommt.
auch am anfang ein leerzeichen!

Re: Eigenes Modul

Verfasst: Sonntag 6. Oktober 2019, 19:46
von sparrow
Zeig halt deinen Code, wie er jetzt aussieht. Das kann ja nicht sein.
Und das ist auch sicher nicht die Ausgabe von repr(), denn das ist ja sinnvoll, weil dann die Anführungszeichen angezeigt werden.

Re: Eigenes Modul

Verfasst: Montag 7. Oktober 2019, 06:43
von Fire Spike
Tut mir Leid.
der Fehler wahr meiner. :flücht: :?
Ich bemerkte erst mit der ausgabe von repr()
der Fehler wahr das am ende aller werte eines pakets eine neue linie kahm.
Das konnte ich lösen indem ich das

Code: Alles auswählen

elif line.startswith('\n'):
    pass
hinzufügte.

der ganze code:

Code: Alles auswählen

def iter_packages():
    info = []
    with open("/var/lib/dpkg/status") as infofile:
        for line in infofile:
            if line.startswith(' '):
                # continue content
                info[-1][-1] += '\n' + line[1:]
            elif line.startswith('\n'):
                pass
            else:
                key, content = line.split(":", 1)
                if key == "Package":
                    if info:
                        yield iter(info)
                    info = []
                info.append([key, content.strip()])
        if info:
            yield iter(info)

def package_exists(package):
    for info in iter_packages():
        key, content = next(info)
        if content == package:
            return True
    return False

def package_values(package, value):
    for info in iter_packages():
        key, content = next(info)
        if content == package:
            for key, content in info:
                if key == value:
                    return content
            raise ValueError("The value does not exists")
    raise KeyError("The package does not exists")

def package_list():
    return [
        next(info)[1]
        for info in iter_packages()
    ]

def count_packages():
    count = 0
    for info in iter_packages():
        count += 1
    return count
habt ihr jetzt noch ideen?

Re: Eigenes Modul

Verfasst: Montag 7. Oktober 2019, 07:15
von Sirius3
Für ein Paket fehlen jetzt noch Tests und Dokumentation. Die Funktionsnamen sind noch etwas uneinheitlich. `package_values` ist etwas verwirrend, da genau ein Wert abgefragt wird. `value` als Argument ist auch verwirrend, da man ja einen Schlüssel übergibt, um einen Wert zu bekommen, und keinen Wert. `package` sollte korrekterweise package_name heißen.
Die Funktionen sind gut, wenn man entweder nach einem Paket sucht, oder von genau einem Paket genau einen Wert wissen will. Für die einfache Benutzung wäre es auch besser, wenn es ein `get_package_values` gäbe, das nicht nur einen Wert zurückliefert, sondern gleich alle Werte als Wörterbuch.

Code: Alles auswählen

STATUS_FILENAME = "/var/lib/dpkg/status"

def iter_packages():
    info = []
    with open(STATUS_FILENAME) as infofile:
        for line in infofile:
            if line.startswith(' '):
                # continue content
                info[-1][-1] += '\n' + line[1:].rstrip()
            elif not line.strip():
                pass
            else:
                key, content = line.split(":", 1)
                if key == "Package":
                    if info:
                        yield dict(info)
                    info = []
                info.append([key, content.strip()])
        if info:
            yield dict(info)

def package_exists(package_name):
    return any(info["Package"] == package_name
        for info in iter_packages())

def get_package(package_name):
    for info in iter_packages():
        if info["Package"] == package_name:
            return info
    raise KeyError("The package does not exist")

def list_package_names():
    return [
        info["Package"]
        for info in iter_packages()
    ]

def count_packages():
    return sum(1 for info in iter_packages())
Wenn aber tatsächlich die Leerzeile einen Block abschließt, müßte `iter_packages` so aussehen:

Code: Alles auswählen

def iter_packages():
    info = []
    with open(STATUS_FILENAME) as infofile:
        for line in infofile:
            if line.startswith(' '):
                # continue content
                info[-1][-1] += '\n' + line[1:].rstrip()
            elif not line.strip():
                # end of block
                if info:
                    yield dict(info)
                info = []
            else:
                key, content = line.split(":", 1)
                info.append([key, content.strip()])
        if info:
            yield dict(info)