Установка python 2.7 в Oracle Linux 6

Дефолтным пайтоном в системе Oracle Linux 6 является версия 2.6. Проблема в том, что эта версия пайтона уже очень устарела и не поддерживается многими библиотеками и даже pip (который надо устанавливать из репозитория Epel) не хочет работать без сильного колдунства – выдаёт ошибку “ssl_.py:90: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail.”

Потому, если требуется использование модулей, устанавливаемых с помощью pip, то нужно установить более новый пайтон.

Его можно установить из репозитория Software Collection Library для Oracle Linux 6.10. Похожая процедура и для других RHEL 6 систем вроде CentOS 6, но там названия репозиториев и пути могут отличаться.

Выполняем команду:

yum repolist all

И ищем искомый репозиторий (по-умолчанию он выключен – disabled):

Чтобы его включить, выполняем команду:

yum-config-manager --enable public_ol6_software_collections

После чего устанавливаем нужный пайтон и утилиту для управления софтверными коллекциями:

yum install python27 scl-utils

Как зависимости сразу должны подтянуться pip и прочие необходимые пакеты.

Теперь включим пайтон 2.7 для текущей сессии:

scl enable python27 bash

Эта команда запускает новый баш, где по-умолчанию используется 2.7 пайтон вместо 2.6. И можно выполнять скрипты или команды в интерпретаторе. К сожалению, сделать 2.7й системным проблематично.

Чтобы скрипты, запущенные по cron, работали с 2.7 необходимо сделать нижеизложенное.

В задаче крона нужно прописывать путь к интерпретатору 2.7, а не просто к пайтону:

updatedb
locate python2.7

В моём случае это было /opt/rh/python27/root/usr/bin/python

Далее необходимо обновить путь загрузки библиотек, иначе пайтон 2.7, запускаясь по крону, будет выдавать ошибку “ImportError: libpython2.7.so.1.0: cannot open shared object file: No such file or directory”:

locate libpython2.7.so.1.0

В моём случае папка с библотеками пайтона: /opt/rh/python27/root/usr/lib64

Затем добавить этот путь строчкой в этот файл:

nano /etc/ld.so.conf

И выполнить команду:

ldconfig

Всё, теперь скрипты, запущенные по крон, будут работать с пайтоном 2.7. Ну и конечно же в скрипте необходимо указывать абсолютные пути к файлам или командам (например, если вызываете по subprocess).

Также не советую обновлять pip, если он попросит. Иначе вполне вероятно придётся откатывать pip примерно как описано в [6].

Пример задачи, которая выполняется раз в месяц и логирует вывод консоли вместе с ошибками, если таковые будут, в файл job.log:

0 7 1 * * su user -c "/opt/rh/python27/root/usr/bin/python /home/user/project/script.py" > /home/user/project/job.log 2>&1

Ссылки:

  1. https://stackoverflow.com/questions/20842732/libpython2-7-so-1-0-cannot-open-shared-object-file-no-such-file-or-directory
  2. https://yum.oracle.com/oracle-linux-python.html
  3. https://stackoverflow.com/questions/51646558/unable-to-install-python-packages-using-pip-in-ubuntu-linux-insecureplatformwar/54582701#54582701
  4. https://stackoverflow.com/questions/29134512/insecureplatformwarning-a-true-sslcontext-object-is-not-available-this-prevent
  5. https://access.redhat.com/documentation/en-us/red_hat_software_collections/3/html/packaging_guide/sect-enabling_the_software_collection
  6. https://iamsysadmin.net/2019/01/17/how-to-roll-back-pip/

Python, как сконвертировать файл .csv в .xlsx

Нижеприведённый код для конвертации csv -> xlsx работает на python 2.7. На 2.6 мне не удалось установить openpyxl с помощью pip install openpyxl, а на 3й версии io.open нужно заменить на просто open:

import io
from openpyxl import Workbook
# Convert to XLSX
csvfile = 'path_to_file.csv'
xlsxfile = 'path_to_file.xlsx'
workbook = Workbook(xlsxfile)
worksheet = workbook.add_worksheet()
with io.open(csvfile, 'rt', encoding='utf8') as f:
    reader = csv.reader(f)
    for r, row in enumerate(reader):
        for c, col in enumerate(row):
            worksheet.write(r, c, col)
