Рубрики
Без рубрики

Публикуйте в DEV автоматически с помощью GitHub

Dev – отличное сообщество для контента разработчика. Если у вас есть статьи, которые вы не хотите, жили все в … Tagged с Python, GitHub, Devapi.

Dev – отличное сообщество для контента разработчика. Если у вас есть статьи, которые вы не хотите жить сразу, как вы можете автоматически публиковать расписание? В этой статье давайте использовать действия GitHub, чтобы вывести ваш контент в Интернете на ваш график.

Действия GitHub – это способ запустить код на серверах GitHub. Сервис фактически является услугой непрерывной интеграции (CI) от GitHub. Чтобы использовать действия GitHub, мы создаем Рабочий процесс в репозитории GIT. Этот рабочий процесс содержит все шаги для запуска кода, о котором мы заботимся.

Чтобы опубликовать в Dev, мы можем:

  1. Используйте рабочий процесс …
  2. Чтобы прочитать график публикации, мы создаем …
  3. И проверьте текущее время …
  4. И опубликовать статью Dev, если дата публикации в прошлом.

Прежде чем мы доберемся до рабочего процесса, давайте посмотрим на код, который будет работать.

Создание графика

Первый шаг в этом процессе – создать график публикации, который может прочитать наш сценарий. Минимально, для этого требуется два типа данных из списка статей:

  1. Удостоверение личности статьи
  2. DateTime, чтобы опубликовать в

Итак, где мы получаем эти вещи?

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

На вашей неопубликованной странице статьи просмотрите источник страницы. Как в Firefox, так и в Chrome вы можете добраться до этого, выбрав «источник страницы просмотра», когда вы щелкните правой кнопкой мыши где -то на странице.

На странице источника поиск Статья-ID с Ctrl+f (или cmd+f на macos). Вы должны найти числовое значение, такое как 168727 Анкет

Теперь у нас есть то, что нам нужно, чтобы построить график. Мы будем использовать формат JSON для расписания, потому что Python может легко прочитать этот формат. Вы можете назвать файл все, что вы хотите, но я позвонил в свой devto-schedule.json и сохранил его в корне моего репозитория.

Нам также нужен DateTime, когда мы хотим опубликовать статью. Я использовал стандарт ISO-8601, чтобы сделать даты, которые мы можем сравнить с текущим временем. Чтобы продолжить наш пример, если я хочу опубликовать статью 22 октября 2019 года в 2 часа дня в восточное время в США, то расписание выглядит так:

[{
    "id": 168727,
    "publish_date": "2019-10-22T18:00:00+00:00",
    "title": "Ep 11"
},
{
    "id": 167526,
    "publish_date": "2019-10-15T18:00:00+00:00",
    "title": "Ep 10"
}]

У нас есть удостоверение личности как id и дата -время как publish_date Анкет publish_date использует UTC для хранения времени. Часовые пояса трудные, поэтому я всегда рекомендую хранить ваши даты в UTC.

Я также включил заглавие Поэтому я знаю, к какой статье ссылается объект в будущем. Название Не нужно, но это полезно для отслеживания статей.

Обратите внимание, что это список JSON. Я включил дополнительную статью, чтобы вы могли увидеть, как это будет выглядеть, если сценарий проверит несколько статей (что является своего рода точкой).

Публикация в Dev

С расписанием, мы готовы посмотреть на код, который выполнит всю работу. Я начну с показывать весь сценарий, и мы разбим его на каждый раздел.

Во -первых, основной файл, publish.py :

"""Publish scheduled articles to DEV."""
import datetime
import json
import logging
import sys

from dateutil.parser import parse

from devto import DEVGateway

logger = logging.getLogger(__name__)


def publish_scheduled_articles():
    dev_gateway = DEVGateway()
    published_articles = dev_gateway.get_published_articles()
    published_article_ids = set([article["id"]
                                for article in published_articles])

    scheduled_articles = get_scheduled_articles()
    for scheduled_article in scheduled_articles:
        if should_publish(scheduled_article, published_article_ids):
            article_id = scheduled_article["id"]
            logger.info(f"Publishing article with id: {article_id}")
            dev_gateway.publish(article_id)


def get_scheduled_articles():
    """Get the schedule."""
    logger.info("Read schedule.")
    with open("devto-schedule.json", "r") as f:
        return json.load(f)


def should_publish(article, published_article_ids):
    """Check if the article should be published.

    This depends on if the date is in the past
    and if the article is already published.
    """
    publish_date = parse(article["publish_date"])
    now = datetime.datetime.now(datetime.timezone.utc)
    return publish_date < now and article["id"] not in published_article_ids


if __name__ == "__main__":
    logging.basicConfig(
        format="%(levelname)s: %(message)s",
        level=logging.INFO, stream=sys.stdout
    )
    publish_scheduled_articles()

А затем подтверждающий файл dev, devto.py :

import os

import requests


class DEVGateway:
    url = "https://dev.to/api"

    def __init__(self):
        self.session = requests.Session()
        self.session.headers = {"api-key": os.environ["DEV_API_KEY"]}

    def get_published_articles(self):
        """Get all the published articles."""
        response = self.session.get(
            f"{self.url}/articles/me/published", params={"per_page": 1000}
        )
        response.raise_for_status()
        return response.json()

    def publish(self, article_id):
        """Publish the article."""
        data = {"article": {"published": True}}
        response = self.session.put(f"{self.url}/articles/{article_id}",
                                    json=data)
        response.raise_for_status()

Вверху вниз вид

Давайте начнем наш экзамен с точки входа в нижней части publish.py файл.

if __name__ == "__main__":
    logging.basicConfig(
        format="%(levelname)s: %(message)s",
        level=logging.INFO, stream=sys.stdout
    )
    publish_scheduled_articles()

Этот код следует за популярным шаблоном Python, чтобы проверить __name__ Атрибут, чтобы увидеть, есть ли это __main__ Анкет Это будет правдой, когда код работает с Python3 publish.py Анкет

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

Следующий звонок – publish_scheduled_articles Анкет Я мог бы назвать это Главный , но я решил дать ему описательное имя. Мы продолжим работать сверху вниз, чтобы увидеть, что publish_scheduled_articles делает.

def publish_scheduled_articles():
    dev_gateway = DEVGateway()
    published_articles = dev_gateway.get_published_articles()
    published_article_ids = set([article["id"]
                                for article in published_articles])

    scheduled_articles = get_scheduled_articles()
    for scheduled_article in scheduled_articles:
        if should_publish(scheduled_article, published_article_ids):
            article_id = scheduled_article["id"]
            logger.info(f"Publishing article with id: {article_id}")
            dev_gateway.publish(article_id)

Общий поток этой функции можно описать как:

  1. Получите то, что уже опубликовано Dev.
  2. Получите то, что запланировано для публикации.
  3. Публикуйте запланированную статью, если она должна быть опубликована.

Чтобы увидеть, что опубликовано на Dev, нам нужно выкопать следующий слой и посмотреть, как мы общаемся с Dev.

Шлюз в разработчик

У Dev есть JSON API, с которым разработчики могут взаимодействовать. Чтобы сохранить чистый слой абстракции, я использовал Шаблон шлюза Чтобы скрыть детали того, как происходит это общение API. Делая это, скрипт взаимодействует с интерфейсом высокого уровня (например, get_publised_articles ), а не возиться с Запросы модуль напрямую.

class DEVGateway:
    url = "https://dev.to/api"

    def __init__(self):
        self.session = requests.Session()
        self.session.headers = {"api-key": os.environ["DEV_API_KEY"]}

    def get_published_articles(self):
        """Get all the published articles."""
        response = self.session.get(
            f"{self.url}/articles/me/published", params={"per_page": 1000}
        )
        response.raise_for_status()
        return response.json()

Когда мы создаем экземпляр шлюза, мы начинаем Запросы. Сессия Это настроено с помощью подходящего ключа API. Мы поговорим о том, как GitHub получает доступ к этому ключу API позже, но вам нужно будет создать ключ на веб -сайте DEV. Вы можете найти это в разделе «Настройки/учетной записи».

С доступным ключом мы можем спросить Дев о наших собственных вещах. Я использовал /статьи/me/опубликовано API, чтобы получить список опубликованных статей. Опубликованные статьи обязаны проверить, что сценарий идентификатор (что означает, что мы можем безопасно запускать его неоднократно, не страдая от странных побочных эффектов).

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

    def publish(self, article_id):
        """Publish the article."""
        data = {"article": {"published": True}}
        response = self.session.put(f"{self.url}/articles/{article_id}",
                                    json=data)
        response.raise_for_status()

Когда скрипт подтверждает, что статья готова к публикации, мы меняем состояние статьи, установив Опубликовано атрибут Верно Анкет Мы делаем эту модификацию состояния с помощью HTTP.

Другой кусочек кода, который стоит обсудить, это response.raise_for_status () Анкет По правде говоря, я был немного ленив, когда писал эту часть сценария. Raise_for_status поднимет исключение всякий раз, когда ответ не является 200 ok Анкет Я вкладываю это в качестве защитника на случай, если что -то об API -изменениях в будущем. Часть, которой не хватает (и лень, которую я упомянул), – это обработка этих исключений.

Когда произойдет сбой запроса, этот сценарий потерпит неудачу и выбросит след. С моей точки зрения, это нормально Анкет Поскольку это будет частью работы CI, единственным человеком, увидевшим Traceback, будет я, и я захочу любые данные, которые отчеты Traceback отчеты. Это один из тех сценариев, где инструмент разработчика может иметь более четкое преимущество, чем код производственного калибра, и быть совершенно разумным.

Проверка графика

Теперь мы видели, как работает ворота. Мы можем перенести наше внимание на график.

def get_scheduled_articles():
    """Get the schedule."""
    logger.info("Read schedule.")
    with open("devto-schedule.json", "r") as f:
        return json.load(f)

Это может быть самый ванильный код во всем сценарии. Этот код считывает файл JSON, который имеет наше расписание статей. Ручка файла передается загрузка Чтобы создать список статей с будущей датой публикации.

    for scheduled_article in scheduled_articles:
        if should_publish(scheduled_article, published_article_ids):
            article_id = scheduled_article["id"]
            logger.info(f"Publishing article with id: {article_id}")
            dev_gateway.publish(article_id)

Вот сердце сценария. Мы:

  1. Пройти через график.
  2. Проверьте, следует ли нам опубликовать статью.
  3. Публикуйте статью, если мы должны.

Почему я ранее взял список опубликованных статей? Теперь мы получаем наш ответ при осмотре должен a_publish который нуждается в опубликованных статьях.

def should_publish(article, published_article_ids):
    """Check if the article should be published.

    This depends on if the date is in the past
    and if the article is already published.
    """
    publish_date = parse(article["publish_date"])
    now = datetime.datetime.now(datetime.timezone.utc)
    return publish_date < now and article["id"] not in published_article_ids

На самом деле есть два фактора, которые следует учитывать при проверке запланированной статьи.

  • Отсюда ли дата публикации статьи в прошлом?
  • Сценарий уже опубликовал статью?

Этот второй вопрос менее очевиден, пока вы не увидите, что эта работа CI работает регулярно. Поскольку мы настроим сценарий для работы каждый час, мы хотим, чтобы мы не пытались публиковать статью несколько раз. должен a_publish Охранники против нескольких публикаций, проверив, если статья id находится в списке опубликованных идентификаторов.

Теперь вы видели сверху вниз, как этот сценарий может опубликовать статьи Dev. Наш следующий шаг – подключить сценарий к действиям GitHub.

Работа на github actions

Действия GitHub работает, определяя рабочие процессы в .github/Workflows каталог в вашем репозитории. Файл рабочего процесса – это файл YAML с командами для выполнения работы.

Мы называем наш рабочий процесс .github/workflows/devto-publisher.yaml , но вы можете назвать это все, что хотите, пока расширение заканчивается .yaml или .yml .

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

name: DEV Publisher
on:
  schedule:
    - cron: '0 * * * *'

jobs:
  publish:
    name: Publish to DEV
    runs-on: ubuntu-latest
    steps:
      - name: Get the code
        uses: actions/checkout@v1
        with:
          fetch-depth: 1
      - name: Set up Python
        uses: actions/setup-python@v1
        with:
          python-version: '3.x'
          architecture: 'x64'
      - name: Install packages
        run: pip install -r requirements/pub.txt
      - name: Run publisher
        run: python bin/publish.py
        env:
          DEV_API_KEY: ${{ secrets.DEV_API_KEY }}

Вы можете при желании дать свой рабочий процесс A имя Анкет Мне нравится добавлять имя, потому что мне более дружелюбно читать в журналах над именем файла. Если имена файлов подходят вам лучше, пропустите это!

