Рекурсивный преобразователь зависимостей пакетов Python Package Index

На моей работе есть изолированная сеть, в которой разработчики пишут приложения для запуска. Эти разработчики часто пишут код на Python. Этот код Python часто требует загрузки модулей из индекса пакетов Python (PyPi) с использованием чего-то вроде pip. Чтобы позволить этим разработчикам запускать свое программное обеспечение в этой изолированной сети, мне нужно предоставить им зеркало PyPi, чтобы они могли получать модули, от которых зависит их программное обеспечение. Для этого я устанавливаю Брандашмыг зеркало на виртуальной машине в моей демилитаризованной зоне и настроили наши виртуальные машины на использование этого зеркала вместо (недоступного с их точки зрения) Интернета. Все идет нормально.

Политика диктует, что программное обеспечение, поступающее в нашу изолированную сеть, должно быть сначала одобрено, а это означает, что я не могу просто сделать полное зеркало всего индекса пакетов Python (если бы у меня даже было место на диске для такой вещи… я не уверен, что я ) и пусть люди имеют все, что хотят. Bandersnatch поддерживает это без проблем; включив это в файл конфигурации, у меня, по сути, есть список ТОЛЬКО утвержденных пакетов, которые будут отображаться на зеркале:

 [allowlist]
 packages =
    absl-py
    astunparse
    caproto
    confluent_kafka
    ConfigArgParse
    ess-streaming-data-types
    flatbuffers
    gast
    gpytorch
    ...<and so on>...

К сожалению, Bandersnatch не разрешает никаких зависимостей в этом списке пакетов, чтобы разрешить зеркалирование и маловероятно, что они будут реализовывать эту функцию. Похоже, что никакие другие зеркальные серверы PyPi также не настроены для этого, но если я что-то пропустил, сообщите мне об этом.

Итак, у меня есть список пакетов Python, от которых мне нужно знать все их зависимости, рекурсивно, чтобы я мог отразить весь этот список. Я не программист на Python, поэтому написал на Perl. Я думаю, что это работает, но я был бы очень признателен за еще один взгляд на это.

Мой код: https://pastebin.com/hR6Yru4W

#!/usr/bin/env perl
use strict;
use warnings;

# This script is a (probably naive) attempt at writing a recursive dependency resolver for the Python package index (PyPi)
# A list of (space seperated) Python Package names provided on the command line are the starting point
# Each package in that list is added to a graph, then every node(vertex) in that graph has edges defined between the node itself
# and each of it's dependencies (which become their own nodes as a result)
# Every node in the graph is iterated through, has it's dependencies(edges) defined, and the process is repeated until 
# the graph stops changing. Once there are no more dependencies to find, it prints out a (alphabetically sorted) list
# of every package that would be required to install every package given as an argument to this script. Note that this list
# WILL INCLUDE the packages that were initially provided as part of the list.

use JSON;
use Graph;

# Our main dependency graph objects
# We need two so we can check if the graph changed between dependency runs
# When the two graphs stay the same after a dependency run, we know our graph is complete
my $Agraph = Graph->new();
my $Bgraph = Graph->new();

# Accepts a list of python packages to check dependencies for
# Returns a list of python packages the arguments depend upon
# Note that this function is NOT RECURSIVE. It ONLY provides the direct dependencies.
sub get_deps {
    my $ret = [];
    my $json = JSON->new;
    foreach my $package ( @_ ) {
        my $curl = `curl -s "https://pypi.org/pypi/$package/json"`;
        my $reqs = $json->decode($curl)->{info}->{requires_dist};
        if (defined($reqs)) {
            foreach my $dep (@$reqs) {
                $dep =~ s/[^a-zA-Z0-9-].*$//;
                push(@$ret, $dep);
            }
        }
    }
    return $ret;
}

# @ARGV is a list of packages we need to find all dependencies for and is the roots of our dependency graph
foreach my $package (@ARGV) {
    $Agraph->add_vertex($package);
}

# A list of packages we have already gotten dependnecies for so we don't check twice
my $checked = [];

# Loop until our graphs stop changing between dependency runs
while ( $Agraph ne $Bgraph ) {
    $Bgraph = $Agraph->deep_copy();

    # Check every single vertice in our graph
    my @vertlist = $Agraph->vertices;
    foreach my $package (@vertlist) {
        # If we haven't checked this package before, get it added to the graph with all of it's dependencies
        if (! grep( /^$package$/, @$checked )) {
            my $deplist = get_deps($package);
            foreach my $dep (@$deplist) {
                $Agraph->add_edge($package, $dep);
            }
            # Add this package to our list of already checked packages so we don't waste time
            push(@$checked, $package);
        }
    }
}

my @fulldeplist = sort $Agraph->vertices;
foreach my $package (@fulldeplist) {
    print "$package\n";
}

2 ответа
2

Расположение кода очень хорошее, с хорошим использованием вертикальных пробелов и последовательным отступом. Он также хорошо использует комментарии. здорово, что вы воспользовались strict и warnings.