workbook.close()

Ссылки:

  1. https://stackoverflow.com/questions/17684610/python-convert-csv-to-xlsx
  2. https://openpyxl.readthedocs.io/en/stable/index.html

Python, как приложить .xlsx файл к письму e-mail.

В принципе, таким способом можно приложить любой файл, в котором хранится не текстовая информация.

Ниже пример скрипта, отсылающего .xlsx файл:

import smtplib
from email.utils import formatdate
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders

dst_emails = 'recipient1@example.com, recipient2@example.com'
xlsxfile = '/home/user/project1/example.xlsx'

# E-mail block
me = "sender@example.com"
# Filling e-mail info
msg = MIMEMultipart()
msg['Subject'] = "Xlsx file"
msg['From'] = me
msg['To'] = dst_emails
to = msg['To'].split()
msg['Date'] = formatdate(localtime=True)
html = """\
        <html>
          <head></head>
          <body>
            <p><br>
                <strong>This is a test e-mail</strong><br>
                <br>
                Place body text here

                Best regards,<br>
                Example user<br>
            </p>
          </body>
        </html>
        """
# Record the MIME type
letter = MIMEText(html, 'html')
# Attach letter into message container.
msg.attach(letter)
# Attach xlsx file
part = MIMEBase('application', "octet-stream")
part.set_payload(open(xlsxfile, "rb").read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="%s"'%(xlsxfile))
msg.attach(part)
# Send the message via example.com SMTP server.
s = smtplib.SMTP('mail.example.com')
# sendmail function takes 3 arguments: sender's address, recipient's address
# and message to send - here it is sent as one string.
s.sendmail(me, to, msg.as_string())
s.quit()

Ссылки:

  1. https://stackoverflow.com/questions/25346001/add-excel-file-attachment-when-sending-python-email

Не работает Ctrl+C/Ctrl+V в PyCharm или другой среде на основе Intellij IDEA

Я ставил pyCharm очень давно и вот, когда пришла пора его использовать, я понял, что у меня не работают быстрые комбинации копирования и вставки Ctrl+C и Ctrl+V. Это просто катастрофа) но интернет подсказал решение:

1. Нажимаем Ctrl+Alt+S:

2. В поиске вверху слева вводит vim и отключаем плагин или меняем комбинации кнопок везде с vim на IDE, где найдёт поиск:

Всё, конец мучениям)

Ссылки:

  1. https://intellij-support.jetbrains.com/hc/en-us/community/posts/206835985–SOLVED-Ctrl-V-and-Ctrl-C-doesn-t-work-in-intellij-IDEA-14-0-2

Предыдущий рабочий день в python

Чтобы получить предыдущий рабочий день в python, делаем нехитрые преобразования:

import datetime, time
lastBusDay = datetime.datetime.today()
shift = datetime.timedelta(max(1,(lastBusDay.weekday() + 6) % 7 - 3))
lastBusDay = lastBusDay - shift

Т. е. мы получаем, например, вторник в среду. А в субботу, вск и Пн мы получим пятницу. Далее можно преобразовать дату в нужный нам формат и пользоваться:

date = lastBusDay.strftime('%Y%m%d')

Python скриптинг: как зайти на FTP или FTPS

Бывает нужно что-то скачать/загрузить на удалённый сервер FTP или FTPS (не путать с SFTP) в скрипте на пайтоне.

Подключаем необходимые библиотеки:

from ftplib import FTP_TLS
from ftplib import FTP

Задаём параметры подключения к FTP хосту:

host = "some_ftp.com"
port = 21
username = "username"
password = "password"

Подключаемся к обычному FTP (в примере скачиваем файл на диск):

ftp = FTP(host)
ftp.login(username, password)
try:
    handle = open('/path_to_local_file/filename', 'wb')
    ftp.retrbinary('RETR /path_to_remote_file/filename, handle.write)
    handle.close()
except:
    pass
ftp.quit()

Все команда нужно указывать по спецификации FTP протокола, например такой.

Осовные команды:
RETR – получить файл с FTP в переменную или по хендлеру записать в локальный файл;
LIST – показать список файлов/директорий в указанной или текущей по-умолчанию;
NLST – то же, что и LIST, но возвращает только имена файлов/директорий;
STOR – загрузить с презаписью файл на FTP-сервер;
и другие

Подключение к FTPS выполняется схожим образом:

try:
    handle = open('/path_to_local_file/filename', 'wb')
    ftps = FTP_TLS(host)
    ftps.login(username, password)
    ftps.prot_p()
    ftps.retrbinary('RETR /path_to_remote_file/filename', handle.write)
    ftps.quit()
    handle.close()
except:
    pass

Для работы FTP_TLS нужен пайтон 2.7+, если у вас только 2.6, то придётся сделать одно из следующего:
1. Доустановить пайтон 2.7, например как описано здесь для Oracle Linux 6:

Редактируем /etc/yum.repos.d/public-yum-ol6.repo и убеждаемся, что enabled=1 в следующем параграфе:

[ol6_software_collections]
name=Software Collection Library release 3.0 packages for Oracle Linux 6 (x86_64)
baseurl=http://yum.oracle.com/repo/OracleLinux/OL6/SoftwareCollections/x86_64/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-oracle
gpgcheck=1
enabled=1

Далее выполняем команды от рута/судо:

yum install scl-utils
yum install python27
source /opt/rh/python27/enable
python --version

Если при запуске скрипта по cron (а скорее всего так и будет) выбивает ошибку вида:

error while loading shared libraries: libpython2.7.so.1.0: cannot open shared object file: No such file or directory

То необходимо добавить путь к библиотеке в /etc/ld.so.conf и запустить команду ldconfig, которая сама всё пропишет.

или

2. Cкачать дистрибутив пайтона 2.7 отсюда и вытащить фал ftplib.py и положить его в папку со скриптом (если скрипт запускается по cron, то это может не сработать):

wget http://www.python.org/ftp/python/2.7.1/Python-2.7.1.tgz
tar -zxvf Python-2.7.1.tgz Python-2.7.1/Lib/ftplib.py
cp Python-2.7.1/Lib/ftplib.py /path_to_script/

или

3. Заходить на FTPS вызывая bash команды например lftp с помощью модуля subprocess.

Ссылки:

  1. https://docs.python.org/3/library/ftplib.html
  2. https://stackoverflow.com/questions/11573817/how-to-download-a-file-via-ftp-with-python-ftplib
  3. https://stackoverflow.com/questions/20842732/libpython2-7-so-1-0-cannot-open-shared-object-file-no-such-file-or-directory
  4. https://docs.cloudera.com/documentation/enterprise/6/6.0/topics/install_python_27.html

Использование библиотеки Mechanize в Python для авторизации, отправки веб-форм и скачивания файлов

Для языка Python существует довольно много библиотек для работы с веб-ресурсами. Это и стандартная urllib / urllib2 и сторонние – mechanize, Twill, Request, Client Form.

В данной статье я рассмотрю работу с библиотекой mechanize, т. к. именно ее инструменты помогли мне “победить” задачу автоматизации скачивания материала с одного веб-сайта.

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

Проблема заключалась в следующих моментах:

  • для скачивания нужно было авторизоваться на сайте. Причем, авторизация с помощью forms, а не Basic HTTP. 
  • необходимо было хранить кукисы, дабы авторизация не пропала. 
  • путем манипуляций со списками (SelectControl) выбирались параметры скачивания. 
  • далее нужно было получить ссылку, содержащую уникальный токен, не позволявший сачать файл по одной и той же ссылке боле 1 раза просто поменяв параметры GET запроса. 
  • и авторизация и скачивание и всё остальное нехорошие программисты сделали на одной html-странице в одной и той же форме, только с разными кнопками типа Submit.

Итак, начнемс. Вначале импорт необходимых библиотек. Mechanize не идет в стандартной поставке с Python  и его необходимо установить или с помозью PiPy или вручную, как написано на официальном сайте (python setup.py install).

import cookielib, shutil, os
from mechanize import Browser

Далее, создаем объекты: основной Browser и объект для хранения кукисов, который прикрепляем к нашему Browser. Объект Browser представляет собой контейнер по смыслу похожий на вкладку настоящего браузера. Т. е. в пределах его можно оществлять навигацию по web-сайту, авторизацию, хранить кукисы и т. д.

br = Browser()
# Create cookie jar and attach it to Browser
cj = cookielib.LWPCookieJar()
br.set_cookiejar(cj)

Затем, добавляем html хэдэры и открываем URL нужной странички в объекте Browser.

# Add some headers
br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')]
# Open url in Browser instance
br.open('http://www.example.com/download.aspx')

Далее выбираем форму, в которую будем вносить данные. nr=0 – индекс формы по счету в коде веб-страницы (0,1,2 …) И вводим данные в поля для авторизации в формате (для элементов типа TextControl):

br[“id_html_элемента”] = “Значение”

id элементов и возможные значения можно посмотреть двумя способами:

  • в браузере Google Chrome нажать правой кнопкой мыши на нужном элементе и выбрать Просмотр исходного кода элемента, затем правой кнопкой на выделенном элементе и Copy as HTML. После этого, вставить текст в любой текстовый редактор. 
  • выполнив код в интерпретаторе python или в отдельном скрипте:

import cookielib
from mechanize import ParseResponse, urlopen
response = urlopen("http://www.eoddata.com/download.aspx")
forms = ParseResponse(response, backwards_compat=False)
form = forms[0]
print form

Вы получите список всех элементов формы и их возможных значений в формате:

<Тип(id=список_значений)>

Вроде такого:

<aspnetForm POST http://www.example.com/download.aspx application/x-www-form-urlencoded
  <SelectControl(ctl00$DataFormat=[17, 2, 36, 16, 27, 28, 33, 32, 8, 24, 35, *9, 1, 39, 11, 14, 6, 5, 4, 12, 3, 26, 15])>
  <SubmitControl(ctl00$Download=Download) (readonly)>
  <TextControl(ctl00$txtEmail=)>
  <PasswordControl(ctl00$txtPassword=)>
  <CheckboxControl(ctl00$Remember=[on])>
  <SubmitControl(ctl00$btnLogin=Login) (readonly)>>

Итак, пример кода основного скрипта для авторизации:

# Select form for modification and "enter" login info
br.select_form(nr=0)
br["ctl00$txtEmail"] = "User"
br["ctl00$txtPassword"] = "Secret"
# Submit form
br.submit(nr=3)

Обратите внимание на строку

br.submit(nr=3)

В скобках указан номер кноки типа Submit по порядку её нахождения в html-коде страницы. Это важно, если в форме есть несколько кнопок типа Submit, но выполняющих различные функции (как в нашем случае). Нумерация 0,1,2…

Далее, выбираем необходимые данные в списках значений, например (значения посмотреть описанными выше методами):

br.select_form(nr=0)
# Select Options
br["ctl00$cboSomething"] = ["EXAMPLE",]
br["ctl00$cboDataFormat"] = ["11",]
# Update page
br.submit(nr=1)

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

Скачиваем файл (считая, что искомый URL на файл находится в переменной download_url):

local_path = "/home/user"
filename = "very_important.txt"
f = br.retrieve(download_url)[0]
# Copy saved file to normal location
shutil.copy2(f,os.path.join(local_path, filename))

В переменной f мы получаем путь к скачанному файлу, обычно это какое-то случайное имя в директории /tmp. Далее перемещаем его в нужное нам место.

Ссылки:

  • http://stockrt.github.io/p/emulating-a-browser-in-python-with-mechanize/
  • http://www.voidspace.org.uk/python/articles/authentication.shtml
  • http://mozgovipc.blogspot.com/2012/06/python-http-basic-authentication-with.html
  • http://docs.python-requests.org/en/latest/
  • http://stackoverflow.com/questions/9541677/urllib2-post-request
  • http://docs.python.org/2/howto/urllib2.html
  • http://stackoverflow.com/questions/9288662/need-more-mechanize-documentation-python
  • http://www.pythonforbeginners.com/cheatsheet/python-mechanize-cheat-sheet
  • http://blog.spritecloud.com/2010/01/posting-forms-with-python/
http://geckich.blogspot.com/

Определение и вызов fabric task в одном Python скрипте

Библиотека fabric для Python является хорошим средством для автоматизации действий (админимстрирования, deployment и т. д.) для инфраструктуры серверов под управлением ОС Linux. Библиотека использует протокол open ssh для выполнения команд и перемещения данных.

Подробнее о библиотеке можно прочитать на официальном сайте или здесь.

Бывают ситуации, когда необходимо в одном скрипте и объявить таск из библиотеки fabric и выполнить его.

Пример структуры такого скрипта:

#!/usr/bin/env python
# Script dependencies: python-devel, fabric (https://github.com/fabric/fabric/archive/master.zip)
# Usage: python script.py
import fabric
from fabric.api import *
# Define ssh-like address user@server_hostname
remote_host = user@server

def some_fab_task():
# Define fabric task here
# hide('everything') helps to execute task with minimum output to console
with settings(hide('everything')):
try:
....
except:
....

# Main function
if __name__ == '__main__':
# Call fabric task from here
fabric.tasks.execute(some_fab_task, hosts=[remote_host])

Для того, чтобы скрипт выполнялся “тихо” – без запросов на ввод пароля для удаленного пользователя, необходимо организовать доступ на удаленноый сервер с помощью ssh-ключей,  например как я описывал здесь.

Update:

Для передачи аргументов в скрипт, указываем их перед hosts=.

fabric.tasks.execute(some_fab_task, arg1, arg2, hosts=[remote_host])

Для передачи xargs, можно указывать их также до или после hosts=

fabric.tasks.execute(some_fab_task, arg1, arg2, hosts=[remote_host], xarg1=value1)
http://geckich.blogspot.com/

Автоматизация скриптов с помощью expect

Недавно открыл для себя интересную консольную утилиту expect. Сия утилита является интерпретатором (со своим языком) для взаимодействия с интерактивными (т. е. требующими какого-либо ввода от пользователя) программами.
Дабы не писать много лишнего, рассмотрим использование expect на примере.
Допустим,

  • есть удалённый sftp-сервер с адресом 192.168.1.2,
  • к которому у нас есть доступы вида user/SECRET, порт 20022,
  • нам нужно автоматически брать с этого сервера файл вида TODAYDATE_smtng_important.txt, где TODAYDATE – текущая дата вида YYYYMMDD,
  • файлик лежит в корневой директории.

При авторизации на sftp-сервере требуется ввод пароля, который никак ни опцией ни указать, ни ключ не прокинуть (предположим, нет административного доступа на sftp-сервер).
Вот именно здесь в автоматизации этой задачи поможет нам линуксовая консольная утилита expect.
создаем файл get_file.sh со следующим содержимым:
#!/usr/bin/expect
# Script to get important file from sftp quetly. Uses expect (yum install expect -y)
# Usage: ./get_file.sh YYYYMMDD
# where YYYYMMDD – today’s date

set today [lindex $argv 0]
spawn sftp -oPort=20022 user@192.168.1.2:${today}_smtng_important.txt /home/user/${today}_smtng_important.txt
expect “password:”
send “SECRET\n”;
interact

Строки после комментария:

  1. Создаем переменную today, присваивая ей первый аргумент, переданный скрипту при запуске.
  2. Выполняем команду для подключения к sftp, где вместо ${today} интерпретатор вставляет значение даты (переданное скрипту)
  3. Далее скрипт ждет указанного запроса (prompt) – здесь возможно много вариантов, в т. ч. и таймауты и различные prompt-ы, об этом можно прочитать в мане или в более расширенных статьях.
  4. Получив указанный выше промпт, посылаем в ответ наш пароль и знак окончания строки (аналог нажатия пользователем [Enter])
  5. Далее скрипт полностью передает управление пользователю.

Запускать скрипт, понятное дело после chmod o+x, например так:
./get_file.sh 20142902
Минусом изложенного подхода, конечно же, является хранение пароля для sftp в открытом, не зашифрованном виде.
Ссылки:

  1. Хорошая статья по командам expect
  2. Туториал по expect
  3. Ман по sftp в Linux
http://geckich.blogspot.com/

Как получить размер свободного дискового пространства точки монтирования в Python (Linux)

Чтобы получить размер свободного пространства для точки монтирования (или размер свободное место на разделе, где находится указанная папка), необходимо выполнить следующее:

import os
st = os.statvfs("/home")
du = st.f_bsize * st.f_bavail
print(du)

Получим значение в байтах, чтобы получить значение, например, в Мб:

du = st.f_bsize * st.f_bavail / 1024 / 1024
http://geckich.blogspot.com/