on:
  schedule:
    - cron: '0 * * * *'

на Раздел имеет решающее значение и определяет, когда произойдет ваше действие. Есть множество различных событий, которые вы можете использовать. Для этого рабочего процесса нам нужен Расписание курок. Все кровавые подробности об этом находятся в Документация Анкет

Если вы знакомы с Cron, этот синтаксис вас не шокирует. Пользователи, не являющиеся криками, могут задаться вопросом, что, черт возьми, происходит. Документы объясняют весь синтаксис, но эта конкретная строка 0 * * * * означает «беги на нулевой минуте каждого часа каждого дня. «Другими словами, – бегите в начале каждого часа».

jobs:
  publish:
    name: Publish to DEV
    runs-on: ubuntu-latest

Все рабочие процессы нуждаются в хотя бы одной работы. Без работы нечего делать. Я назвал эту работу публиковать , учитывая это дружелюбное имя и определило, какую операционную систему нужно работать (а именно, Ubuntu).

    steps:
      - name: Get the code
        uses: actions/checkout@v1
        with:
          fetch-depth: 1
      - name: Set up Python
        uses: actions/setup-python@v1
        with:
          python-version: '3.x'
          architecture: 'x64'
      - name: Install packages
        run: pip install -r requirements/pub.txt
      - name: Run publisher
        run: python bin/publish.py
        env:
          DEV_API_KEY: ${{ secrets.DEV_API_KEY }}

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

  1. Получите код
  2. Установите Python
  3. Установите пакеты
  4. Запустить издатель

Первые два шага в процессе вытягивают из заранее построенных действий, разработанных GitHub. GitHub дает нам эти действия, чтобы сэкономить время от выяснения этих вещей. Например, без Действия/проверка@v1 Действие, как бы мы получили местный клон? Это было бы действительно сложно.

Третий шаг начинает использовать конфигурацию, специфичную для моей работы. Для того, чтобы сценарий запустил, ему нужны установленные зависимости, чтобы мы могли импортировать Запросы и DateUtil Анкет Я определил Требования/pub.txt Файл, который включает в себя все необходимые зависимости.

Вот как выглядит файл требований:

#
# This file is autogenerated by pip-compile
# To update, run:
#
#    pip-compile requirements/pub.in
#
certifi==2019.9.11        # via requests
chardet==3.0.4            # via requests
idna==2.8                 # via requests
python-dateutil==2.8.0
requests==2.22.0
six==1.12.0               # via python-dateutil
urllib3==1.25.3           # via requests

Наконец, мы добираемся до шага, который управляет издателем.

      - name: Run publisher
        run: python bin/publish.py
        env:
          DEV_API_KEY: ${{ secrets.DEV_API_KEY }}

Это довольно ваниль, за исключением Dev_api_key линия. Посмотри на Devgateway .__ init__ метод Метод тянет из Os.environ Чтобы получить ключ API. В нашем файле YAML мы проводим Dev_api_key переменная среды к секреты. Dev_api_key . Нам нужно рассказать GitHub о нашем ключе API.

В ваших настройках репозитория GitHub мы можем определить секреты. Эти секреты доступны для файлов рабочих процессов, как мы видим на шаге «Run Publisher». Создать секрет по имени Dev_api_key который содержит ваш ключ Dev API.

Теперь все на месте, чтобы запустить издатель. Этот рабочий процесс будет выполняться каждый час и запустить публиковать работа. Это задание настроит среду и выполнит publish.py сценарий Сценарий позвонит Dev и опубликует любые статьи в вашем расписании, которые должны быть опубликованы.

Чему мы научились?

В этой статье я попытался показать несколько разных тем.

  1. Как общаться с Dev API
  2. Как создать файл, который компьютер может читать и разрабатывать (т.е. расписание JSON)
  3. Как использовать действия GitHub для запуска некоторого кода для вашего репозитория.

Хотите увидеть весь этот код в контексте? Репозиторий Для этого сайта есть все файлы, которые мы рассмотрели.

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

Если у вас есть вопросы или понравились этой статье, пожалуйста, не стесняйтесь написать мне в Twitter в @mblayman или поделиться, если другие тоже могут быть заинтересованы.

Эта статья впервые появилась на mattlayman.com Анкет

Оригинал: “https://dev.to/mblayman/publish-to-dev-automatically-with-github-actions-71n”