Kapcsolódás külső rendszerekhez Pythonban

Kategória: Python.

Fájlkezelés

Fájlt megnyitni az f = open(filename, mode) hívással tudunk Első paraméterként a fájlnevet kell megadnunk, az opcionális második paraméterrel pedig azt, hogy mit szeretnénk vele kezdeni: 'r' - olvasás (alapértelmezett), 'a' - hozzáfűzés, 'w' - írás, 'x' - létrehozás. Olvasni az s = f.read(), míg írni a f.write(s) függvénnyel tudunk. Lezárni fájlt az f.close() hívással lehet.

Fájl létrehozása:

fw = open('fruits.txt', 'w')
fw.write('apple orange banana')
fw.close()

Fájl olvasása:

fr = open('fruits.txt', 'r')
fruits = fr.read()
fr.close()
print(fruits) # apple orange banana

Az előzővel azonos működést eredményez a következő szintaxis:

with open('fruits.txt', 'r') as fr:
    fruits = fr.read()
    print(fruits) # apple orange banana

Itt tehát nem kell lezárni a streamet, az automatikusan megtörténik.

Egy sort a readline() függvénnyel tudunk beolvasni.

Azt gondolom, hogy elmondhatjuk erről azt, hogy egyszerű. A könnyűt semmiképpen sem használom szívesen, de azt, hogy egyszerű, szerintem teljesen indokolt jelen esetben. Érdemes összehasonlítani pl. a Java Fájlkezeléssel, hogy lássuk, valóban helytálló az "egyszerű" szó.

CSV fájlok kezelése

A CSV fájl felfogható speciális felépítésű szövegfájlként, a Python viszont külön könyvtárral segít ennek kezelésében. CSV fájl létrehozása:

import csv
 
with open('test.csv', 'w', newline='') as my_csv_file:
    my_writer = csv.writer(my_csv_file)
    my_writer.writerow(['apple', 3])
    my_writer.writerow(['banana', 2])
    my_writer.writerow(['orange', 5])

CSV fájl olvasása:

import csv
 
with open('test.csv', newline='') as my_csv_file:
    my_reader = csv.reader(my_csv_file)
    for row in my_reader:
        print(row)

A reader() és writer() függvényeknél számos paraméter beállítható, mindenekelőtt az elválasztó karakter (delimiter; alapértelmezésben a vessző, innen is ered a CSV elnevezés mint comma-separated file, azaz vesszővel elválasztott fájl, de mivel sok esetben előfordul magában a tartalomban a vessző, többnyire meg szoktuk változtatni), az idézőjel (quotechar, "), az escape karakter (escape) stb., melyekről a specifikációban (https://docs.python.org/3/library/csv.html) egy kiváló összefoglalót találunk.

Szerializáció

A szerializáció önmagában nem jelent kapcsolatot semmilyen külső rendszerrel, ám szorosan együtt jár azzal. A probléma a következő: egy osztályt szeretnénk valami módon elmenteni, majd visszaállítani. A problémát a következő okozza:

  • Az osztályban lehetnek eltérő típusú elemek. Pl. egyszerre kellene kódolni egész számot és szöveget is, megadva azt, hogy a memóriában melyik adat mettől meddig terjed.
  • A hivatkozások más osztályokra több problémát is felvet. Egyrészt az önmagában megnehezíti a feladatot, ha nemcsak primitív típusok vannak, ugyanakkor a körkörös hivatkozás problémáját is meg kell oldani.
  • Külön problémát jelentenek az olyan struktúrák lementése, mint pl. a lista, a halmaz vagy a szótár.
  • Vannak olyan változók, amelyeket nem lehet (vagy legalábbis nem érdemes) szerializálni. Ilyen pl. egy hivatkozás adatbázis kapcsolatra.
  • És lehetnek olyan adatok is, amit nem szeretnénk szerializálni, pl. jelszavak.

A feladat tehát jóval összetettebb, mint amilyennek elsőre tűnik, és tipikusan erre kész megoldásokat használunk. A szerializáció és deszerializáció műveletét egyes progamozási nyelvekben marshallingnak és unmarshallingnak hívjuk. A Pythonban a pickle csomag hajtja végre ezt a műveletet.

Először lássuk a fájlba írásos módszert!

import pickle
 
my_dict = {'fruit': 'apple', 'color': 'red', 'number': 5}
pickle.dump(my_dict, open('my_dict.p', 'wb'))

Ennek az eredménye egy my_dict.p fájl, melynek a tartalma bináris. Visszafejtése:

import pickle
 
my_dict = pickle.load(open('my_dict.p', 'rb'))
print(my_dict)
# {'fruit': 'apple', 'color': 'red', 'number': 5}

A fenti fájl nemcsak alfanumerikus karaktereket tartalmaz, viszont a speciális karakterek számos helyen problémát okozhatnak. Csak alfanumerikus karaktereket tartalmazó stringget a következőképpen használhatunk:

import pickle
import codecs
 
my_dict = {'fruit': 'apple', 'color': 'red', 'number': 5}
serialized = codecs.encode(pickle.dumps(my_dict), "base64").decode()
print(serialized)
# gASVLgAAAAAAAAB9lCiMBWZydWl0lIwFYXBwbGWUjAVjb2xvcpSMA3JlZJSMBm51bWJlcpRLBXUu
 
deserialized = pickle.loads(codecs.decode(serialized.encode(), "base64"))
print(deserialized)
# {'fruit': 'apple', 'color': 'red', 'number': 5}

Ez utóbbit elmenthetjük adatbázisban, vagy akár hálózaton keresztül is átküldhetjük.

Adatbázis elérés

Az adatbázis kapcsolat létrehozásához szükség van az adott adatbázisra jellemző ún. connector komponensre. Pl. MySQL adatbázis esetén a következőt kell telepíteni:

pip install mysql-connector-python

Tegyük fel, hogy a saját gépen fut egy MySQL szerver, csaba azonosítóval és farago jelszóval (innen letölthető: https://dev.mysql.com/downloads/installer/). Az alábbi példában láthatjuk a legfontosabb műveleteket: kapcsolódás a szerverhez, adatbázis törlése és létrehozása, kapcsolódás az adatbázishoz, tábla törlése és létrehozása, beszúrás, lekérdezés.

import mysql.connector
 
myconnection = mysql.connector.connect(
  host='localhost',
  user='csaba',
  password='farago'
)
mycursor = myconnection.cursor()
mycursor.execute('DROP DATABASE IF EXISTS mydatabase')
mycursor.execute('CREATE DATABASE mydatabase')
 
mydb = mysql.connector.connect(
  host='localhost',
  user='csaba',
  password='farago',
  database='mydatabase'
)
 
mycursor = mydb.cursor()
mycursor.execute('DROP TABLE IF EXISTS persons')
mycursor.execute('CREATE TABLE persons(id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)')
 
insert_person = 'INSERT INTO persons(name, age) VALUES (%s, %s)'
pista = ('Pista', 28)
joska = ('Joska', 54)
mycursor.execute(insert_person, pista)
mycursor.execute(insert_person, joska)
 
mycursor.execute('SELECT * FROM persons')
myrecords = mycursor.fetchall()
for myrecord in myrecords:
    print(myrecord)

Adatbázis létrehozása memóriában

A H2 adatbázist létre tudjuk hozni programból is.

Ehhez fel kell telepíteni a Java-t:

  • Töltsük le innen: https://java.com/.
  • Telepítsük fel.
  • Próbáljuk ki parancssorból, hogy indul-e: java -version
  • Adjuk a környezeti változókhoz a JAVA_HOME-ot: a start menüben keressünk rá erre: A rendszer környezeti változóinak módosítása (Edit the system environment variables) → Környezeti változók (Environment Variables) → Új… (New…) → a Változó (Variable) legyen JAVA_HOME, az Érték (Value) pedig ahova feltelepült, pl. c:\Program Files\jre1.8.0_301\.

A https://www.h2database.com/ oldalról töltsük le a H2 adatbázis megfelelő verzióját, pl. a Windows telepítőt, majd telepítsük fel. A célkönyvtár bin alkönyvtárában lesz egy h2-1.4.200.jar nevű fájl; azt másoljuk a Python projektünkbe.

A futtatáshoz szükség van a jaydebeapi modulra; ezt telepítsük fel:

pip install jaydebeapi

Példakód:

import jaydebeapi
 
kapcsolat = jaydebeapi.connect("org.h2.Driver", "jdbc:h2:mem:teszt", ["SA", ""], "h2-1.4.200.jar")
kurzor = kapcsolat.cursor()
kurzor.execute("CREATE TABLE teszt_tabla(ID INT PRIMARY KEY, SZOVEG VARCHAR(255), SZAM INT)")
kurzor.execute("INSERT INTO teszt_tabla VALUES(1, 'alma', 3)")
kurzor.execute("INSERT INTO teszt_tabla VALUES(2, 'szilva', 2)")
kurzor.execute("INSERT INTO teszt_tabla VALUES(3, 'meggy', 5)")
kurzor.execute("SELECT * FROM teszt_tabla")
adatok = kurzor.fetchall()
kurzor.close()
kapcsolat.close()
print(adatok) # [(1, 'alma', 3), (2, 'szilva', 2), (3, 'meggy', 5)]

Ha minden jól ment, akkor sikerült létrehozni egy adattáblát 3 adatmezővel, beletenni példa adatokat, majd lekérdezni.

Nagy mennyiségű adatformátum

A HDF5 nagy mennyiségű adatok bináris tárolására szolgáló adatformátum. Ez a Pythontól független szabvány. Vannak benne csoportok (group) és adathalmazok (dataset). A csoportok rekurzívan egymásba ágyazhatóak, amelyek tehát más csoportokat és adathalmazokat tartalmazhatnak. A gyökér is tartalmazhat adathalmazt. A csoport és az adathalmaz is egy kulcs-érték pár: a kulcs az adott elem neve, az érték pedig csoport esetén maga a csoport, adathalmaz esetén pedig maga az adat. Ez utóbbi általában valamiféle tömb.

Pythonban a h5py könyvtár segítségével tudunk HDF5 fájlokat kezelni. Telepítése a szokásos:

pip install h5py

Az alábbi példa létrehoz egy olyan HDF5 fájlt, ami a gyökérben tartalmaz két adathalmazt, van benne egy csoport, és azon belül is van két adathalmaz:

import h5py
 
with h5py.File('mytestfile.hdf5', 'w') as my_hdf5_file:
    my_hdf5_file.create_dataset('my_numbers', data=(2, 3, 5))
    my_hdf5_file.create_dataset('my_fruits', data=('apple', 'banana', 'orange'))
    my_group = my_hdf5_file.create_group('my_group')
    my_group.create_dataset('my_bools', data=(True, False, False))
    my_group.create_dataset('my_floats', data=(3.14, 2.71, 10.0))

Az eredmény mytestfile.hdf5, ami egy bináris fájl. Beolvasása:

import h5py
import numpy as np
 
with h5py.File('mytestfile.hdf5', 'r') as my_hdf5_file:
    print(list(my_hdf5_file.keys())) # ['my_fruits', 'my_group', 'my_numbers']
    my_numbers = my_hdf5_file['my_numbers']
    print(my_numbers.shape) # (3,)
    print(my_numbers.dtype) # int32
    print(np.array(my_numbers)) # [2 3 5]
    print(np.array(my_hdf5_file['my_fruits'])) # [b'apple' b'banana' b'orange']
    my_group = my_hdf5_file['my_group']
    print(np.array(my_group['my_bools'])) # [ True False False]
    print(np.array(my_group['my_floats'])) # [ 3.14  2.71 10.  ]

Érdemes megfigyelni azt, hogy a NumPy segítségével lett megvalósítva az adathalmaz.

Távoli metódushívás

A távoli metódushívás azt jelenti, hogy van két külön rendszer, tipikusan két külön számítógépen, és az egyik az egyik meghívja egy másik függvényét.

Egyszerűbb az eset akkor, ha a szerver és a kliens is ugyanabban a programozási nyelvben íródott, esetünkben Pythonban. Az rpyc csomag segítségével tudunk távolról is hívható metódusokat létrehozni ill. meghívni. Mivel nem része az alaprendszernek, először telepíteni kell:

pip install rpyc

A szerver kód:

from rpyc import Service
from rpyc.utils.server import ThreadedServer
 
class MyService(Service):
    def exposed_sum(self, a: int, b: int) -> int:
        return a + b
 
my_server = ThreadedServer(MyService, port=9999)
my_server.start()

A kiajánlott, tehát távolról is hívható függvények elé az exposed_ előtagot kell tenni. Majd indítsuk el a programot szokásos módon.

A kliens kód:

import rpyc
 
connection = rpyc.connect('localhost', 9999)
result = connection.root.sum(3, 2)
connection.close()
print(result) # 5

Itt az exposed_ előtag nélkül kell a függvényt meghívni.

Összetettebb esetekben a programozási nyelvek eltérőek. Több szabványt is létrehoztak az idők folyamán, pl. CORBA vagy WSDL, és a jelentősebb programozási nyelvekre el is készültek a könyvtárak. Pl. a pyws segítségével volt lehetőség WSDL szervert létrehozni Pythonban. A múlt idő amiatt indokolt, mert ez régebbi Pythonhoz készült, az írás pillanatában viszont már szintaxis hibás, és nincs karbantartva. Az írás pillanatában ugyanis már egyre kevesebbet használnak tulajdonképpeni távoli metódushívást, a helyét átvette a REST alapú adatkapcsolat: a szerver általában egy webszerver, melyet HTTP hívásokon (GET, POST stb.) keresztül tudunk elérni. Az adatcsere formátuma többnyire JSON.

Üzenetkezelés

Számos üzenetkezelő rendszer létezik (pl. ActiveMQ, HornetQ, RabbitMQ stb.), az alapelveik viszont azonosak: az üzenetek kézbestése. A két alapvető üzenetkezelő rendszer:

  • Cső (queue): a küldő által küldött üzenetek egy "csőbe" kerülnek, a fogadók pedig a legkorábban küldött üzenetet tudják feldolgozni. Egy üzenetet csak egyszer dolgoznak fel, így ha több kliens is figyeli a csövet, akkor csak az egyik fogja megkapni.
  • Téma (topic): akárhány kliens megkaphatja az üzenetet. Ebben az esetben az üzenetekre fel kell iratkozni, emiatt ezt a modellt publish-subscribe-nak is hívják.

Univerzális üzenetkezelő rendszer sajnos nem létezik; mindegyik programozási nyelvnek külön-külön kell támogatnia mindegyik rendszert. Itt most a RabbitMQ-t vizsgáljuk meg Python segítségével. A telepítése kínszenvedés, a hivatalos leírás pedig iskolapéldája annak, hogy hogyan nem lenne szabad ezt csinálni; tele van a leírás felesleges marhasággal. A telepítés lépései egyszerűen:

Érdemes mindkettőt rendszergazdai jogokkal telepíteni. Ez utóbbi engedélyeket kér, amit adjunk meg. Ellenőrzés: a Szolgáltatások (Services) programban nézzük meg, hogy a RabbitMQ fut-e.

Pythonban a pika könyvtár segítségével tudunk csatlakozni a RabbitMQ-hoz. Telepítése a szokásos:

pip install pika

Queue fogadó:

import pika
 
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello')
 
def callback(ch, method, properties, body):
    print(f'Received: {body}')
 
channel.basic_consume(queue='hello', on_message_callback=callback, auto_ack=True)
print('Waiting for messages.')
channel.start_consuming()

Queue-ba küldő:

import pika
 
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello')
message = 'Hello World!'
channel.basic_publish(exchange='', routing_key='hello', body=message)
print('Sent:', message)
connection.close()

Topic fogadó:

import pika
 
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='topic_logs', exchange_type='topic')
result = channel.queue_declare('', exclusive=True)
queue_name = result.method.queue
channel.queue_bind(exchange='topic_logs', queue=queue_name, routing_key='my_topic')
print('Waiting for logs.')
 
def callback(ch, method, properties, body):
    print(f'Received from {method.routing_key}: {body}')
 
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()

Topic-ba küldő:

import pika
 
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='topic_logs', exchange_type='topic')
routing_key = 'my_topic'
message = 'Hello World!'
channel.basic_publish(exchange='topic_logs', routing_key=routing_key, body=message)
print(f'Sent to {routing_key}: {message}')
connection.close()

Titkosítás

Ha külső rendszerekhez kapcsolódunk, akkor mindenképpen fontos a biztonságra is gondolnunk. Ez a terület is óriási, önmagában lehetne csak erről külön weboldalt létrehozni, így itt sem célunk a terület részletes bemutatása. A biztonság egyik fontos összetevője a titkosítás: az adat lehetőleg ne jusson avatatlan szemek elé. Pythonban számos könyvtár támogatja ezt a területet, az egyik közülük a cryptography. Nálam a kipróbálás pillanatában már fel volt telepítve (minden bizonnyal egy másik könyvtár függőségeként), de ha nem lenne, a szokásos módon telepíthetjük:

pip install cryptography

A titkosítás két fő művelete a bekódolás és a kikódolás. Mindkettőre példa:

from cryptography.fernet import Fernet
 
message = 'Hello, world!'
print(message) # Hello, world!
 
key = Fernet.generate_key()
print(key) # b'-ESLqM5fHp8ti7gPbES9P-CxCVvJN3q5oDgSHBImHGk='
fernet = Fernet(key)
encrypted = fernet.encrypt(message.encode())
print(encrypted) # b'gAAAAABg_TixnDYfVxrpXQQy_V9IXMAD19SRtqRQXbPHugSMbyfSJ8zo1nrdUXEyD_WbS_qT960mknE5cYHiaxHCtZ5g0Wf0aA=='
 
decrypted = fernet.decrypt(encrypted).decode()
print(decrypted) # Hello, world!

A valóságban a becsomagolás és a kicsomagolás többnyire fizikailag két külön helyen történik. A gyakorlatban a fentiek az alábbi részekre oszlanak:

  • Kód generálása: fernet = Fernet(key).
  • A kód megosztása a felek között.
  • A kód használata a kliens és a szerver oldalon is (fernet = Fernet(key)).

A kód megosztása és tárolása újabb kérdéseket vet fel, ami ezen a pontot már túlmutat ennek a szakasznak a keretein.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License