в grep линия, было бы неплохо использовать \Q и \E чтобы избежать любых потенциальных метасимволов регулярных выражений, которые могут быть в вашем $package переменная. Смотрите также цитата

Остальные предложения в основном относятся к хорошему стилю написания кода, и некоторые из них являются моими личными предпочтениями.

Когда я use модули, такие как JSON, мне нравится импортировать только то, что используется кодом, чтобы не загромождать пространство имен. В вашем случае, поскольку вы используете объектно-ориентированный интерфейс, вы можете указать пустой список; Я покажу это ниже.

Когда у меня есть комментарии в заголовке, я предпочитаю размещать их внутри Perl. ПОД (=head, и т. д.). Это обеспечивает справочную страницу командной строки для вас, когда вы используете перлдок команда:

perldoc script.pl

При разыменовывании ссылки на массив я предпочитаю использовать дополнительные фигурные скобки:

@{ $ret }

Обратитесь к этому перлкритик политика для более подробной информации: Perl::Critic::Policy::References::ProhibitDoubleSigil.

Поскольку обратные кавычки (``) можно легко пропустить, я предпочитаю использовать дх оператор котирования; он больше выделяется в коде.

Вот код с изменениями. Я также прогнал ваш код через средство проверки орфографии и исправил некоторые опечатки в ваших комментариях (dependnecies, seperated).

#!/usr/bin/env perl

use strict;
use warnings;

=head1 Description

 This script is a (probably naive) attempt at writing a recursive dependency resolver for the Python package index (PyPi)
 A list of (space separated) Python Package names provided on the command line are the starting point
 Each package in that list is added to a graph, then every node(vertex) in that graph has edges defined between the node itself
 and each of it's dependencies (which become their own nodes as a result)
 Every node in the graph is iterated through, has it's dependencies(edges) defined, and the process is repeated until 
 the graph stops changing. Once there are no more dependencies to find, it prints out a (alphabetically sorted) list
 of every package that would be required to install every package given as an argument to this script. Note that this list
 WILL INCLUDE the packages that were initially provided as part of the list.

=cut

use JSON  qw();
use Graph qw();

# Our main dependency graph objects
# We need two so we can check if the graph changed between dependency runs
# When the two graphs stay the same after a dependency run, we know our graph is complete
my $Agraph = Graph->new();
my $Bgraph = Graph->new();

# Accepts a list of python packages to check dependencies for
# Returns a list of python packages the arguments depend upon
# Note that this function is NOT RECURSIVE. It ONLY provides the direct dependencies.
sub get_deps {
    my $ret = [];
    my $json = JSON->new;
    foreach my $package ( @_ ) {
        my $curl = qx(curl -s "https://pypi.org/pypi/$package/json");
        my $reqs = $json->decode($curl)->{info}->{requires_dist};
        if (defined($reqs)) {
            foreach my $dep (@{ $reqs }) {
                $dep =~ s/[^a-zA-Z0-9-].*$//;
                push(@{ $ret }, $dep);
            }
        }
    }
    return $ret;
}

# @ARGV is a list of packages we need to find all dependencies for and is the roots of our dependency graph
foreach my $package (@ARGV) {
    $Agraph->add_vertex($package);
}

# A list of packages we have already gotten dependencies for so we don't check twice
my $checked = [];

# Loop until our graphs stop changing between dependency runs
while ( $Agraph ne $Bgraph ) {
    $Bgraph = $Agraph->deep_copy();

    # Check every single vertice in our graph
    my @vertlist = $Agraph->vertices;
    foreach my $package (@vertlist) {
        # If we haven't checked this package before, get it added to the graph with all of it's dependencies
        if (! grep( /^\Q$package\E$/, @{ $checked } )) {
            my $deplist = get_deps($package);
            foreach my $dep (@{ $deplist }) {
                $Agraph->add_edge($package, $dep);
            }
            # Add this package to our list of already checked packages so we don't waste time
            push(@{ $checked }, $package);
        }
    }
}

my @fulldeplist = sort $Agraph->vertices;
foreach my $package (@fulldeplist) {
    print "$package\n";
}

Мне нравится использовать эту линию для warningsно это может быть слишком ограничительным для вкусов некоторых людей:

use warnings FATAL => 'all';

ФАТАЛЬНО

Я подозреваю, что PIP Python с «виртуальной средой» можно использовать для отслеживания зависимостей для создания набора каталогов, которые затем можно просмотреть перед копированием в ваш «огороженный сад».

Это избавит ваших разработчиков от необходимости устанавливать что-либо (они автоматически получат все одобренные библиотеки), но будет работать только в том случае, если библиотеки будут перекрестно совместимы.

Основное преимущество заключается в том, что вы хотите просмотреть код библиотеки до того, как он будет скопирован в вашу автономную среду. Также может быть полезно выявить некоторые несовместимости между пакетами, если разработанные приложения должны работать вместе.

Делиться

Улучшить этот ответ

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *