Причины ошибок тривиальны:
- Первая ошибка указывает, что _service is None, и это очевидно.
- Вторая ошибка вызвана сигнатурами, согласно python при использовании @Slot (str) это означает, что он получит только один параметр из QML, но в QML вы передаете 3 параметра.
Кажется, что вы скопировали и вставил мое предыдущее решение, не понимая, как оно работает.
В мое предыдущее решение служба - это экземпляр календаря Google, а CalendarBackend - это объект QObject, который позволяет использовать службу без блокировки Qt eventl oop (поэтому используются потоки), а CalendarProvider предоставляет QML только некоторые методы.
import functools
import logging
import os
import pickle
import sys
import threading
from PySide2 import QtCore, QtGui, QtQml
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
SCOPES = ["https://www.googleapis.com/auth/calendar"]
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
logging.basicConfig(level=logging.DEBUG)
def qdatetime_to_string(x):
if isinstance(x, dict):
for k, v in x.items():
if isinstance(v, QtCore.QDateTime):
x[k] = v.toString(QtCore.Qt.ISODate)
else:
qdatetime_to_string(v)
elif isinstance(x, list):
for i, e in enumerate(x):
if isinstance(e, QtCore.QDateTime):
x[i] = e.toString(QtCore.Qt.ISODate)
else:
qdatetime_to_string(v)
class Reply(QtCore.QObject):
finished = QtCore.Signal()
def __init__(self, func, args=(), kwargs=None, parent=None):
super().__init__(parent)
self._results = None
self._is_finished = False
self._error_str = ""
threading.Thread(
target=self._execute, args=(func, args, kwargs), daemon=True
).start()
@property
def results(self):
return self._results
@property
def error_str(self):
return self._error_str
def is_finished(self):
return self._is_finished
def has_error(self):
return bool(self._error_str)
def _execute(self, func, args, kwargs):
if kwargs is None:
kwargs = {}
try:
self._results = func(*args, **kwargs)
except Exception as e:
self._error_str = str(e)
self._is_finished = True
self.finished.emit()
def convert_to_reply(func):
def wrapper(*args, **kwargs):
reply = Reply(func, args, kwargs)
return reply
return wrapper
class CalendarBackend(QtCore.QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._service = None
@property
def service(self):
if self._service is None:
reply = self._update_credentials()
loop = QtCore.QEventLoop()
reply.finished.connect(loop.quit)
loop.exec_()
if not reply.has_error():
self._service = reply.results
else:
logging.debug(reply.error_str)
return self._service
@convert_to_reply
def _update_credentials(self):
creds = None
if os.path.exists("token.pickle"):
with open("token.pickle", "rb") as token:
creds = pickle.load(token)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
"credentials.json", SCOPES
)
creds = flow.run_local_server(port=0)
with open("token.pickle", "wb") as token:
pickle.dump(creds, token)
return build("calendar", "v3", credentials=creds, cache_discovery=False)
@convert_to_reply
def insert(self, **kwargs):
return self.service.events().insert(**kwargs).execute()
class CalendarProvider(QtCore.QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._backend = CalendarBackend()
@QtCore.Slot("QVariant")
def createEvent(self, parameters):
kw = parameters.toVariant()
if isinstance(kw, dict):
qdatetime_to_string(kw)
reply = self._backend.insert(**kw)
wrapper = functools.partial(self.handle_finished_create_event, reply)
reply.finished.connect(wrapper)
def handle_finished_create_event(self, reply):
if reply.has_error():
logging.debug(reply.error_str)
else:
event = reply.results
link = event.get("htmlLink", "")
logging.debug("Event created: %s" % (link,))
QtGui.QDesktopServices.openUrl(QtCore.QUrl(link))
if __name__ == "__main__":
app = QtGui.QGuiApplication(sys.argv)
QtQml.qmlRegisterType(CalendarProvider, "MyCalendar", 1, 0, "CalendarProvider")
engine = QtQml.QQmlApplicationEngine()
filename = os.path.join(CURRENT_DIR, "main.qml")
engine.load(QtCore.QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
import QtQuick 2.15
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.15
import MyCalendar 1.0
ApplicationWindow {
visible: true
width: 640
height: 400
minimumWidth: 400
minimumHeight: 300
color: "#f4f4f4"
title: "Calendar Example"
CalendarProvider {
id: provider
}
GridLayout{
anchors.centerIn: parent
columns: 3
TextField {
id: eventstart
placeholderText: qsTr("Start Time 01/12/2020 14:35:00")
selectByMouse: true
}
TextField {
id: eventend
placeholderText: qsTr("End Time 01/12/2020 16:35:00")
selectByMouse: true
}
TextField {
id: eventinfo
placeholderText: qsTr("Event Name")
selectByMouse: true
}
Button{
text: "Create Event"
Layout.row: 1
Layout.column: 1
Layout.alignment: Qt.AlignHCenter
onClicked: {
var dt_start = Date.fromLocaleString(Qt.locale(), eventstart.text, "dd/MM/yyyy hh:mm:ss")
var dt_end = Date.fromLocaleString(Qt.locale(), eventend.text, "dd/MM/yyyy hh:mm:ss")
if(dt_start.getDate() && dt_end.getDate()){
provider.createEvent({
calendarId: "primary",
body: {
summary: eventinfo.text,
start: {
dateTime: dt_start,
timeZone: "America/Chicago",
},
end: {
dateTime: dt_end,
timeZone: "America/Chicago",
},
}
})
}
}
}
}
}