Pythonic способ перебора нескольких запросов (и избежать раздувания моего кода)

#python #pandas #for-loop #nested-loops

#python #pandas #for-цикл #вложенные циклы

Вопрос:

У меня есть следующий блок кода:

 from jira import JIRA
import pandas as pd

cert_path = 'C:\cert.crt'

start_date = '2020-10-01'
end_date = '2020-10-31'

a_session = JIRA(server='https://jira.myinstance-A.com', options={'verify': cert_path}, kerberos=True)

b_session = JIRA(server='https://jira.myinstance-B.com', options={'verify': cert_path}, kerberos=True)

c_session = JIRA(server='https://jira.myinstance-C.com', options={'verify': cert_path}, kerberos=True)



query_1 = 'project = "Test Project 1" and issuetype = Incident and resolution = Resolved and updated >= {} and updated <= {}'.format(start_date, end_date)

query_2 = 'project = "Test Project 2" and issuetype = Incident and resolution = Resolved and updated >= {} and updated <= {}'.format(start_date, end_date)

query_3 = 'project = "Test Project 3" and issuetype = Defect and resolution = Resolved and releasedate >= {} and releasedate <= {}'.format(start_date, end_date)

query_4 = 'project = "Test Project 4" and issuetype = Enhancement and resolution = Done and completed >= {} and completed <= {}'.format(start_date, end_date)

block_size = 100
block_num = 0

all_issues = []
while True:
    start = block_num * block_size
    issues = a_session.search_issues(query_1, start, block_size)
    if len(issues) == 0:
        break
    block_num  = 1
    for issue in issues:
        all_issues.append(issue)

issues = pd.DataFrame()

for issue in all_issues:
    d = {
        'key' : issue.key,
        'type' : issue.fields.type,
        'creator' : issue.fields.creator,
        'resolution' : issue.fields.resolution
    }

    issues = issues.append(d, ignore_index=True)
  

Этот код работает нормально и позволяет мне:

  1. извлекать данные, связанные только с query_1 (которые подключаются к a_session )
  2. сохраните эти данные в фрейме данных Pandas

Теперь я хотел бы иметь возможность:

a. извлеките данные, связанные с query_2 (которые также подключаются к a_session ), и сохраните их в issues dataframe

б. извлеките данные, связанные с query_3 (к которым подключается b_session ), и сохраните их в issues dataframe

c. извлеките данные, связанные с query_4 (к которым подключается c_session ), и сохраните их в issues dataframe

Обратите внимание, что структура query_3 and query_4 отличается от query_1 структуры and query_2 (среди прочего, имена полей отличаются).

Я мог бы написать один ГИГАНТСКИЙ скрипт (который, вероятно, сработал бы). Но я уверен, что есть более элегантный способ приблизиться к этому (возможно, с помощью вложенного цикла).

Каков наилучший способ адаптации этого блока кода таким образом, чтобы он обрабатывал случаи a, b и c выше?

Любая помощь была бы очень признательна этому новичку в Python! Заранее спасибо!



ОБНОВЛЕНИЕ 1:

Я использовал (очень элегантное) решение, предложенное @Nick ODell . Код работает нормально, но по какой-то причине я получаю None результат. Я провел последние несколько часов, пытаясь отладить это, и моя основная теория заключается в том, что имена полей не передаются (как они есть d в исходном блоке кода, который я опубликовал).

Я попытался изменить get_all_issues функцию следующим образом:

 def get_all_issues(session, query):
    start = 0
    all_issues = []
    while True:
        issues = session.search_issues(query, start, block_size)
        if len(issues) == 0:
            # No more issues
            break
        start  = len(issues)
        for issue in issues:
            all_issues.append(issue)

    issues = pd.DataFrame

    for issue in all_issues:
        d = {
            'key' : issue.key,
            'type' : issue.fields.type,
            'creator' : issue.fields.creator,
            'resolution' : issue.fields.resolution
             }

    issues = issues.append(d, ignore_index=True)
  

But, now there is an error message saying:

 ValueError:  All objects passed were None.
  

How would we amend the get_all_issues() function such that we can nest the following for loop and pass in the name fields, as follows?

 for issue in all_issues:
    d = {
        'key' : issue.key,
        'type' : issue.fields.type,
        'creator' : issue.fields.creator,
        'resolution' : issue.fields.resolution
    }

    issues = issues.append(d, ignore_index=True)
  


ОБНОВЛЕНИЕ 2:

Вместо использования pd.json_normalize(issues) я использовал pd.DataFrame(проблемы) и добавил словарь имен полей. Следующий код работает **, потому что все поля существуют в a_session , b_session , и c_session **:

 def get_all_issues(session, query):

    block_size = 50
    block_num = 0
    
    start = 0
    all_issues = []
    while True:
        issues = session.search_issues(query, start, block_size)
        if len(issues) == 0:
            # No more issues
            break
        start  = len(issues)
        for issue in issues:
            all_issues.append(issue)

    issues = pd.DataFrame(issues)

    for issue in all_issues:
        d = {
            'key' : issue.key,
            'type' : issue.fields.type,
            'creator' : issue.fields.creator,
            'resolution' : issue.fields.resolution
             }

        issues = issues.append(d, ignore_index=True)

    return issues
  

Затем я добавил 3 новых пользовательских поля в словарь:

     for issue in all_issues:
        d = {
            'key' : issue.key,
            'type' : issue.fields.type,
            'creator' : issue.fields.creator,
            'resolution' : issue.fields.resolution,
            'system_change' : issue.fields.customfield_123,
            'system_resources' : issue.fields.customfield_456,
            'system_backup' : issue.fields.customfield_789
             }
  

Пользовательское поле 123 существует в a_session и b_session , но не в c_session . Пользовательское поле 456 существует только в c_session . И, пользовательское поле 789 существует в b_session и c_session .

Это приводит к следующей ошибке: AttributeError: type object 'PropertyHolder' has no attribute 'customfield_123' .

Может ли кто-нибудь предложить элегантное решение для решения этой проблемы? (т. Е. Возможность иметь словарь с любым количеством полей, а код «понимает», какие поля относятся к данному сеансу) Спасибо!

Ответ №1:

Вот как я бы подошел к вашей проблеме. У меня нет экземпляра Jira для тестирования, поэтому этот код не протестирован.

Сначала определите функцию для извлечения всех проблем из данного сеанса для данного запроса:

 def get_all_issues(session, query):
    start = 0
    all_issues = []
    while True:
        issues = session.search_issues(query, start, block_size)
        if len(issues) == 0:
            # No more issues
            break
        start  = len(issues)
        for issue in issues:
            all_issues.append(issue)
    # Flatten JSON
    # This idea is from
    # https://levelup.gitconnected.com/jira-api-with-python-and-pandas-c1226fd41219
    return pd.json_normalize(issues)
  

Далее я бы составил список запросов и соответствующий сервер.

 queries = [
    (a_session, query_1),
    (a_session, query_2),
    (b_session, query_3),
    (c_session, query_4),
]
  

Затем я бы перебирал каждую пару сеансов и запросов, вызывая функцию, которую я только что определил, и сохранял фрейм данных, который я получаю каждый раз.

 dataframes = []

for session, query in queries:
    dataframe = get_all_issues(session, query)
    dataframes.append(dataframe)
  

Теперь имена полей для каждого из этих фреймов данных не будут одинаковыми. Однако Pandas на самом деле терпим к этому, и если столбец присутствует в одном наборе данных, но отсутствует в другом, Pandas заполнит недостающий столбец значениями NaN. Итак, просто объедините строки из каждого фрейма данных вместе:

 all = pd.concat(dataframes)
  

… и это все!

Комментарии:

1. По какой-либо причине использование подхода, предложенного @Nick ODell, приводит к пустому набору. Моя основная теория заключается в том, что имена полей не передаются (отсюда None и результат). Как бы мы использовали pd.DataFrame вместо pd.json_normalize и передавали имена полей (как я сделал выше)? Заранее спасибо!

2. @equanimity Не уверен. На самом деле я не могу протестировать этот код, поэтому мне трудно сказать, что может быть не так. Не могли бы вы дать мне копию содержимого issues переменной? например print(repr(issues)) (при условии, что вам разрешено ее публиковать.)

3. когда я print(repr(issues)) , я получаю: Name Error: name 'issues' is not defined . У меня есть работающий код, но без использования pd.json_normalize(issues) . Теперь проблема, с которой я сталкиваюсь, заключается в том, что некоторые поля существуют в определенных сеансах, но не в других. Я опубликую еще одно обновление выше.

4. Я вижу, что вы изменили issues all_issues в своей версии кода. Поэтому вы можете опубликовать содержимое print(repr(all_issues)) ?

5. когда я печатаю (repr(all_issues)), я получаю: Ошибка имени: имя ‘all_issues’ не определено.

Ответ №2:

Вместо того, чтобы создавать отдельные переменные для запросов, поместите их в список:

 queries = [
    'query 1 here...',
    'query 2 here...',
]
  

А затем выполните итерацию по списку:

 for query in queries:
    process(query)
  

Комментарии:

1. Как мне справиться с тем фактом, что 3 из 4 запросов ссылаются на разные <x>_session переменные?