commit 79843aa2ae8693885cc3524bd1bf726ac11c4352 Author: Moirtz Wagner Date: Wed Sep 2 20:46:59 2026 +0200 SolarManager unter Versionsverwaltung Erster Stand der Hintergrundprozesse, die auf der Synology unter /volume1/homes/wagner/SolarManager laufen: der Manager selbst, die Sammler je Geraet, die MQTT-Bruecke, der Wecker und - neu hinzugezogen - der AutoAction-Runner, der als Hintergrundprozess hierher gehoert und nicht ins Web-Verzeichnis. Zugangsdaten stehen nicht mehr im Quelltext, sondern in config.ini, die nicht mit eingecheckt wird. Vorlage ist config.ini.example, gelesen wird sie von konfig.py. Betroffen waren solarManager.py (Datenbank und Wattpilot), zeit.py, gatherWaterData.py, wecker.py und skoda_testdaten.py, das sich das Passwort bisher aus dem Quelltext eines anderen Moduls herausgesucht hat. Die Kia-Anbindung ist mit dem Fahrzeug entfallen: kiaTest.py, gatherCarData.py und hyundai_kia_connect_api sind nicht mehr dabei, ebenso gatherInverterData.py, auf das nur noch eine auskommentierte Zeile zeigte. Die mitgelieferten Bibliotheken bleiben im Repository - die NAS hat kein pip, sie muessen neben den Skripten liegen. Co-Authored-By: Claude Opus 5 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9829860 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# Alles mit LF im Repository. Die Start-Skripte laufen auf der Synology, und +# ein CRLF im Shebang macht daraus ein "bad interpreter: no such file or +# directory" - ein Fehler, der sich schlecht suchen laesst. +* text=auto eol=lf + +*.zip binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7cb6e98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Zugangsdaten - nichts davon gehoert ins Repository +config.ini +skoda.conf +*.conf +secrets.conf + +# Protokolle. wattpilotshell.log ist allein 329 MB gross, die Logs machen +# 93 Prozent des Verzeichnisses aus. +*.log + +# Python +__pycache__/ +*.py[cod] +*$py.class + +# IDE +.vscode/ +.idea/ +*.swp +*~ + +# Betriebssystem +.DS_Store +Thumbs.db +@eaDir/ + +# Temporaeres +*.tmp +*.bak +*.backup diff --git a/Saldierung.txt b/Saldierung.txt new file mode 100644 index 0000000..c584d46 --- /dev/null +++ b/Saldierung.txt @@ -0,0 +1,4 @@ +Bi-directional table update for better meter matching: +ALTER TABLE `EnergyFlow` ADD `gridPcons` FLOAT NOT NULL AFTER `gridP`, ADD `gridPfeed` FLOAT NOT NULL AFTER `gridPcons`; +UPDATE EnergyFlow SET gridPcons = gridP WHERE gridP > 0 AND gridPcons = 0; +UPDATE EnergyFlow SET gridPfeed = -gridP WHERE gridP < 0 AND gridPfeed = 0; \ No newline at end of file diff --git a/Wattpilot/Wattpilot.py b/Wattpilot/Wattpilot.py new file mode 100644 index 0000000..e311839 --- /dev/null +++ b/Wattpilot/Wattpilot.py @@ -0,0 +1,673 @@ +import sys +sys.path.append("./") +import websocket +import json +import hashlib +import random +import threading +import hmac +import logging + +import base64 +import subprocess + +from time import sleep +from types import SimpleNamespace + + + +_LOGGER = logging.getLogger(__name__) + + + +class LoadMode(): + """Wrapper Class to represent the Load Mode of the Wattpilot""" + DEFAULT=3 + ECO=4 + NEXTTRIP=5 + + +class Wattpilot(object): + + supported_events = [ + # Wattpilot events: + "wp_auth", + "wp_authError", + "wp_authSuccess", + "wp_clearInverters", + "wp_connect", + "wp_deltaStatus", + "wp_disconnect", + "wp_fullStatus", + "wp_fullStatus_finished", + "wp_hello", + "wp_init", + "wp_property", + "wp_response", + "wp_updateInverter", + # WebSocketApp events: + "ws_close", + "ws_error", + "ws_message", + "ws_open", + ] + + carValues = {} + alwValues = {} + astValues = {} + lmoValues = {} + ustValues = {} + errValues = {} + acsValues = {} + + lmoValues[3] = "Default" + lmoValues[4] = "Eco" + lmoValues[5] = "Next Trip" + + astValues[0] = "open" + astValues[1] = "locked" + astValues[2] = "auto" + + carValues[1] = "no car" + carValues[2] = "charging" + carValues[3] = "ready" + carValues[4] = "complete" + + alwValues[0] = False + alwValues[1] = True + + ustValues[0] = "Normal" + ustValues[1] = "AutoUnlock" + ustValues[2] = "AlwaysLock" + + errValues[0] = "Unknown Error" + errValues[1] = "Idle" + errValues[2] = "Charging" + errValues[3] = "Wait Car" + errValues[4] = "Complete" + errValues[5] = "Error" + + acsValues[0] = "Open" + acsValues[1] = "Wait" + + + @property + def allProps(self): + """Returns a dictionary with all properties""" + return self._allProps + + @property + def allPropsInitialized(self): + """Returns true, if all properties have been initialized""" + return self._allPropsInitialized + + @property + def cableType(self): + """Returns the Cable Type (Ampere) of the connected cable""" + return self._cableType + + @property + def frequency(self): + """Returns the power frequency""" + return self._frequency + + @property + def phases(self): + """returns the phases""" + return self._phases + + @property + def energyCounterSinceStart(self): + """Returns used kwh since start of charging""" + return self._energyCounterSinceStart + + @property + def errorState(self): + """Returns error State""" + return self._errorState + + @property + def cableLock(self): + return self._cableLock + + @property + def energyCounterTotal(self): + return self._energyCounterTotal + + @property + def serial(self): + """Returns the serial number of Wattpilot Device (read only)""" + return self._serial + @serial.setter + def serial(self,value): + self._serial = value + if (self._password is not None) & (self._serial is not None): + self._hashedpassword = base64.b64encode(hashlib.pbkdf2_hmac('sha512',self._password.encode(),self._serial.encode(),100000,256))[:32] + + @property + def name(self): + """Returns the name of Wattpilot Device (read only)""" + return self._name + + + @property + def hostname(self): + """Returns the DNS Hostname of Wattpilot Device (read only)""" + return self._hostname + + @property + def friendlyName(self): + """Returns the friendly name of Wattpilot Device (read only)""" + return self._friendlyName + + @property + def manufacturer(self): + """Returns the Manufacturer of Wattpilot Device (read only)""" + return self._manufacturer + + @property + def devicetype(self): + return self._devicetype + + @property + def protocol(self): + return self._protocol + + @property + def secured(self): + return self._secured + + @property + def password(self): + return self._password + @password.setter + def password(self,value): + self._password = value + if (self._password is not None) & (self._serial is not None): + self._hashedpassword = base64.b64encode(hashlib.pbkdf2_hmac('sha512',self._password.encode(),self._serial.encode(),100000,256))[:32] + + + @property + def url(self): + return self._url + @url.setter + def url(self,value): + self._url = value + + @property + def connected(self): + return self._connected + + @property + def voltage1(self): + return self._voltage1 + + @property + def voltage2(self): + return self._voltage2 + + @property + def voltage3(self): + return self._voltage3 + + @property + def voltageN(self): + return self._voltageN + + @property + def amps1(self): + return self._amps1 + + @property + def amps2(self): + return self._amps2 + + @property + def amps3(self): + return self._amps3 + + @property + def power1(self): + return self._power1 + + @property + def power2(self): + return self._power2 + + @property + def power3(self): + return self._power3 + + @property + def powerN(self): + return self._powerN + + @property + def power(self): + return self._power + + @property + def version(self): + return self._version + + @property + def amp(self): + return self._amp + + @property + def AccessState(self): + return self._AccessState + + @property + def firmware(self): + """Returns the Firmwareversion of Wattpilot Device (read only)""" + return self._firmware + + @property + def ftt(self): + """Returns the The desired charged time for NextTrip""" + return self._ftt + + @property + def WifiSSID(self): + """Returns the SSID of the Wifi network currently connected (read only)""" + return self._WifiSSID + + @property + def AllowCharging(self): + return self._AllowCharging + + @property + def mode(self): + return self._mode + + @property + def carConnected(self): + return self._carConnected + + @property + def cae(self): + """Returns true if Cloud API Access is enabled (read only)""" + return self._cae + + @property + def cak(self): + """Returns the API Key for Cloud API Access (read only)""" + return self._cak + + + def __str__(self): + """Returns a String representation of the core Wattpilot attributes""" + if self.connected: + ret = "Wattpilot: " + str(self.name) + "\n" + ret = ret + "Serial: " + str(self.serial) + "\n" + ret = ret + "Connected: " + str(self.connected) + "\n" + ret = ret + "Car Connected: " + str(self.carConnected) + "\n" + ret = ret + "Charge Status " + str(self.AllowCharging) + "\n" + ret = ret + "Mode: " + str(self.mode) + "\n" + ret = ret + "Power: " + str(self.amp) + "\n" + ret = ret + "Charge: " + "%.2f" % self.power + "kW" + " ---- " + str(self.voltage1) + "V/" + str(self.voltage2) + "V/" + str(self.voltage3) + "V" + " -- " + ret = ret + "%.2f" % self.amps1 + "A/" + "%.2f" % self.amps2 + "A/" + "%.2f" % self.amps3 + "A" + " -- " + ret = ret + "%.2f" % self.power1 + "kW/" + "%.2f" % self.power2 + "kW/" + "%.2f" % self.power3 + "kW" + "\n" + else: + ret = "Not connected" + + return ret + def connect(self): + self._wst = threading.Thread(target=self._wsapp.run_forever) + self._wst.daemon = True + self._wst.start() + self.__call_event_handler("wp_connect") + _LOGGER.info("Wattpilot connected") + + def disconnect(self): + self._wsapp.close() + self._connected=False + self._auto_reconnect=False # Do not reconnect on explicit disconnect + self.__call_event_handler("wp_disconnect") + _LOGGER.info("Wattpilot disconnected") + + # Wattpilot Event Handling + + def add_event_handler(self,event_type,callback_fn): + if event_type not in self._event_handler: + self._event_handler[event_type] = [] + self._event_handler[event_type].append(callback_fn) + + def remove_event_handler(self,event_type,callback_fn): + if event_type in self._event_handler and callback_fn in self._event_handler[event_type]: + self._event_handler[event_type].remove(callback_fn) + + def __call_event_handler(self, event_type, *args): + _LOGGER.debug(f"Calling event handler for event type '{event_type} ...") + for callback_fn in self._event_handler[event_type]: + event = { + "type": event_type, + "wp": self, + } + callback_fn(event,*args) + + + def set_power(self,power): + self.send_update("amp",power) + + def set_mode(self,mode): + self.send_update("lmo",mode) + + + def send_update(self,name,value): + message = {} + message["type"]="setValue" + self.__requestid = self.__requestid+1 + message["requestId"]=self.__requestid + message["key"]=name + message["value"]=value + if (self._secured is not None): + if (self._secured > 0): + self.__send(message,True) + else: + self.__send(message) + else: + self.__send(message) + + def unpairInverter(self,InverterID): + message = {} + message["type"]="unpairInverter" + self.__requestid = self.__requestid+1 + message["requestId"]=self.__requestid + message["inverterId"]=InverterID + if (self._secured is not None): + if (self._secured > 0): + self.__send(message,True) + else: + self.__send(message) + else: + self.__send(message) + + def pairInverter(self,InverterID): + message = {} + message["type"]="pairInverter" + self.__requestid = self.__requestid+1 + message["requestId"]=self.__requestid + message["inverterId"]=InverterID + if (self._secured is not None): + if (self._secured > 0): + self.__send(message,True) + else: + self.__send(message) + else: + self.__send(message) + + def __update_property(self,name,value): + + self._allProps[name] = value + if name=="acs": + self._AccessState = Wattpilot.acsValues[value] + + if name=="cbl": + self._cableType = value + + if name=="fhz": + self._frequency = value + + if name=="pha": + self._phases = value + + if name=="wh": + self._energyCounterSinceStart = value + + if name=="err": + self._errorState = Wattpilot.errValues[value] + + if name=="ust": + self._cableLock = Wattpilot.ustValues[value] + + if name=="eto": + self._energyCounterTotal = value + + if name=="cae": + self._cae = value + if name=="cak": + self._cak = value + if name=="lmo": + self._mode = Wattpilot.lmoValues[value] + if name=="car": + self._carConnected = Wattpilot.carValues[value] + if name=="alw": + self._AllowCharging = Wattpilot.alwValues[value] + if name=="nrg": + self._voltage1=value[0] + self._voltage2=value[1] + self._voltage3=value[2] + self._voltageN=value[3] + self._amps1=value[4] + self._amps2=value[5] + self._amps3=value[6] + self._power1=value[7]*0.001 + self._power2=value[8]*0.001 + self._power3=value[9]*0.001 + self._powerN=value[10]*0.001 + self._power=value[11]*0.001 + if name=="amp": + self._amp = value + if name=="version": + self._version = value + if name=="ast": + self._AllowCharging = self._astValues[value] + if name=="fwv": + self._firmware = value + if name=="wss": + self._WifiSSID=value + if name=="ftt": + self._ftt=value + if name=="upd": + if value=="0": + self._updateAvailable = False + else: + self._updateAvailable = True + self.__call_event_handler("wp_property",name,value) + + def __on_hello(self,message): + _LOGGER.info("Connected to WattPilot Serial %s",message.serial) + if hasattr(message,"hostname"): + self._name=message.hostname + self.serial = message.serial + if hasattr(message,"hostname"): + self._hostname=message.hostname + if hasattr(message,"version"): + self._version=message.version + self._manufacturer=message.manufacturer + self._devicetype=message.devicetype + self._protocol=message.protocol + if hasattr(message,"secured"): + self._secured=message.secured + self.__call_event_handler("wp_hello",message) + + def __on_auth(self,wsapp,message): + ran = random.randrange(10**80) + self._token3 = "%064x" % ran + self._token3 = self._token3[:32] + hash1 = hashlib.sha256((message.token1.encode()+self._hashedpassword)).hexdigest() + hash = hashlib.sha256((self._token3 + message.token2+hash1).encode()).hexdigest() + response = {} + response["type"] = "auth" + response["token3"] = self._token3 + response["hash"] = hash + self.__send(response) + self.__call_event_handler("wp_auth",message) + + def __send(self,message,secure=False): + # If the connection to wattpilot is over a unsecure channel (http) all send messages are wrapped in + # a "securedMsg" Message which contains the original messageobject and a sha256 HMAC Hashed created + # using the password + if secure: + messageid=message["requestId"] + payload=json.dumps(message) + h = hmac.new(bytearray(self._hashedpassword), bytearray(payload.encode()), hashlib.sha256 ) + message={} + message["type"]="securedMsg" + message["data"]=payload + message["requestId"]=str(messageid)+"sm" + message["hmac"]=h.hexdigest() + + _LOGGER.debug("Message send: %s",json.dumps(message) ) + self._wsapp.send(json.dumps(message)) + + def __on_AuthSuccess(self,message): + self._connected = True + self.__call_event_handler("wp_authSuccess",message) + _LOGGER.info("Authentication successful") + + def __on_FullStatus(self,message): + props = message.status.__dict__ + for key in props: + self.__update_property(key,props[key]) + self.__call_event_handler("wp_fullStatus",message) + self._allPropsInitialized = not message.partial + _LOGGER.info("Status part") + if message.partial == False: + self.__call_event_handler("wp_fullStatus_finished",message) + _LOGGER.info("Status complete") + + def __on_AuthError(self,message): + if message.message=="Wrong password": + self._wsapp.close() + _LOGGER.error("Authentication failed: %s" , message.message) + self.__call_event_handler("wp_authError",message) + + def __on_DeltaStatus(self,message): + props = message.status.__dict__ + for key in props: + self.__update_property(key,props[key]) + self.__call_event_handler("wp_deltaStatus",message) + + + def __on_clearInverters(self,message): + self.__call_event_handler("wp_clearInverters",message) + + def __on_updateInverter(self,message): + self.__call_event_handler("wp_updateInverter",message) + + def __on_response(self,message): + if message.success: + if hasattr(message,"status"): + props = message.status.__dict__ + for key in props: + self.__update_property(key,props[key]) + else: + _LOGGER.error("Error Sending Request %s. Message: %s" ,message.requestId,message.message) + self.__call_event_handler("wp_response",message) + + def __on_open(self,wsapp): + self.__call_event_handler("ws_open",wsapp) + + def __on_error(self,wsapp,err): + self.__call_event_handler("ws_error",wsapp,err) + _LOGGER.error(f"Error received from WebSocketApp: {err}") + + def __on_close(self,wsapp,code,msg): + self._connected=False + self.__call_event_handler("ws_close",wsapp,code,msg) + if (self._auto_reconnect): + sleep(self._reconnect_interval) + self._wsapp.run_forever() + + def __on_message(self, wsapp, message): + ## called whenever a message through websocket is received + _LOGGER.debug("Message received: %s", message) + msg=json.loads(message, object_hook=lambda d: SimpleNamespace(**d)) + self.__call_event_handler("ws_message",message) + if (msg.type == 'hello'): # Hello Message -> Received upon connection before auth + self.__on_hello(msg) + if (msg.type == 'authRequired'): # Auth Required -> Received after hello + self.__on_auth(wsapp,msg) + if (msg.type == 'response'): # Response Message -> Received after sending a update and contains result of update + self.__on_response(msg) + if (msg.type == 'authSuccess'): # Auth Success -> Received after sending correct authentication message + self.__on_AuthSuccess(msg) + if (msg.type == 'authError'): # Auth Error -> Received after sending incorrect authentication message (e.g. wrong password) + self.__on_AuthError(msg) + if (msg.type == 'fullStatus'): # Full Status -> Received after successful connection. Contains all properties of Wattpilot + self.__on_FullStatus(msg) + if (msg.type == 'deltaStatus'): # Delta Status -> Whenever a property changes a Delta Status is send + self.__on_DeltaStatus(msg) + if (msg.type == 'clearInverters'): # Unknown + self.__on_clearInverters(msg) + if (msg.type == 'updateInverter'): # Contains information of connected Photovoltaik inverter / powermeter + self.__on_updateInverter(msg) + + + def __init__(self, ip ,password,serial=None,cloud=False): + self._auto_reconnect = True + self._reconnect_interval = 30 + self._websocket_default_timeout = 10 + self.__requestid = 0 + self._name = None + self._hostname = None + self._friendlyName = None + self._manufacturer = None + self._devicetype = None + self._protocol = None + self._secured = None + self._serial = None + self._password = None + + self.password = password + + if(cloud): + self._url= "wss://app.wattpilot.io/app/" + serial + "?version=1.2.9" + else: + self._url = "ws://"+ip+"/ws" + _LOGGER.info("URL: %s",self._url); + self.serial = None + self._connected = False + self._allProps={} + self._allPropsInitialized=False + self._voltage1=None + self._voltage2=None + self._voltage3=None + self._voltageN=None + self._amps1=None + self._amps2=None + self._amps3=None + self._power1=None + self._power2=None + self._power3=None + self._powerN=None + self._power=None + self._version = None + self._amp = None + self._AccessState = None + self._firmware = None + self._ftt = None + self._WifiSSID = None + self._AllowCharging = None + self._mode=None + self._carConnected=None + self._cae=None + self._cak=None + # Initialize callback lists: + self._event_handler = {} + for event_type in self.supported_events: + self._event_handler[event_type] = [] + + self._wst=threading.Thread() + + websocket.setdefaulttimeout(self._websocket_default_timeout) + self._wsapp = websocket.WebSocketApp( + self.url, + on_close=self.__on_close, + on_error=self.__on_error, + on_message=self.__on_message, + on_open=self.__on_open, + ) + self.__call_event_handler("wp_init") + _LOGGER.info ("Wattpilot %s initialized",self.serial) + + +def wp_handle_events(event, *args): + _LOGGER.debug(f"wp_handle_events(event={event},{args})") + _LOGGER.debug(f"wp_handle_events(): MQTT client not yet initialized - status publishing skipped.") + if event['type'] == 'fullStatus': + _LOGGER.debug(f"wp_handle_events(Hz={wp.frequency})") + return diff --git a/aiohttp/__init__.py b/aiohttp/__init__.py new file mode 100644 index 0000000..6ffafdf --- /dev/null +++ b/aiohttp/__init__.py @@ -0,0 +1,230 @@ +__version__ = "4.0.0a2.dev0" + +from typing import TYPE_CHECKING, Tuple + +from . import hdrs +from .client import ( + BaseConnector, + ClientConnectionError, + ClientConnectorCertificateError, + ClientConnectorError, + ClientConnectorSSLError, + ClientError, + ClientHttpProxyError, + ClientOSError, + ClientPayloadError, + ClientProxyConnectionError, + ClientRequest, + ClientResponse, + ClientResponseError, + ClientSession, + ClientSSLError, + ClientTimeout, + ClientWebSocketResponse, + ContentTypeError, + Fingerprint, + InvalidURL, + NamedPipeConnector, + RequestInfo, + ServerConnectionError, + ServerDisconnectedError, + ServerFingerprintMismatch, + ServerTimeoutError, + TCPConnector, + TooManyRedirects, + UnixConnector, + WSServerHandshakeError, + request, +) +from .cookiejar import CookieJar, DummyCookieJar +from .formdata import FormData +from .helpers import BasicAuth, ChainMapProxy, ETag +from .http import ( + HttpVersion, + HttpVersion10, + HttpVersion11, + WebSocketError, + WSCloseCode, + WSMessage, + WSMsgType, +) +from .multipart import ( + BadContentDispositionHeader, + BadContentDispositionParam, + BodyPartReader, + MultipartReader, + MultipartWriter, + content_disposition_filename, + parse_content_disposition, +) +from .payload import ( + PAYLOAD_REGISTRY, + AsyncIterablePayload, + BufferedReaderPayload, + BytesIOPayload, + BytesPayload, + IOBasePayload, + JsonPayload, + Payload, + StringIOPayload, + StringPayload, + TextIOPayload, + get_payload, + payload_type, +) +from .resolver import AsyncResolver, DefaultResolver, ThreadedResolver +from .streams import ( + EMPTY_PAYLOAD, + DataQueue, + EofStream, + FlowControlDataQueue, + StreamReader, +) +from .tracing import ( + TraceConfig, + TraceConnectionCreateEndParams, + TraceConnectionCreateStartParams, + TraceConnectionQueuedEndParams, + TraceConnectionQueuedStartParams, + TraceConnectionReuseconnParams, + TraceDnsCacheHitParams, + TraceDnsCacheMissParams, + TraceDnsResolveHostEndParams, + TraceDnsResolveHostStartParams, + TraceRequestChunkSentParams, + TraceRequestEndParams, + TraceRequestExceptionParams, + TraceRequestRedirectParams, + TraceRequestStartParams, + TraceResponseChunkReceivedParams, +) + +if TYPE_CHECKING: # pragma: no cover + # At runtime these are lazy-loaded at the bottom of the file. + from .worker import GunicornUVLoopWebWorker, GunicornWebWorker + +__all__: Tuple[str, ...] = ( + "hdrs", + # client + "BaseConnector", + "ClientConnectionError", + "ClientConnectorCertificateError", + "ClientConnectorError", + "ClientConnectorSSLError", + "ClientError", + "ClientHttpProxyError", + "ClientOSError", + "ClientPayloadError", + "ClientProxyConnectionError", + "ClientResponse", + "ClientRequest", + "ClientResponseError", + "ClientSSLError", + "ClientSession", + "ClientTimeout", + "ClientWebSocketResponse", + "ContentTypeError", + "Fingerprint", + "InvalidURL", + "RequestInfo", + "ServerConnectionError", + "ServerDisconnectedError", + "ServerFingerprintMismatch", + "ServerTimeoutError", + "TCPConnector", + "TooManyRedirects", + "UnixConnector", + "NamedPipeConnector", + "WSServerHandshakeError", + "request", + # cookiejar + "CookieJar", + "DummyCookieJar", + # formdata + "FormData", + # helpers + "BasicAuth", + "ChainMapProxy", + "ETag", + # http + "HttpVersion", + "HttpVersion10", + "HttpVersion11", + "WSMsgType", + "WSCloseCode", + "WSMessage", + "WebSocketError", + # multipart + "BadContentDispositionHeader", + "BadContentDispositionParam", + "BodyPartReader", + "MultipartReader", + "MultipartWriter", + "content_disposition_filename", + "parse_content_disposition", + # payload + "AsyncIterablePayload", + "BufferedReaderPayload", + "BytesIOPayload", + "BytesPayload", + "IOBasePayload", + "JsonPayload", + "PAYLOAD_REGISTRY", + "Payload", + "StringIOPayload", + "StringPayload", + "TextIOPayload", + "get_payload", + "payload_type", + # resolver + "AsyncResolver", + "DefaultResolver", + "ThreadedResolver", + # streams + "DataQueue", + "EMPTY_PAYLOAD", + "EofStream", + "FlowControlDataQueue", + "StreamReader", + # tracing + "TraceConfig", + "TraceConnectionCreateEndParams", + "TraceConnectionCreateStartParams", + "TraceConnectionQueuedEndParams", + "TraceConnectionQueuedStartParams", + "TraceConnectionReuseconnParams", + "TraceDnsCacheHitParams", + "TraceDnsCacheMissParams", + "TraceDnsResolveHostEndParams", + "TraceDnsResolveHostStartParams", + "TraceRequestChunkSentParams", + "TraceRequestEndParams", + "TraceRequestExceptionParams", + "TraceRequestRedirectParams", + "TraceRequestStartParams", + "TraceResponseChunkReceivedParams", + # workers (imported lazily with __getattr__) + "GunicornUVLoopWebWorker", + "GunicornWebWorker", +) + + +def __dir__() -> Tuple[str, ...]: + return __all__ + ("__author__", "__doc__") + + +def __getattr__(name: str) -> object: + global GunicornUVLoopWebWorker, GunicornWebWorker + + # Importing gunicorn takes a long time (>100ms), so only import if actually needed. + if name in ("GunicornUVLoopWebWorker", "GunicornWebWorker"): + try: + from .worker import GunicornUVLoopWebWorker as guv, GunicornWebWorker as gw + except ImportError: + return None + + GunicornUVLoopWebWorker = guv # type: ignore[misc] + GunicornWebWorker = gw # type: ignore[misc] + return guv if name == "GunicornUVLoopWebWorker" else gw + + raise AttributeError(f"module {__name__} has no attribute {name}") diff --git a/aiohttp/_cparser.pxd b/aiohttp/_cparser.pxd new file mode 100644 index 0000000..c2cd5a9 --- /dev/null +++ b/aiohttp/_cparser.pxd @@ -0,0 +1,158 @@ +from libc.stdint cimport int32_t, uint8_t, uint16_t, uint64_t + + +cdef extern from "../vendor/llhttp/build/llhttp.h": + + struct llhttp__internal_s: + int32_t _index + void* _span_pos0 + void* _span_cb0 + int32_t error + const char* reason + const char* error_pos + void* data + void* _current + uint64_t content_length + uint8_t type + uint8_t method + uint8_t http_major + uint8_t http_minor + uint8_t header_state + uint8_t lenient_flags + uint8_t upgrade + uint8_t finish + uint16_t flags + uint16_t status_code + void* settings + + ctypedef llhttp__internal_s llhttp__internal_t + ctypedef llhttp__internal_t llhttp_t + + ctypedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length) except -1 + ctypedef int (*llhttp_cb)(llhttp_t*) except -1 + + struct llhttp_settings_s: + llhttp_cb on_message_begin + llhttp_data_cb on_url + llhttp_data_cb on_status + llhttp_data_cb on_header_field + llhttp_data_cb on_header_value + llhttp_cb on_headers_complete + llhttp_data_cb on_body + llhttp_cb on_message_complete + llhttp_cb on_chunk_header + llhttp_cb on_chunk_complete + + llhttp_cb on_url_complete + llhttp_cb on_status_complete + llhttp_cb on_header_field_complete + llhttp_cb on_header_value_complete + + ctypedef llhttp_settings_s llhttp_settings_t + + enum llhttp_errno: + HPE_OK, + HPE_INTERNAL, + HPE_STRICT, + HPE_LF_EXPECTED, + HPE_UNEXPECTED_CONTENT_LENGTH, + HPE_CLOSED_CONNECTION, + HPE_INVALID_METHOD, + HPE_INVALID_URL, + HPE_INVALID_CONSTANT, + HPE_INVALID_VERSION, + HPE_INVALID_HEADER_TOKEN, + HPE_INVALID_CONTENT_LENGTH, + HPE_INVALID_CHUNK_SIZE, + HPE_INVALID_STATUS, + HPE_INVALID_EOF_STATE, + HPE_INVALID_TRANSFER_ENCODING, + HPE_CB_MESSAGE_BEGIN, + HPE_CB_HEADERS_COMPLETE, + HPE_CB_MESSAGE_COMPLETE, + HPE_CB_CHUNK_HEADER, + HPE_CB_CHUNK_COMPLETE, + HPE_PAUSED, + HPE_PAUSED_UPGRADE, + HPE_USER + + ctypedef llhttp_errno llhttp_errno_t + + enum llhttp_flags: + F_CHUNKED, + F_CONTENT_LENGTH + + enum llhttp_type: + HTTP_REQUEST, + HTTP_RESPONSE, + HTTP_BOTH + + enum llhttp_method: + HTTP_DELETE, + HTTP_GET, + HTTP_HEAD, + HTTP_POST, + HTTP_PUT, + HTTP_CONNECT, + HTTP_OPTIONS, + HTTP_TRACE, + HTTP_COPY, + HTTP_LOCK, + HTTP_MKCOL, + HTTP_MOVE, + HTTP_PROPFIND, + HTTP_PROPPATCH, + HTTP_SEARCH, + HTTP_UNLOCK, + HTTP_BIND, + HTTP_REBIND, + HTTP_UNBIND, + HTTP_ACL, + HTTP_REPORT, + HTTP_MKACTIVITY, + HTTP_CHECKOUT, + HTTP_MERGE, + HTTP_MSEARCH, + HTTP_NOTIFY, + HTTP_SUBSCRIBE, + HTTP_UNSUBSCRIBE, + HTTP_PATCH, + HTTP_PURGE, + HTTP_MKCALENDAR, + HTTP_LINK, + HTTP_UNLINK, + HTTP_SOURCE, + HTTP_PRI, + HTTP_DESCRIBE, + HTTP_ANNOUNCE, + HTTP_SETUP, + HTTP_PLAY, + HTTP_PAUSE, + HTTP_TEARDOWN, + HTTP_GET_PARAMETER, + HTTP_SET_PARAMETER, + HTTP_REDIRECT, + HTTP_RECORD, + HTTP_FLUSH + + ctypedef llhttp_method llhttp_method_t; + + void llhttp_settings_init(llhttp_settings_t* settings) + void llhttp_init(llhttp_t* parser, llhttp_type type, + const llhttp_settings_t* settings) + + llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) + + int llhttp_should_keep_alive(const llhttp_t* parser) + + void llhttp_resume_after_upgrade(llhttp_t* parser) + + llhttp_errno_t llhttp_get_errno(const llhttp_t* parser) + const char* llhttp_get_error_reason(const llhttp_t* parser) + const char* llhttp_get_error_pos(const llhttp_t* parser) + + const char* llhttp_method_name(llhttp_method_t method) + + void llhttp_set_lenient_headers(llhttp_t* parser, int enabled) + void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled) + void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled) diff --git a/aiohttp/_find_header.h b/aiohttp/_find_header.h new file mode 100644 index 0000000..99b7b4f --- /dev/null +++ b/aiohttp/_find_header.h @@ -0,0 +1,14 @@ +#ifndef _FIND_HEADERS_H +#define _FIND_HEADERS_H + +#ifdef __cplusplus +extern "C" { +#endif + +int find_header(const char *str, int size); + + +#ifdef __cplusplus +} +#endif +#endif diff --git a/aiohttp/_find_header.pxd b/aiohttp/_find_header.pxd new file mode 100644 index 0000000..37a6c37 --- /dev/null +++ b/aiohttp/_find_header.pxd @@ -0,0 +1,2 @@ +cdef extern from "_find_header.h": + int find_header(char *, int) diff --git a/aiohttp/_helpers.pyi b/aiohttp/_helpers.pyi new file mode 100644 index 0000000..1e35893 --- /dev/null +++ b/aiohttp/_helpers.pyi @@ -0,0 +1,6 @@ +from typing import Any + +class reify: + def __init__(self, wrapped: Any) -> None: ... + def __get__(self, inst: Any, owner: Any) -> Any: ... + def __set__(self, inst: Any, value: Any) -> None: ... diff --git a/aiohttp/_helpers.pyx b/aiohttp/_helpers.pyx new file mode 100644 index 0000000..665f367 --- /dev/null +++ b/aiohttp/_helpers.pyx @@ -0,0 +1,35 @@ +cdef class reify: + """Use as a class method decorator. It operates almost exactly like + the Python `@property` decorator, but it puts the result of the + method it decorates into the instance dict after the first call, + effectively replacing the function it decorates with an instance + variable. It is, in Python parlance, a data descriptor. + + """ + + cdef object wrapped + cdef object name + + def __init__(self, wrapped): + self.wrapped = wrapped + self.name = wrapped.__name__ + + @property + def __doc__(self): + return self.wrapped.__doc__ + + def __get__(self, inst, owner): + try: + try: + return inst._cache[self.name] + except KeyError: + val = self.wrapped(inst) + inst._cache[self.name] = val + return val + except AttributeError: + if inst is None: + return self + raise + + def __set__(self, inst, value): + raise AttributeError("reified property is read-only") diff --git a/aiohttp/_http_parser.pyx b/aiohttp/_http_parser.pyx new file mode 100644 index 0000000..2b4b844 --- /dev/null +++ b/aiohttp/_http_parser.pyx @@ -0,0 +1,844 @@ +#cython: language_level=3 +# +# Based on https://github.com/MagicStack/httptools +# +from __future__ import absolute_import, print_function + +from cpython cimport ( + Py_buffer, + PyBUF_SIMPLE, + PyBuffer_Release, + PyBytes_AsString, + PyBytes_AsStringAndSize, + PyObject_GetBuffer, +) +from cpython.mem cimport PyMem_Free, PyMem_Malloc +from libc.limits cimport ULLONG_MAX +from libc.string cimport memcpy + +from multidict import CIMultiDict as _CIMultiDict, CIMultiDictProxy as _CIMultiDictProxy +from yarl import URL as _URL + +from aiohttp import hdrs +from aiohttp.helpers import DEBUG + +from .http_exceptions import ( + BadHttpMessage, + BadStatusLine, + ContentLengthError, + InvalidHeader, + InvalidURLError, + LineTooLong, + PayloadEncodingError, + TransferEncodingError, +) +from .http_parser import DeflateBuffer as _DeflateBuffer +from .http_writer import ( + HttpVersion as _HttpVersion, + HttpVersion10 as _HttpVersion10, + HttpVersion11 as _HttpVersion11, +) +from .streams import EMPTY_PAYLOAD as _EMPTY_PAYLOAD, StreamReader as _StreamReader + +cimport cython + +from aiohttp cimport _cparser as cparser + +include "_headers.pxi" + +from aiohttp cimport _find_header + +DEF DEFAULT_FREELIST_SIZE = 250 + +cdef extern from "Python.h": + int PyByteArray_Resize(object, Py_ssize_t) except -1 + Py_ssize_t PyByteArray_Size(object) except -1 + char* PyByteArray_AsString(object) + +__all__ = ('HttpRequestParser', 'HttpResponseParser', + 'RawRequestMessage', 'RawResponseMessage') + +cdef object URL = _URL +cdef object URL_build = URL.build +cdef object CIMultiDict = _CIMultiDict +cdef object CIMultiDictProxy = _CIMultiDictProxy +cdef object HttpVersion = _HttpVersion +cdef object HttpVersion10 = _HttpVersion10 +cdef object HttpVersion11 = _HttpVersion11 +cdef object SEC_WEBSOCKET_KEY1 = hdrs.SEC_WEBSOCKET_KEY1 +cdef object CONTENT_ENCODING = hdrs.CONTENT_ENCODING +cdef object EMPTY_PAYLOAD = _EMPTY_PAYLOAD +cdef object StreamReader = _StreamReader +cdef object DeflateBuffer = _DeflateBuffer + + +cdef inline object extend(object buf, const char* at, size_t length): + cdef Py_ssize_t s + cdef char* ptr + s = PyByteArray_Size(buf) + PyByteArray_Resize(buf, s + length) + ptr = PyByteArray_AsString(buf) + memcpy(ptr + s, at, length) + + +DEF METHODS_COUNT = 46; + +cdef list _http_method = [] + +for i in range(METHODS_COUNT): + _http_method.append( + cparser.llhttp_method_name( i).decode('ascii')) + + +cdef inline str http_method_str(int i): + if i < METHODS_COUNT: + return _http_method[i] + else: + return "" + +cdef inline object find_header(bytes raw_header): + cdef Py_ssize_t size + cdef char *buf + cdef int idx + PyBytes_AsStringAndSize(raw_header, &buf, &size) + idx = _find_header.find_header(buf, size) + if idx == -1: + return raw_header.decode('utf-8', 'surrogateescape') + return headers[idx] + + +@cython.freelist(DEFAULT_FREELIST_SIZE) +cdef class RawRequestMessage: + cdef readonly str method + cdef readonly str path + cdef readonly object version # HttpVersion + cdef readonly object headers # CIMultiDict + cdef readonly object raw_headers # tuple + cdef readonly object should_close + cdef readonly object compression + cdef readonly object upgrade + cdef readonly object chunked + cdef readonly object url # yarl.URL + + def __init__(self, method, path, version, headers, raw_headers, + should_close, compression, upgrade, chunked, url): + self.method = method + self.path = path + self.version = version + self.headers = headers + self.raw_headers = raw_headers + self.should_close = should_close + self.compression = compression + self.upgrade = upgrade + self.chunked = chunked + self.url = url + + def __repr__(self): + info = [] + info.append(("method", self.method)) + info.append(("path", self.path)) + info.append(("version", self.version)) + info.append(("headers", self.headers)) + info.append(("raw_headers", self.raw_headers)) + info.append(("should_close", self.should_close)) + info.append(("compression", self.compression)) + info.append(("upgrade", self.upgrade)) + info.append(("chunked", self.chunked)) + info.append(("url", self.url)) + sinfo = ', '.join(name + '=' + repr(val) for name, val in info) + return '' + + def _replace(self, **dct): + cdef RawRequestMessage ret + ret = _new_request_message(self.method, + self.path, + self.version, + self.headers, + self.raw_headers, + self.should_close, + self.compression, + self.upgrade, + self.chunked, + self.url) + if "method" in dct: + ret.method = dct["method"] + if "path" in dct: + ret.path = dct["path"] + if "version" in dct: + ret.version = dct["version"] + if "headers" in dct: + ret.headers = dct["headers"] + if "raw_headers" in dct: + ret.raw_headers = dct["raw_headers"] + if "should_close" in dct: + ret.should_close = dct["should_close"] + if "compression" in dct: + ret.compression = dct["compression"] + if "upgrade" in dct: + ret.upgrade = dct["upgrade"] + if "chunked" in dct: + ret.chunked = dct["chunked"] + if "url" in dct: + ret.url = dct["url"] + return ret + +cdef _new_request_message(str method, + str path, + object version, + object headers, + object raw_headers, + bint should_close, + object compression, + bint upgrade, + bint chunked, + object url): + cdef RawRequestMessage ret + ret = RawRequestMessage.__new__(RawRequestMessage) + ret.method = method + ret.path = path + ret.version = version + ret.headers = headers + ret.raw_headers = raw_headers + ret.should_close = should_close + ret.compression = compression + ret.upgrade = upgrade + ret.chunked = chunked + ret.url = url + return ret + + +@cython.freelist(DEFAULT_FREELIST_SIZE) +cdef class RawResponseMessage: + cdef readonly object version # HttpVersion + cdef readonly int code + cdef readonly str reason + cdef readonly object headers # CIMultiDict + cdef readonly object raw_headers # tuple + cdef readonly object should_close + cdef readonly object compression + cdef readonly object upgrade + cdef readonly object chunked + + def __init__(self, version, code, reason, headers, raw_headers, + should_close, compression, upgrade, chunked): + self.version = version + self.code = code + self.reason = reason + self.headers = headers + self.raw_headers = raw_headers + self.should_close = should_close + self.compression = compression + self.upgrade = upgrade + self.chunked = chunked + + def __repr__(self): + info = [] + info.append(("version", self.version)) + info.append(("code", self.code)) + info.append(("reason", self.reason)) + info.append(("headers", self.headers)) + info.append(("raw_headers", self.raw_headers)) + info.append(("should_close", self.should_close)) + info.append(("compression", self.compression)) + info.append(("upgrade", self.upgrade)) + info.append(("chunked", self.chunked)) + sinfo = ', '.join(name + '=' + repr(val) for name, val in info) + return '' + + +cdef _new_response_message(object version, + int code, + str reason, + object headers, + object raw_headers, + bint should_close, + object compression, + bint upgrade, + bint chunked): + cdef RawResponseMessage ret + ret = RawResponseMessage.__new__(RawResponseMessage) + ret.version = version + ret.code = code + ret.reason = reason + ret.headers = headers + ret.raw_headers = raw_headers + ret.should_close = should_close + ret.compression = compression + ret.upgrade = upgrade + ret.chunked = chunked + return ret + + +@cython.internal +cdef class HttpParser: + + cdef: + cparser.llhttp_t* _cparser + cparser.llhttp_settings_t* _csettings + + bytearray _raw_name + bytearray _raw_value + bint _has_value + + object _protocol + object _loop + object _timer + + size_t _max_line_size + size_t _max_field_size + size_t _max_headers + bint _response_with_body + bint _read_until_eof + + bint _started + object _url + bytearray _buf + str _path + str _reason + object _headers + list _raw_headers + bint _upgraded + list _messages + object _payload + bint _payload_error + object _payload_exception + object _last_error + bint _auto_decompress + int _limit + + str _content_encoding + + Py_buffer py_buf + + def __cinit__(self): + self._cparser = \ + PyMem_Malloc(sizeof(cparser.llhttp_t)) + if self._cparser is NULL: + raise MemoryError() + + self._csettings = \ + PyMem_Malloc(sizeof(cparser.llhttp_settings_t)) + if self._csettings is NULL: + raise MemoryError() + + def __dealloc__(self): + PyMem_Free(self._cparser) + PyMem_Free(self._csettings) + + cdef _init( + self, cparser.llhttp_type mode, + object protocol, object loop, int limit, + object timer=None, + size_t max_line_size=8190, size_t max_headers=32768, + size_t max_field_size=8190, payload_exception=None, + bint response_with_body=True, bint read_until_eof=False, + bint auto_decompress=True, + ): + cparser.llhttp_settings_init(self._csettings) + cparser.llhttp_init(self._cparser, mode, self._csettings) + self._cparser.data = self + self._cparser.content_length = 0 + + self._protocol = protocol + self._loop = loop + self._timer = timer + + self._buf = bytearray() + self._payload = None + self._payload_error = 0 + self._payload_exception = payload_exception + self._messages = [] + + self._raw_name = bytearray() + self._raw_value = bytearray() + self._has_value = False + + self._max_line_size = max_line_size + self._max_headers = max_headers + self._max_field_size = max_field_size + self._response_with_body = response_with_body + self._read_until_eof = read_until_eof + self._upgraded = False + self._auto_decompress = auto_decompress + self._content_encoding = None + + self._csettings.on_url = cb_on_url + self._csettings.on_status = cb_on_status + self._csettings.on_header_field = cb_on_header_field + self._csettings.on_header_value = cb_on_header_value + self._csettings.on_headers_complete = cb_on_headers_complete + self._csettings.on_body = cb_on_body + self._csettings.on_message_begin = cb_on_message_begin + self._csettings.on_message_complete = cb_on_message_complete + self._csettings.on_chunk_header = cb_on_chunk_header + self._csettings.on_chunk_complete = cb_on_chunk_complete + + self._last_error = None + self._limit = limit + + cdef _process_header(self): + if self._raw_name: + raw_name = bytes(self._raw_name) + raw_value = bytes(self._raw_value) + + name = find_header(raw_name) + value = raw_value.decode('utf-8', 'surrogateescape') + + self._headers.add(name, value) + + if name is CONTENT_ENCODING: + self._content_encoding = value + + PyByteArray_Resize(self._raw_name, 0) + PyByteArray_Resize(self._raw_value, 0) + self._has_value = False + self._raw_headers.append((raw_name, raw_value)) + + cdef _on_header_field(self, char* at, size_t length): + cdef Py_ssize_t size + cdef char *buf + if self._has_value: + self._process_header() + + size = PyByteArray_Size(self._raw_name) + PyByteArray_Resize(self._raw_name, size + length) + buf = PyByteArray_AsString(self._raw_name) + memcpy(buf + size, at, length) + + cdef _on_header_value(self, char* at, size_t length): + cdef Py_ssize_t size + cdef char *buf + + size = PyByteArray_Size(self._raw_value) + PyByteArray_Resize(self._raw_value, size + length) + buf = PyByteArray_AsString(self._raw_value) + memcpy(buf + size, at, length) + self._has_value = True + + cdef _on_headers_complete(self): + self._process_header() + + method = http_method_str(self._cparser.method) + should_close = not cparser.llhttp_should_keep_alive(self._cparser) + upgrade = self._cparser.upgrade + chunked = self._cparser.flags & cparser.F_CHUNKED + + raw_headers = tuple(self._raw_headers) + headers = CIMultiDictProxy(self._headers) + + if upgrade or self._cparser.method == cparser.HTTP_CONNECT: + self._upgraded = True + + # do not support old websocket spec + if SEC_WEBSOCKET_KEY1 in headers: + raise InvalidHeader(SEC_WEBSOCKET_KEY1) + + encoding = None + enc = self._content_encoding + if enc is not None: + self._content_encoding = None + enc = enc.lower() + if enc in ('gzip', 'deflate', 'br'): + encoding = enc + + if self._cparser.type == cparser.HTTP_REQUEST: + msg = _new_request_message( + method, self._path, + self.http_version(), headers, raw_headers, + should_close, encoding, upgrade, chunked, self._url) + else: + msg = _new_response_message( + self.http_version(), self._cparser.status_code, self._reason, + headers, raw_headers, should_close, encoding, + upgrade, chunked) + + if ( + ULLONG_MAX > self._cparser.content_length > 0 or chunked or + self._cparser.method == cparser.HTTP_CONNECT or + (self._cparser.status_code >= 199 and + self._cparser.content_length == 0 and + self._read_until_eof) + ): + payload = StreamReader( + self._protocol, timer=self._timer, loop=self._loop, + limit=self._limit) + else: + payload = EMPTY_PAYLOAD + + self._payload = payload + if encoding is not None and self._auto_decompress: + self._payload = DeflateBuffer(payload, encoding) + + if not self._response_with_body: + payload = EMPTY_PAYLOAD + + self._messages.append((msg, payload)) + + cdef _on_message_complete(self): + self._payload.feed_eof() + self._payload = None + + cdef _on_chunk_header(self): + self._payload.begin_http_chunk_receiving() + + cdef _on_chunk_complete(self): + self._payload.end_http_chunk_receiving() + + cdef object _on_status_complete(self): + pass + + cdef inline http_version(self): + cdef cparser.llhttp_t* parser = self._cparser + + if parser.http_major == 1: + if parser.http_minor == 0: + return HttpVersion10 + elif parser.http_minor == 1: + return HttpVersion11 + + return HttpVersion(parser.http_major, parser.http_minor) + + ### Public API ### + + def feed_eof(self): + cdef bytes desc + + if self._payload is not None: + if self._cparser.flags & cparser.F_CHUNKED: + raise TransferEncodingError( + "Not enough data for satisfy transfer length header.") + elif self._cparser.flags & cparser.F_CONTENT_LENGTH: + raise ContentLengthError( + "Not enough data for satisfy content length header.") + elif cparser.llhttp_get_errno(self._cparser) != cparser.HPE_OK: + desc = cparser.llhttp_get_error_reason(self._cparser) + raise PayloadEncodingError(desc.decode('latin-1')) + else: + self._payload.feed_eof() + elif self._started: + self._on_headers_complete() + if self._messages: + return self._messages[-1][0] + + def feed_data(self, data): + cdef: + size_t data_len + size_t nb + cdef cparser.llhttp_errno_t errno + + PyObject_GetBuffer(data, &self.py_buf, PyBUF_SIMPLE) + data_len = self.py_buf.len + + errno = cparser.llhttp_execute( + self._cparser, + self.py_buf.buf, + data_len) + + if errno is cparser.HPE_PAUSED_UPGRADE: + cparser.llhttp_resume_after_upgrade(self._cparser) + + nb = cparser.llhttp_get_error_pos(self._cparser) - self.py_buf.buf + + PyBuffer_Release(&self.py_buf) + + if errno not in (cparser.HPE_OK, cparser.HPE_PAUSED_UPGRADE): + if self._payload_error == 0: + if self._last_error is not None: + ex = self._last_error + self._last_error = None + else: + after = cparser.llhttp_get_error_pos(self._cparser) + before = data[:after - self.py_buf.buf] + after_b = after.split(b"\r\n", 1)[0] + before = before.rsplit(b"\r\n", 1)[-1] + data = before + after_b + pointer = " " * (len(repr(before))-1) + "^" + ex = parser_error_from_errno(self._cparser, data, pointer) + self._payload = None + raise ex + + if self._messages: + messages = self._messages + self._messages = [] + else: + messages = () + + if self._upgraded: + return messages, True, data[nb:] + else: + return messages, False, b'' + + def set_upgraded(self, val): + self._upgraded = val + + +cdef class HttpRequestParser(HttpParser): + + def __init__( + self, protocol, loop, int limit, timer=None, + size_t max_line_size=8190, size_t max_headers=32768, + size_t max_field_size=8190, payload_exception=None, + bint response_with_body=True, bint read_until_eof=False, + bint auto_decompress=True, + ): + self._init(cparser.HTTP_REQUEST, protocol, loop, limit, timer, + max_line_size, max_headers, max_field_size, + payload_exception, response_with_body, read_until_eof, + auto_decompress) + + cdef object _on_status_complete(self): + cdef int idx1, idx2 + if not self._buf: + return + self._path = self._buf.decode('utf-8', 'surrogateescape') + try: + idx3 = len(self._path) + if self._cparser.method == cparser.HTTP_CONNECT: + # authority-form, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.3 + self._url = URL.build(authority=self._path, encoded=True) + elif idx3 > 1 and self._path[0] == '/': + # origin-form, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.1 + idx1 = self._path.find("?") + if idx1 == -1: + query = "" + idx2 = self._path.find("#") + if idx2 == -1: + path = self._path + fragment = "" + else: + path = self._path[0: idx2] + fragment = self._path[idx2+1:] + + else: + path = self._path[0:idx1] + idx1 += 1 + idx2 = self._path.find("#", idx1+1) + if idx2 == -1: + query = self._path[idx1:] + fragment = "" + else: + query = self._path[idx1: idx2] + fragment = self._path[idx2+1:] + + self._url = URL.build( + path=path, + query_string=query, + fragment=fragment, + encoded=True, + ) + else: + # absolute-form for proxy maybe, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.2 + self._url = URL(self._path, encoded=True) + finally: + PyByteArray_Resize(self._buf, 0) + + +cdef class HttpResponseParser(HttpParser): + + def __init__( + self, protocol, loop, int limit, timer=None, + size_t max_line_size=8190, size_t max_headers=32768, + size_t max_field_size=8190, payload_exception=None, + bint response_with_body=True, bint read_until_eof=False, + bint auto_decompress=True + ): + self._init(cparser.HTTP_RESPONSE, protocol, loop, limit, timer, + max_line_size, max_headers, max_field_size, + payload_exception, response_with_body, read_until_eof, + auto_decompress) + # Use strict parsing on dev mode, so users are warned about broken servers. + if not DEBUG: + cparser.llhttp_set_lenient_headers(self._cparser, 1) + cparser.llhttp_set_lenient_optional_cr_before_lf(self._cparser, 1) + cparser.llhttp_set_lenient_spaces_after_chunk_size(self._cparser, 1) + + cdef object _on_status_complete(self): + if self._buf: + self._reason = self._buf.decode('utf-8', 'surrogateescape') + PyByteArray_Resize(self._buf, 0) + else: + self._reason = self._reason or '' + +cdef int cb_on_message_begin(cparser.llhttp_t* parser) except -1: + cdef HttpParser pyparser = parser.data + + pyparser._started = True + pyparser._headers = CIMultiDict() + pyparser._raw_headers = [] + PyByteArray_Resize(pyparser._buf, 0) + pyparser._path = None + pyparser._reason = None + return 0 + + +cdef int cb_on_url(cparser.llhttp_t* parser, + const char *at, size_t length) except -1: + cdef HttpParser pyparser = parser.data + try: + if length > pyparser._max_line_size: + raise LineTooLong( + 'Status line is too long', pyparser._max_line_size, length) + extend(pyparser._buf, at, length) + except BaseException as ex: + pyparser._last_error = ex + return -1 + else: + return 0 + + +cdef int cb_on_status(cparser.llhttp_t* parser, + const char *at, size_t length) except -1: + cdef HttpParser pyparser = parser.data + cdef str reason + try: + if length > pyparser._max_line_size: + raise LineTooLong( + 'Status line is too long', pyparser._max_line_size, length) + extend(pyparser._buf, at, length) + except BaseException as ex: + pyparser._last_error = ex + return -1 + else: + return 0 + + +cdef int cb_on_header_field(cparser.llhttp_t* parser, + const char *at, size_t length) except -1: + cdef HttpParser pyparser = parser.data + cdef Py_ssize_t size + try: + pyparser._on_status_complete() + size = len(pyparser._raw_name) + length + if size > pyparser._max_field_size: + raise LineTooLong( + 'Header name is too long', pyparser._max_field_size, size) + pyparser._on_header_field(at, length) + except BaseException as ex: + pyparser._last_error = ex + return -1 + else: + return 0 + + +cdef int cb_on_header_value(cparser.llhttp_t* parser, + const char *at, size_t length) except -1: + cdef HttpParser pyparser = parser.data + cdef Py_ssize_t size + try: + size = len(pyparser._raw_value) + length + if size > pyparser._max_field_size: + raise LineTooLong( + 'Header value is too long', pyparser._max_field_size, size) + pyparser._on_header_value(at, length) + except BaseException as ex: + pyparser._last_error = ex + return -1 + else: + return 0 + + +cdef int cb_on_headers_complete(cparser.llhttp_t* parser) except -1: + cdef HttpParser pyparser = parser.data + try: + pyparser._on_status_complete() + pyparser._on_headers_complete() + except BaseException as exc: + pyparser._last_error = exc + return -1 + else: + if ( + pyparser._cparser.upgrade or + pyparser._cparser.method == cparser.HTTP_CONNECT + ): + return 2 + else: + return 0 + + +cdef int cb_on_body(cparser.llhttp_t* parser, + const char *at, size_t length) except -1: + cdef HttpParser pyparser = parser.data + cdef bytes body = at[:length] + try: + pyparser._payload.feed_data(body, length) + except BaseException as exc: + if pyparser._payload_exception is not None: + pyparser._payload.set_exception(pyparser._payload_exception(str(exc))) + else: + pyparser._payload.set_exception(exc) + pyparser._payload_error = 1 + return -1 + else: + return 0 + + +cdef int cb_on_message_complete(cparser.llhttp_t* parser) except -1: + cdef HttpParser pyparser = parser.data + try: + pyparser._started = False + pyparser._on_message_complete() + except BaseException as exc: + pyparser._last_error = exc + return -1 + else: + return 0 + + +cdef int cb_on_chunk_header(cparser.llhttp_t* parser) except -1: + cdef HttpParser pyparser = parser.data + try: + pyparser._on_chunk_header() + except BaseException as exc: + pyparser._last_error = exc + return -1 + else: + return 0 + + +cdef int cb_on_chunk_complete(cparser.llhttp_t* parser) except -1: + cdef HttpParser pyparser = parser.data + try: + pyparser._on_chunk_complete() + except BaseException as exc: + pyparser._last_error = exc + return -1 + else: + return 0 + + +cdef parser_error_from_errno(cparser.llhttp_t* parser, data, pointer): + cdef cparser.llhttp_errno_t errno = cparser.llhttp_get_errno(parser) + cdef bytes desc = cparser.llhttp_get_error_reason(parser) + + if errno in (cparser.HPE_CB_MESSAGE_BEGIN, + cparser.HPE_CB_HEADERS_COMPLETE, + cparser.HPE_CB_MESSAGE_COMPLETE, + cparser.HPE_CB_CHUNK_HEADER, + cparser.HPE_CB_CHUNK_COMPLETE, + cparser.HPE_INVALID_CONSTANT, + cparser.HPE_INVALID_HEADER_TOKEN, + cparser.HPE_INVALID_CONTENT_LENGTH, + cparser.HPE_INVALID_CHUNK_SIZE, + cparser.HPE_INVALID_EOF_STATE, + cparser.HPE_INVALID_TRANSFER_ENCODING): + cls = BadHttpMessage + + elif errno == cparser.HPE_INVALID_STATUS: + cls = BadStatusLine + + elif errno == cparser.HPE_INVALID_METHOD: + cls = BadStatusLine + + elif errno == cparser.HPE_INVALID_VERSION: + cls = BadStatusLine + + elif errno == cparser.HPE_INVALID_URL: + cls = InvalidURLError + + else: + cls = BadHttpMessage + + return cls("{}:\n\n {!r}\n {}".format(desc.decode("latin-1"), data, pointer)) diff --git a/aiohttp/_http_writer.pyx b/aiohttp/_http_writer.pyx new file mode 100644 index 0000000..eff8521 --- /dev/null +++ b/aiohttp/_http_writer.pyx @@ -0,0 +1,163 @@ +from cpython.bytes cimport PyBytes_FromStringAndSize +from cpython.exc cimport PyErr_NoMemory +from cpython.mem cimport PyMem_Free, PyMem_Malloc, PyMem_Realloc +from cpython.object cimport PyObject_Str +from libc.stdint cimport uint8_t, uint64_t +from libc.string cimport memcpy + +from multidict import istr + +DEF BUF_SIZE = 16 * 1024 # 16KiB +cdef char BUFFER[BUF_SIZE] + +cdef object _istr = istr + + +# ----------------- writer --------------------------- + +cdef struct Writer: + char *buf + Py_ssize_t size + Py_ssize_t pos + + +cdef inline void _init_writer(Writer* writer): + writer.buf = &BUFFER[0] + writer.size = BUF_SIZE + writer.pos = 0 + + +cdef inline void _release_writer(Writer* writer): + if writer.buf != BUFFER: + PyMem_Free(writer.buf) + + +cdef inline int _write_byte(Writer* writer, uint8_t ch): + cdef char * buf + cdef Py_ssize_t size + + if writer.pos == writer.size: + # reallocate + size = writer.size + BUF_SIZE + if writer.buf == BUFFER: + buf = PyMem_Malloc(size) + if buf == NULL: + PyErr_NoMemory() + return -1 + memcpy(buf, writer.buf, writer.size) + else: + buf = PyMem_Realloc(writer.buf, size) + if buf == NULL: + PyErr_NoMemory() + return -1 + writer.buf = buf + writer.size = size + writer.buf[writer.pos] = ch + writer.pos += 1 + return 0 + + +cdef inline int _write_utf8(Writer* writer, Py_UCS4 symbol): + cdef uint64_t utf = symbol + + if utf < 0x80: + return _write_byte(writer, utf) + elif utf < 0x800: + if _write_byte(writer, (0xc0 | (utf >> 6))) < 0: + return -1 + return _write_byte(writer, (0x80 | (utf & 0x3f))) + elif 0xD800 <= utf <= 0xDFFF: + # surogate pair, ignored + return 0 + elif utf < 0x10000: + if _write_byte(writer, (0xe0 | (utf >> 12))) < 0: + return -1 + if _write_byte(writer, (0x80 | ((utf >> 6) & 0x3f))) < 0: + return -1 + return _write_byte(writer, (0x80 | (utf & 0x3f))) + elif utf > 0x10FFFF: + # symbol is too large + return 0 + else: + if _write_byte(writer, (0xf0 | (utf >> 18))) < 0: + return -1 + if _write_byte(writer, + (0x80 | ((utf >> 12) & 0x3f))) < 0: + return -1 + if _write_byte(writer, + (0x80 | ((utf >> 6) & 0x3f))) < 0: + return -1 + return _write_byte(writer, (0x80 | (utf & 0x3f))) + + +cdef inline int _write_str(Writer* writer, str s): + cdef Py_UCS4 ch + for ch in s: + if _write_utf8(writer, ch) < 0: + return -1 + + +# --------------- _serialize_headers ---------------------- + +cdef str to_str(object s): + typ = type(s) + if typ is str: + return s + elif typ is _istr: + return PyObject_Str(s) + elif not isinstance(s, str): + raise TypeError("Cannot serialize non-str key {!r}".format(s)) + else: + return str(s) + + +cdef void _safe_header(str string) except *: + if "\r" in string or "\n" in string: + raise ValueError( + "Newline or carriage return character detected in HTTP status message or " + "header. This is a potential security issue." + ) + + +def _serialize_headers(str status_line, headers): + cdef Writer writer + cdef object key + cdef object val + cdef bytes ret + + _init_writer(&writer) + + for key, val in headers.items(): + _safe_header(to_str(key)) + _safe_header(to_str(val)) + + try: + if _write_str(&writer, status_line) < 0: + raise + if _write_byte(&writer, b'\r') < 0: + raise + if _write_byte(&writer, b'\n') < 0: + raise + + for key, val in headers.items(): + if _write_str(&writer, to_str(key)) < 0: + raise + if _write_byte(&writer, b':') < 0: + raise + if _write_byte(&writer, b' ') < 0: + raise + if _write_str(&writer, to_str(val)) < 0: + raise + if _write_byte(&writer, b'\r') < 0: + raise + if _write_byte(&writer, b'\n') < 0: + raise + + if _write_byte(&writer, b'\r') < 0: + raise + if _write_byte(&writer, b'\n') < 0: + raise + + return PyBytes_FromStringAndSize(writer.buf, writer.pos) + finally: + _release_writer(&writer) diff --git a/aiohttp/_websocket.pyx b/aiohttp/_websocket.pyx new file mode 100644 index 0000000..94318d2 --- /dev/null +++ b/aiohttp/_websocket.pyx @@ -0,0 +1,56 @@ +from cpython cimport PyBytes_AsString + + +#from cpython cimport PyByteArray_AsString # cython still not exports that +cdef extern from "Python.h": + char* PyByteArray_AsString(bytearray ba) except NULL + +from libc.stdint cimport uint32_t, uint64_t, uintmax_t + + +def _websocket_mask_cython(object mask, object data): + """Note, this function mutates its `data` argument + """ + cdef: + Py_ssize_t data_len, i + # bit operations on signed integers are implementation-specific + unsigned char * in_buf + const unsigned char * mask_buf + uint32_t uint32_msk + uint64_t uint64_msk + + assert len(mask) == 4 + + if not isinstance(mask, bytes): + mask = bytes(mask) + + if isinstance(data, bytearray): + data = data + else: + data = bytearray(data) + + data_len = len(data) + in_buf = PyByteArray_AsString(data) + mask_buf = PyBytes_AsString(mask) + uint32_msk = (mask_buf)[0] + + # TODO: align in_data ptr to achieve even faster speeds + # does it need in python ?! malloc() always aligns to sizeof(long) bytes + + if sizeof(size_t) >= 8: + uint64_msk = uint32_msk + uint64_msk = (uint64_msk << 32) | uint32_msk + + while data_len >= 8: + (in_buf)[0] ^= uint64_msk + in_buf += 8 + data_len -= 8 + + + while data_len >= 4: + (in_buf)[0] ^= uint32_msk + in_buf += 4 + data_len -= 4 + + for i in range(0, data_len): + in_buf[i] ^= mask_buf[i] diff --git a/aiohttp/abc.py b/aiohttp/abc.py new file mode 100644 index 0000000..41200b9 --- /dev/null +++ b/aiohttp/abc.py @@ -0,0 +1,214 @@ +import logging +from abc import ABC, abstractmethod +from collections.abc import Sized +from http.cookies import BaseCookie, Morsel +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Dict, + Generator, + Iterable, + List, + Optional, + Tuple, +) + +from multidict import CIMultiDict +from yarl import URL + +from .typedefs import LooseCookies + +if TYPE_CHECKING: # pragma: no cover + from .web_app import Application + from .web_exceptions import HTTPException + from .web_request import BaseRequest, Request + from .web_response import StreamResponse +else: + BaseRequest = Request = Application = StreamResponse = None + HTTPException = None + + +class AbstractRouter(ABC): + def __init__(self) -> None: + self._frozen = False + + def post_init(self, app: Application) -> None: + """Post init stage. + + Not an abstract method for sake of backward compatibility, + but if the router wants to be aware of the application + it can override this. + """ + + @property + def frozen(self) -> bool: + return self._frozen + + def freeze(self) -> None: + """Freeze router.""" + self._frozen = True + + @abstractmethod + async def resolve(self, request: Request) -> "AbstractMatchInfo": + """Return MATCH_INFO for given request""" + + +class AbstractMatchInfo(ABC): + @property # pragma: no branch + @abstractmethod + def handler(self) -> Callable[[Request], Awaitable[StreamResponse]]: + """Execute matched request handler""" + + @property + @abstractmethod + def expect_handler( + self, + ) -> Callable[[Request], Awaitable[Optional[StreamResponse]]]: + """Expect handler for 100-continue processing""" + + @property # pragma: no branch + @abstractmethod + def http_exception(self) -> Optional[HTTPException]: + """HTTPException instance raised on router's resolving, or None""" + + @abstractmethod # pragma: no branch + def get_info(self) -> Dict[str, Any]: + """Return a dict with additional info useful for introspection""" + + @property # pragma: no branch + @abstractmethod + def apps(self) -> Tuple[Application, ...]: + """Stack of nested applications. + + Top level application is left-most element. + + """ + + @abstractmethod + def add_app(self, app: Application) -> None: + """Add application to the nested apps stack.""" + + @abstractmethod + def freeze(self) -> None: + """Freeze the match info. + + The method is called after route resolution. + + After the call .add_app() is forbidden. + + """ + + +class AbstractView(ABC): + """Abstract class based view.""" + + def __init__(self, request: Request) -> None: + self._request = request + + @property + def request(self) -> Request: + """Request instance.""" + return self._request + + @abstractmethod + def __await__(self) -> Generator[Any, None, StreamResponse]: + """Execute the view handler.""" + + +class AbstractResolver(ABC): + """Abstract DNS resolver.""" + + @abstractmethod + async def resolve(self, host: str, port: int, family: int) -> List[Dict[str, Any]]: + """Return IP address for given hostname""" + + @abstractmethod + async def close(self) -> None: + """Release resolver""" + + +if TYPE_CHECKING: # pragma: no cover + IterableBase = Iterable[Morsel[str]] +else: + IterableBase = Iterable + + +ClearCookiePredicate = Callable[["Morsel[str]"], bool] + + +class AbstractCookieJar(Sized, IterableBase): + """Abstract Cookie Jar.""" + + @abstractmethod + def clear(self, predicate: Optional[ClearCookiePredicate] = None) -> None: + """Clear all cookies if no predicate is passed.""" + + @abstractmethod + def clear_domain(self, domain: str) -> None: + """Clear all cookies for domain and all subdomains.""" + + @abstractmethod + def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None: + """Update cookies.""" + + @abstractmethod + def filter_cookies(self, request_url: URL) -> "BaseCookie[str]": + """Return the jar's cookies filtered by their attributes.""" + + +class AbstractStreamWriter(ABC): + """Abstract stream writer.""" + + buffer_size = 0 + output_size = 0 + length: Optional[int] = 0 + + @abstractmethod + async def write(self, chunk: bytes) -> None: + """Write chunk into stream.""" + + @abstractmethod + async def write_eof(self, chunk: bytes = b"") -> None: + """Write last chunk.""" + + @abstractmethod + async def drain(self) -> None: + """Flush the write buffer.""" + + @abstractmethod + def enable_compression(self, encoding: str = "deflate") -> None: + """Enable HTTP body compression""" + + @abstractmethod + def enable_chunking(self) -> None: + """Enable HTTP chunked mode""" + + @abstractmethod + async def write_headers( + self, status_line: str, headers: "CIMultiDict[str]" + ) -> None: + """Write HTTP headers""" + + +class AbstractAccessLogger(ABC): + """Abstract writer to access log.""" + + def __init__(self, logger: logging.Logger, log_format: str) -> None: + self.logger = logger + self.log_format = log_format + + @abstractmethod + def log(self, request: BaseRequest, response: StreamResponse, time: float) -> None: + """Emit log to logger.""" + + +class AbstractAsyncAccessLogger(ABC): + """Abstract asynchronous writer to access log.""" + + @abstractmethod + async def log( + self, request: BaseRequest, response: StreamResponse, request_start: float + ) -> None: + """Emit log to logger.""" diff --git a/aiohttp/base_protocol.py b/aiohttp/base_protocol.py new file mode 100644 index 0000000..4c9f0a7 --- /dev/null +++ b/aiohttp/base_protocol.py @@ -0,0 +1,90 @@ +import asyncio +from typing import Optional, cast + +from .tcp_helpers import tcp_nodelay + + +class BaseProtocol(asyncio.Protocol): + __slots__ = ( + "_loop", + "_paused", + "_drain_waiter", + "_connection_lost", + "_reading_paused", + "transport", + ) + + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: + self._loop: asyncio.AbstractEventLoop = loop + self._paused = False + self._drain_waiter: Optional[asyncio.Future[None]] = None + self._reading_paused = False + + self.transport: Optional[asyncio.Transport] = None + + @property + def connected(self) -> bool: + """Return True if the connection is open.""" + return self.transport is not None + + def pause_writing(self) -> None: + assert not self._paused + self._paused = True + + def resume_writing(self) -> None: + assert self._paused + self._paused = False + + waiter = self._drain_waiter + if waiter is not None: + self._drain_waiter = None + if not waiter.done(): + waiter.set_result(None) + + def pause_reading(self) -> None: + if not self._reading_paused and self.transport is not None: + try: + self.transport.pause_reading() + except (AttributeError, NotImplementedError, RuntimeError): + pass + self._reading_paused = True + + def resume_reading(self) -> None: + if self._reading_paused and self.transport is not None: + try: + self.transport.resume_reading() + except (AttributeError, NotImplementedError, RuntimeError): + pass + self._reading_paused = False + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + tr = cast(asyncio.Transport, transport) + tcp_nodelay(tr, True) + self.transport = tr + + def connection_lost(self, exc: Optional[BaseException]) -> None: + # Wake up the writer if currently paused. + self.transport = None + if not self._paused: + return + waiter = self._drain_waiter + if waiter is None: + return + self._drain_waiter = None + if waiter.done(): + return + if exc is None: + waiter.set_result(None) + else: + waiter.set_exception(exc) + + async def _drain_helper(self) -> None: + if not self.connected: + raise ConnectionResetError("Connection lost") + if not self._paused: + return + waiter = self._drain_waiter + if waiter is None: + waiter = self._loop.create_future() + self._drain_waiter = waiter + await asyncio.shield(waiter) diff --git a/aiohttp/client.py b/aiohttp/client.py new file mode 100644 index 0000000..1d9d9fe --- /dev/null +++ b/aiohttp/client.py @@ -0,0 +1,1289 @@ +"""HTTP Client for asyncio.""" + +import asyncio +import base64 +import dataclasses +import hashlib +import json +import os +import sys +import traceback +import warnings +from contextlib import suppress +from types import SimpleNamespace, TracebackType +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Collection, + Coroutine, + Final, + FrozenSet, + Generator, + Generic, + Iterable, + List, + Literal, + Mapping, + Optional, + Set, + Tuple, + Type, + TypeVar, + Union, + final, +) + +from multidict import CIMultiDict, MultiDict, MultiDictProxy, istr +from yarl import URL + +from . import hdrs, http, payload +from .abc import AbstractCookieJar +from .client_exceptions import ( + ClientConnectionError, + ClientConnectorCertificateError, + ClientConnectorError, + ClientConnectorSSLError, + ClientError, + ClientHttpProxyError, + ClientOSError, + ClientPayloadError, + ClientProxyConnectionError, + ClientResponseError, + ClientSSLError, + ContentTypeError, + InvalidURL, + ServerConnectionError, + ServerDisconnectedError, + ServerFingerprintMismatch, + ServerTimeoutError, + TooManyRedirects, + WSServerHandshakeError, +) +from .client_reqrep import ( + SSL_ALLOWED_TYPES, + ClientRequest, + ClientResponse, + Fingerprint, + RequestInfo, +) +from .client_ws import ( + DEFAULT_WS_CLIENT_TIMEOUT, + ClientWebSocketResponse, + ClientWSTimeout, +) +from .connector import BaseConnector, NamedPipeConnector, TCPConnector, UnixConnector +from .cookiejar import CookieJar +from .helpers import ( + _SENTINEL, + BasicAuth, + TimeoutHandle, + ceil_timeout, + get_env_proxy_for_url, + sentinel, + strip_auth_from_url, +) +from .http import WS_KEY, HttpVersion, WebSocketReader, WebSocketWriter +from .http_websocket import WSHandshakeError, WSMessage, ws_ext_gen, ws_ext_parse +from .streams import FlowControlDataQueue +from .tracing import Trace, TraceConfig +from .typedefs import JSONEncoder, LooseCookies, LooseHeaders, StrOrURL + +__all__ = ( + # client_exceptions + "ClientConnectionError", + "ClientConnectorCertificateError", + "ClientConnectorError", + "ClientConnectorSSLError", + "ClientError", + "ClientHttpProxyError", + "ClientOSError", + "ClientPayloadError", + "ClientProxyConnectionError", + "ClientResponseError", + "ClientSSLError", + "ContentTypeError", + "InvalidURL", + "ServerConnectionError", + "ServerDisconnectedError", + "ServerFingerprintMismatch", + "ServerTimeoutError", + "TooManyRedirects", + "WSServerHandshakeError", + # client_reqrep + "ClientRequest", + "ClientResponse", + "Fingerprint", + "RequestInfo", + # connector + "BaseConnector", + "TCPConnector", + "UnixConnector", + "NamedPipeConnector", + # client_ws + "ClientWebSocketResponse", + # client + "ClientSession", + "ClientTimeout", + "request", +) + + +if TYPE_CHECKING: + from ssl import SSLContext +else: + SSLContext = None + + +@dataclasses.dataclass(frozen=True) +class ClientTimeout: + total: Optional[float] = None + connect: Optional[float] = None + sock_read: Optional[float] = None + sock_connect: Optional[float] = None + ceil_threshold: float = 5 + + # pool_queue_timeout: Optional[float] = None + # dns_resolution_timeout: Optional[float] = None + # socket_connect_timeout: Optional[float] = None + # connection_acquiring_timeout: Optional[float] = None + # new_connection_timeout: Optional[float] = None + # http_header_timeout: Optional[float] = None + # response_body_timeout: Optional[float] = None + + # to create a timeout specific for a single request, either + # - create a completely new one to overwrite the default + # - or use https://docs.python.org/3/library/dataclasses.html#dataclasses.replace + # to overwrite the defaults + + +# 5 Minute default read timeout +DEFAULT_TIMEOUT: Final[ClientTimeout] = ClientTimeout(total=5 * 60) + +_RetType = TypeVar("_RetType") +_CharsetResolver = Callable[[ClientResponse, bytes], str] + + +@final +class ClientSession: + """First-class interface for making HTTP requests.""" + + __slots__ = ( + "_base_url", + "_source_traceback", + "_connector", + "_loop", + "_cookie_jar", + "_connector_owner", + "_default_auth", + "_version", + "_json_serialize", + "_requote_redirect_url", + "_timeout", + "_raise_for_status", + "_auto_decompress", + "_trust_env", + "_default_headers", + "_skip_auto_headers", + "_request_class", + "_response_class", + "_ws_response_class", + "_trace_configs", + "_read_bufsize", + "_max_line_size", + "_max_field_size", + "_resolve_charset", + ) + + def __init__( + self, + base_url: Optional[StrOrURL] = None, + *, + connector: Optional[BaseConnector] = None, + cookies: Optional[LooseCookies] = None, + headers: Optional[LooseHeaders] = None, + skip_auto_headers: Optional[Iterable[str]] = None, + auth: Optional[BasicAuth] = None, + json_serialize: JSONEncoder = json.dumps, + request_class: Type[ClientRequest] = ClientRequest, + response_class: Type[ClientResponse] = ClientResponse, + ws_response_class: Type[ClientWebSocketResponse] = ClientWebSocketResponse, + version: HttpVersion = http.HttpVersion11, + cookie_jar: Optional[AbstractCookieJar] = None, + connector_owner: bool = True, + raise_for_status: Union[ + bool, Callable[[ClientResponse], Awaitable[None]] + ] = False, + timeout: Union[_SENTINEL, ClientTimeout, None] = sentinel, + auto_decompress: bool = True, + trust_env: bool = False, + requote_redirect_url: bool = True, + trace_configs: Optional[List[TraceConfig]] = None, + read_bufsize: int = 2**16, + max_line_size: int = 8190, + max_field_size: int = 8190, + fallback_charset_resolver: _CharsetResolver = lambda r, b: "utf-8", + ) -> None: + if base_url is None or isinstance(base_url, URL): + self._base_url: Optional[URL] = base_url + else: + self._base_url = URL(base_url) + assert ( + self._base_url.origin() == self._base_url + ), "Only absolute URLs without path part are supported" + + loop = asyncio.get_running_loop() + + if connector is None: + connector = TCPConnector() + + # Initialize these three attrs before raising any exception, + # they are used in __del__ + self._connector: Optional[BaseConnector] = connector + self._loop = loop + if loop.get_debug(): + self._source_traceback: Optional[ + traceback.StackSummary + ] = traceback.extract_stack(sys._getframe(1)) + else: + self._source_traceback = None + + if connector._loop is not loop: + raise RuntimeError("Session and connector have to use same event loop") + + if cookie_jar is None: + cookie_jar = CookieJar() + self._cookie_jar = cookie_jar + + if cookies is not None: + self._cookie_jar.update_cookies(cookies) + + self._connector_owner = connector_owner + self._default_auth = auth + self._version = version + self._json_serialize = json_serialize + if timeout is sentinel or timeout is None: + self._timeout = DEFAULT_TIMEOUT + else: + self._timeout = timeout + self._raise_for_status = raise_for_status + self._auto_decompress = auto_decompress + self._trust_env = trust_env + self._requote_redirect_url = requote_redirect_url + self._read_bufsize = read_bufsize + self._max_line_size = max_line_size + self._max_field_size = max_field_size + + # Convert to list of tuples + if headers: + real_headers: CIMultiDict[str] = CIMultiDict(headers) + else: + real_headers = CIMultiDict() + self._default_headers: CIMultiDict[str] = real_headers + if skip_auto_headers is not None: + self._skip_auto_headers = frozenset(istr(i) for i in skip_auto_headers) + else: + self._skip_auto_headers = frozenset() + + self._request_class = request_class + self._response_class = response_class + self._ws_response_class = ws_response_class + + self._trace_configs = trace_configs or [] + for trace_config in self._trace_configs: + trace_config.freeze() + + self._resolve_charset = fallback_charset_resolver + + def __init_subclass__(cls: Type["ClientSession"]) -> None: + raise TypeError( + "Inheritance class {} from ClientSession " + "is forbidden".format(cls.__name__) + ) + + def __del__(self, _warnings: Any = warnings) -> None: + if not self.closed: + _warnings.warn( + f"Unclosed client session {self!r}", + ResourceWarning, + source=self, + ) + context = {"client_session": self, "message": "Unclosed client session"} + if self._source_traceback is not None: + context["source_traceback"] = self._source_traceback + self._loop.call_exception_handler(context) + + def request( + self, method: str, url: StrOrURL, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP request.""" + return _RequestContextManager(self._request(method, url, **kwargs)) + + def _build_url(self, str_or_url: StrOrURL) -> URL: + url = URL(str_or_url) + if self._base_url is None: + return url + else: + assert not url.is_absolute() and url.path.startswith("/") + return self._base_url.join(url) + + async def _request( + self, + method: str, + str_or_url: StrOrURL, + *, + params: Optional[Mapping[str, str]] = None, + data: Any = None, + json: Any = None, + cookies: Optional[LooseCookies] = None, + headers: Optional[LooseHeaders] = None, + skip_auto_headers: Optional[Iterable[str]] = None, + auth: Optional[BasicAuth] = None, + allow_redirects: bool = True, + max_redirects: int = 10, + compress: Optional[str] = None, + chunked: Optional[bool] = None, + expect100: bool = False, + raise_for_status: Union[ + None, bool, Callable[[ClientResponse], Awaitable[None]] + ] = None, + read_until_eof: bool = True, + proxy: Optional[StrOrURL] = None, + proxy_auth: Optional[BasicAuth] = None, + timeout: Union[ClientTimeout, _SENTINEL, None] = sentinel, + ssl: Optional[Union[SSLContext, Literal[False], Fingerprint]] = None, + server_hostname: Optional[str] = None, + proxy_headers: Optional[LooseHeaders] = None, + trace_request_ctx: Optional[SimpleNamespace] = None, + read_bufsize: Optional[int] = None, + auto_decompress: Optional[bool] = None, + max_line_size: Optional[int] = None, + max_field_size: Optional[int] = None, + ) -> ClientResponse: + # NOTE: timeout clamps existing connect and read timeouts. We cannot + # set the default to None because we need to detect if the user wants + # to use the existing timeouts by setting timeout to None. + + if self.closed: + raise RuntimeError("Session is closed") + + if not isinstance(ssl, SSL_ALLOWED_TYPES): + raise TypeError( + "ssl should be SSLContext, bool, Fingerprint, " + "or None, got {!r} instead.".format(ssl) + ) + + if data is not None and json is not None: + raise ValueError( + "data and json parameters can not be used at the same time" + ) + elif json is not None: + data = payload.JsonPayload(json, dumps=self._json_serialize) + + redirects = 0 + history = [] + version = self._version + params = params or {} + + # Merge with default headers and transform to CIMultiDict + headers = self._prepare_headers(headers) + proxy_headers = self._prepare_headers(proxy_headers) + + try: + url = self._build_url(str_or_url) + except ValueError as e: + raise InvalidURL(str_or_url) from e + + skip_headers = set(self._skip_auto_headers) + if skip_auto_headers is not None: + for i in skip_auto_headers: + skip_headers.add(istr(i)) + + if proxy is not None: + try: + proxy = URL(proxy) + except ValueError as e: + raise InvalidURL(proxy) from e + + if timeout is sentinel or timeout is None: + real_timeout: ClientTimeout = self._timeout + else: + real_timeout = timeout + # timeout is cumulative for all request operations + # (request, redirects, responses, data consuming) + tm = TimeoutHandle( + self._loop, real_timeout.total, ceil_threshold=real_timeout.ceil_threshold + ) + handle = tm.start() + + if read_bufsize is None: + read_bufsize = self._read_bufsize + + if auto_decompress is None: + auto_decompress = self._auto_decompress + + if max_line_size is None: + max_line_size = self._max_line_size + + if max_field_size is None: + max_field_size = self._max_field_size + + traces = [ + Trace( + self, + trace_config, + trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx), + ) + for trace_config in self._trace_configs + ] + + for trace in traces: + await trace.send_request_start(method, url.update_query(params), headers) + + timer = tm.timer() + try: + with timer: + while True: + url, auth_from_url = strip_auth_from_url(url) + if auth and auth_from_url: + raise ValueError( + "Cannot combine AUTH argument with " + "credentials encoded in URL" + ) + + if auth is None: + auth = auth_from_url + if auth is None: + auth = self._default_auth + # It would be confusing if we support explicit + # Authorization header with auth argument + if auth is not None and hdrs.AUTHORIZATION in headers: + raise ValueError( + "Cannot combine AUTHORIZATION header " + "with AUTH argument or credentials " + "encoded in URL" + ) + + all_cookies = self._cookie_jar.filter_cookies(url) + + if cookies is not None: + tmp_cookie_jar = CookieJar() + tmp_cookie_jar.update_cookies(cookies) + req_cookies = tmp_cookie_jar.filter_cookies(url) + if req_cookies: + all_cookies.load(req_cookies) + + if proxy is not None: + proxy = URL(proxy) + elif self._trust_env: + with suppress(LookupError): + proxy, proxy_auth = get_env_proxy_for_url(url) + + req = self._request_class( + method, + url, + params=params, + headers=headers, + skip_auto_headers=skip_headers, + data=data, + cookies=all_cookies, + auth=auth, + version=version, + compress=compress, + chunked=chunked, + expect100=expect100, + loop=self._loop, + response_class=self._response_class, + proxy=proxy, + proxy_auth=proxy_auth, + timer=timer, + session=self, + ssl=ssl, + server_hostname=server_hostname, + proxy_headers=proxy_headers, + traces=traces, + trust_env=self.trust_env, + ) + + # connection timeout + try: + async with ceil_timeout( + real_timeout.connect, + ceil_threshold=real_timeout.ceil_threshold, + ): + assert self._connector is not None + conn = await self._connector.connect( + req, traces=traces, timeout=real_timeout + ) + except asyncio.TimeoutError as exc: + raise ServerTimeoutError( + f"Connection timeout to host {url}" + ) from exc + + assert conn.transport is not None + + assert conn.protocol is not None + conn.protocol.set_response_params( + timer=timer, + skip_payload=method.upper() == "HEAD", + read_until_eof=read_until_eof, + auto_decompress=auto_decompress, + read_timeout=real_timeout.sock_read, + read_bufsize=read_bufsize, + timeout_ceil_threshold=self._connector._timeout_ceil_threshold, + max_line_size=max_line_size, + max_field_size=max_field_size, + ) + + try: + try: + resp = await req.send(conn) + try: + await resp.start(conn) + except BaseException: + resp.close() + raise + except BaseException: + conn.close() + raise + except ClientError: + raise + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + raise ClientOSError(*exc.args) from exc + + self._cookie_jar.update_cookies(resp.cookies, resp.url) + + # redirects + if resp.status in (301, 302, 303, 307, 308) and allow_redirects: + for trace in traces: + await trace.send_request_redirect( + method, url.update_query(params), headers, resp + ) + + redirects += 1 + history.append(resp) + if max_redirects and redirects >= max_redirects: + resp.close() + raise TooManyRedirects( + history[0].request_info, tuple(history) + ) + + # For 301 and 302, mimic IE, now changed in RFC + # https://github.com/kennethreitz/requests/pull/269 + if (resp.status == 303 and resp.method != hdrs.METH_HEAD) or ( + resp.status in (301, 302) and resp.method == hdrs.METH_POST + ): + method = hdrs.METH_GET + data = None + if headers.get(hdrs.CONTENT_LENGTH): + headers.pop(hdrs.CONTENT_LENGTH) + + r_url = resp.headers.get(hdrs.LOCATION) or resp.headers.get( + hdrs.URI + ) + if r_url is None: + # see github.com/aio-libs/aiohttp/issues/2022 + break + else: + # reading from correct redirection + # response is forbidden + resp.release() + + try: + parsed_url = URL( + r_url, encoded=not self._requote_redirect_url + ) + + except ValueError as e: + raise InvalidURL(r_url) from e + + scheme = parsed_url.scheme + if scheme not in ("http", "https", ""): + resp.close() + raise ValueError("Can redirect only to http or https") + elif not scheme: + parsed_url = url.join(parsed_url) + + is_same_host_https_redirect = ( + url.host == parsed_url.host + and parsed_url.scheme == "https" + and url.scheme == "http" + ) + + if ( + url.origin() != parsed_url.origin() + and not is_same_host_https_redirect + ): + auth = None + headers.pop(hdrs.AUTHORIZATION, None) + + url = parsed_url + params = {} + resp.release() + continue + + break + + # check response status + if raise_for_status is None: + raise_for_status = self._raise_for_status + + if raise_for_status is None: + pass + elif callable(raise_for_status): + await raise_for_status(resp) + elif raise_for_status: + resp.raise_for_status() + + # register connection + if handle is not None: + if resp.connection is not None: + resp.connection.add_callback(handle.cancel) + else: + handle.cancel() + + resp._history = tuple(history) + + for trace in traces: + await trace.send_request_end( + method, url.update_query(params), headers, resp + ) + return resp + + except BaseException as e: + # cleanup timer + tm.close() + if handle: + handle.cancel() + handle = None + + for trace in traces: + await trace.send_request_exception( + method, url.update_query(params), headers, e + ) + raise + + def ws_connect( + self, + url: StrOrURL, + *, + method: str = hdrs.METH_GET, + protocols: Collection[str] = (), + timeout: Union[ClientWSTimeout, float, _SENTINEL, None] = sentinel, + receive_timeout: Optional[float] = None, + autoclose: bool = True, + autoping: bool = True, + heartbeat: Optional[float] = None, + auth: Optional[BasicAuth] = None, + origin: Optional[str] = None, + params: Optional[Mapping[str, str]] = None, + headers: Optional[LooseHeaders] = None, + proxy: Optional[StrOrURL] = None, + proxy_auth: Optional[BasicAuth] = None, + ssl: Union[SSLContext, Literal[False], None, Fingerprint] = None, + proxy_headers: Optional[LooseHeaders] = None, + compress: int = 0, + max_msg_size: int = 4 * 1024 * 1024, + ) -> "_WSRequestContextManager": + """Initiate websocket connection.""" + return _WSRequestContextManager( + self._ws_connect( + url, + method=method, + protocols=protocols, + timeout=timeout, + receive_timeout=receive_timeout, + autoclose=autoclose, + autoping=autoping, + heartbeat=heartbeat, + auth=auth, + origin=origin, + params=params, + headers=headers, + proxy=proxy, + proxy_auth=proxy_auth, + ssl=ssl, + proxy_headers=proxy_headers, + compress=compress, + max_msg_size=max_msg_size, + ) + ) + + async def _ws_connect( + self, + url: StrOrURL, + *, + method: str = hdrs.METH_GET, + protocols: Collection[str] = (), + timeout: Union[ClientWSTimeout, float, _SENTINEL, None] = sentinel, + receive_timeout: Optional[float] = None, + autoclose: bool = True, + autoping: bool = True, + heartbeat: Optional[float] = None, + auth: Optional[BasicAuth] = None, + origin: Optional[str] = None, + params: Optional[Mapping[str, str]] = None, + headers: Optional[LooseHeaders] = None, + proxy: Optional[StrOrURL] = None, + proxy_auth: Optional[BasicAuth] = None, + ssl: Union[SSLContext, Literal[False], None, Fingerprint] = None, + proxy_headers: Optional[LooseHeaders] = None, + compress: int = 0, + max_msg_size: int = 4 * 1024 * 1024, + ) -> ClientWebSocketResponse: + if timeout is sentinel or timeout is None: + ws_timeout = DEFAULT_WS_CLIENT_TIMEOUT + else: + if isinstance(timeout, ClientWSTimeout): + ws_timeout = timeout + else: + warnings.warn( + "parameter 'timeout' of type 'float' " + "is deprecated, please use " + "'timeout=ClientWSTimeout(ws_close=...)'", + DeprecationWarning, + stacklevel=2, + ) + ws_timeout = ClientWSTimeout(ws_close=timeout) + + if receive_timeout is not None: + warnings.warn( + "float parameter 'receive_timeout' " + "is deprecated, please use parameter " + "'timeout=ClientWSTimeout(ws_receive=...)'", + DeprecationWarning, + stacklevel=2, + ) + ws_timeout = dataclasses.replace(ws_timeout, ws_receive=receive_timeout) + + if headers is None: + real_headers: CIMultiDict[str] = CIMultiDict() + else: + real_headers = CIMultiDict(headers) + + default_headers = { + hdrs.UPGRADE: "websocket", + hdrs.CONNECTION: "Upgrade", + hdrs.SEC_WEBSOCKET_VERSION: "13", + } + + for key, value in default_headers.items(): + real_headers.setdefault(key, value) + + sec_key = base64.b64encode(os.urandom(16)) + real_headers[hdrs.SEC_WEBSOCKET_KEY] = sec_key.decode() + + if protocols: + real_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = ",".join(protocols) + if origin is not None: + real_headers[hdrs.ORIGIN] = origin + if compress: + extstr = ws_ext_gen(compress=compress) + real_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = extstr + + if not isinstance(ssl, SSL_ALLOWED_TYPES): + raise TypeError( + "ssl should be SSLContext, bool, Fingerprint, " + "or None, got {!r} instead.".format(ssl) + ) + + # send request + resp = await self.request( + method, + url, + params=params, + headers=real_headers, + read_until_eof=False, + auth=auth, + proxy=proxy, + proxy_auth=proxy_auth, + ssl=ssl, + proxy_headers=proxy_headers, + ) + + try: + # check handshake + if resp.status != 101: + raise WSServerHandshakeError( + resp.request_info, + resp.history, + message="Invalid response status", + status=resp.status, + headers=resp.headers, + ) + + if resp.headers.get(hdrs.UPGRADE, "").lower() != "websocket": + raise WSServerHandshakeError( + resp.request_info, + resp.history, + message="Invalid upgrade header", + status=resp.status, + headers=resp.headers, + ) + + if resp.headers.get(hdrs.CONNECTION, "").lower() != "upgrade": + raise WSServerHandshakeError( + resp.request_info, + resp.history, + message="Invalid connection header", + status=resp.status, + headers=resp.headers, + ) + + # key calculation + r_key = resp.headers.get(hdrs.SEC_WEBSOCKET_ACCEPT, "") + match = base64.b64encode(hashlib.sha1(sec_key + WS_KEY).digest()).decode() + if r_key != match: + raise WSServerHandshakeError( + resp.request_info, + resp.history, + message="Invalid challenge response", + status=resp.status, + headers=resp.headers, + ) + + # websocket protocol + protocol = None + if protocols and hdrs.SEC_WEBSOCKET_PROTOCOL in resp.headers: + resp_protocols = [ + proto.strip() + for proto in resp.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",") + ] + + for proto in resp_protocols: + if proto in protocols: + protocol = proto + break + + # websocket compress + notakeover = False + if compress: + compress_hdrs = resp.headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS) + if compress_hdrs: + try: + compress, notakeover = ws_ext_parse(compress_hdrs) + except WSHandshakeError as exc: + raise WSServerHandshakeError( + resp.request_info, + resp.history, + message=exc.args[0], + status=resp.status, + headers=resp.headers, + ) from exc + else: + compress = 0 + notakeover = False + + conn = resp.connection + assert conn is not None + conn_proto = conn.protocol + assert conn_proto is not None + transport = conn.transport + assert transport is not None + reader: FlowControlDataQueue[WSMessage] = FlowControlDataQueue( + conn_proto, 2**16, loop=self._loop + ) + conn_proto.set_parser(WebSocketReader(reader, max_msg_size), reader) + writer = WebSocketWriter( + conn_proto, + transport, + use_mask=True, + compress=compress, + notakeover=notakeover, + ) + except BaseException: + resp.close() + raise + else: + return self._ws_response_class( + reader, + writer, + protocol, + resp, + ws_timeout, + autoclose, + autoping, + self._loop, + heartbeat=heartbeat, + compress=compress, + client_notakeover=notakeover, + ) + + def _prepare_headers(self, headers: Optional[LooseHeaders]) -> "CIMultiDict[str]": + """Add default headers and transform it to CIMultiDict""" + # Convert headers to MultiDict + result = CIMultiDict(self._default_headers) + if headers: + if not isinstance(headers, (MultiDictProxy, MultiDict)): + headers = CIMultiDict(headers) + added_names: Set[str] = set() + for key, value in headers.items(): + if key in added_names: + result.add(key, value) + else: + result[key] = value + added_names.add(key) + return result + + def get( + self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP GET request.""" + return _RequestContextManager( + self._request(hdrs.METH_GET, url, allow_redirects=allow_redirects, **kwargs) + ) + + def options( + self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP OPTIONS request.""" + return _RequestContextManager( + self._request( + hdrs.METH_OPTIONS, url, allow_redirects=allow_redirects, **kwargs + ) + ) + + def head( + self, url: StrOrURL, *, allow_redirects: bool = False, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP HEAD request.""" + return _RequestContextManager( + self._request( + hdrs.METH_HEAD, url, allow_redirects=allow_redirects, **kwargs + ) + ) + + def post( + self, url: StrOrURL, *, data: Any = None, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP POST request.""" + return _RequestContextManager( + self._request(hdrs.METH_POST, url, data=data, **kwargs) + ) + + def put( + self, url: StrOrURL, *, data: Any = None, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP PUT request.""" + return _RequestContextManager( + self._request(hdrs.METH_PUT, url, data=data, **kwargs) + ) + + def patch( + self, url: StrOrURL, *, data: Any = None, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP PATCH request.""" + return _RequestContextManager( + self._request(hdrs.METH_PATCH, url, data=data, **kwargs) + ) + + def delete(self, url: StrOrURL, **kwargs: Any) -> "_RequestContextManager": + """Perform HTTP DELETE request.""" + return _RequestContextManager(self._request(hdrs.METH_DELETE, url, **kwargs)) + + async def close(self) -> None: + """Close underlying connector. + + Release all acquired resources. + """ + if not self.closed: + if self._connector is not None and self._connector_owner: + await self._connector.close() + self._connector = None + + @property + def closed(self) -> bool: + """Is client session closed. + + A readonly property. + """ + return self._connector is None or self._connector.closed + + @property + def connector(self) -> Optional[BaseConnector]: + """Connector instance used for the session.""" + return self._connector + + @property + def cookie_jar(self) -> AbstractCookieJar: + """The session cookies.""" + return self._cookie_jar + + @property + def version(self) -> Tuple[int, int]: + """The session HTTP protocol version.""" + return self._version + + @property + def requote_redirect_url(self) -> bool: + """Do URL requoting on redirection handling.""" + return self._requote_redirect_url + + @property + def timeout(self) -> ClientTimeout: + """Timeout for the session.""" + return self._timeout + + @property + def headers(self) -> "CIMultiDict[str]": + """The default headers of the client session.""" + return self._default_headers + + @property + def skip_auto_headers(self) -> FrozenSet[istr]: + """Headers for which autogeneration should be skipped""" + return self._skip_auto_headers + + @property + def auth(self) -> Optional[BasicAuth]: + """An object that represents HTTP Basic Authorization""" + return self._default_auth + + @property + def json_serialize(self) -> JSONEncoder: + """Json serializer callable""" + return self._json_serialize + + @property + def connector_owner(self) -> bool: + """Should connector be closed on session closing""" + return self._connector_owner + + @property + def raise_for_status( + self, + ) -> Union[bool, Callable[[ClientResponse], Awaitable[None]]]: + """Should `ClientResponse.raise_for_status()` be called for each response.""" + return self._raise_for_status + + @property + def auto_decompress(self) -> bool: + """Should the body response be automatically decompressed.""" + return self._auto_decompress + + @property + def trust_env(self) -> bool: + """ + Should proxies information from environment or netrc be trusted. + + Information is from HTTP_PROXY / HTTPS_PROXY environment variables + or ~/.netrc file if present. + """ + return self._trust_env + + @property + def trace_configs(self) -> List[TraceConfig]: + """A list of TraceConfig instances used for client tracing""" + return self._trace_configs + + def detach(self) -> None: + """Detach connector from session without closing the former. + + Session is switched to closed state anyway. + """ + self._connector = None + + async def __aenter__(self) -> "ClientSession": + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + await self.close() + + +class _BaseRequestContextManager(Coroutine[Any, Any, _RetType], Generic[_RetType]): + __slots__ = ("_coro", "_resp") + + def __init__(self, coro: Coroutine["asyncio.Future[Any]", None, _RetType]) -> None: + self._coro = coro + + def send(self, arg: None) -> "asyncio.Future[Any]": + return self._coro.send(arg) + + def throw(self, arg: BaseException) -> None: # type: ignore[override] + self._coro.throw(arg) # type: ignore[unused-awaitable] + + def close(self) -> None: + return self._coro.close() + + def __await__(self) -> Generator[Any, None, _RetType]: + ret = self._coro.__await__() + return ret + + def __iter__(self) -> Generator[Any, None, _RetType]: + return self.__await__() + + async def __aenter__(self) -> _RetType: + self._resp = await self._coro + return self._resp + + +class _RequestContextManager(_BaseRequestContextManager[ClientResponse]): + __slots__ = () + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + tb: Optional[TracebackType], + ) -> None: + # We're basing behavior on the exception as it can be caused by + # user code unrelated to the status of the connection. If you + # would like to close a connection you must do that + # explicitly. Otherwise connection error handling should kick in + # and close/recycle the connection as required. + self._resp.release() + + +class _WSRequestContextManager(_BaseRequestContextManager[ClientWebSocketResponse]): + __slots__ = () + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + tb: Optional[TracebackType], + ) -> None: + await self._resp.close() + + +class _SessionRequestContextManager: + __slots__ = ("_coro", "_resp", "_session") + + def __init__( + self, + coro: Coroutine["asyncio.Future[Any]", None, ClientResponse], + session: ClientSession, + ) -> None: + self._coro = coro + self._resp: Optional[ClientResponse] = None + self._session = session + + async def __aenter__(self) -> ClientResponse: + try: + self._resp = await self._coro + except BaseException: + await self._session.close() + raise + else: + return self._resp + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + tb: Optional[TracebackType], + ) -> None: + assert self._resp is not None + self._resp.close() + await self._session.close() + + +def request( + method: str, + url: StrOrURL, + *, + params: Optional[Mapping[str, str]] = None, + data: Any = None, + json: Any = None, + headers: Optional[LooseHeaders] = None, + skip_auto_headers: Optional[Iterable[str]] = None, + auth: Optional[BasicAuth] = None, + allow_redirects: bool = True, + max_redirects: int = 10, + compress: Optional[str] = None, + chunked: Optional[bool] = None, + expect100: bool = False, + raise_for_status: Optional[bool] = None, + read_until_eof: bool = True, + proxy: Optional[StrOrURL] = None, + proxy_auth: Optional[BasicAuth] = None, + timeout: Union[ClientTimeout, _SENTINEL] = sentinel, + cookies: Optional[LooseCookies] = None, + version: HttpVersion = http.HttpVersion11, + connector: Optional[BaseConnector] = None, + read_bufsize: Optional[int] = None, + max_line_size: int = 8190, + max_field_size: int = 8190, +) -> _SessionRequestContextManager: + """Constructs and sends a request. + + Returns response object. + method - HTTP method + url - request url + params - (optional) Dictionary or bytes to be sent in the query + string of the new request + data - (optional) Dictionary, bytes, or file-like object to + send in the body of the request + json - (optional) Any json compatible python object + headers - (optional) Dictionary of HTTP Headers to send with + the request + cookies - (optional) Dict object to send with the request + auth - (optional) BasicAuth named tuple represent HTTP Basic Auth + auth - aiohttp.helpers.BasicAuth + allow_redirects - (optional) If set to False, do not follow + redirects + version - Request HTTP version. + compress - Set to True if request has to be compressed + with deflate encoding. + chunked - Set to chunk size for chunked transfer encoding. + expect100 - Expect 100-continue response from server. + connector - BaseConnector sub-class instance to support + connection pooling. + read_until_eof - Read response until eof if response + does not have Content-Length header. + loop - Optional event loop. + timeout - Optional ClientTimeout settings structure, 5min + total timeout by default. + Usage:: + >>> import aiohttp + >>> async with aiohttp.request('GET', 'http://python.org/') as resp: + ... print(resp) + ... data = await resp.read() + + """ + connector_owner = False + if connector is None: + connector_owner = True + connector = TCPConnector(force_close=True) + + session = ClientSession( + cookies=cookies, + version=version, + timeout=timeout, + connector=connector, + connector_owner=connector_owner, + ) + + return _SessionRequestContextManager( + session._request( + method, + url, + params=params, + data=data, + json=json, + headers=headers, + skip_auto_headers=skip_auto_headers, + auth=auth, + allow_redirects=allow_redirects, + max_redirects=max_redirects, + compress=compress, + chunked=chunked, + expect100=expect100, + raise_for_status=raise_for_status, + read_until_eof=read_until_eof, + proxy=proxy, + proxy_auth=proxy_auth, + read_bufsize=read_bufsize, + max_line_size=max_line_size, + max_field_size=max_field_size, + ), + session, + ) diff --git a/aiohttp/client_exceptions.py b/aiohttp/client_exceptions.py new file mode 100644 index 0000000..f7023ef --- /dev/null +++ b/aiohttp/client_exceptions.py @@ -0,0 +1,313 @@ +"""HTTP related errors.""" + +import asyncio +from typing import TYPE_CHECKING, Any, Optional, Tuple, Union + +from .http_parser import RawResponseMessage +from .typedefs import LooseHeaders + +try: + import ssl + + SSLContext = ssl.SSLContext +except ImportError: # pragma: no cover + ssl = SSLContext = None # type: ignore[assignment] + + +if TYPE_CHECKING: # pragma: no cover + from .client_reqrep import ClientResponse, ConnectionKey, Fingerprint, RequestInfo +else: + RequestInfo = ClientResponse = ConnectionKey = None + +__all__ = ( + "ClientError", + "ClientConnectionError", + "ClientOSError", + "ClientConnectorError", + "ClientProxyConnectionError", + "ClientSSLError", + "ClientConnectorSSLError", + "ClientConnectorCertificateError", + "ServerConnectionError", + "ServerTimeoutError", + "ServerDisconnectedError", + "ServerFingerprintMismatch", + "ClientResponseError", + "ClientHttpProxyError", + "WSServerHandshakeError", + "ContentTypeError", + "ClientPayloadError", + "InvalidURL", +) + + +class ClientError(Exception): + """Base class for client connection errors.""" + + +class ClientResponseError(ClientError): + """Base class for exceptions that occur after getting a response. + + request_info: An instance of RequestInfo. + history: A sequence of responses, if redirects occurred. + status: HTTP status code. + message: Error message. + headers: Response headers. + """ + + def __init__( + self, + request_info: RequestInfo, + history: Tuple[ClientResponse, ...], + *, + status: Optional[int] = None, + message: str = "", + headers: Optional[LooseHeaders] = None, + ) -> None: + self.request_info = request_info + if status is not None: + self.status = status + else: + self.status = 0 + self.message = message + self.headers = headers + self.history = history + self.args = (request_info, history) + + def __str__(self) -> str: + return "{}, message={!r}, url={!r}".format( + self.status, + self.message, + self.request_info.real_url, + ) + + def __repr__(self) -> str: + args = f"{self.request_info!r}, {self.history!r}" + if self.status != 0: + args += f", status={self.status!r}" + if self.message != "": + args += f", message={self.message!r}" + if self.headers is not None: + args += f", headers={self.headers!r}" + return f"{type(self).__name__}({args})" + + +class ContentTypeError(ClientResponseError): + """ContentType found is not valid.""" + + +class WSServerHandshakeError(ClientResponseError): + """websocket server handshake error.""" + + +class ClientHttpProxyError(ClientResponseError): + """HTTP proxy error. + + Raised in :class:`aiohttp.connector.TCPConnector` if + proxy responds with status other than ``200 OK`` + on ``CONNECT`` request. + """ + + +class TooManyRedirects(ClientResponseError): + """Client was redirected too many times.""" + + +class ClientConnectionError(ClientError): + """Base class for client socket errors.""" + + +class ClientOSError(ClientConnectionError, OSError): + """OSError error.""" + + +class ClientConnectorError(ClientOSError): + """Client connector error. + + Raised in :class:`aiohttp.connector.TCPConnector` if + a connection can not be established. + """ + + def __init__(self, connection_key: ConnectionKey, os_error: OSError) -> None: + self._conn_key = connection_key + self._os_error = os_error + super().__init__(os_error.errno, os_error.strerror) + self.args = (connection_key, os_error) + + @property + def os_error(self) -> OSError: + return self._os_error + + @property + def host(self) -> str: + return self._conn_key.host + + @property + def port(self) -> Optional[int]: + return self._conn_key.port + + @property + def ssl(self) -> Union[SSLContext, None, bool, "Fingerprint"]: + return self._conn_key.ssl + + def __str__(self) -> str: + return "Cannot connect to host {0.host}:{0.port} ssl:{1} [{2}]".format( + self, self.ssl if self.ssl is not None else "default", self.strerror + ) + + # OSError.__reduce__ does too much black magick + __reduce__ = BaseException.__reduce__ + + +class ClientProxyConnectionError(ClientConnectorError): + """Proxy connection error. + + Raised in :class:`aiohttp.connector.TCPConnector` if + connection to proxy can not be established. + """ + + +class UnixClientConnectorError(ClientConnectorError): + """Unix connector error. + + Raised in :py:class:`aiohttp.connector.UnixConnector` + if connection to unix socket can not be established. + """ + + def __init__( + self, path: str, connection_key: ConnectionKey, os_error: OSError + ) -> None: + self._path = path + super().__init__(connection_key, os_error) + + @property + def path(self) -> str: + return self._path + + def __str__(self) -> str: + return "Cannot connect to unix socket {0.path} ssl:{1} [{2}]".format( + self, self.ssl if self.ssl is not None else "default", self.strerror + ) + + +class ServerConnectionError(ClientConnectionError): + """Server connection errors.""" + + +class ServerDisconnectedError(ServerConnectionError): + """Server disconnected.""" + + def __init__(self, message: Union[RawResponseMessage, str, None] = None) -> None: + if message is None: + message = "Server disconnected" + + self.args = (message,) + self.message = message + + +class ServerTimeoutError(ServerConnectionError, asyncio.TimeoutError): + """Server timeout error.""" + + +class ServerFingerprintMismatch(ServerConnectionError): + """SSL certificate does not match expected fingerprint.""" + + def __init__(self, expected: bytes, got: bytes, host: str, port: int) -> None: + self.expected = expected + self.got = got + self.host = host + self.port = port + self.args = (expected, got, host, port) + + def __repr__(self) -> str: + return "<{} expected={!r} got={!r} host={!r} port={!r}>".format( + self.__class__.__name__, self.expected, self.got, self.host, self.port + ) + + +class ClientPayloadError(ClientError): + """Response payload error.""" + + +class InvalidURL(ClientError, ValueError): + """Invalid URL. + + URL used for fetching is malformed, e.g. it doesn't contains host + part. + """ + + # Derive from ValueError for backward compatibility + + def __init__(self, url: Any) -> None: + # The type of url is not yarl.URL because the exception can be raised + # on URL(url) call + super().__init__(url) + + @property + def url(self) -> Any: + return self.args[0] + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.url}>" + + +class ClientSSLError(ClientConnectorError): + """Base error for ssl.*Errors.""" + + +if ssl is not None: + cert_errors = (ssl.CertificateError,) + cert_errors_bases = ( + ClientSSLError, + ssl.CertificateError, + ) + + ssl_errors = (ssl.SSLError,) + ssl_error_bases = (ClientSSLError, ssl.SSLError) +else: # pragma: no cover + cert_errors = tuple() + cert_errors_bases = ( + ClientSSLError, + ValueError, + ) + + ssl_errors = tuple() + ssl_error_bases = (ClientSSLError,) + + +class ClientConnectorSSLError(*ssl_error_bases): # type: ignore[misc] + """Response ssl error.""" + + +class ClientConnectorCertificateError(*cert_errors_bases): # type: ignore[misc] + """Response certificate error.""" + + def __init__( + self, connection_key: ConnectionKey, certificate_error: Exception + ) -> None: + self._conn_key = connection_key + self._certificate_error = certificate_error + self.args = (connection_key, certificate_error) + + @property + def certificate_error(self) -> Exception: + return self._certificate_error + + @property + def host(self) -> str: + return self._conn_key.host + + @property + def port(self) -> Optional[int]: + return self._conn_key.port + + @property + def ssl(self) -> bool: + return self._conn_key.is_ssl + + def __str__(self) -> str: + return ( + "Cannot connect to host {0.host}:{0.port} ssl:{0.ssl} " + "[{0.certificate_error.__class__.__name__}: " + "{0.certificate_error.args}]".format(self) + ) diff --git a/aiohttp/client_proto.py b/aiohttp/client_proto.py new file mode 100644 index 0000000..3d42cf5 --- /dev/null +++ b/aiohttp/client_proto.py @@ -0,0 +1,269 @@ +import asyncio +from contextlib import suppress +from typing import Any, Optional, Tuple + +from .base_protocol import BaseProtocol +from .client_exceptions import ( + ClientOSError, + ClientPayloadError, + ServerDisconnectedError, + ServerTimeoutError, +) +from .helpers import BaseTimerContext, set_exception, set_result +from .http import HttpResponseParser, RawResponseMessage, WebSocketReader +from .streams import EMPTY_PAYLOAD, DataQueue, StreamReader + + +class ResponseHandler(BaseProtocol, DataQueue[Tuple[RawResponseMessage, StreamReader]]): + """Helper class to adapt between Protocol and StreamReader.""" + + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: + BaseProtocol.__init__(self, loop=loop) + DataQueue.__init__(self, loop) + + self._should_close = False + + self._payload: Optional[StreamReader] = None + self._skip_payload = False + self._payload_parser: Optional[WebSocketReader] = None + + self._timer = None + + self._tail = b"" + self._upgraded = False + self._parser: Optional[HttpResponseParser] = None + + self._read_timeout: Optional[float] = None + self._read_timeout_handle: Optional[asyncio.TimerHandle] = None + + self._timeout_ceil_threshold: Optional[float] = 5 + + self.closed: asyncio.Future[None] = self._loop.create_future() + + @property + def upgraded(self) -> bool: + return self._upgraded + + @property + def should_close(self) -> bool: + if self._payload is not None and not self._payload.is_eof(): + return True + + return ( + self._should_close + or self._upgraded + or self.exception() is not None + or self._payload_parser is not None + or len(self) > 0 + or bool(self._tail) + ) + + def force_close(self) -> None: + self._should_close = True + + def close(self) -> None: + transport = self.transport + if transport is not None: + transport.close() + self.transport = None + self._payload = None + self._drop_timeout() + + def is_connected(self) -> bool: + return self.transport is not None and not self.transport.is_closing() + + def connection_lost(self, exc: Optional[BaseException]) -> None: + self._drop_timeout() + + if exc is not None: + set_exception(self.closed, exc) + else: + set_result(self.closed, None) + + if self._payload_parser is not None: + with suppress(Exception): + self._payload_parser.feed_eof() + + uncompleted = None + if self._parser is not None: + try: + uncompleted = self._parser.feed_eof() + except Exception: + if self._payload is not None: + self._payload.set_exception( + ClientPayloadError("Response payload is not completed") + ) + + if not self.is_eof(): + if isinstance(exc, OSError): + exc = ClientOSError(*exc.args) + if exc is None: + exc = ServerDisconnectedError(uncompleted) + # assigns self._should_close to True as side effect, + # we do it anyway below + self.set_exception(exc) + + self._should_close = True + self._parser = None + self._payload = None + self._payload_parser = None + self._reading_paused = False + + super().connection_lost(exc) + + def eof_received(self) -> None: + # should call parser.feed_eof() most likely + self._drop_timeout() + + def pause_reading(self) -> None: + super().pause_reading() + self._drop_timeout() + + def resume_reading(self) -> None: + super().resume_reading() + self._reschedule_timeout() + + def set_exception(self, exc: BaseException) -> None: + self._should_close = True + self._drop_timeout() + super().set_exception(exc) + + def set_parser(self, parser: Any, payload: Any) -> None: + # TODO: actual types are: + # parser: WebSocketReader + # payload: FlowControlDataQueue + # but they are not generi enough + # Need an ABC for both types + self._payload = payload + self._payload_parser = parser + + self._drop_timeout() + + if self._tail: + data, self._tail = self._tail, b"" + self.data_received(data) + + def set_response_params( + self, + *, + timer: Optional[BaseTimerContext] = None, + skip_payload: bool = False, + read_until_eof: bool = False, + auto_decompress: bool = True, + read_timeout: Optional[float] = None, + read_bufsize: int = 2**16, + timeout_ceil_threshold: float = 5, + max_line_size: int = 8190, + max_field_size: int = 8190, + ) -> None: + self._skip_payload = skip_payload + + self._read_timeout = read_timeout + + self._timeout_ceil_threshold = timeout_ceil_threshold + + self._parser = HttpResponseParser( + self, + self._loop, + read_bufsize, + timer=timer, + payload_exception=ClientPayloadError, + response_with_body=not skip_payload, + read_until_eof=read_until_eof, + auto_decompress=auto_decompress, + max_line_size=max_line_size, + max_field_size=max_field_size, + ) + + if self._tail: + data, self._tail = self._tail, b"" + self.data_received(data) + + def _drop_timeout(self) -> None: + if self._read_timeout_handle is not None: + self._read_timeout_handle.cancel() + self._read_timeout_handle = None + + def _reschedule_timeout(self) -> None: + timeout = self._read_timeout + if self._read_timeout_handle is not None: + self._read_timeout_handle.cancel() + + if timeout: + self._read_timeout_handle = self._loop.call_later( + timeout, self._on_read_timeout + ) + else: + self._read_timeout_handle = None + + def start_timeout(self) -> None: + self._reschedule_timeout() + + def _on_read_timeout(self) -> None: + exc = ServerTimeoutError("Timeout on reading data from socket") + self.set_exception(exc) + if self._payload is not None: + self._payload.set_exception(exc) + + def data_received(self, data: bytes) -> None: + self._reschedule_timeout() + + if not data: + return + + # custom payload parser + if self._payload_parser is not None: + eof, tail = self._payload_parser.feed_data(data) + if eof: + self._payload = None + self._payload_parser = None + + if tail: + self.data_received(tail) + return + else: + if self._upgraded or self._parser is None: + # i.e. websocket connection, websocket parser is not set yet + self._tail += data + else: + # parse http messages + try: + messages, upgraded, tail = self._parser.feed_data(data) + except BaseException as exc: + if self.transport is not None: + # connection.release() could be called BEFORE + # data_received(), the transport is already + # closed in this case + self.transport.close() + # should_close is True after the call + self.set_exception(exc) + return + + self._upgraded = upgraded + + payload: Optional[StreamReader] = None + for message, payload in messages: + if message.should_close: + self._should_close = True + + self._payload = payload + + if self._skip_payload or message.code in (204, 304): + self.feed_data((message, EMPTY_PAYLOAD), 0) + else: + self.feed_data((message, payload), 0) + if payload is not None: + # new message(s) was processed + # register timeout handler unsubscribing + # either on end-of-stream or immediately for + # EMPTY_PAYLOAD + if payload is not EMPTY_PAYLOAD: + payload.on_eof(self._drop_timeout) + else: + self._drop_timeout() + + if tail: + if upgraded: + self.data_received(tail) + else: + self._tail = tail diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py new file mode 100644 index 0000000..6766473 --- /dev/null +++ b/aiohttp/client_reqrep.py @@ -0,0 +1,1089 @@ +import asyncio +import codecs +import contextlib +import dataclasses +import functools +import io +import re +import sys +import traceback +import warnings +from hashlib import md5, sha1, sha256 +from http.cookies import CookieError, Morsel, SimpleCookie +from types import MappingProxyType, TracebackType +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Iterable, + List, + Literal, + Mapping, + Optional, + Tuple, + Type, + Union, + cast, +) + +from multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy +from yarl import URL + +from . import hdrs, helpers, http, multipart, payload +from .abc import AbstractStreamWriter +from .client_exceptions import ( + ClientConnectionError, + ClientOSError, + ClientResponseError, + ContentTypeError, + InvalidURL, + ServerFingerprintMismatch, +) +from .compression_utils import HAS_BROTLI +from .formdata import FormData +from .hdrs import CONTENT_TYPE +from .helpers import ( + BaseTimerContext, + BasicAuth, + HeadersMixin, + TimerNoop, + basicauth_from_netrc, + is_expected_content_type, + netrc_from_env, + noop, + parse_mimetype, + reify, + set_result, +) +from .http import SERVER_SOFTWARE, HttpVersion10, HttpVersion11, StreamWriter +from .log import client_logger +from .streams import StreamReader +from .typedefs import ( + DEFAULT_JSON_DECODER, + JSONDecoder, + LooseCookies, + LooseHeaders, + RawHeaders, +) + +try: + import ssl + from ssl import SSLContext +except ImportError: # pragma: no cover + ssl = None # type: ignore[assignment] + SSLContext = object # type: ignore[misc,assignment] + + +__all__ = ("ClientRequest", "ClientResponse", "RequestInfo", "Fingerprint") + + +if TYPE_CHECKING: # pragma: no cover + from .client import ClientSession + from .connector import Connection + from .tracing import Trace + + +_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") + + +def _gen_default_accept_encoding() -> str: + return "gzip, deflate, br" if HAS_BROTLI else "gzip, deflate" + + +@dataclasses.dataclass(frozen=True) +class ContentDisposition: + type: Optional[str] + parameters: "MappingProxyType[str, str]" + filename: Optional[str] + + +@dataclasses.dataclass(frozen=True) +class RequestInfo: + url: URL + method: str + headers: "CIMultiDictProxy[str]" + real_url: URL + + +class Fingerprint: + HASHFUNC_BY_DIGESTLEN = { + 16: md5, + 20: sha1, + 32: sha256, + } + + def __init__(self, fingerprint: bytes) -> None: + digestlen = len(fingerprint) + hashfunc = self.HASHFUNC_BY_DIGESTLEN.get(digestlen) + if not hashfunc: + raise ValueError("fingerprint has invalid length") + elif hashfunc is md5 or hashfunc is sha1: + raise ValueError( + "md5 and sha1 are insecure and " "not supported. Use sha256." + ) + self._hashfunc = hashfunc + self._fingerprint = fingerprint + + @property + def fingerprint(self) -> bytes: + return self._fingerprint + + def check(self, transport: asyncio.Transport) -> None: + if not transport.get_extra_info("sslcontext"): + return + sslobj = transport.get_extra_info("ssl_object") + cert = sslobj.getpeercert(binary_form=True) + got = self._hashfunc(cert).digest() + if got != self._fingerprint: + host, port, *_ = transport.get_extra_info("peername") + raise ServerFingerprintMismatch(self._fingerprint, got, host, port) + + +if ssl is not None: + SSL_ALLOWED_TYPES = (ssl.SSLContext, bool, Fingerprint, type(None)) +else: # pragma: no cover + SSL_ALLOWED_TYPES = type(None) + + +@dataclasses.dataclass(frozen=True) +class ConnectionKey: + # the key should contain an information about used proxy / TLS + # to prevent reusing wrong connections from a pool + host: str + port: Optional[int] + is_ssl: bool + ssl: Union[SSLContext, None, Literal[False], Fingerprint] + proxy: Optional[URL] + proxy_auth: Optional[BasicAuth] + proxy_headers_hash: Optional[int] # hash(CIMultiDict) + + +class ClientRequest: + GET_METHODS = { + hdrs.METH_GET, + hdrs.METH_HEAD, + hdrs.METH_OPTIONS, + hdrs.METH_TRACE, + } + POST_METHODS = {hdrs.METH_PATCH, hdrs.METH_POST, hdrs.METH_PUT} + ALL_METHODS = GET_METHODS.union(POST_METHODS).union({hdrs.METH_DELETE}) + + DEFAULT_HEADERS = { + hdrs.ACCEPT: "*/*", + hdrs.ACCEPT_ENCODING: _gen_default_accept_encoding(), + } + + body = b"" + auth = None + response = None + + _writer = None # async task for streaming data + _continue = None # waiter future for '100 Continue' response + + # N.B. + # Adding __del__ method with self._writer closing doesn't make sense + # because _writer is instance method, thus it keeps a reference to self. + # Until writer has finished finalizer will not be called. + + def __init__( + self, + method: str, + url: URL, + *, + params: Optional[Mapping[str, str]] = None, + headers: Optional[LooseHeaders] = None, + skip_auto_headers: Iterable[str] = frozenset(), + data: Any = None, + cookies: Optional[LooseCookies] = None, + auth: Optional[BasicAuth] = None, + version: http.HttpVersion = http.HttpVersion11, + compress: Optional[str] = None, + chunked: Optional[bool] = None, + expect100: bool = False, + loop: asyncio.AbstractEventLoop, + response_class: Optional[Type["ClientResponse"]] = None, + proxy: Optional[URL] = None, + proxy_auth: Optional[BasicAuth] = None, + timer: Optional[BaseTimerContext] = None, + session: Optional["ClientSession"] = None, + ssl: Union[SSLContext, Literal[False], Fingerprint, None] = None, + proxy_headers: Optional[LooseHeaders] = None, + traces: Optional[List["Trace"]] = None, + trust_env: bool = False, + server_hostname: Optional[str] = None, + ): + match = _CONTAINS_CONTROL_CHAR_RE.search(method) + if match: + raise ValueError( + f"Method cannot contain non-token characters {method!r} " + f"(found at least {match.group()!r})" + ) + assert isinstance(url, URL), url + assert isinstance(proxy, (URL, type(None))), proxy + # FIXME: session is None in tests only, need to fix tests + # assert session is not None + self._session = cast("ClientSession", session) + if params: + q = MultiDict(url.query) + url2 = url.with_query(params) + q.extend(url2.query) + url = url.with_query(q) + self.original_url = url + self.url = url.with_fragment(None) + self.method = method.upper() + self.chunked = chunked + self.compress = compress + self.loop = loop + self.length = None + if response_class is None: + real_response_class = ClientResponse + else: + real_response_class = response_class + self.response_class: Type[ClientResponse] = real_response_class + self._timer = timer if timer is not None else TimerNoop() + self._ssl = ssl + self.server_hostname = server_hostname + + if loop.get_debug(): + self._source_traceback = traceback.extract_stack(sys._getframe(1)) + + self.update_version(version) + self.update_host(url) + self.update_headers(headers) + self.update_auto_headers(skip_auto_headers) + self.update_cookies(cookies) + self.update_content_encoding(data) + self.update_auth(auth, trust_env) + self.update_proxy(proxy, proxy_auth, proxy_headers) + + self.update_body_from_data(data) + if data is not None or self.method not in self.GET_METHODS: + self.update_transfer_encoding() + self.update_expect_continue(expect100) + if traces is None: + traces = [] + self._traces = traces + + def is_ssl(self) -> bool: + return self.url.scheme in ("https", "wss") + + @property + def ssl(self) -> Union["SSLContext", None, Literal[False], Fingerprint]: + return self._ssl + + @property + def connection_key(self) -> ConnectionKey: + proxy_headers = self.proxy_headers + if proxy_headers: + h: Optional[int] = hash(tuple((k, v) for k, v in proxy_headers.items())) + else: + h = None + return ConnectionKey( + self.host, + self.port, + self.is_ssl(), + self.ssl, + self.proxy, + self.proxy_auth, + h, + ) + + @property + def host(self) -> str: + ret = self.url.raw_host + assert ret is not None + return ret + + @property + def port(self) -> Optional[int]: + return self.url.port + + @property + def request_info(self) -> RequestInfo: + headers: CIMultiDictProxy[str] = CIMultiDictProxy(self.headers) + return RequestInfo(self.url, self.method, headers, self.original_url) + + def update_host(self, url: URL) -> None: + """Update destination host, port and connection type (ssl).""" + # get host/port + if not url.raw_host: + raise InvalidURL(url) + + # basic auth info + username, password = url.user, url.password + if username: + self.auth = helpers.BasicAuth(username, password or "") + + def update_version(self, version: Union[http.HttpVersion, str]) -> None: + """Convert request version to two elements tuple. + + parser HTTP version '1.1' => (1, 1) + """ + if isinstance(version, str): + v = [part.strip() for part in version.split(".", 1)] + try: + version = http.HttpVersion(int(v[0]), int(v[1])) + except ValueError: + raise ValueError( + f"Can not parse http version number: {version}" + ) from None + self.version = version + + def update_headers(self, headers: Optional[LooseHeaders]) -> None: + """Update request headers.""" + self.headers: CIMultiDict[str] = CIMultiDict() + + # add host + netloc = cast(str, self.url.raw_host) + if helpers.is_ipv6_address(netloc): + netloc = f"[{netloc}]" + # See https://github.com/aio-libs/aiohttp/issues/3636. + netloc = netloc.rstrip(".") + if self.url.port is not None and not self.url.is_default_port(): + netloc += ":" + str(self.url.port) + self.headers[hdrs.HOST] = netloc + + if headers: + if isinstance(headers, (dict, MultiDictProxy, MultiDict)): + headers = headers.items() # type: ignore[assignment] + + for key, value in headers: # type: ignore[misc] + # A special case for Host header + if key.lower() == "host": + self.headers[key] = value + else: + self.headers.add(key, value) + + def update_auto_headers(self, skip_auto_headers: Iterable[str]) -> None: + self.skip_auto_headers = CIMultiDict( + (hdr, None) for hdr in sorted(skip_auto_headers) + ) + used_headers = self.headers.copy() + used_headers.extend(self.skip_auto_headers) # type: ignore[arg-type] + + for hdr, val in self.DEFAULT_HEADERS.items(): + if hdr not in used_headers: + self.headers.add(hdr, val) + + if hdrs.USER_AGENT not in used_headers: + self.headers[hdrs.USER_AGENT] = SERVER_SOFTWARE + + def update_cookies(self, cookies: Optional[LooseCookies]) -> None: + """Update request cookies header.""" + if not cookies: + return + + c: SimpleCookie[str] = SimpleCookie() + if hdrs.COOKIE in self.headers: + c.load(self.headers.get(hdrs.COOKIE, "")) + del self.headers[hdrs.COOKIE] + + if isinstance(cookies, Mapping): + iter_cookies = cookies.items() + else: + iter_cookies = cookies # type: ignore[assignment] + for name, value in iter_cookies: + if isinstance(value, Morsel): + # Preserve coded_value + mrsl_val = value.get(value.key, Morsel()) + mrsl_val.set(value.key, value.value, value.coded_value) + c[name] = mrsl_val + else: + c[name] = value # type: ignore[assignment] + + self.headers[hdrs.COOKIE] = c.output(header="", sep=";").strip() + + def update_content_encoding(self, data: Any) -> None: + """Set request content encoding.""" + if data is None: + return + + enc = self.headers.get(hdrs.CONTENT_ENCODING, "").lower() + if enc: + if self.compress: + raise ValueError( + "compress can not be set " "if Content-Encoding header is set" + ) + elif self.compress: + if not isinstance(self.compress, str): + self.compress = "deflate" + self.headers[hdrs.CONTENT_ENCODING] = self.compress + self.chunked = True # enable chunked, no need to deal with length + + def update_transfer_encoding(self) -> None: + """Analyze transfer-encoding header.""" + te = self.headers.get(hdrs.TRANSFER_ENCODING, "").lower() + + if "chunked" in te: + if self.chunked: + raise ValueError( + "chunked can not be set " + 'if "Transfer-Encoding: chunked" header is set' + ) + + elif self.chunked: + if hdrs.CONTENT_LENGTH in self.headers: + raise ValueError( + "chunked can not be set " "if Content-Length header is set" + ) + + self.headers[hdrs.TRANSFER_ENCODING] = "chunked" + else: + if hdrs.CONTENT_LENGTH not in self.headers: + self.headers[hdrs.CONTENT_LENGTH] = str(len(self.body)) + + def update_auth(self, auth: Optional[BasicAuth], trust_env: bool = False) -> None: + """Set basic auth.""" + if auth is None: + auth = self.auth + if auth is None and trust_env and self.url.host is not None: + netrc_obj = netrc_from_env() + with contextlib.suppress(LookupError): + auth = basicauth_from_netrc(netrc_obj, self.url.host) + if auth is None: + return + + if not isinstance(auth, helpers.BasicAuth): + raise TypeError("BasicAuth() tuple is required instead") + + self.headers[hdrs.AUTHORIZATION] = auth.encode() + + def update_body_from_data(self, body: Any) -> None: + if body is None: + return + + # FormData + if isinstance(body, FormData): + body = body() + + try: + body = payload.PAYLOAD_REGISTRY.get(body, disposition=None) + except payload.LookupError: + boundary = None + if CONTENT_TYPE in self.headers: + boundary = parse_mimetype(self.headers[CONTENT_TYPE]).parameters.get( + "boundary" + ) + body = FormData(body, boundary=boundary)() + + self.body = body + + # enable chunked encoding if needed + if not self.chunked: + if hdrs.CONTENT_LENGTH not in self.headers: + size = body.size + if size is None: + self.chunked = True + else: + if hdrs.CONTENT_LENGTH not in self.headers: + self.headers[hdrs.CONTENT_LENGTH] = str(size) + + # copy payload headers + assert body.headers + for key, value in body.headers.items(): + if key in self.headers: + continue + if key in self.skip_auto_headers: + continue + self.headers[key] = value + + def update_expect_continue(self, expect: bool = False) -> None: + if expect: + self.headers[hdrs.EXPECT] = "100-continue" + elif self.headers.get(hdrs.EXPECT, "").lower() == "100-continue": + expect = True + + if expect: + self._continue = self.loop.create_future() + + def update_proxy( + self, + proxy: Optional[URL], + proxy_auth: Optional[BasicAuth], + proxy_headers: Optional[LooseHeaders], + ) -> None: + if proxy_auth and not isinstance(proxy_auth, helpers.BasicAuth): + raise ValueError("proxy_auth must be None or BasicAuth() tuple") + self.proxy = proxy + self.proxy_auth = proxy_auth + self.proxy_headers = proxy_headers + + def keep_alive(self) -> bool: + if self.version < HttpVersion10: + # keep alive not supported at all + return False + if self.version == HttpVersion10: + if self.headers.get(hdrs.CONNECTION) == "keep-alive": + return True + else: # no headers means we close for Http 1.0 + return False + elif self.headers.get(hdrs.CONNECTION) == "close": + return False + + return True + + async def write_bytes( + self, writer: AbstractStreamWriter, conn: "Connection" + ) -> None: + """Support coroutines that yields bytes objects.""" + # 100 response + if self._continue is not None: + await writer.drain() + await self._continue + + protocol = conn.protocol + assert protocol is not None + try: + if isinstance(self.body, payload.Payload): + await self.body.write(writer) + else: + if isinstance(self.body, (bytes, bytearray)): + self.body = (self.body,) # type: ignore[assignment] + + for chunk in self.body: + await writer.write(chunk) # type: ignore[arg-type] + + await writer.write_eof() + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + protocol.set_exception(exc) + else: + new_exc = ClientOSError( + exc.errno, "Can not write request body for %s" % self.url + ) + new_exc.__context__ = exc + new_exc.__cause__ = exc + protocol.set_exception(new_exc) + except asyncio.CancelledError as exc: + if not conn.closed: + protocol.set_exception(exc) + except Exception as exc: + protocol.set_exception(exc) + else: + protocol.start_timeout() + finally: + self._writer = None + + async def send(self, conn: "Connection") -> "ClientResponse": + # Specify request target: + # - CONNECT request must send authority form URI + # - not CONNECT proxy must send absolute form URI + # - most common is origin form URI + if self.method == hdrs.METH_CONNECT: + connect_host = self.url.raw_host + assert connect_host is not None + if helpers.is_ipv6_address(connect_host): + connect_host = f"[{connect_host}]" + path = f"{connect_host}:{self.url.port}" + elif self.proxy and not self.is_ssl(): + path = str(self.url) + else: + path = self.url.raw_path + if self.url.raw_query_string: + path += "?" + self.url.raw_query_string + + protocol = conn.protocol + assert protocol is not None + writer = StreamWriter( + protocol, + self.loop, + on_chunk_sent=functools.partial( + self._on_chunk_request_sent, self.method, self.url + ), + on_headers_sent=functools.partial( + self._on_headers_request_sent, self.method, self.url + ), + ) + + if self.compress: + writer.enable_compression(self.compress) + + if self.chunked is not None: + writer.enable_chunking() + + # set default content-type + if ( + self.method in self.POST_METHODS + and hdrs.CONTENT_TYPE not in self.skip_auto_headers + and hdrs.CONTENT_TYPE not in self.headers + ): + self.headers[hdrs.CONTENT_TYPE] = "application/octet-stream" + + # set the connection header + connection = self.headers.get(hdrs.CONNECTION) + if not connection: + if self.keep_alive(): + if self.version == HttpVersion10: + connection = "keep-alive" + else: + if self.version == HttpVersion11: + connection = "close" + + if connection is not None: + self.headers[hdrs.CONNECTION] = connection + + # status + headers + status_line = "{0} {1} HTTP/{2[0]}.{2[1]}".format( + self.method, path, self.version + ) + await writer.write_headers(status_line, self.headers) + + self._writer = self.loop.create_task(self.write_bytes(writer, conn)) + + response_class = self.response_class + assert response_class is not None + self.response = response_class( + self.method, + self.original_url, + writer=self._writer, + continue100=self._continue, + timer=self._timer, + request_info=self.request_info, + traces=self._traces, + loop=self.loop, + session=self._session, + ) + return self.response + + async def close(self) -> None: + if self._writer is not None: + try: + await self._writer + finally: + self._writer = None + + def terminate(self) -> None: + if self._writer is not None: + if not self.loop.is_closed(): + self._writer.cancel() + self._writer = None + + async def _on_chunk_request_sent(self, method: str, url: URL, chunk: bytes) -> None: + for trace in self._traces: + await trace.send_request_chunk_sent(method, url, chunk) + + async def _on_headers_request_sent( + self, method: str, url: URL, headers: "CIMultiDict[str]" + ) -> None: + for trace in self._traces: + await trace.send_request_headers(method, url, headers) + + +class ClientResponse(HeadersMixin): + # Some of these attributes are None when created, + # but will be set by the start() method. + # As the end user will likely never see the None values, we cheat the types below. + # from the Status-Line of the response + version = None # HTTP-Version + status: int = None # type: ignore[assignment] # Status-Code + reason = None # Reason-Phrase + + content: StreamReader = None # type: ignore[assignment] # Payload stream + _headers: CIMultiDictProxy[str] = None # type: ignore[assignment] + _raw_headers: RawHeaders = None # type: ignore[assignment] + + _connection = None # current connection + _source_traceback: Optional[traceback.StackSummary] = None + # set up by ClientRequest after ClientResponse object creation + # post-init stage allows to not change ctor signature + _closed = True # to allow __del__ for non-initialized properly response + _released = False + + def __init__( + self, + method: str, + url: URL, + *, + writer: "asyncio.Task[None]", + continue100: Optional["asyncio.Future[bool]"], + timer: Optional[BaseTimerContext], + request_info: RequestInfo, + traces: List["Trace"], + loop: asyncio.AbstractEventLoop, + session: "ClientSession", + ) -> None: + assert isinstance(url, URL) + super().__init__() + + self.method = method + self.cookies: SimpleCookie[str] = SimpleCookie() + + self._real_url = url + self._url = url.with_fragment(None) + self._body: Optional[bytes] = None + self._writer: Optional[asyncio.Task[None]] = writer + self._continue = continue100 # None by default + self._closed = True + self._history: Tuple[ClientResponse, ...] = () + self._request_info = request_info + self._timer = timer if timer is not None else TimerNoop() + self._cache: Dict[str, Any] = {} + self._traces = traces + self._loop = loop + # store a reference to session #1985 + self._session: Optional[ClientSession] = session + # Save reference to _resolve_charset, so that get_encoding() will still + # work after the response has finished reading the body. + if session is None: + # TODO: Fix session=None in tests (see ClientRequest.__init__). + self._resolve_charset: Callable[ + ["ClientResponse", bytes], str + ] = lambda *_: "utf-8" + else: + self._resolve_charset = session._resolve_charset + if loop.get_debug(): + self._source_traceback = traceback.extract_stack(sys._getframe(1)) + + @reify + def url(self) -> URL: + return self._url + + @reify + def real_url(self) -> URL: + return self._real_url + + @reify + def host(self) -> str: + assert self._url.host is not None + return self._url.host + + @reify + def headers(self) -> "CIMultiDictProxy[str]": + return self._headers + + @reify + def raw_headers(self) -> RawHeaders: + return self._raw_headers + + @reify + def request_info(self) -> RequestInfo: + return self._request_info + + @reify + def content_disposition(self) -> Optional[ContentDisposition]: + raw = self._headers.get(hdrs.CONTENT_DISPOSITION) + if raw is None: + return None + disposition_type, params_dct = multipart.parse_content_disposition(raw) + params = MappingProxyType(params_dct) + filename = multipart.content_disposition_filename(params) + return ContentDisposition(disposition_type, params, filename) + + def __del__(self, _warnings: Any = warnings) -> None: + if self._closed: + return + + if self._connection is not None: + self._connection.release() + self._cleanup_writer() + + if self._loop.get_debug(): + _warnings.warn( + f"Unclosed response {self!r}", ResourceWarning, source=self + ) + context = {"client_response": self, "message": "Unclosed response"} + if self._source_traceback: + context["source_traceback"] = self._source_traceback + self._loop.call_exception_handler(context) + + def __repr__(self) -> str: + out = io.StringIO() + ascii_encodable_url = str(self.url) + if self.reason: + ascii_encodable_reason = self.reason.encode( + "ascii", "backslashreplace" + ).decode("ascii") + else: + ascii_encodable_reason = self.reason + print( + "".format( + ascii_encodable_url, self.status, ascii_encodable_reason + ), + file=out, + ) + print(self.headers, file=out) + return out.getvalue() + + @property + def connection(self) -> Optional["Connection"]: + return self._connection + + @reify + def history(self) -> Tuple["ClientResponse", ...]: + """A sequence of responses, if redirects occurred.""" + return self._history + + @reify + def links(self) -> "MultiDictProxy[MultiDictProxy[Union[str, URL]]]": + links_str = ", ".join(self.headers.getall("link", [])) + + if not links_str: + return MultiDictProxy(MultiDict()) + + links: MultiDict[MultiDictProxy[Union[str, URL]]] = MultiDict() + + for val in re.split(r",(?=\s*<)", links_str): + match = re.match(r"\s*<(.*)>(.*)", val) + if match is None: # pragma: no cover + # the check exists to suppress mypy error + continue + url, params_str = match.groups() + params = params_str.split(";")[1:] + + link: MultiDict[Union[str, URL]] = MultiDict() + + for param in params: + match = re.match(r"^\s*(\S*)\s*=\s*(['\"]?)(.*?)(\2)\s*$", param, re.M) + if match is None: # pragma: no cover + # the check exists to suppress mypy error + continue + key, _, value, _ = match.groups() + + link.add(key, value) + + key = link.get("rel", url) + + link.add("url", self.url.join(URL(url))) + + links.add(str(key), MultiDictProxy(link)) + + return MultiDictProxy(links) + + async def start(self, connection: "Connection") -> "ClientResponse": + """Start response processing.""" + self._closed = False + self._protocol = connection.protocol + self._connection = connection + + with self._timer: + while True: + # read response + try: + protocol = self._protocol + message, payload = await protocol.read() # type: ignore[union-attr] + except http.HttpProcessingError as exc: + raise ClientResponseError( + self.request_info, + self.history, + status=exc.code, + message=exc.message, + headers=exc.headers, + ) from exc + + if message.code < 100 or message.code > 199 or message.code == 101: + break + + if self._continue is not None: + set_result(self._continue, True) + self._continue = None + + # payload eof handler + payload.on_eof(self._response_eof) + + # response status + self.version = message.version + self.status = message.code + self.reason = message.reason + + # headers + self._headers = message.headers # type is CIMultiDictProxy + self._raw_headers = message.raw_headers # type is Tuple[bytes, bytes] + + # payload + self.content = payload + + # cookies + for hdr in self.headers.getall(hdrs.SET_COOKIE, ()): + try: + self.cookies.load(hdr) + except CookieError as exc: + client_logger.warning("Can not load response cookies: %s", exc) + return self + + def _response_eof(self) -> None: + if self._closed: + return + + if self._connection is not None: + # websocket, protocol could be None because + # connection could be detached + if ( + self._connection.protocol is not None + and self._connection.protocol.upgraded + ): + return + + self._connection.release() + self._connection = None + + self._closed = True + self._cleanup_writer() + + @property + def closed(self) -> bool: + return self._closed + + def close(self) -> None: + if not self._released: + self._notify_content() + if self._closed: + return + + self._closed = True + if self._loop.is_closed(): + return + + if self._connection is not None: + self._connection.close() + self._connection = None + self._cleanup_writer() + + def release(self) -> Any: + if not self._released: + self._notify_content() + if self._closed: + return noop() + + self._closed = True + if self._connection is not None: + self._connection.release() + self._connection = None + + self._cleanup_writer() + return noop() + + @property + def ok(self) -> bool: + """Returns ``True`` if ``status`` is less than ``400``, ``False`` if not. + + This is **not** a check for ``200 OK`` but a check that the response + status is under 400. + """ + return 400 > self.status + + def raise_for_status(self) -> None: + if not self.ok: + # reason should always be not None for a started response + assert self.reason is not None + self.release() + raise ClientResponseError( + self.request_info, + self.history, + status=self.status, + message=self.reason, + headers=self.headers, + ) + + def _cleanup_writer(self) -> None: + if self._writer is not None: + self._writer.cancel() + self._writer = None + self._session = None + + def _notify_content(self) -> None: + content = self.content + # content can be None here, but the types are cheated elsewhere. + if content and content.exception() is None: # type: ignore[truthy-bool] + content.set_exception(ClientConnectionError("Connection closed")) + self._released = True + + async def wait_for_close(self) -> None: + if self._writer is not None: + try: + await self._writer + finally: + self._writer = None + self.release() + + async def read(self) -> bytes: + """Read response payload.""" + if self._body is None: + try: + self._body = await self.content.read() + for trace in self._traces: + await trace.send_response_chunk_received( + self.method, self.url, self._body + ) + except BaseException: + self.close() + raise + elif self._released: + raise ClientConnectionError("Connection closed") + + return self._body + + def get_encoding(self) -> str: + ctype = self.headers.get(hdrs.CONTENT_TYPE, "").lower() + mimetype = helpers.parse_mimetype(ctype) + + encoding = mimetype.parameters.get("charset") + if encoding: + with contextlib.suppress(LookupError): + return codecs.lookup(encoding).name + + if mimetype.type == "application" and ( + mimetype.subtype == "json" or mimetype.subtype == "rdap" + ): + # RFC 7159 states that the default encoding is UTF-8. + # RFC 7483 defines application/rdap+json + return "utf-8" + + if self._body is None: + raise RuntimeError( + "Cannot compute fallback encoding of a not yet read body" + ) + + return self._resolve_charset(self, self._body) + + async def text(self, encoding: Optional[str] = None, errors: str = "strict") -> str: + """Read response payload and decode.""" + if self._body is None: + await self.read() + + if encoding is None: + encoding = self.get_encoding() + + return self._body.decode(encoding, errors=errors) # type: ignore[union-attr] + + async def json( + self, + *, + encoding: Optional[str] = None, + loads: JSONDecoder = DEFAULT_JSON_DECODER, + content_type: Optional[str] = "application/json", + ) -> Any: + """Read and decodes JSON response.""" + if self._body is None: + await self.read() + + if content_type: + if not is_expected_content_type(self.content_type, content_type): + raise ContentTypeError( + self.request_info, + self.history, + message=( + "Attempt to decode JSON with " + "unexpected mimetype: %s" % self.content_type + ), + headers=self.headers, + ) + + if encoding is None: + encoding = self.get_encoding() + + return loads(self._body.decode(encoding)) # type: ignore[union-attr] + + async def __aenter__(self) -> "ClientResponse": + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + # similar to _RequestContextManager, we do not need to check + # for exceptions, response object can close connection + # if state is broken + self.release() diff --git a/aiohttp/client_ws.py b/aiohttp/client_ws.py new file mode 100644 index 0000000..0a010fa --- /dev/null +++ b/aiohttp/client_ws.py @@ -0,0 +1,327 @@ +"""WebSocket client for asyncio.""" + +import asyncio +import dataclasses +import sys +from typing import Any, Final, Optional, cast + +from .client_exceptions import ClientError +from .client_reqrep import ClientResponse +from .helpers import call_later, set_result +from .http import ( + WS_CLOSED_MESSAGE, + WS_CLOSING_MESSAGE, + WebSocketError, + WSCloseCode, + WSMessage, + WSMsgType, +) +from .http_websocket import WebSocketWriter # WSMessage +from .streams import EofStream, FlowControlDataQueue +from .typedefs import ( + DEFAULT_JSON_DECODER, + DEFAULT_JSON_ENCODER, + JSONDecoder, + JSONEncoder, +) + +if sys.version_info >= (3, 11): + import asyncio as async_timeout +else: + import async_timeout + + +@dataclasses.dataclass(frozen=True) +class ClientWSTimeout: + ws_receive: Optional[float] = None + ws_close: Optional[float] = None + + +DEFAULT_WS_CLIENT_TIMEOUT: Final[ClientWSTimeout] = ClientWSTimeout( + ws_receive=None, ws_close=10.0 +) + + +class ClientWebSocketResponse: + def __init__( + self, + reader: "FlowControlDataQueue[WSMessage]", + writer: WebSocketWriter, + protocol: Optional[str], + response: ClientResponse, + timeout: ClientWSTimeout, + autoclose: bool, + autoping: bool, + loop: asyncio.AbstractEventLoop, + *, + heartbeat: Optional[float] = None, + compress: int = 0, + client_notakeover: bool = False, + ) -> None: + self._response = response + self._conn = response.connection + + self._writer = writer + self._reader = reader + self._protocol = protocol + self._closed = False + self._closing = False + self._close_code: Optional[int] = None + self._timeout: ClientWSTimeout = timeout + self._autoclose = autoclose + self._autoping = autoping + self._heartbeat = heartbeat + self._heartbeat_cb: Optional[asyncio.TimerHandle] = None + if heartbeat is not None: + self._pong_heartbeat = heartbeat / 2.0 + self._pong_response_cb: Optional[asyncio.TimerHandle] = None + self._loop = loop + self._waiting: Optional[asyncio.Future[bool]] = None + self._exception: Optional[BaseException] = None + self._compress = compress + self._client_notakeover = client_notakeover + + self._reset_heartbeat() + + def _cancel_heartbeat(self) -> None: + if self._pong_response_cb is not None: + self._pong_response_cb.cancel() + self._pong_response_cb = None + + if self._heartbeat_cb is not None: + self._heartbeat_cb.cancel() + self._heartbeat_cb = None + + def _reset_heartbeat(self) -> None: + self._cancel_heartbeat() + + if self._heartbeat is not None: + self._heartbeat_cb = call_later( + self._send_heartbeat, + self._heartbeat, + self._loop, + timeout_ceil_threshold=self._conn._connector._timeout_ceil_threshold + if self._conn is not None + else 5, + ) + + def _send_heartbeat(self) -> None: + if self._heartbeat is not None and not self._closed: + # fire-and-forget a task is not perfect but maybe ok for + # sending ping. Otherwise we need a long-living heartbeat + # task in the class. + self._loop.create_task(self._writer.ping()) # type: ignore[unused-awaitable] + + if self._pong_response_cb is not None: + self._pong_response_cb.cancel() + self._pong_response_cb = call_later( + self._pong_not_received, + self._pong_heartbeat, + self._loop, + timeout_ceil_threshold=self._conn._connector._timeout_ceil_threshold + if self._conn is not None + else 5, + ) + + def _pong_not_received(self) -> None: + if not self._closed: + self._closed = True + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = asyncio.TimeoutError() + self._response.close() + + @property + def closed(self) -> bool: + return self._closed + + @property + def close_code(self) -> Optional[int]: + return self._close_code + + @property + def protocol(self) -> Optional[str]: + return self._protocol + + @property + def compress(self) -> int: + return self._compress + + @property + def client_notakeover(self) -> bool: + return self._client_notakeover + + def get_extra_info(self, name: str, default: Any = None) -> Any: + """extra info from connection transport""" + conn = self._response.connection + if conn is None: + return default + transport = conn.transport + if transport is None: + return default + return transport.get_extra_info(name, default) + + def exception(self) -> Optional[BaseException]: + return self._exception + + async def ping(self, message: bytes = b"") -> None: + await self._writer.ping(message) + + async def pong(self, message: bytes = b"") -> None: + await self._writer.pong(message) + + async def send_str(self, data: str, compress: Optional[int] = None) -> None: + if not isinstance(data, str): + raise TypeError("data argument must be str (%r)" % type(data)) + await self._writer.send(data, binary=False, compress=compress) + + async def send_bytes(self, data: bytes, compress: Optional[int] = None) -> None: + if not isinstance(data, (bytes, bytearray, memoryview)): + raise TypeError("data argument must be byte-ish (%r)" % type(data)) + await self._writer.send(data, binary=True, compress=compress) + + async def send_json( + self, + data: Any, + compress: Optional[int] = None, + *, + dumps: JSONEncoder = DEFAULT_JSON_ENCODER, + ) -> None: + await self.send_str(dumps(data), compress=compress) + + async def close(self, *, code: int = WSCloseCode.OK, message: bytes = b"") -> bool: + # we need to break `receive()` cycle first, + # `close()` may be called from different task + if self._waiting is not None and not self._closed: + self._reader.feed_data(WS_CLOSING_MESSAGE, 0) + await self._waiting + + if not self._closed: + self._cancel_heartbeat() + self._closed = True + try: + await self._writer.close(code, message) + except asyncio.CancelledError: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._response.close() + raise + except Exception as exc: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = exc + self._response.close() + return True + + if self._closing: + self._response.close() + return True + + while True: + try: + async with async_timeout.timeout(self._timeout.ws_close): + msg = await self._reader.read() + except asyncio.CancelledError: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._response.close() + raise + except Exception as exc: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = exc + self._response.close() + return True + + if msg.type == WSMsgType.CLOSE: + self._close_code = msg.data + self._response.close() + return True + else: + return False + + async def receive(self, timeout: Optional[float] = None) -> WSMessage: + while True: + if self._waiting is not None: + raise RuntimeError("Concurrent call to receive() is not allowed") + + if self._closed: + return WS_CLOSED_MESSAGE + elif self._closing: + await self.close() + return WS_CLOSED_MESSAGE + + try: + self._waiting = self._loop.create_future() + try: + async with async_timeout.timeout( + timeout or self._timeout.ws_receive + ): + msg = await self._reader.read() + self._reset_heartbeat() + finally: + waiter = self._waiting + self._waiting = None + set_result(waiter, True) + except (asyncio.CancelledError, asyncio.TimeoutError): + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + raise + except EofStream: + self._close_code = WSCloseCode.OK + await self.close() + return WSMessage(WSMsgType.CLOSED, None, None) + except ClientError: + self._closed = True + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + return WS_CLOSED_MESSAGE + except WebSocketError as exc: + self._close_code = exc.code + await self.close(code=exc.code) + return WSMessage(WSMsgType.ERROR, exc, None) + except Exception as exc: + self._exception = exc + self._closing = True + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + await self.close() + return WSMessage(WSMsgType.ERROR, exc, None) + + if msg.type == WSMsgType.CLOSE: + self._closing = True + self._close_code = msg.data + # Could be closed elsewhere while awaiting reader + if not self._closed and self._autoclose: # type: ignore[redundant-expr] + await self.close() + elif msg.type == WSMsgType.CLOSING: + self._closing = True + elif msg.type == WSMsgType.PING and self._autoping: + await self.pong(msg.data) + continue + elif msg.type == WSMsgType.PONG and self._autoping: + continue + + return msg + + async def receive_str(self, *, timeout: Optional[float] = None) -> str: + msg = await self.receive(timeout) + if msg.type != WSMsgType.TEXT: + raise TypeError(f"Received message {msg.type}:{msg.data!r} is not str") + return cast(str, msg.data) + + async def receive_bytes(self, *, timeout: Optional[float] = None) -> bytes: + msg = await self.receive(timeout) + if msg.type != WSMsgType.BINARY: + raise TypeError(f"Received message {msg.type}:{msg.data!r} is not bytes") + return cast(bytes, msg.data) + + async def receive_json( + self, + *, + loads: JSONDecoder = DEFAULT_JSON_DECODER, + timeout: Optional[float] = None, + ) -> Any: + data = await self.receive_str(timeout=timeout) + return loads(data) + + def __aiter__(self) -> "ClientWebSocketResponse": + return self + + async def __anext__(self) -> WSMessage: + msg = await self.receive() + if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED): + raise StopAsyncIteration + return msg diff --git a/aiohttp/compression_utils.py b/aiohttp/compression_utils.py new file mode 100644 index 0000000..52791fe --- /dev/null +++ b/aiohttp/compression_utils.py @@ -0,0 +1,151 @@ +import asyncio +import zlib +from concurrent.futures import Executor +from typing import Optional, cast + +try: + try: + import brotlicffi as brotli + except ImportError: + import brotli + + HAS_BROTLI = True +except ImportError: # pragma: no cover + HAS_BROTLI = False + +MAX_SYNC_CHUNK_SIZE = 1024 + + +def encoding_to_mode( + encoding: Optional[str] = None, + suppress_deflate_header: bool = False, +) -> int: + if encoding == "gzip": + return 16 + zlib.MAX_WBITS + + return -zlib.MAX_WBITS if suppress_deflate_header else zlib.MAX_WBITS + + +class ZlibBaseHandler: + def __init__( + self, + mode: int, + executor: Optional[Executor] = None, + max_sync_chunk_size: Optional[int] = MAX_SYNC_CHUNK_SIZE, + ): + self._mode = mode + self._executor = executor + self._max_sync_chunk_size = max_sync_chunk_size + + +class ZLibCompressor(ZlibBaseHandler): + def __init__( + self, + encoding: Optional[str] = None, + suppress_deflate_header: bool = False, + level: Optional[int] = None, + wbits: Optional[int] = None, + strategy: int = zlib.Z_DEFAULT_STRATEGY, + executor: Optional[Executor] = None, + max_sync_chunk_size: Optional[int] = MAX_SYNC_CHUNK_SIZE, + ): + super().__init__( + mode=encoding_to_mode(encoding, suppress_deflate_header) + if wbits is None + else wbits, + executor=executor, + max_sync_chunk_size=max_sync_chunk_size, + ) + if level is None: + self._compressor = zlib.compressobj(wbits=self._mode, strategy=strategy) + else: + self._compressor = zlib.compressobj( + wbits=self._mode, strategy=strategy, level=level + ) + + def compress_sync(self, data: bytes) -> bytes: + return self._compressor.compress(data) + + async def compress(self, data: bytes) -> bytes: + if ( + self._max_sync_chunk_size is not None + and len(data) > self._max_sync_chunk_size + ): + return await asyncio.get_event_loop().run_in_executor( + self._executor, self.compress_sync, data + ) + return self.compress_sync(data) + + def flush(self, mode: int = zlib.Z_FINISH) -> bytes: + return self._compressor.flush(mode) + + +class ZLibDecompressor(ZlibBaseHandler): + def __init__( + self, + encoding: Optional[str] = None, + suppress_deflate_header: bool = False, + executor: Optional[Executor] = None, + max_sync_chunk_size: Optional[int] = MAX_SYNC_CHUNK_SIZE, + ): + super().__init__( + mode=encoding_to_mode(encoding, suppress_deflate_header), + executor=executor, + max_sync_chunk_size=max_sync_chunk_size, + ) + self._decompressor = zlib.decompressobj(wbits=self._mode) + + def decompress_sync(self, data: bytes, max_length: int = 0) -> bytes: + return self._decompressor.decompress(data, max_length) + + async def decompress(self, data: bytes, max_length: int = 0) -> bytes: + if ( + self._max_sync_chunk_size is not None + and len(data) > self._max_sync_chunk_size + ): + return await asyncio.get_event_loop().run_in_executor( + self._executor, self.decompress_sync, data, max_length + ) + return self.decompress_sync(data, max_length) + + def flush(self, length: int = 0) -> bytes: + return ( + self._decompressor.flush(length) + if length > 0 + else self._decompressor.flush() + ) + + @property + def eof(self) -> bool: + return self._decompressor.eof + + @property + def unconsumed_tail(self) -> bytes: + return self._decompressor.unconsumed_tail + + @property + def unused_data(self) -> bytes: + return self._decompressor.unused_data + + +class BrotliDecompressor: + # Supports both 'brotlipy' and 'Brotli' packages + # since they share an import name. The top branches + # are for 'brotlipy' and bottom branches for 'Brotli' + def __init__(self) -> None: + if not HAS_BROTLI: + raise RuntimeError( + "The brotli decompression is not available. " + "Please install `Brotli` module" + ) + self._obj = brotli.Decompressor() + + def decompress_sync(self, data: bytes) -> bytes: + if hasattr(self._obj, "decompress"): + return cast(bytes, self._obj.decompress(data)) + return cast(bytes, self._obj.process(data)) + + def flush(self) -> bytes: + if hasattr(self._obj, "flush"): + return cast(bytes, self._obj.flush()) + return b"" diff --git a/aiohttp/connector.py b/aiohttp/connector.py new file mode 100644 index 0000000..01a1ca8 --- /dev/null +++ b/aiohttp/connector.py @@ -0,0 +1,1386 @@ +import asyncio +import dataclasses +import functools +import logging +import random +import sys +import traceback +import warnings +from collections import defaultdict, deque +from contextlib import suppress +from http import HTTPStatus +from http.cookies import SimpleCookie +from itertools import cycle, islice +from time import monotonic +from types import TracebackType +from typing import ( # noqa + TYPE_CHECKING, + Any, + Awaitable, + Callable, + DefaultDict, + Dict, + Iterator, + List, + Literal, + Optional, + Set, + Tuple, + Type, + Union, + cast, +) + +from . import hdrs, helpers +from .abc import AbstractResolver +from .client_exceptions import ( + ClientConnectionError, + ClientConnectorCertificateError, + ClientConnectorError, + ClientConnectorSSLError, + ClientHttpProxyError, + ClientProxyConnectionError, + ServerFingerprintMismatch, + UnixClientConnectorError, + cert_errors, + ssl_errors, +) +from .client_proto import ResponseHandler +from .client_reqrep import SSL_ALLOWED_TYPES, ClientRequest, Fingerprint +from .helpers import _SENTINEL, ceil_timeout, is_ip_address, sentinel, set_result +from .locks import EventResultOrError +from .resolver import DefaultResolver + +try: + import ssl + + SSLContext = ssl.SSLContext +except ImportError: # pragma: no cover + ssl = None # type: ignore[assignment] + SSLContext = object # type: ignore[misc,assignment] + + +__all__ = ("BaseConnector", "TCPConnector", "UnixConnector", "NamedPipeConnector") + + +if TYPE_CHECKING: # pragma: no cover + from .client import ClientTimeout + from .client_reqrep import ConnectionKey + from .tracing import Trace + + +class Connection: + _source_traceback = None + _transport = None + + def __init__( + self, + connector: "BaseConnector", + key: "ConnectionKey", + protocol: ResponseHandler, + loop: asyncio.AbstractEventLoop, + ) -> None: + self._key = key + self._connector = connector + self._loop = loop + self._protocol: Optional[ResponseHandler] = protocol + self._callbacks: List[Callable[[], None]] = [] + + if loop.get_debug(): + self._source_traceback = traceback.extract_stack(sys._getframe(1)) + + def __repr__(self) -> str: + return f"Connection<{self._key}>" + + def __del__(self, _warnings: Any = warnings) -> None: + if self._protocol is not None: + _warnings.warn( + f"Unclosed connection {self!r}", ResourceWarning, source=self + ) + if self._loop.is_closed(): + return + + self._connector._release(self._key, self._protocol, should_close=True) + + context = {"client_connection": self, "message": "Unclosed connection"} + if self._source_traceback is not None: + context["source_traceback"] = self._source_traceback + self._loop.call_exception_handler(context) + + @property + def transport(self) -> Optional[asyncio.Transport]: + if self._protocol is None: + return None + return self._protocol.transport + + @property + def protocol(self) -> Optional[ResponseHandler]: + return self._protocol + + def add_callback(self, callback: Callable[[], None]) -> None: + if callback is not None: + self._callbacks.append(callback) + + def _notify_release(self) -> None: + callbacks, self._callbacks = self._callbacks[:], [] + + for cb in callbacks: + with suppress(Exception): + cb() + + def close(self) -> None: + self._notify_release() + + if self._protocol is not None: + self._connector._release(self._key, self._protocol, should_close=True) + self._protocol = None + + def release(self) -> None: + self._notify_release() + + if self._protocol is not None: + self._connector._release( + self._key, self._protocol, should_close=self._protocol.should_close + ) + self._protocol = None + + @property + def closed(self) -> bool: + return self._protocol is None or not self._protocol.is_connected() + + +class _TransportPlaceholder: + """placeholder for BaseConnector.connect function""" + + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: + fut = loop.create_future() + fut.set_result(None) + self.closed: asyncio.Future[Optional[Exception]] = fut + + def close(self) -> None: + pass + + +class BaseConnector: + """Base connector class. + + keepalive_timeout - (optional) Keep-alive timeout. + force_close - Set to True to force close and do reconnect + after each request (and between redirects). + limit - The total number of simultaneous connections. + limit_per_host - Number of simultaneous connections to one host. + enable_cleanup_closed - Enables clean-up closed ssl transports. + Disabled by default. + timeout_ceil_threshold - Trigger ceiling of timeout values when + it's above timeout_ceil_threshold. + loop - Optional event loop. + """ + + _closed = True # prevent AttributeError in __del__ if ctor was failed + _source_traceback = None + + # abort transport after 2 seconds (cleanup broken connections) + _cleanup_closed_period = 2.0 + + def __init__( + self, + *, + keepalive_timeout: Union[_SENTINEL, None, float] = sentinel, + force_close: bool = False, + limit: int = 100, + limit_per_host: int = 0, + enable_cleanup_closed: bool = False, + timeout_ceil_threshold: float = 5, + ) -> None: + if force_close: + if keepalive_timeout is not None and keepalive_timeout is not sentinel: + raise ValueError( + "keepalive_timeout cannot " "be set if force_close is True" + ) + else: + if keepalive_timeout is sentinel: + keepalive_timeout = 15.0 + + self._timeout_ceil_threshold = timeout_ceil_threshold + + loop = asyncio.get_running_loop() + + self._closed = False + if loop.get_debug(): + self._source_traceback = traceback.extract_stack(sys._getframe(1)) + + self._conns: Dict[ConnectionKey, List[Tuple[ResponseHandler, float]]] = {} + self._limit = limit + self._limit_per_host = limit_per_host + self._acquired: Set[ResponseHandler] = set() + self._acquired_per_host: DefaultDict[ + ConnectionKey, Set[ResponseHandler] + ] = defaultdict(set) + self._keepalive_timeout = cast(float, keepalive_timeout) + self._force_close = force_close + + # {host_key: FIFO list of waiters} + self._waiters = defaultdict(deque) # type: ignore[var-annotated] + + self._loop = loop + self._factory = functools.partial(ResponseHandler, loop=loop) + + self.cookies: SimpleCookie[str] = SimpleCookie() + + # start keep-alive connection cleanup task + self._cleanup_handle: Optional[asyncio.TimerHandle] = None + + # start cleanup closed transports task + self._cleanup_closed_handle: Optional[asyncio.TimerHandle] = None + self._cleanup_closed_disabled = not enable_cleanup_closed + self._cleanup_closed_transports: List[Optional[asyncio.Transport]] = [] + self._cleanup_closed() + + def __del__(self, _warnings: Any = warnings) -> None: + if self._closed: + return + if not self._conns: + return + + conns = [repr(c) for c in self._conns.values()] + + self._close_immediately() + + _warnings.warn(f"Unclosed connector {self!r}", ResourceWarning, source=self) + context = { + "connector": self, + "connections": conns, + "message": "Unclosed connector", + } + if self._source_traceback is not None: + context["source_traceback"] = self._source_traceback + self._loop.call_exception_handler(context) + + async def __aenter__(self) -> "BaseConnector": + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]] = None, + exc_value: Optional[BaseException] = None, + exc_traceback: Optional[TracebackType] = None, + ) -> None: + await self.close() + + @property + def force_close(self) -> bool: + """Ultimately close connection on releasing if True.""" + return self._force_close + + @property + def limit(self) -> int: + """The total number for simultaneous connections. + + If limit is 0 the connector has no limit. + The default limit size is 100. + """ + return self._limit + + @property + def limit_per_host(self) -> int: + """The limit for simultaneous connections to the same endpoint. + + Endpoints are the same if they are have equal + (host, port, is_ssl) triple. + """ + return self._limit_per_host + + def _cleanup(self) -> None: + """Cleanup unused transports.""" + if self._cleanup_handle: + self._cleanup_handle.cancel() + # _cleanup_handle should be unset, otherwise _release() will not + # recreate it ever! + self._cleanup_handle = None + + now = self._loop.time() + timeout = self._keepalive_timeout + + if self._conns: + connections = {} + deadline = now - timeout + for key, conns in self._conns.items(): + alive = [] + for proto, use_time in conns: + if proto.is_connected(): + if use_time - deadline < 0: + transport = proto.transport + proto.close() + if key.is_ssl and not self._cleanup_closed_disabled: + self._cleanup_closed_transports.append(transport) + else: + alive.append((proto, use_time)) + else: + transport = proto.transport + proto.close() + if key.is_ssl and not self._cleanup_closed_disabled: + self._cleanup_closed_transports.append(transport) + + if alive: + connections[key] = alive + + self._conns = connections + + if self._conns: + self._cleanup_handle = helpers.weakref_handle( + self, + "_cleanup", + timeout, + self._loop, + timeout_ceil_threshold=self._timeout_ceil_threshold, + ) + + def _drop_acquired_per_host( + self, key: "ConnectionKey", val: ResponseHandler + ) -> None: + acquired_per_host = self._acquired_per_host + if key not in acquired_per_host: + return + conns = acquired_per_host[key] + conns.remove(val) + if not conns: + del self._acquired_per_host[key] + + def _cleanup_closed(self) -> None: + """Double confirmation for transport close. + + Some broken ssl servers may leave socket open without proper close. + """ + if self._cleanup_closed_handle: + self._cleanup_closed_handle.cancel() + + for transport in self._cleanup_closed_transports: + if transport is not None: + transport.abort() + + self._cleanup_closed_transports = [] + + if not self._cleanup_closed_disabled: + self._cleanup_closed_handle = helpers.weakref_handle( + self, + "_cleanup_closed", + self._cleanup_closed_period, + self._loop, + timeout_ceil_threshold=self._timeout_ceil_threshold, + ) + + async def close(self) -> None: + """Close all opened transports.""" + waiters = self._close_immediately() + if waiters: + results = await asyncio.gather(*waiters, return_exceptions=True) + for res in results: + if isinstance(res, Exception): + err_msg = "Error while closing connector: " + repr(res) + logging.error(err_msg) + + def _close_immediately(self) -> List["asyncio.Future[None]"]: + waiters: List["asyncio.Future[None]"] = [] + + if self._closed: + return waiters + + self._closed = True + + try: + if self._loop.is_closed(): + return waiters + + # cancel cleanup task + if self._cleanup_handle: + self._cleanup_handle.cancel() + + # cancel cleanup close task + if self._cleanup_closed_handle: + self._cleanup_closed_handle.cancel() + + for data in self._conns.values(): + for proto, t0 in data: + proto.close() + waiters.append(proto.closed) + + for proto in self._acquired: + proto.close() + waiters.append(proto.closed) + + # TODO (A.Yushovskiy, 24-May-2019) collect transp. closing futures + for transport in self._cleanup_closed_transports: + if transport is not None: + transport.abort() + + return waiters + + finally: + self._conns.clear() + self._acquired.clear() + self._waiters.clear() + self._cleanup_handle = None + self._cleanup_closed_transports.clear() + self._cleanup_closed_handle = None + + @property + def closed(self) -> bool: + """Is connector closed. + + A readonly property. + """ + return self._closed + + def _available_connections(self, key: "ConnectionKey") -> int: + """ + Return number of available connections. + + The limit, limit_per_host and the connection key are taken into account. + + If it returns less than 1 means that there are no connections + available. + """ + if self._limit: + # total calc available connections + available = self._limit - len(self._acquired) + + # check limit per host + if ( + self._limit_per_host + and available > 0 + and key in self._acquired_per_host + ): + acquired = self._acquired_per_host.get(key) + assert acquired is not None + available = self._limit_per_host - len(acquired) + + elif self._limit_per_host and key in self._acquired_per_host: + # check limit per host + acquired = self._acquired_per_host.get(key) + assert acquired is not None + available = self._limit_per_host - len(acquired) + else: + available = 1 + + return available + + async def connect( + self, req: ClientRequest, traces: List["Trace"], timeout: "ClientTimeout" + ) -> Connection: + """Get from pool or create new connection.""" + key = req.connection_key + available = self._available_connections(key) + + # Wait if there are no available connections or if there are/were + # waiters (i.e. don't steal connection from a waiter about to wake up) + if available <= 0 or key in self._waiters: + fut = self._loop.create_future() + + # This connection will now count towards the limit. + self._waiters[key].append(fut) + + if traces: + for trace in traces: + await trace.send_connection_queued_start() + + try: + await fut + except BaseException as e: + if key in self._waiters: + # remove a waiter even if it was cancelled, normally it's + # removed when it's notified + try: + self._waiters[key].remove(fut) + except ValueError: # fut may no longer be in list + pass + + raise e + finally: + if key in self._waiters and not self._waiters[key]: + del self._waiters[key] + + if traces: + for trace in traces: + await trace.send_connection_queued_end() + + proto = self._get(key) + if proto is None: + placeholder = cast(ResponseHandler, _TransportPlaceholder(self._loop)) + self._acquired.add(placeholder) + self._acquired_per_host[key].add(placeholder) + + if traces: + for trace in traces: + await trace.send_connection_create_start() + + try: + proto = await self._create_connection(req, traces, timeout) + if self._closed: + proto.close() + raise ClientConnectionError("Connector is closed.") + except BaseException: + if not self._closed: + self._acquired.remove(placeholder) + self._drop_acquired_per_host(key, placeholder) + self._release_waiter() + raise + else: + if not self._closed: + self._acquired.remove(placeholder) + self._drop_acquired_per_host(key, placeholder) + + if traces: + for trace in traces: + await trace.send_connection_create_end() + else: + if traces: + # Acquire the connection to prevent race conditions with limits + placeholder = cast(ResponseHandler, _TransportPlaceholder(self._loop)) + self._acquired.add(placeholder) + self._acquired_per_host[key].add(placeholder) + for trace in traces: + await trace.send_connection_reuseconn() + self._acquired.remove(placeholder) + self._drop_acquired_per_host(key, placeholder) + + self._acquired.add(proto) + self._acquired_per_host[key].add(proto) + return Connection(self, key, proto, self._loop) + + def _get(self, key: "ConnectionKey") -> Optional[ResponseHandler]: + try: + conns = self._conns[key] + except KeyError: + return None + + t1 = self._loop.time() + while conns: + proto, t0 = conns.pop() + if proto.is_connected(): + if t1 - t0 > self._keepalive_timeout: + transport = proto.transport + proto.close() + # only for SSL transports + if key.is_ssl and not self._cleanup_closed_disabled: + self._cleanup_closed_transports.append(transport) + else: + if not conns: + # The very last connection was reclaimed: drop the key + del self._conns[key] + return proto + else: + transport = proto.transport + proto.close() + if key.is_ssl and not self._cleanup_closed_disabled: + self._cleanup_closed_transports.append(transport) + + # No more connections: drop the key + del self._conns[key] + return None + + def _release_waiter(self) -> None: + """ + Iterates over all waiters until one to be released is found. + + The one to be released is not finished and + belongs to a host that has available connections. + """ + if not self._waiters: + return + + # Having the dict keys ordered this avoids to iterate + # at the same order at each call. + queues = list(self._waiters.keys()) + random.shuffle(queues) + + for key in queues: + if self._available_connections(key) < 1: + continue + + waiters = self._waiters[key] + while waiters: + waiter = waiters.popleft() + if not waiter.done(): + waiter.set_result(None) + return + + def _release_acquired(self, key: "ConnectionKey", proto: ResponseHandler) -> None: + if self._closed: + # acquired connection is already released on connector closing + return + + try: + self._acquired.remove(proto) + self._drop_acquired_per_host(key, proto) + except KeyError: # pragma: no cover + # this may be result of undetermenistic order of objects + # finalization due garbage collection. + pass + else: + self._release_waiter() + + def _release( + self, + key: "ConnectionKey", + protocol: ResponseHandler, + *, + should_close: bool = False, + ) -> None: + if self._closed: + # acquired connection is already released on connector closing + return + + self._release_acquired(key, protocol) + + if self._force_close: + should_close = True + + if should_close or protocol.should_close: + transport = protocol.transport + protocol.close() + # TODO: Remove once fixed: https://bugs.python.org/issue39951 + # See PR #6321 + set_result(protocol.closed, None) + + if key.is_ssl and not self._cleanup_closed_disabled: + self._cleanup_closed_transports.append(transport) + else: + conns = self._conns.get(key) + if conns is None: + conns = self._conns[key] = [] + conns.append((protocol, self._loop.time())) + + if self._cleanup_handle is None: + self._cleanup_handle = helpers.weakref_handle( + self, + "_cleanup", + self._keepalive_timeout, + self._loop, + timeout_ceil_threshold=self._timeout_ceil_threshold, + ) + + async def _create_connection( + self, req: ClientRequest, traces: List["Trace"], timeout: "ClientTimeout" + ) -> ResponseHandler: + raise NotImplementedError() + + +class _DNSCacheTable: + def __init__(self, ttl: Optional[float] = None) -> None: + self._addrs_rr: Dict[Tuple[str, int], Tuple[Iterator[Dict[str, Any]], int]] = {} + self._timestamps: Dict[Tuple[str, int], float] = {} + self._ttl = ttl + + def __contains__(self, host: object) -> bool: + return host in self._addrs_rr + + def add(self, key: Tuple[str, int], addrs: List[Dict[str, Any]]) -> None: + self._addrs_rr[key] = (cycle(addrs), len(addrs)) + + if self._ttl is not None: + self._timestamps[key] = monotonic() + + def remove(self, key: Tuple[str, int]) -> None: + self._addrs_rr.pop(key, None) + + if self._ttl is not None: + self._timestamps.pop(key, None) + + def clear(self) -> None: + self._addrs_rr.clear() + self._timestamps.clear() + + def next_addrs(self, key: Tuple[str, int]) -> List[Dict[str, Any]]: + loop, length = self._addrs_rr[key] + addrs = list(islice(loop, length)) + # Consume one more element to shift internal state of `cycle` + next(loop) + return addrs + + def expired(self, key: Tuple[str, int]) -> bool: + if self._ttl is None: + return False + + return self._timestamps[key] + self._ttl < monotonic() + + +class TCPConnector(BaseConnector): + """TCP connector. + + verify_ssl - Set to True to check ssl certifications. + fingerprint - Pass the binary sha256 + digest of the expected certificate in DER format to verify + that the certificate the server presents matches. See also + https://en.wikipedia.org/wiki/Transport_Layer_Security#Certificate_pinning + resolver - Enable DNS lookups and use this + resolver + use_dns_cache - Use memory cache for DNS lookups. + ttl_dns_cache - Max seconds having cached a DNS entry, None forever. + family - socket address family + local_addr - local tuple of (host, port) to bind socket to + + keepalive_timeout - (optional) Keep-alive timeout. + force_close - Set to True to force close and do reconnect + after each request (and between redirects). + limit - The total number of simultaneous connections. + limit_per_host - Number of simultaneous connections to one host. + enable_cleanup_closed - Enables clean-up closed ssl transports. + Disabled by default. + loop - Optional event loop. + """ + + def __init__( + self, + *, + use_dns_cache: bool = True, + ttl_dns_cache: Optional[int] = 10, + family: int = 0, + ssl: Union[None, Literal[False], Fingerprint, SSLContext] = None, + local_addr: Optional[Tuple[str, int]] = None, + resolver: Optional[AbstractResolver] = None, + keepalive_timeout: Union[None, float, _SENTINEL] = sentinel, + force_close: bool = False, + limit: int = 100, + limit_per_host: int = 0, + enable_cleanup_closed: bool = False, + timeout_ceil_threshold: float = 5, + ) -> None: + super().__init__( + keepalive_timeout=keepalive_timeout, + force_close=force_close, + limit=limit, + limit_per_host=limit_per_host, + enable_cleanup_closed=enable_cleanup_closed, + timeout_ceil_threshold=timeout_ceil_threshold, + ) + + if not isinstance(ssl, SSL_ALLOWED_TYPES): + raise TypeError( + "ssl should be SSLContext, bool, Fingerprint, " + "or None, got {!r} instead.".format(ssl) + ) + self._ssl = ssl + if resolver is None: + resolver = DefaultResolver() + self._resolver: AbstractResolver = resolver + + self._use_dns_cache = use_dns_cache + self._cached_hosts = _DNSCacheTable(ttl=ttl_dns_cache) + self._throttle_dns_events: Dict[Tuple[str, int], EventResultOrError] = {} + self._family = family + self._local_addr = local_addr + + def _close_immediately(self) -> List["asyncio.Future[None]"]: + for ev in self._throttle_dns_events.values(): + ev.cancel() + return super()._close_immediately() + + @property + def family(self) -> int: + """Socket family like AF_INET.""" + return self._family + + @property + def use_dns_cache(self) -> bool: + """True if local DNS caching is enabled.""" + return self._use_dns_cache + + def clear_dns_cache( + self, host: Optional[str] = None, port: Optional[int] = None + ) -> None: + """Remove specified host/port or clear all dns local cache.""" + if host is not None and port is not None: + self._cached_hosts.remove((host, port)) + elif host is not None or port is not None: + raise ValueError("either both host and port " "or none of them are allowed") + else: + self._cached_hosts.clear() + + async def _resolve_host( + self, host: str, port: int, traces: Optional[List["Trace"]] = None + ) -> List[Dict[str, Any]]: + if is_ip_address(host): + return [ + { + "hostname": host, + "host": host, + "port": port, + "family": self._family, + "proto": 0, + "flags": 0, + } + ] + + if not self._use_dns_cache: + if traces: + for trace in traces: + await trace.send_dns_resolvehost_start(host) + + res = await self._resolver.resolve(host, port, family=self._family) + + if traces: + for trace in traces: + await trace.send_dns_resolvehost_end(host) + + return res + + key = (host, port) + + if (key in self._cached_hosts) and (not self._cached_hosts.expired(key)): + # get result early, before any await (#4014) + result = self._cached_hosts.next_addrs(key) + + if traces: + for trace in traces: + await trace.send_dns_cache_hit(host) + return result + + if key in self._throttle_dns_events: + # get event early, before any await (#4014) + event = self._throttle_dns_events[key] + if traces: + for trace in traces: + await trace.send_dns_cache_hit(host) + await event.wait() + else: + # update dict early, before any await (#4014) + self._throttle_dns_events[key] = EventResultOrError(self._loop) + if traces: + for trace in traces: + await trace.send_dns_cache_miss(host) + try: + if traces: + for trace in traces: + await trace.send_dns_resolvehost_start(host) + + addrs = await self._resolver.resolve(host, port, family=self._family) + if traces: + for trace in traces: + await trace.send_dns_resolvehost_end(host) + + self._cached_hosts.add(key, addrs) + self._throttle_dns_events[key].set() + except BaseException as e: + # any DNS exception, independently of the implementation + # is set for the waiters to raise the same exception. + self._throttle_dns_events[key].set(exc=e) + raise + finally: + self._throttle_dns_events.pop(key) + + return self._cached_hosts.next_addrs(key) + + async def _create_connection( + self, req: ClientRequest, traces: List["Trace"], timeout: "ClientTimeout" + ) -> ResponseHandler: + """Create connection. + + Has same keyword arguments as BaseEventLoop.create_connection. + """ + if req.proxy: + _, proto = await self._create_proxy_connection(req, traces, timeout) + else: + _, proto = await self._create_direct_connection(req, traces, timeout) + + return proto + + @staticmethod + @functools.lru_cache(None) + def _make_ssl_context(verified: bool) -> SSLContext: + if verified: + return ssl.create_default_context() + else: + sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + sslcontext.options |= ssl.OP_NO_SSLv2 + sslcontext.options |= ssl.OP_NO_SSLv3 + sslcontext.check_hostname = False + sslcontext.verify_mode = ssl.CERT_NONE + try: + sslcontext.options |= ssl.OP_NO_COMPRESSION + except AttributeError as attr_err: + warnings.warn( + "{!s}: The Python interpreter is compiled " + "against OpenSSL < 1.0.0. Ref: " + "https://docs.python.org/3/library/ssl.html" + "#ssl.OP_NO_COMPRESSION".format(attr_err), + ) + sslcontext.set_default_verify_paths() + return sslcontext + + def _get_ssl_context(self, req: ClientRequest) -> Optional[SSLContext]: + """Logic to get the correct SSL context + + 0. if req.ssl is false, return None + + 1. if ssl_context is specified in req, use it + 2. if _ssl_context is specified in self, use it + 3. otherwise: + 1. if verify_ssl is not specified in req, use self.ssl_context + (will generate a default context according to self.verify_ssl) + 2. if verify_ssl is True in req, generate a default SSL context + 3. if verify_ssl is False in req, generate a SSL context that + won't verify + """ + if req.is_ssl(): + if ssl is None: # pragma: no cover + raise RuntimeError("SSL is not supported.") + sslcontext = req.ssl + if isinstance(sslcontext, ssl.SSLContext): + return sslcontext + if sslcontext is not None: + # not verified or fingerprinted + return self._make_ssl_context(False) + sslcontext = self._ssl + if isinstance(sslcontext, ssl.SSLContext): + return sslcontext + if sslcontext is not None: + # not verified or fingerprinted + return self._make_ssl_context(False) + return self._make_ssl_context(True) + else: + return None + + def _get_fingerprint(self, req: ClientRequest) -> Optional["Fingerprint"]: + ret = req.ssl + if isinstance(ret, Fingerprint): + return ret + ret = self._ssl + if isinstance(ret, Fingerprint): + return ret + return None + + async def _wrap_create_connection( + self, + *args: Any, + req: ClientRequest, + timeout: "ClientTimeout", + client_error: Type[Exception] = ClientConnectorError, + **kwargs: Any, + ) -> Tuple[asyncio.Transport, ResponseHandler]: + try: + async with ceil_timeout( + timeout.sock_connect, ceil_threshold=timeout.ceil_threshold + ): + return await self._loop.create_connection(*args, **kwargs) + except cert_errors as exc: + raise ClientConnectorCertificateError(req.connection_key, exc) from exc + except ssl_errors as exc: + raise ClientConnectorSSLError(req.connection_key, exc) from exc + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + raise client_error(req.connection_key, exc) from exc + + def _warn_about_tls_in_tls( + self, + underlying_transport: asyncio.Transport, + req: ClientRequest, + ) -> None: + """Issue a warning if the requested URL has HTTPS scheme.""" + if req.request_info.url.scheme != "https": + return + + asyncio_supports_tls_in_tls = getattr( + underlying_transport, + "_start_tls_compatible", + False, + ) + + if asyncio_supports_tls_in_tls: + return + + warnings.warn( + "An HTTPS request is being sent through an HTTPS proxy. " + "This support for TLS in TLS is known to be disabled " + "in the stdlib asyncio. This is why you'll probably see " + "an error in the log below.\n\n" + "It is possible to enable it via monkeypatching. " + "For more details, see:\n" + "* https://bugs.python.org/issue37179\n" + "* https://github.com/python/cpython/pull/28073\n\n" + "You can temporarily patch this as follows:\n" + "* https://docs.aiohttp.org/en/stable/client_advanced.html#proxy-support\n" + "* https://github.com/aio-libs/aiohttp/discussions/6044\n", + RuntimeWarning, + source=self, + # Why `4`? At least 3 of the calls in the stack originate + # from the methods in this class. + stacklevel=3, + ) + + async def _start_tls_connection( + self, + underlying_transport: asyncio.Transport, + req: ClientRequest, + timeout: "ClientTimeout", + client_error: Type[Exception] = ClientConnectorError, + ) -> Tuple[asyncio.BaseTransport, ResponseHandler]: + """Wrap the raw TCP transport with TLS.""" + tls_proto = self._factory() # Create a brand new proto for TLS + + # Safety of the `cast()` call here is based on the fact that + # internally `_get_ssl_context()` only returns `None` when + # `req.is_ssl()` evaluates to `False` which is never gonna happen + # in this code path. Of course, it's rather fragile + # maintainability-wise but this is to be solved separately. + sslcontext = cast(ssl.SSLContext, self._get_ssl_context(req)) + + try: + async with ceil_timeout( + timeout.sock_connect, ceil_threshold=timeout.ceil_threshold + ): + try: + tls_transport = await self._loop.start_tls( + underlying_transport, + tls_proto, + sslcontext, + server_hostname=req.server_hostname or req.host, + ssl_handshake_timeout=timeout.total, + ) + except BaseException: + # We need to close the underlying transport since + # `start_tls()` probably failed before it had a + # chance to do this: + underlying_transport.close() + raise + except cert_errors as exc: + raise ClientConnectorCertificateError(req.connection_key, exc) from exc + except ssl_errors as exc: + raise ClientConnectorSSLError(req.connection_key, exc) from exc + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + raise client_error(req.connection_key, exc) from exc + except TypeError as type_err: + # Example cause looks like this: + # TypeError: transport is not supported by start_tls() + + raise ClientConnectionError( + "Cannot initialize a TLS-in-TLS connection to host " + f"{req.host!s}:{req.port:d} through an underlying connection " + f"to an HTTPS proxy {req.proxy!s} ssl:{req.ssl or 'default'} " + f"[{type_err!s}]" + ) from type_err + else: + if tls_transport is None: + msg = "Failed to start TLS (possibly caused by closing transport)" + raise client_error(req.connection_key, OSError(msg)) + tls_proto.connection_made( + tls_transport + ) # Kick the state machine of the new TLS protocol + + return tls_transport, tls_proto + + async def _create_direct_connection( + self, + req: ClientRequest, + traces: List["Trace"], + timeout: "ClientTimeout", + *, + client_error: Type[Exception] = ClientConnectorError, + ) -> Tuple[asyncio.Transport, ResponseHandler]: + sslcontext = self._get_ssl_context(req) + fingerprint = self._get_fingerprint(req) + + host = req.url.raw_host + assert host is not None + # Replace multiple trailing dots with a single one. + # A trailing dot is only present for fully-qualified domain names. + # See https://github.com/aio-libs/aiohttp/pull/7364. + if host.endswith(".."): + host = host.rstrip(".") + "." + port = req.port + assert port is not None + host_resolved = asyncio.ensure_future( + self._resolve_host(host, port, traces=traces), loop=self._loop + ) + try: + # Cancelling this lookup should not cancel the underlying lookup + # or else the cancel event will get broadcast to all the waiters + # across all connections. + hosts = await asyncio.shield(host_resolved) + except asyncio.CancelledError: + + def drop_exception(fut: "asyncio.Future[List[Dict[str, Any]]]") -> None: + with suppress(Exception, asyncio.CancelledError): + fut.result() + + host_resolved.add_done_callback(drop_exception) + raise + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + # in case of proxy it is not ClientProxyConnectionError + # it is problem of resolving proxy ip itself + raise ClientConnectorError(req.connection_key, exc) from exc + + last_exc: Optional[Exception] = None + + for hinfo in hosts: + host = hinfo["host"] + port = hinfo["port"] + + # Strip trailing dots, certificates contain FQDN without dots. + # See https://github.com/aio-libs/aiohttp/issues/3636 + server_hostname = ( + (req.server_hostname or hinfo["hostname"]).rstrip(".") + if sslcontext + else None + ) + + try: + transp, proto = await self._wrap_create_connection( + self._factory, + host, + port, + timeout=timeout, + ssl=sslcontext, + family=hinfo["family"], + proto=hinfo["proto"], + flags=hinfo["flags"], + server_hostname=server_hostname, + local_addr=self._local_addr, + req=req, + client_error=client_error, + ) + except ClientConnectorError as exc: + last_exc = exc + continue + + if req.is_ssl() and fingerprint: + try: + fingerprint.check(transp) + except ServerFingerprintMismatch as exc: + transp.close() + if not self._cleanup_closed_disabled: + self._cleanup_closed_transports.append(transp) + last_exc = exc + continue + + return transp, proto + assert last_exc is not None + raise last_exc + + async def _create_proxy_connection( + self, req: ClientRequest, traces: List["Trace"], timeout: "ClientTimeout" + ) -> Tuple[asyncio.BaseTransport, ResponseHandler]: + headers: Dict[str, str] = {} + if req.proxy_headers is not None: + headers = req.proxy_headers # type: ignore[assignment] + headers[hdrs.HOST] = req.headers[hdrs.HOST] + + url = req.proxy + assert url is not None + proxy_req = ClientRequest( + hdrs.METH_GET, + url, + headers=headers, + auth=req.proxy_auth, + loop=self._loop, + ssl=req.ssl, + ) + + # create connection to proxy server + transport, proto = await self._create_direct_connection( + proxy_req, [], timeout, client_error=ClientProxyConnectionError + ) + + # Many HTTP proxies has buggy keepalive support. Let's not + # reuse connection but close it after processing every + # response. + proto.force_close() + + auth = proxy_req.headers.pop(hdrs.AUTHORIZATION, None) + if auth is not None: + if not req.is_ssl(): + req.headers[hdrs.PROXY_AUTHORIZATION] = auth + else: + proxy_req.headers[hdrs.PROXY_AUTHORIZATION] = auth + + if req.is_ssl(): + self._warn_about_tls_in_tls(transport, req) + + # For HTTPS requests over HTTP proxy + # we must notify proxy to tunnel connection + # so we send CONNECT command: + # CONNECT www.python.org:443 HTTP/1.1 + # Host: www.python.org + # + # next we must do TLS handshake and so on + # to do this we must wrap raw socket into secure one + # asyncio handles this perfectly + proxy_req.method = hdrs.METH_CONNECT + proxy_req.url = req.url + key = dataclasses.replace( + req.connection_key, proxy=None, proxy_auth=None, proxy_headers_hash=None + ) + conn = Connection(self, key, proto, self._loop) + proxy_resp = await proxy_req.send(conn) + try: + protocol = conn._protocol + assert protocol is not None + + # read_until_eof=True will ensure the connection isn't closed + # once the response is received and processed allowing + # START_TLS to work on the connection below. + protocol.set_response_params( + read_until_eof=True, + timeout_ceil_threshold=self._timeout_ceil_threshold, + ) + resp = await proxy_resp.start(conn) + except BaseException: + proxy_resp.close() + conn.close() + raise + else: + conn._protocol = None + conn._transport = None + try: + if resp.status != 200: + message = resp.reason + if message is None: + message = HTTPStatus(resp.status).phrase + raise ClientHttpProxyError( + proxy_resp.request_info, + resp.history, + status=resp.status, + message=message, + headers=resp.headers, + ) + except BaseException: + # It shouldn't be closed in `finally` because it's fed to + # `loop.start_tls()` and the docs say not to touch it after + # passing there. + transport.close() + raise + + return await self._start_tls_connection( + # Access the old transport for the last time before it's + # closed and forgotten forever: + transport, + req=req, + timeout=timeout, + ) + finally: + proxy_resp.close() + + return transport, proto + + +class UnixConnector(BaseConnector): + """Unix socket connector. + + path - Unix socket path. + keepalive_timeout - (optional) Keep-alive timeout. + force_close - Set to True to force close and do reconnect + after each request (and between redirects). + limit - The total number of simultaneous connections. + limit_per_host - Number of simultaneous connections to one host. + loop - Optional event loop. + """ + + def __init__( + self, + path: str, + force_close: bool = False, + keepalive_timeout: Union[_SENTINEL, float, None] = sentinel, + limit: int = 100, + limit_per_host: int = 0, + ) -> None: + super().__init__( + force_close=force_close, + keepalive_timeout=keepalive_timeout, + limit=limit, + limit_per_host=limit_per_host, + ) + self._path = path + + @property + def path(self) -> str: + """Path to unix socket.""" + return self._path + + async def _create_connection( + self, req: ClientRequest, traces: List["Trace"], timeout: "ClientTimeout" + ) -> ResponseHandler: + try: + async with ceil_timeout( + timeout.sock_connect, ceil_threshold=timeout.ceil_threshold + ): + _, proto = await self._loop.create_unix_connection( + self._factory, self._path + ) + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + raise UnixClientConnectorError(self.path, req.connection_key, exc) from exc + + return proto + + +class NamedPipeConnector(BaseConnector): + """Named pipe connector. + + Only supported by the proactor event loop. + See also: https://docs.python.org/3/library/asyncio-eventloop.html + + path - Windows named pipe path. + keepalive_timeout - (optional) Keep-alive timeout. + force_close - Set to True to force close and do reconnect + after each request (and between redirects). + limit - The total number of simultaneous connections. + limit_per_host - Number of simultaneous connections to one host. + loop - Optional event loop. + """ + + def __init__( + self, + path: str, + force_close: bool = False, + keepalive_timeout: Union[_SENTINEL, float, None] = sentinel, + limit: int = 100, + limit_per_host: int = 0, + ) -> None: + super().__init__( + force_close=force_close, + keepalive_timeout=keepalive_timeout, + limit=limit, + limit_per_host=limit_per_host, + ) + if not isinstance( + self._loop, asyncio.ProactorEventLoop # type: ignore[attr-defined] + ): + raise RuntimeError( + "Named Pipes only available in proactor " "loop under windows" + ) + self._path = path + + @property + def path(self) -> str: + """Path to the named pipe.""" + return self._path + + async def _create_connection( + self, req: ClientRequest, traces: List["Trace"], timeout: "ClientTimeout" + ) -> ResponseHandler: + try: + async with ceil_timeout( + timeout.sock_connect, ceil_threshold=timeout.ceil_threshold + ): + _, proto = await self._loop.create_pipe_connection( # type: ignore[attr-defined] + self._factory, self._path + ) + # the drain is required so that the connection_made is called + # and transport is set otherwise it is not set before the + # `assert conn.transport is not None` + # in client.py's _request method + await asyncio.sleep(0) + # other option is to manually set transport like + # `proto.transport = trans` + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + raise ClientConnectorError(req.connection_key, exc) from exc + + return cast(ResponseHandler, proto) diff --git a/aiohttp/cookiejar.py b/aiohttp/cookiejar.py new file mode 100644 index 0000000..a35c15f --- /dev/null +++ b/aiohttp/cookiejar.py @@ -0,0 +1,417 @@ +import contextlib +import datetime +import os # noqa +import pathlib +import pickle +import re +import warnings +from collections import defaultdict +from http.cookies import BaseCookie, Morsel, SimpleCookie +from typing import ( # noqa + DefaultDict, + Dict, + Iterable, + Iterator, + List, + Mapping, + Optional, + Set, + Tuple, + Union, + cast, +) + +from yarl import URL + +from .abc import AbstractCookieJar, ClearCookiePredicate +from .helpers import is_ip_address, next_whole_second +from .typedefs import LooseCookies, PathLike, StrOrURL + +__all__ = ("CookieJar", "DummyCookieJar") + + +CookieItem = Union[str, "Morsel[str]"] + + +class CookieJar(AbstractCookieJar): + """Implements cookie storage adhering to RFC 6265.""" + + DATE_TOKENS_RE = re.compile( + r"[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]*" + r"(?P[\x00-\x08\x0A-\x1F\d:a-zA-Z\x7F-\xFF]+)" + ) + + DATE_HMS_TIME_RE = re.compile(r"(\d{1,2}):(\d{1,2}):(\d{1,2})") + + DATE_DAY_OF_MONTH_RE = re.compile(r"(\d{1,2})") + + DATE_MONTH_RE = re.compile( + "(jan)|(feb)|(mar)|(apr)|(may)|(jun)|(jul)|" "(aug)|(sep)|(oct)|(nov)|(dec)", + re.I, + ) + + DATE_YEAR_RE = re.compile(r"(\d{2,4})") + + MAX_TIME = datetime.datetime.max.replace(tzinfo=datetime.timezone.utc) + + MAX_32BIT_TIME = datetime.datetime.fromtimestamp(2**31 - 1, datetime.timezone.utc) + + def __init__( + self, + *, + unsafe: bool = False, + quote_cookie: bool = True, + treat_as_secure_origin: Union[StrOrURL, List[StrOrURL], None] = None + ) -> None: + self._cookies: DefaultDict[Tuple[str, str], SimpleCookie[str]] = defaultdict( + SimpleCookie + ) + self._host_only_cookies: Set[Tuple[str, str]] = set() + self._unsafe = unsafe + self._quote_cookie = quote_cookie + if treat_as_secure_origin is None: + treat_as_secure_origin = [] + elif isinstance(treat_as_secure_origin, URL): + treat_as_secure_origin = [treat_as_secure_origin.origin()] + elif isinstance(treat_as_secure_origin, str): + treat_as_secure_origin = [URL(treat_as_secure_origin).origin()] + else: + treat_as_secure_origin = [ + URL(url).origin() if isinstance(url, str) else url.origin() + for url in treat_as_secure_origin + ] + self._treat_as_secure_origin = treat_as_secure_origin + self._next_expiration = next_whole_second() + self._expirations: Dict[Tuple[str, str, str], datetime.datetime] = {} + # #4515: datetime.max may not be representable on 32-bit platforms + self._max_time = self.MAX_TIME + try: + self._max_time.timestamp() + except OverflowError: + self._max_time = self.MAX_32BIT_TIME + + def save(self, file_path: PathLike) -> None: + file_path = pathlib.Path(file_path) + with file_path.open(mode="wb") as f: + pickle.dump(self._cookies, f, pickle.HIGHEST_PROTOCOL) + + def load(self, file_path: PathLike) -> None: + file_path = pathlib.Path(file_path) + with file_path.open(mode="rb") as f: + self._cookies = pickle.load(f) + + def clear(self, predicate: Optional[ClearCookiePredicate] = None) -> None: + if predicate is None: + self._next_expiration = next_whole_second() + self._cookies.clear() + self._host_only_cookies.clear() + self._expirations.clear() + return + + to_del = [] + now = datetime.datetime.now(datetime.timezone.utc) + for (domain, path), cookie in self._cookies.items(): + for name, morsel in cookie.items(): + key = (domain, path, name) + if ( + key in self._expirations and self._expirations[key] <= now + ) or predicate(morsel): + to_del.append(key) + + for domain, path, name in to_del: + self._host_only_cookies.discard((domain, name)) + key = (domain, path, name) + if key in self._expirations: + del self._expirations[(domain, path, name)] + self._cookies[(domain, path)].pop(name, None) + + next_expiration = min(self._expirations.values(), default=self._max_time) + try: + self._next_expiration = next_expiration.replace( + microsecond=0 + ) + datetime.timedelta(seconds=1) + except OverflowError: + self._next_expiration = self._max_time + + def clear_domain(self, domain: str) -> None: + self.clear(lambda x: self._is_domain_match(domain, x["domain"])) + + def __iter__(self) -> "Iterator[Morsel[str]]": + self._do_expiration() + for val in self._cookies.values(): + yield from val.values() + + def __len__(self) -> int: + return sum(1 for i in self) + + def _do_expiration(self) -> None: + self.clear(lambda x: False) + + def _expire_cookie( + self, when: datetime.datetime, domain: str, path: str, name: str + ) -> None: + self._next_expiration = min(self._next_expiration, when) + self._expirations[(domain, path, name)] = when + + def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None: + """Update cookies.""" + hostname = response_url.raw_host + + if not self._unsafe and is_ip_address(hostname): + # Don't accept cookies from IPs + return + + if isinstance(cookies, Mapping): + cookies = cookies.items() + + for name, cookie in cookies: + if not isinstance(cookie, Morsel): + tmp: SimpleCookie[str] = SimpleCookie() + tmp[name] = cookie # type: ignore[assignment] + cookie = tmp[name] + + domain = cookie["domain"] + + # ignore domains with trailing dots + if domain.endswith("."): + domain = "" + del cookie["domain"] + + if not domain and hostname is not None: + # Set the cookie's domain to the response hostname + # and set its host-only-flag + self._host_only_cookies.add((hostname, name)) + domain = cookie["domain"] = hostname + + if domain.startswith("."): + # Remove leading dot + domain = domain[1:] + cookie["domain"] = domain + + if hostname and not self._is_domain_match(domain, hostname): + # Setting cookies for different domains is not allowed + continue + + path = cookie["path"] + if not path or not path.startswith("/"): + # Set the cookie's path to the response path + path = response_url.path + if not path.startswith("/"): + path = "/" + else: + # Cut everything from the last slash to the end + path = "/" + path[1 : path.rfind("/")] + cookie["path"] = path + + max_age = cookie["max-age"] + if max_age: + try: + delta_seconds = int(max_age) + try: + max_age_expiration = datetime.datetime.now( + datetime.timezone.utc + ) + datetime.timedelta(seconds=delta_seconds) + except OverflowError: + max_age_expiration = self._max_time + self._expire_cookie(max_age_expiration, domain, path, name) + except ValueError: + cookie["max-age"] = "" + + else: + expires = cookie["expires"] + if expires: + expire_time = self._parse_date(expires) + if expire_time: + self._expire_cookie(expire_time, domain, path, name) + else: + cookie["expires"] = "" + + self._cookies[(domain, path)][name] = cookie + + self._do_expiration() + + def filter_cookies( + self, request_url: URL = URL() + ) -> Union["BaseCookie[str]", "SimpleCookie[str]"]: + """Returns this jar's cookies filtered by their attributes.""" + self._do_expiration() + if not isinstance(request_url, URL): + warnings.warn( + "The method accepts yarl.URL instances only, got {}".format( + type(request_url) + ), + DeprecationWarning, + ) + request_url = URL(request_url) + filtered: Union["SimpleCookie[str]", "BaseCookie[str]"] = ( + SimpleCookie() if self._quote_cookie else BaseCookie() + ) + hostname = request_url.raw_host or "" + request_origin = URL() + with contextlib.suppress(ValueError): + request_origin = request_url.origin() + + is_not_secure = ( + request_url.scheme not in ("https", "wss") + and request_origin not in self._treat_as_secure_origin + ) + + # Point 2: https://www.rfc-editor.org/rfc/rfc6265.html#section-5.4 + for cookie in sorted(self, key=lambda c: len(c["path"])): + name = cookie.key + domain = cookie["domain"] + + # Send shared cookies + if not domain: + filtered[name] = cookie.value + continue + + if not self._unsafe and is_ip_address(hostname): + continue + + if (domain, name) in self._host_only_cookies: + if domain != hostname: + continue + elif not self._is_domain_match(domain, hostname): + continue + + if not self._is_path_match(request_url.path, cookie["path"]): + continue + + if is_not_secure and cookie["secure"]: + continue + + # It's critical we use the Morsel so the coded_value + # (based on cookie version) is preserved + mrsl_val = cast("Morsel[str]", cookie.get(cookie.key, Morsel())) + mrsl_val.set(cookie.key, cookie.value, cookie.coded_value) + filtered[name] = mrsl_val + + return filtered + + @staticmethod + def _is_domain_match(domain: str, hostname: str) -> bool: + """Implements domain matching adhering to RFC 6265.""" + if hostname == domain: + return True + + if not hostname.endswith(domain): + return False + + non_matching = hostname[: -len(domain)] + + if not non_matching.endswith("."): + return False + + return not is_ip_address(hostname) + + @staticmethod + def _is_path_match(req_path: str, cookie_path: str) -> bool: + """Implements path matching adhering to RFC 6265.""" + if not req_path.startswith("/"): + req_path = "/" + + if req_path == cookie_path: + return True + + if not req_path.startswith(cookie_path): + return False + + if cookie_path.endswith("/"): + return True + + non_matching = req_path[len(cookie_path) :] + + return non_matching.startswith("/") + + @classmethod + def _parse_date(cls, date_str: str) -> Optional[datetime.datetime]: + """Implements date string parsing adhering to RFC 6265.""" + if not date_str: + return None + + found_time = False + found_day = False + found_month = False + found_year = False + + hour = minute = second = 0 + day = 0 + month = 0 + year = 0 + + for token_match in cls.DATE_TOKENS_RE.finditer(date_str): + token = token_match.group("token") + + if not found_time: + time_match = cls.DATE_HMS_TIME_RE.match(token) + if time_match: + found_time = True + hour, minute, second = (int(s) for s in time_match.groups()) + continue + + if not found_day: + day_match = cls.DATE_DAY_OF_MONTH_RE.match(token) + if day_match: + found_day = True + day = int(day_match.group()) + continue + + if not found_month: + month_match = cls.DATE_MONTH_RE.match(token) + if month_match: + found_month = True + assert month_match.lastindex is not None + month = month_match.lastindex + continue + + if not found_year: + year_match = cls.DATE_YEAR_RE.match(token) + if year_match: + found_year = True + year = int(year_match.group()) + + if 70 <= year <= 99: + year += 1900 + elif 0 <= year <= 69: + year += 2000 + + if False in (found_day, found_month, found_year, found_time): + return None + + if not 1 <= day <= 31: + return None + + if year < 1601 or hour > 23 or minute > 59 or second > 59: + return None + + return datetime.datetime( + year, month, day, hour, minute, second, tzinfo=datetime.timezone.utc + ) + + +class DummyCookieJar(AbstractCookieJar): + """Implements a dummy cookie storage. + + It can be used with the ClientSession when no cookie processing is needed. + + """ + + def __iter__(self) -> "Iterator[Morsel[str]]": + while False: + yield None + + def __len__(self) -> int: + return 0 + + def clear(self, predicate: Optional[ClearCookiePredicate] = None) -> None: + pass + + def clear_domain(self, domain: str) -> None: + pass + + def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None: + pass + + def filter_cookies(self, request_url: URL) -> "BaseCookie[str]": + return SimpleCookie() diff --git a/aiohttp/formdata.py b/aiohttp/formdata.py new file mode 100644 index 0000000..e159fb6 --- /dev/null +++ b/aiohttp/formdata.py @@ -0,0 +1,173 @@ +import io +from typing import Any, Iterable, List, Optional +from urllib.parse import urlencode + +from multidict import MultiDict, MultiDictProxy + +from . import hdrs, multipart, payload +from .helpers import guess_filename +from .payload import Payload + +__all__ = ("FormData",) + + +class FormData: + """Helper class for form body generation. + + Supports multipart/form-data and application/x-www-form-urlencoded. + """ + + def __init__( + self, + fields: Iterable[Any] = (), + quote_fields: bool = True, + charset: Optional[str] = None, + boundary: Optional[str] = None, + ) -> None: + self._boundary = boundary + self._writer = multipart.MultipartWriter("form-data", boundary=self._boundary) + self._fields: List[Any] = [] + self._is_multipart = False + self._is_processed = False + self._quote_fields = quote_fields + self._charset = charset + + if isinstance(fields, dict): + fields = list(fields.items()) + elif not isinstance(fields, (list, tuple)): + fields = (fields,) + self.add_fields(*fields) + + @property + def is_multipart(self) -> bool: + return self._is_multipart + + def add_field( + self, + name: str, + value: Any, + *, + content_type: Optional[str] = None, + filename: Optional[str] = None, + content_transfer_encoding: Optional[str] = None, + ) -> None: + if isinstance(value, io.IOBase): + self._is_multipart = True + elif isinstance(value, (bytes, bytearray, memoryview)): + if filename is None and content_transfer_encoding is None: + filename = name + + type_options: MultiDict[str] = MultiDict({"name": name}) + if filename is not None and not isinstance(filename, str): + raise TypeError( + "filename must be an instance of str. " "Got: %s" % filename + ) + if filename is None and isinstance(value, io.IOBase): + filename = guess_filename(value, name) + if filename is not None: + type_options["filename"] = filename + self._is_multipart = True + + headers = {} + if content_type is not None: + if not isinstance(content_type, str): + raise TypeError( + "content_type must be an instance of str. " "Got: %s" % content_type + ) + headers[hdrs.CONTENT_TYPE] = content_type + self._is_multipart = True + if content_transfer_encoding is not None: + if not isinstance(content_transfer_encoding, str): + raise TypeError( + "content_transfer_encoding must be an instance" + " of str. Got: %s" % content_transfer_encoding + ) + headers[hdrs.CONTENT_TRANSFER_ENCODING] = content_transfer_encoding + self._is_multipart = True + + self._fields.append((type_options, headers, value)) + + def add_fields(self, *fields: Any) -> None: + to_add = list(fields) + + while to_add: + rec = to_add.pop(0) + + if isinstance(rec, io.IOBase): + k = guess_filename(rec, "unknown") + self.add_field(k, rec) # type: ignore[arg-type] + + elif isinstance(rec, (MultiDictProxy, MultiDict)): + to_add.extend(rec.items()) + + elif isinstance(rec, (list, tuple)) and len(rec) == 2: + k, fp = rec + self.add_field(k, fp) # type: ignore[arg-type] + + else: + raise TypeError( + "Only io.IOBase, multidict and (name, file) " + "pairs allowed, use .add_field() for passing " + "more complex parameters, got {!r}".format(rec) + ) + + def _gen_form_urlencoded(self) -> payload.BytesPayload: + # form data (x-www-form-urlencoded) + data = [] + for type_options, _, value in self._fields: + data.append((type_options["name"], value)) + + charset = self._charset if self._charset is not None else "utf-8" + + if charset == "utf-8": + content_type = "application/x-www-form-urlencoded" + else: + content_type = "application/x-www-form-urlencoded; " "charset=%s" % charset + + return payload.BytesPayload( + urlencode(data, doseq=True, encoding=charset).encode(), + content_type=content_type, + ) + + def _gen_form_data(self) -> multipart.MultipartWriter: + """Encode a list of fields using the multipart/form-data MIME format""" + if self._is_processed: + raise RuntimeError("Form data has been processed already") + for dispparams, headers, value in self._fields: + try: + if hdrs.CONTENT_TYPE in headers: + part = payload.get_payload( + value, + content_type=headers[hdrs.CONTENT_TYPE], + headers=headers, + encoding=self._charset, + ) + else: + part = payload.get_payload( + value, headers=headers, encoding=self._charset + ) + except Exception as exc: + raise TypeError( + "Can not serialize value type: %r\n " + "headers: %r\n value: %r" % (type(value), headers, value) + ) from exc + + if dispparams: + part.set_content_disposition( + "form-data", quote_fields=self._quote_fields, **dispparams + ) + # FIXME cgi.FieldStorage doesn't likes body parts with + # Content-Length which were sent via chunked transfer encoding + assert part.headers is not None + part.headers.popall(hdrs.CONTENT_LENGTH, None) + + self._writer.append_payload(part) + + self._is_processed = True + return self._writer + + def __call__(self) -> Payload: + if self._is_multipart: + return self._gen_form_data() + else: + return self._gen_form_urlencoded() diff --git a/aiohttp/hdrs.py b/aiohttp/hdrs.py new file mode 100644 index 0000000..2f1f5e0 --- /dev/null +++ b/aiohttp/hdrs.py @@ -0,0 +1,108 @@ +"""HTTP Headers constants.""" + +# After changing the file content call ./tools/gen.py +# to regenerate the headers parser +from typing import Final, Set + +from multidict import istr + +METH_ANY: Final[str] = "*" +METH_CONNECT: Final[str] = "CONNECT" +METH_HEAD: Final[str] = "HEAD" +METH_GET: Final[str] = "GET" +METH_DELETE: Final[str] = "DELETE" +METH_OPTIONS: Final[str] = "OPTIONS" +METH_PATCH: Final[str] = "PATCH" +METH_POST: Final[str] = "POST" +METH_PUT: Final[str] = "PUT" +METH_TRACE: Final[str] = "TRACE" + +METH_ALL: Final[Set[str]] = { + METH_CONNECT, + METH_HEAD, + METH_GET, + METH_DELETE, + METH_OPTIONS, + METH_PATCH, + METH_POST, + METH_PUT, + METH_TRACE, +} + +ACCEPT: Final[istr] = istr("Accept") +ACCEPT_CHARSET: Final[istr] = istr("Accept-Charset") +ACCEPT_ENCODING: Final[istr] = istr("Accept-Encoding") +ACCEPT_LANGUAGE: Final[istr] = istr("Accept-Language") +ACCEPT_RANGES: Final[istr] = istr("Accept-Ranges") +ACCESS_CONTROL_MAX_AGE: Final[istr] = istr("Access-Control-Max-Age") +ACCESS_CONTROL_ALLOW_CREDENTIALS: Final[istr] = istr("Access-Control-Allow-Credentials") +ACCESS_CONTROL_ALLOW_HEADERS: Final[istr] = istr("Access-Control-Allow-Headers") +ACCESS_CONTROL_ALLOW_METHODS: Final[istr] = istr("Access-Control-Allow-Methods") +ACCESS_CONTROL_ALLOW_ORIGIN: Final[istr] = istr("Access-Control-Allow-Origin") +ACCESS_CONTROL_EXPOSE_HEADERS: Final[istr] = istr("Access-Control-Expose-Headers") +ACCESS_CONTROL_REQUEST_HEADERS: Final[istr] = istr("Access-Control-Request-Headers") +ACCESS_CONTROL_REQUEST_METHOD: Final[istr] = istr("Access-Control-Request-Method") +AGE: Final[istr] = istr("Age") +ALLOW: Final[istr] = istr("Allow") +AUTHORIZATION: Final[istr] = istr("Authorization") +CACHE_CONTROL: Final[istr] = istr("Cache-Control") +CONNECTION: Final[istr] = istr("Connection") +CONTENT_DISPOSITION: Final[istr] = istr("Content-Disposition") +CONTENT_ENCODING: Final[istr] = istr("Content-Encoding") +CONTENT_LANGUAGE: Final[istr] = istr("Content-Language") +CONTENT_LENGTH: Final[istr] = istr("Content-Length") +CONTENT_LOCATION: Final[istr] = istr("Content-Location") +CONTENT_MD5: Final[istr] = istr("Content-MD5") +CONTENT_RANGE: Final[istr] = istr("Content-Range") +CONTENT_TRANSFER_ENCODING: Final[istr] = istr("Content-Transfer-Encoding") +CONTENT_TYPE: Final[istr] = istr("Content-Type") +COOKIE: Final[istr] = istr("Cookie") +DATE: Final[istr] = istr("Date") +DESTINATION: Final[istr] = istr("Destination") +DIGEST: Final[istr] = istr("Digest") +ETAG: Final[istr] = istr("Etag") +EXPECT: Final[istr] = istr("Expect") +EXPIRES: Final[istr] = istr("Expires") +FORWARDED: Final[istr] = istr("Forwarded") +FROM: Final[istr] = istr("From") +HOST: Final[istr] = istr("Host") +IF_MATCH: Final[istr] = istr("If-Match") +IF_MODIFIED_SINCE: Final[istr] = istr("If-Modified-Since") +IF_NONE_MATCH: Final[istr] = istr("If-None-Match") +IF_RANGE: Final[istr] = istr("If-Range") +IF_UNMODIFIED_SINCE: Final[istr] = istr("If-Unmodified-Since") +KEEP_ALIVE: Final[istr] = istr("Keep-Alive") +LAST_EVENT_ID: Final[istr] = istr("Last-Event-ID") +LAST_MODIFIED: Final[istr] = istr("Last-Modified") +LINK: Final[istr] = istr("Link") +LOCATION: Final[istr] = istr("Location") +MAX_FORWARDS: Final[istr] = istr("Max-Forwards") +ORIGIN: Final[istr] = istr("Origin") +PRAGMA: Final[istr] = istr("Pragma") +PROXY_AUTHENTICATE: Final[istr] = istr("Proxy-Authenticate") +PROXY_AUTHORIZATION: Final[istr] = istr("Proxy-Authorization") +RANGE: Final[istr] = istr("Range") +REFERER: Final[istr] = istr("Referer") +RETRY_AFTER: Final[istr] = istr("Retry-After") +SEC_WEBSOCKET_ACCEPT: Final[istr] = istr("Sec-WebSocket-Accept") +SEC_WEBSOCKET_VERSION: Final[istr] = istr("Sec-WebSocket-Version") +SEC_WEBSOCKET_PROTOCOL: Final[istr] = istr("Sec-WebSocket-Protocol") +SEC_WEBSOCKET_EXTENSIONS: Final[istr] = istr("Sec-WebSocket-Extensions") +SEC_WEBSOCKET_KEY: Final[istr] = istr("Sec-WebSocket-Key") +SEC_WEBSOCKET_KEY1: Final[istr] = istr("Sec-WebSocket-Key1") +SERVER: Final[istr] = istr("Server") +SET_COOKIE: Final[istr] = istr("Set-Cookie") +TE: Final[istr] = istr("TE") +TRAILER: Final[istr] = istr("Trailer") +TRANSFER_ENCODING: Final[istr] = istr("Transfer-Encoding") +UPGRADE: Final[istr] = istr("Upgrade") +URI: Final[istr] = istr("URI") +USER_AGENT: Final[istr] = istr("User-Agent") +VARY: Final[istr] = istr("Vary") +VIA: Final[istr] = istr("Via") +WANT_DIGEST: Final[istr] = istr("Want-Digest") +WARNING: Final[istr] = istr("Warning") +WWW_AUTHENTICATE: Final[istr] = istr("WWW-Authenticate") +X_FORWARDED_FOR: Final[istr] = istr("X-Forwarded-For") +X_FORWARDED_HOST: Final[istr] = istr("X-Forwarded-Host") +X_FORWARDED_PROTO: Final[istr] = istr("X-Forwarded-Proto") diff --git a/aiohttp/helpers.py b/aiohttp/helpers.py new file mode 100644 index 0000000..cbed287 --- /dev/null +++ b/aiohttp/helpers.py @@ -0,0 +1,1065 @@ +"""Various helper functions""" + +import asyncio +import base64 +import binascii +import contextlib +import dataclasses +import datetime +import enum +import functools +import inspect +import netrc +import os +import platform +import re +import sys +import time +import warnings +import weakref +from collections import namedtuple +from contextlib import suppress +from email.parser import HeaderParser +from email.utils import parsedate +from http.cookies import SimpleCookie +from math import ceil +from pathlib import Path +from types import TracebackType +from typing import ( + Any, + Callable, + ContextManager, + Dict, + Generator, + Generic, + Iterable, + Iterator, + List, + Mapping, + Optional, + Pattern, + Protocol, + Tuple, + Type, + TypeVar, + Union, + final, + get_args, + overload, +) +from urllib.parse import quote +from urllib.request import getproxies, proxy_bypass + +from multidict import CIMultiDict, MultiDict, MultiDictProxy +from yarl import URL + +from . import hdrs +from .log import client_logger +from .typedefs import PathLike # noqa + +if sys.version_info >= (3, 11): + import asyncio as async_timeout +else: + import async_timeout + +__all__ = ("BasicAuth", "ChainMapProxy", "ETag") + +PY_310 = sys.version_info >= (3, 10) + +COOKIE_MAX_LENGTH = 4096 + +_T = TypeVar("_T") +_S = TypeVar("_S") + +_SENTINEL = enum.Enum("_SENTINEL", "sentinel") +sentinel = _SENTINEL.sentinel + +NO_EXTENSIONS = bool(os.environ.get("AIOHTTP_NO_EXTENSIONS")) + +DEBUG = sys.flags.dev_mode or ( + not sys.flags.ignore_environment and bool(os.environ.get("PYTHONASYNCIODEBUG")) +) + + +CHAR = {chr(i) for i in range(0, 128)} +CTL = {chr(i) for i in range(0, 32)} | { + chr(127), +} +SEPARATORS = { + "(", + ")", + "<", + ">", + "@", + ",", + ";", + ":", + "\\", + '"', + "/", + "[", + "]", + "?", + "=", + "{", + "}", + " ", + chr(9), +} +TOKEN = CHAR ^ CTL ^ SEPARATORS + + +class noop: + def __await__(self) -> Generator[None, None, None]: + yield + + +json_re = re.compile(r"(?:application/|[\w.-]+/[\w.+-]+?\+)json$", re.IGNORECASE) + + +class BasicAuth(namedtuple("BasicAuth", ["login", "password", "encoding"])): + """Http basic authentication helper.""" + + def __new__( + cls, login: str, password: str = "", encoding: str = "latin1" + ) -> "BasicAuth": + if login is None: + raise ValueError("None is not allowed as login value") + + if password is None: + raise ValueError("None is not allowed as password value") + + if ":" in login: + raise ValueError('A ":" is not allowed in login (RFC 1945#section-11.1)') + + return super().__new__(cls, login, password, encoding) + + @classmethod + def decode(cls, auth_header: str, encoding: str = "latin1") -> "BasicAuth": + """Create a BasicAuth object from an Authorization HTTP header.""" + try: + auth_type, encoded_credentials = auth_header.split(" ", 1) + except ValueError: + raise ValueError("Could not parse authorization header.") + + if auth_type.lower() != "basic": + raise ValueError("Unknown authorization method %s" % auth_type) + + try: + decoded = base64.b64decode( + encoded_credentials.encode("ascii"), validate=True + ).decode(encoding) + except binascii.Error: + raise ValueError("Invalid base64 encoding.") + + try: + # RFC 2617 HTTP Authentication + # https://www.ietf.org/rfc/rfc2617.txt + # the colon must be present, but the username and password may be + # otherwise blank. + username, password = decoded.split(":", 1) + except ValueError: + raise ValueError("Invalid credentials.") + + return cls(username, password, encoding=encoding) + + @classmethod + def from_url(cls, url: URL, *, encoding: str = "latin1") -> Optional["BasicAuth"]: + """Create BasicAuth from url.""" + if not isinstance(url, URL): + raise TypeError("url should be yarl.URL instance") + if url.user is None: + return None + return cls(url.user, url.password or "", encoding=encoding) + + def encode(self) -> str: + """Encode credentials.""" + creds = (f"{self.login}:{self.password}").encode(self.encoding) + return "Basic %s" % base64.b64encode(creds).decode(self.encoding) + + +def strip_auth_from_url(url: URL) -> Tuple[URL, Optional[BasicAuth]]: + auth = BasicAuth.from_url(url) + if auth is None: + return url, None + else: + return url.with_user(None), auth + + +def netrc_from_env() -> Optional[netrc.netrc]: + """Load netrc from file. + + Attempt to load it from the path specified by the env-var + NETRC or in the default location in the user's home directory. + + Returns None if it couldn't be found or fails to parse. + """ + netrc_env = os.environ.get("NETRC") + + if netrc_env is not None: + netrc_path = Path(netrc_env) + else: + try: + home_dir = Path.home() + except RuntimeError as e: # pragma: no cover + # if pathlib can't resolve home, it may raise a RuntimeError + client_logger.debug( + "Could not resolve home directory when " + "trying to look for .netrc file: %s", + e, + ) + return None + + netrc_path = home_dir / ( + "_netrc" if platform.system() == "Windows" else ".netrc" + ) + + try: + return netrc.netrc(str(netrc_path)) + except netrc.NetrcParseError as e: + client_logger.warning("Could not parse .netrc file: %s", e) + except OSError as e: + netrc_exists = False + with contextlib.suppress(OSError): + netrc_exists = netrc_path.is_file() + # we couldn't read the file (doesn't exist, permissions, etc.) + if netrc_env or netrc_exists: + # only warn if the environment wanted us to load it, + # or it appears like the default file does actually exist + client_logger.warning("Could not read .netrc file: %s", e) + + return None + + +@dataclasses.dataclass(frozen=True) +class ProxyInfo: + proxy: URL + proxy_auth: Optional[BasicAuth] + + +def basicauth_from_netrc(netrc_obj: Optional[netrc.netrc], host: str) -> BasicAuth: + """ + Return :py:class:`~aiohttp.BasicAuth` credentials for ``host`` from ``netrc_obj``. + + :raises LookupError: if ``netrc_obj`` is :py:data:`None` or if no + entry is found for the ``host``. + """ + if netrc_obj is None: + raise LookupError("No .netrc file found") + auth_from_netrc = netrc_obj.authenticators(host) + + if auth_from_netrc is None: + raise LookupError(f"No entry for {host!s} found in the `.netrc` file.") + login, account, password = auth_from_netrc + + # TODO(PY311): username = login or account + # Up to python 3.10, account could be None if not specified, + # and login will be empty string if not specified. From 3.11, + # login and account will be empty string if not specified. + username = login if (login or account is None) else account + + # TODO(PY311): Remove this, as password will be empty string + # if not specified + if password is None: + password = "" + + return BasicAuth(username, password) + + +def proxies_from_env() -> Dict[str, ProxyInfo]: + proxy_urls = { + k: URL(v) + for k, v in getproxies().items() + if k in ("http", "https", "ws", "wss") + } + netrc_obj = netrc_from_env() + stripped = {k: strip_auth_from_url(v) for k, v in proxy_urls.items()} + ret = {} + for proto, val in stripped.items(): + proxy, auth = val + if proxy.scheme in ("https", "wss"): + client_logger.warning( + "%s proxies %s are not supported, ignoring", proxy.scheme.upper(), proxy + ) + continue + if netrc_obj and auth is None: + if proxy.host is not None: + try: + auth = basicauth_from_netrc(netrc_obj, proxy.host) + except LookupError: + auth = None + ret[proto] = ProxyInfo(proxy, auth) + return ret + + +def get_env_proxy_for_url(url: URL) -> Tuple[URL, Optional[BasicAuth]]: + """Get a permitted proxy for the given URL from the env.""" + if url.host is not None and proxy_bypass(url.host): + raise LookupError(f"Proxying is disallowed for `{url.host!r}`") + + proxies_in_env = proxies_from_env() + try: + proxy_info = proxies_in_env[url.scheme] + except KeyError: + raise LookupError(f"No proxies found for `{url!s}` in the env") + else: + return proxy_info.proxy, proxy_info.proxy_auth + + +@dataclasses.dataclass(frozen=True) +class MimeType: + type: str + subtype: str + suffix: str + parameters: "MultiDictProxy[str]" + + +@functools.lru_cache(maxsize=56) +def parse_mimetype(mimetype: str) -> MimeType: + """Parses a MIME type into its components. + + mimetype is a MIME type string. + + Returns a MimeType object. + + Example: + + >>> parse_mimetype('text/html; charset=utf-8') + MimeType(type='text', subtype='html', suffix='', + parameters={'charset': 'utf-8'}) + + """ + if not mimetype: + return MimeType( + type="", subtype="", suffix="", parameters=MultiDictProxy(MultiDict()) + ) + + parts = mimetype.split(";") + params: MultiDict[str] = MultiDict() + for item in parts[1:]: + if not item: + continue + key, _, value = item.partition("=") + params.add(key.lower().strip(), value.strip(' "')) + + fulltype = parts[0].strip().lower() + if fulltype == "*": + fulltype = "*/*" + + mtype, _, stype = fulltype.partition("/") + stype, _, suffix = stype.partition("+") + + return MimeType( + type=mtype, subtype=stype, suffix=suffix, parameters=MultiDictProxy(params) + ) + + +def guess_filename(obj: Any, default: Optional[str] = None) -> Optional[str]: + name = getattr(obj, "name", None) + if name and isinstance(name, str) and name[0] != "<" and name[-1] != ">": + return Path(name).name + return default + + +not_qtext_re = re.compile(r"[^\041\043-\133\135-\176]") +QCONTENT = {chr(i) for i in range(0x20, 0x7F)} | {"\t"} + + +def quoted_string(content: str) -> str: + """Return 7-bit content as quoted-string. + + Format content into a quoted-string as defined in RFC5322 for + Internet Message Format. Notice that this is not the 8-bit HTTP + format, but the 7-bit email format. Content must be in usascii or + a ValueError is raised. + """ + if not (QCONTENT > set(content)): + raise ValueError(f"bad content for quoted-string {content!r}") + return not_qtext_re.sub(lambda x: "\\" + x.group(0), content) + + +def content_disposition_header( + disptype: str, quote_fields: bool = True, _charset: str = "utf-8", **params: str +) -> str: + """Sets ``Content-Disposition`` header for MIME. + + This is the MIME payload Content-Disposition header from RFC 2183 + and RFC 7579 section 4.2, not the HTTP Content-Disposition from + RFC 6266. + + disptype is a disposition type: inline, attachment, form-data. + Should be valid extension token (see RFC 2183) + + quote_fields performs value quoting to 7-bit MIME headers + according to RFC 7578. Set to quote_fields to False if recipient + can take 8-bit file names and field values. + + _charset specifies the charset to use when quote_fields is True. + + params is a dict with disposition params. + """ + if not disptype or not (TOKEN > set(disptype)): + raise ValueError("bad content disposition type {!r}" "".format(disptype)) + + value = disptype + if params: + lparams = [] + for key, val in params.items(): + if not key or not (TOKEN > set(key)): + raise ValueError( + "bad content disposition parameter" " {!r}={!r}".format(key, val) + ) + if quote_fields: + if key.lower() == "filename": + qval = quote(val, "", encoding=_charset) + lparams.append((key, '"%s"' % qval)) + else: + try: + qval = quoted_string(val) + except ValueError: + qval = "".join( + (_charset, "''", quote(val, "", encoding=_charset)) + ) + lparams.append((key + "*", qval)) + else: + lparams.append((key, '"%s"' % qval)) + else: + qval = val.replace("\\", "\\\\").replace('"', '\\"') + lparams.append((key, '"%s"' % qval)) + sparams = "; ".join("=".join(pair) for pair in lparams) + value = "; ".join((value, sparams)) + return value + + +def is_expected_content_type( + response_content_type: str, expected_content_type: str +) -> bool: + """Checks if received content type is processable as an expected one. + + Both arguments should be given without parameters. + """ + if expected_content_type == "application/json": + return json_re.match(response_content_type) is not None + return expected_content_type in response_content_type + + +class _TSelf(Protocol, Generic[_T]): + _cache: Dict[str, _T] + + +class reify(Generic[_T]): + """Use as a class method decorator. + + It operates almost exactly like + the Python `@property` decorator, but it puts the result of the + method it decorates into the instance dict after the first call, + effectively replacing the function it decorates with an instance + variable. It is, in Python parlance, a data descriptor. + """ + + def __init__(self, wrapped: Callable[..., _T]) -> None: + self.wrapped = wrapped + self.__doc__ = wrapped.__doc__ + self.name = wrapped.__name__ + + def __get__(self, inst: _TSelf[_T], owner: Optional[Type[Any]] = None) -> _T: + try: + try: + return inst._cache[self.name] + except KeyError: + val = self.wrapped(inst) + inst._cache[self.name] = val + return val + except AttributeError: + if inst is None: + return self + raise + + def __set__(self, inst: _TSelf[_T], value: _T) -> None: + raise AttributeError("reified property is read-only") + + +reify_py = reify + +try: + from ._helpers import reify as reify_c + + if not NO_EXTENSIONS: + reify = reify_c # type: ignore[misc,assignment] +except ImportError: + pass + +_ipv4_pattern = ( + r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}" + r"(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" +) +_ipv6_pattern = ( + r"^(?:(?:(?:[A-F0-9]{1,4}:){6}|(?=(?:[A-F0-9]{0,4}:){0,6}" + r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}$)(([0-9A-F]{1,4}:){0,5}|:)" + r"((:[0-9A-F]{1,4}){1,5}:|:)|::(?:[A-F0-9]{1,4}:){5})" + r"(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}" + r"(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])|(?:[A-F0-9]{1,4}:){7}" + r"[A-F0-9]{1,4}|(?=(?:[A-F0-9]{0,4}:){0,7}[A-F0-9]{0,4}$)" + r"(([0-9A-F]{1,4}:){1,7}|:)((:[0-9A-F]{1,4}){1,7}|:)|(?:[A-F0-9]{1,4}:){7}" + r":|:(:[A-F0-9]{1,4}){7})$" +) +_ipv4_regex = re.compile(_ipv4_pattern) +_ipv6_regex = re.compile(_ipv6_pattern, flags=re.IGNORECASE) +_ipv4_regexb = re.compile(_ipv4_pattern.encode("ascii")) +_ipv6_regexb = re.compile(_ipv6_pattern.encode("ascii"), flags=re.IGNORECASE) + + +def _is_ip_address( + regex: Pattern[str], regexb: Pattern[bytes], host: Optional[Union[str, bytes]] +) -> bool: + if host is None: + return False + if isinstance(host, str): + return bool(regex.match(host)) + elif isinstance(host, (bytes, bytearray, memoryview)): + return bool(regexb.match(host)) + else: + raise TypeError(f"{host} [{type(host)}] is not a str or bytes") + + +is_ipv4_address = functools.partial(_is_ip_address, _ipv4_regex, _ipv4_regexb) +is_ipv6_address = functools.partial(_is_ip_address, _ipv6_regex, _ipv6_regexb) + + +def is_ip_address(host: Optional[Union[str, bytes, bytearray, memoryview]]) -> bool: + return is_ipv4_address(host) or is_ipv6_address(host) + + +def next_whole_second() -> datetime.datetime: + """Return current time rounded up to the next whole second.""" + return datetime.datetime.now(datetime.timezone.utc).replace( + microsecond=0 + ) + datetime.timedelta(seconds=0) + + +_cached_current_datetime: Optional[int] = None +_cached_formatted_datetime = "" + + +def rfc822_formatted_time() -> str: + global _cached_current_datetime + global _cached_formatted_datetime + + now = int(time.time()) + if now != _cached_current_datetime: + # Weekday and month names for HTTP date/time formatting; + # always English! + # Tuples are constants stored in codeobject! + _weekdayname = ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun") + _monthname = ( + "", # Dummy so we can use 1-based month numbers + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ) + + year, month, day, hh, mm, ss, wd, *tail = time.gmtime(now) + _cached_formatted_datetime = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % ( + _weekdayname[wd], + day, + _monthname[month], + year, + hh, + mm, + ss, + ) + _cached_current_datetime = now + return _cached_formatted_datetime + + +def _weakref_handle(info: "Tuple[weakref.ref[object], str]") -> None: + ref, name = info + ob = ref() + if ob is not None: + with suppress(Exception): + getattr(ob, name)() + + +def weakref_handle( + ob: object, + name: str, + timeout: Optional[float], + loop: asyncio.AbstractEventLoop, + timeout_ceil_threshold: float = 5, +) -> Optional[asyncio.TimerHandle]: + if timeout is not None and timeout > 0: + when = loop.time() + timeout + if timeout >= timeout_ceil_threshold: + when = ceil(when) + + return loop.call_at(when, _weakref_handle, (weakref.ref(ob), name)) + return None + + +def call_later( + cb: Callable[[], Any], + timeout: Optional[float], + loop: asyncio.AbstractEventLoop, + timeout_ceil_threshold: float = 5, +) -> Optional[asyncio.TimerHandle]: + if timeout is not None and timeout > 0: + when = loop.time() + timeout + if timeout > timeout_ceil_threshold: + when = ceil(when) + return loop.call_at(when, cb) + return None + + +class TimeoutHandle: + """Timeout handle""" + + def __init__( + self, + loop: asyncio.AbstractEventLoop, + timeout: Optional[float], + ceil_threshold: float = 5, + ) -> None: + self._timeout = timeout + self._loop = loop + self._ceil_threshold = ceil_threshold + self._callbacks: List[ + Tuple[Callable[..., None], Tuple[Any, ...], Dict[str, Any]] + ] = [] + + def register( + self, callback: Callable[..., None], *args: Any, **kwargs: Any + ) -> None: + self._callbacks.append((callback, args, kwargs)) + + def close(self) -> None: + self._callbacks.clear() + + def start(self) -> Optional[asyncio.Handle]: + timeout = self._timeout + if timeout is not None and timeout > 0: + when = self._loop.time() + timeout + if timeout >= self._ceil_threshold: + when = ceil(when) + return self._loop.call_at(when, self.__call__) + else: + return None + + def timer(self) -> "BaseTimerContext": + if self._timeout is not None and self._timeout > 0: + timer = TimerContext(self._loop) + self.register(timer.timeout) + return timer + else: + return TimerNoop() + + def __call__(self) -> None: + for cb, args, kwargs in self._callbacks: + with suppress(Exception): + cb(*args, **kwargs) + + self._callbacks.clear() + + +class BaseTimerContext(ContextManager["BaseTimerContext"]): + def assert_timeout(self) -> None: + """Raise TimeoutError if timeout has been exceeded.""" + + +class TimerNoop(BaseTimerContext): + def __enter__(self) -> BaseTimerContext: + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + return + + +class TimerContext(BaseTimerContext): + """Low resolution timeout context manager""" + + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: + self._loop = loop + self._tasks: List[asyncio.Task[Any]] = [] + self._cancelled = False + + def assert_timeout(self) -> None: + """Raise TimeoutError if timer has already been cancelled.""" + if self._cancelled: + raise asyncio.TimeoutError from None + + def __enter__(self) -> BaseTimerContext: + task = asyncio.current_task(loop=self._loop) + + if task is None: + raise RuntimeError( + "Timeout context manager should be used " "inside a task" + ) + + if self._cancelled: + raise asyncio.TimeoutError from None + + self._tasks.append(task) + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> Optional[bool]: + if self._tasks: + self._tasks.pop() # type: ignore[unused-awaitable] + + if exc_type is asyncio.CancelledError and self._cancelled: + raise asyncio.TimeoutError from None + return None + + def timeout(self) -> None: + if not self._cancelled: + for task in set(self._tasks): + task.cancel() + + self._cancelled = True + + +def ceil_timeout( + delay: Optional[float], ceil_threshold: float = 5 +) -> async_timeout.Timeout: + if delay is None or delay <= 0: + return async_timeout.timeout(None) + + loop = asyncio.get_running_loop() + now = loop.time() + when = now + delay + if delay > ceil_threshold: + when = ceil(when) + return async_timeout.timeout_at(when) + + +class HeadersMixin: + __slots__ = ("_content_type", "_content_dict", "_stored_content_type") + + def __init__(self) -> None: + super().__init__() + self._content_type: Optional[str] = None + self._content_dict: Optional[Dict[str, str]] = None + self._stored_content_type: Union[str, _SENTINEL] = sentinel + + def _parse_content_type(self, raw: str) -> None: + self._stored_content_type = raw + if raw is None: + # default value according to RFC 2616 + self._content_type = "application/octet-stream" + self._content_dict = {} + else: + msg = HeaderParser().parsestr("Content-Type: " + raw) + self._content_type = msg.get_content_type() + params = msg.get_params(()) + self._content_dict = dict(params[1:]) # First element is content type again + + @property + def content_type(self) -> str: + """The value of content part for Content-Type HTTP header.""" + raw = self._headers.get(hdrs.CONTENT_TYPE) # type: ignore[attr-defined] + if self._stored_content_type != raw: + self._parse_content_type(raw) + return self._content_type # type: ignore[return-value] + + @property + def charset(self) -> Optional[str]: + """The value of charset part for Content-Type HTTP header.""" + raw = self._headers.get(hdrs.CONTENT_TYPE) # type: ignore[attr-defined] + if self._stored_content_type != raw: + self._parse_content_type(raw) + return self._content_dict.get("charset") # type: ignore[union-attr] + + @property + def content_length(self) -> Optional[int]: + """The value of Content-Length HTTP header.""" + content_length = self._headers.get( # type: ignore[attr-defined] + hdrs.CONTENT_LENGTH + ) + + if content_length is not None: + return int(content_length) + else: + return None + + +def set_result(fut: "asyncio.Future[_T]", result: _T) -> None: + if not fut.done(): + fut.set_result(result) + + +def set_exception(fut: "asyncio.Future[_T]", exc: BaseException) -> None: + if not fut.done(): + fut.set_exception(exc) + + +@functools.total_ordering +class AppKey(Generic[_T]): + """Keys for static typing support in Application.""" + + __slots__ = ("_name", "_t", "__orig_class__") + + # This may be set by Python when instantiating with a generic type. We need to + # support this, in order to support types that are not concrete classes, + # like Iterable, which can't be passed as the second parameter to __init__. + __orig_class__: Type[object] + + def __init__(self, name: str, t: Optional[Type[_T]] = None): + # Prefix with module name to help deduplicate key names. + frame = inspect.currentframe() + while frame: + if frame.f_code.co_name == "": + module: str = frame.f_globals["__name__"] + break + frame = frame.f_back + else: + raise RuntimeError("Failed to get module name.") + + # https://github.com/python/mypy/issues/14209 + self._name = module + "." + name # type: ignore[possibly-undefined] + self._t = t + + def __lt__(self, other: object) -> bool: + if isinstance(other, AppKey): + return self._name < other._name + return True # Order AppKey above other types. + + def __repr__(self) -> str: + t = self._t + if t is None: + with suppress(AttributeError): + # Set to type arg. + t = get_args(self.__orig_class__)[0] + + if t is None: + t_repr = "<>" + elif isinstance(t, type): + if t.__module__ == "builtins": + t_repr = t.__qualname__ + else: + t_repr = f"{t.__module__}.{t.__qualname__}" + else: + t_repr = repr(t) + return f"" + + +@final +class ChainMapProxy(Mapping[Union[str, AppKey[Any]], Any]): + __slots__ = ("_maps",) + + def __init__(self, maps: Iterable[Mapping[Union[str, AppKey[Any]], Any]]) -> None: + self._maps = tuple(maps) + + def __init_subclass__(cls) -> None: + raise TypeError( + "Inheritance class {} from ChainMapProxy " + "is forbidden".format(cls.__name__) + ) + + @overload # type: ignore[override] + def __getitem__(self, key: AppKey[_T]) -> _T: + ... + + @overload + def __getitem__(self, key: str) -> Any: + ... + + def __getitem__(self, key: Union[str, AppKey[_T]]) -> Any: + for mapping in self._maps: + try: + return mapping[key] + except KeyError: + pass + raise KeyError(key) + + @overload # type: ignore[override] + def get(self, key: AppKey[_T], default: _S) -> Union[_T, _S]: + ... + + @overload + def get(self, key: AppKey[_T], default: None = ...) -> Optional[_T]: + ... + + @overload + def get(self, key: str, default: Any = ...) -> Any: + ... + + def get(self, key: Union[str, AppKey[_T]], default: Any = None) -> Any: + try: + return self[key] + except KeyError: + return default + + def __len__(self) -> int: + # reuses stored hash values if possible + return len(set().union(*self._maps)) + + def __iter__(self) -> Iterator[Union[str, AppKey[Any]]]: + d: Dict[Union[str, AppKey[Any]], Any] = {} + for mapping in reversed(self._maps): + # reuses stored hash values if possible + d.update(mapping) + return iter(d) + + def __contains__(self, key: object) -> bool: + return any(key in m for m in self._maps) + + def __bool__(self) -> bool: + return any(self._maps) + + def __repr__(self) -> str: + content = ", ".join(map(repr, self._maps)) + return f"ChainMapProxy({content})" + + +class CookieMixin: + # The `_cookies` slots is not defined here because non-empty slots cannot + # be combined with an Exception base class, as is done in HTTPException. + # CookieMixin subclasses with slots should define the `_cookies` + # slot themselves. + __slots__ = () + + def __init__(self) -> None: + super().__init__() + # Mypy doesn't like that _cookies isn't in __slots__. + # See the comment on this class's __slots__ for why this is OK. + self._cookies: SimpleCookie[str] = SimpleCookie() # type: ignore[misc] + + @property + def cookies(self) -> "SimpleCookie[str]": + return self._cookies + + def set_cookie( + self, + name: str, + value: str, + *, + expires: Optional[str] = None, + domain: Optional[str] = None, + max_age: Optional[Union[int, str]] = None, + path: str = "/", + secure: Optional[bool] = None, + httponly: Optional[bool] = None, + version: Optional[str] = None, + samesite: Optional[str] = None, + ) -> None: + """Set or update response cookie. + + Sets new cookie or updates existent with new value. + Also updates only those params which are not None. + """ + old = self._cookies.get(name) + if old is not None and old.coded_value == "": + # deleted cookie + self._cookies.pop(name, None) + + self._cookies[name] = value + c = self._cookies[name] + + if expires is not None: + c["expires"] = expires + elif c.get("expires") == "Thu, 01 Jan 1970 00:00:00 GMT": + del c["expires"] + + if domain is not None: + c["domain"] = domain + + if max_age is not None: + c["max-age"] = str(max_age) + elif "max-age" in c: + del c["max-age"] + + c["path"] = path + + if secure is not None: + c["secure"] = secure + if httponly is not None: + c["httponly"] = httponly + if version is not None: + c["version"] = version + if samesite is not None: + c["samesite"] = samesite + + if DEBUG: + cookie_length = len(c.output(header="")[1:]) + if cookie_length > COOKIE_MAX_LENGTH: + warnings.warn( + "The size of is too large, it might get ignored by the client.", + UserWarning, + stacklevel=2, + ) + + def del_cookie( + self, name: str, *, domain: Optional[str] = None, path: str = "/" + ) -> None: + """Delete cookie. + + Creates new empty expired cookie. + """ + # TODO: do we need domain/path here? + self._cookies.pop(name, None) + self.set_cookie( + name, + "", + max_age=0, + expires="Thu, 01 Jan 1970 00:00:00 GMT", + domain=domain, + path=path, + ) + + +def populate_with_cookies( + headers: "CIMultiDict[str]", cookies: "SimpleCookie[str]" +) -> None: + for cookie in cookies.values(): + value = cookie.output(header="")[1:] + headers.add(hdrs.SET_COOKIE, value) + + +# https://tools.ietf.org/html/rfc7232#section-2.3 +_ETAGC = r"[!#-}\x80-\xff]+" +_ETAGC_RE = re.compile(_ETAGC) +_QUOTED_ETAG = rf'(W/)?"({_ETAGC})"' +QUOTED_ETAG_RE = re.compile(_QUOTED_ETAG) +LIST_QUOTED_ETAG_RE = re.compile(rf"({_QUOTED_ETAG})(?:\s*,\s*|$)|(.)") + +ETAG_ANY = "*" + + +@dataclasses.dataclass(frozen=True) +class ETag: + value: str + is_weak: bool = False + + +def validate_etag_value(value: str) -> None: + if value != ETAG_ANY and not _ETAGC_RE.fullmatch(value): + raise ValueError( + f"Value {value!r} is not a valid etag. Maybe it contains '\"'?" + ) + + +def parse_http_date(date_str: Optional[str]) -> Optional[datetime.datetime]: + """Process a date string, return a datetime object""" + if date_str is not None: + timetuple = parsedate(date_str) + if timetuple is not None: + with suppress(ValueError): + return datetime.datetime(*timetuple[:6], tzinfo=datetime.timezone.utc) + return None diff --git a/aiohttp/http.py b/aiohttp/http.py new file mode 100644 index 0000000..244d71c --- /dev/null +++ b/aiohttp/http.py @@ -0,0 +1,60 @@ +import sys + +from . import __version__ +from .http_exceptions import HttpProcessingError +from .http_parser import ( + HeadersParser, + HttpParser, + HttpRequestParser, + HttpResponseParser, + RawRequestMessage, + RawResponseMessage, +) +from .http_websocket import ( + WS_CLOSED_MESSAGE, + WS_CLOSING_MESSAGE, + WS_KEY, + WebSocketError, + WebSocketReader, + WebSocketWriter, + WSCloseCode, + WSMessage, + WSMsgType, + ws_ext_gen, + ws_ext_parse, +) +from .http_writer import HttpVersion, HttpVersion10, HttpVersion11, StreamWriter + +__all__ = ( + "HttpProcessingError", + "SERVER_SOFTWARE", + # .http_writer + "StreamWriter", + "HttpVersion", + "HttpVersion10", + "HttpVersion11", + # .http_parser + "HeadersParser", + "HttpParser", + "HttpRequestParser", + "HttpResponseParser", + "RawRequestMessage", + "RawResponseMessage", + # .http_websocket + "WS_CLOSED_MESSAGE", + "WS_CLOSING_MESSAGE", + "WS_KEY", + "WebSocketReader", + "WebSocketWriter", + "ws_ext_gen", + "ws_ext_parse", + "WSMessage", + "WebSocketError", + "WSMsgType", + "WSCloseCode", +) + + +SERVER_SOFTWARE: str = "Python/{0[0]}.{0[1]} aiohttp/{1}".format( + sys.version_info, __version__ +) diff --git a/aiohttp/http_exceptions.py b/aiohttp/http_exceptions.py new file mode 100644 index 0000000..728824f --- /dev/null +++ b/aiohttp/http_exceptions.py @@ -0,0 +1,105 @@ +"""Low-level http related exceptions.""" + + +from textwrap import indent +from typing import Optional, Union + +from .typedefs import _CIMultiDict + +__all__ = ("HttpProcessingError",) + + +class HttpProcessingError(Exception): + """HTTP error. + + Shortcut for raising HTTP errors with custom code, message and headers. + + code: HTTP Error code. + message: (optional) Error message. + headers: (optional) Headers to be sent in response, a list of pairs + """ + + code = 0 + message = "" + headers = None + + def __init__( + self, + *, + code: Optional[int] = None, + message: str = "", + headers: Optional[_CIMultiDict] = None, + ) -> None: + if code is not None: + self.code = code + self.headers = headers + self.message = message + + def __str__(self) -> str: + msg = indent(self.message, " ") + return f"{self.code}, message:\n{msg}" + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}: {self.code}, message={self.message!r}>" + + +class BadHttpMessage(HttpProcessingError): + code = 400 + message = "Bad Request" + + def __init__(self, message: str, *, headers: Optional[_CIMultiDict] = None) -> None: + super().__init__(message=message, headers=headers) + self.args = (message,) + + +class HttpBadRequest(BadHttpMessage): + code = 400 + message = "Bad Request" + + +class PayloadEncodingError(BadHttpMessage): + """Base class for payload errors""" + + +class ContentEncodingError(PayloadEncodingError): + """Content encoding error.""" + + +class TransferEncodingError(PayloadEncodingError): + """transfer encoding error.""" + + +class ContentLengthError(PayloadEncodingError): + """Not enough data for satisfy content length header.""" + + +class LineTooLong(BadHttpMessage): + def __init__( + self, line: str, limit: str = "Unknown", actual_size: str = "Unknown" + ) -> None: + super().__init__( + f"Got more than {limit} bytes ({actual_size}) when reading {line}." + ) + self.args = (line, limit, actual_size) + + +class InvalidHeader(BadHttpMessage): + def __init__(self, hdr: Union[bytes, str]) -> None: + if isinstance(hdr, bytes): + hdr = hdr.decode("utf-8", "surrogateescape") + super().__init__(f"Invalid HTTP Header: {hdr}") + self.hdr = hdr + self.args = (hdr,) + + +class BadStatusLine(BadHttpMessage): + def __init__(self, line: str = "") -> None: + if not isinstance(line, str): + line = repr(line) + super().__init__(f"Bad status line {line!r}") + self.args = (line,) + self.line = line + + +class InvalidURLError(BadHttpMessage): + pass diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py new file mode 100644 index 0000000..6eb30e4 --- /dev/null +++ b/aiohttp/http_parser.py @@ -0,0 +1,930 @@ +import abc +import asyncio +import re +import string +from contextlib import suppress +from enum import IntEnum +from typing import ( + Final, + Generic, + List, + NamedTuple, + Optional, + Pattern, + Set, + Tuple, + Type, + TypeVar, + Union, +) + +from multidict import CIMultiDict, CIMultiDictProxy, istr +from yarl import URL + +from . import hdrs +from .base_protocol import BaseProtocol +from .compression_utils import HAS_BROTLI, BrotliDecompressor, ZLibDecompressor +from .helpers import NO_EXTENSIONS, BaseTimerContext +from .http_exceptions import ( + BadHttpMessage, + BadStatusLine, + ContentEncodingError, + ContentLengthError, + InvalidHeader, + LineTooLong, + TransferEncodingError, +) +from .http_writer import HttpVersion, HttpVersion10 +from .log import internal_logger +from .streams import EMPTY_PAYLOAD, StreamReader +from .typedefs import RawHeaders + +__all__ = ( + "HeadersParser", + "HttpParser", + "HttpRequestParser", + "HttpResponseParser", + "RawRequestMessage", + "RawResponseMessage", +) + +ASCIISET: Final[Set[str]] = set(string.printable) + +# See https://tools.ietf.org/html/rfc7230#section-3.1.1 +# and https://tools.ietf.org/html/rfc7230#appendix-B +# +# method = token +# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / +# "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA +# token = 1*tchar +METHRE: Final[Pattern[str]] = re.compile(r"[!#$%&'*+\-.^_`|~0-9A-Za-z]+") +VERSRE: Final[Pattern[str]] = re.compile(r"HTTP/(\d+).(\d+)") +HDRRE: Final[Pattern[bytes]] = re.compile(rb"[\x00-\x1F\x7F()<>@,;:\[\]={} \t\\\\\"]") + + +class RawRequestMessage(NamedTuple): + method: str + path: str + version: HttpVersion + headers: CIMultiDictProxy[str] + raw_headers: RawHeaders + should_close: bool + compression: Optional[str] + upgrade: bool + chunked: bool + url: URL + + +class RawResponseMessage(NamedTuple): + version: HttpVersion + code: int + reason: str + headers: CIMultiDictProxy[str] + raw_headers: RawHeaders + should_close: bool + compression: Optional[str] + upgrade: bool + chunked: bool + + +_MsgT = TypeVar("_MsgT", RawRequestMessage, RawResponseMessage) + + +class ParseState(IntEnum): + PARSE_NONE = 0 + PARSE_LENGTH = 1 + PARSE_CHUNKED = 2 + PARSE_UNTIL_EOF = 3 + + +class ChunkState(IntEnum): + PARSE_CHUNKED_SIZE = 0 + PARSE_CHUNKED_CHUNK = 1 + PARSE_CHUNKED_CHUNK_EOF = 2 + PARSE_MAYBE_TRAILERS = 3 + PARSE_TRAILERS = 4 + + +class HeadersParser: + def __init__( + self, + max_line_size: int = 8190, + max_field_size: int = 8190, + ) -> None: + self.max_line_size = max_line_size + self.max_field_size = max_field_size + + def parse_headers( + self, lines: List[bytes] + ) -> Tuple["CIMultiDictProxy[str]", RawHeaders]: + headers: CIMultiDict[str] = CIMultiDict() + raw_headers = [] + + lines_idx = 1 + line = lines[1] + line_count = len(lines) + + while line: + # Parse initial header name : value pair. + try: + bname, bvalue = line.split(b":", 1) + except ValueError: + raise InvalidHeader(line) from None + + bname = bname.strip(b" \t") + bvalue = bvalue.lstrip() + if HDRRE.search(bname): + raise InvalidHeader(bname) + if len(bname) > self.max_field_size: + raise LineTooLong( + "request header name {}".format( + bname.decode("utf8", "backslashreplace") + ), + str(self.max_field_size), + str(len(bname)), + ) + + header_length = len(bvalue) + + # next line + lines_idx += 1 + line = lines[lines_idx] + + # consume continuation lines + continuation = line and line[0] in (32, 9) # (' ', '\t') + + if continuation: + bvalue_lst = [bvalue] + while continuation: + header_length += len(line) + if header_length > self.max_field_size: + raise LineTooLong( + "request header field {}".format( + bname.decode("utf8", "backslashreplace") + ), + str(self.max_field_size), + str(header_length), + ) + bvalue_lst.append(line) + + # next line + lines_idx += 1 + if lines_idx < line_count: + line = lines[lines_idx] + if line: + continuation = line[0] in (32, 9) # (' ', '\t') + else: + line = b"" + break + bvalue = b"".join(bvalue_lst) + else: + if header_length > self.max_field_size: + raise LineTooLong( + "request header field {}".format( + bname.decode("utf8", "backslashreplace") + ), + str(self.max_field_size), + str(header_length), + ) + + bvalue = bvalue.strip() + name = bname.decode("utf-8", "surrogateescape") + value = bvalue.decode("utf-8", "surrogateescape") + + headers.add(name, value) + raw_headers.append((bname, bvalue)) + + return (CIMultiDictProxy(headers), tuple(raw_headers)) + + +class HttpParser(abc.ABC, Generic[_MsgT]): + def __init__( + self, + protocol: BaseProtocol, + loop: asyncio.AbstractEventLoop, + limit: int, + max_line_size: int = 8190, + max_field_size: int = 8190, + timer: Optional[BaseTimerContext] = None, + code: Optional[int] = None, + method: Optional[str] = None, + readall: bool = False, + payload_exception: Optional[Type[BaseException]] = None, + response_with_body: bool = True, + read_until_eof: bool = False, + auto_decompress: bool = True, + ) -> None: + self.protocol = protocol + self.loop = loop + self.max_line_size = max_line_size + self.max_field_size = max_field_size + self.timer = timer + self.code = code + self.method = method + self.readall = readall + self.payload_exception = payload_exception + self.response_with_body = response_with_body + self.read_until_eof = read_until_eof + + self._lines: List[bytes] = [] + self._tail = b"" + self._upgraded = False + self._payload = None + self._payload_parser: Optional[HttpPayloadParser] = None + self._auto_decompress = auto_decompress + self._limit = limit + self._headers_parser = HeadersParser(max_line_size, max_field_size) + + @abc.abstractmethod + def parse_message(self, lines: List[bytes]) -> _MsgT: + pass + + def feed_eof(self) -> Optional[_MsgT]: + if self._payload_parser is not None: + self._payload_parser.feed_eof() + self._payload_parser = None + else: + # try to extract partial message + if self._tail: + self._lines.append(self._tail) + + if self._lines: + if self._lines[-1] != "\r\n": + self._lines.append(b"") + with suppress(Exception): + return self.parse_message(self._lines) + return None + + def feed_data( + self, + data: bytes, + SEP: bytes = b"\r\n", + EMPTY: bytes = b"", + CONTENT_LENGTH: istr = hdrs.CONTENT_LENGTH, + METH_CONNECT: str = hdrs.METH_CONNECT, + SEC_WEBSOCKET_KEY1: istr = hdrs.SEC_WEBSOCKET_KEY1, + ) -> Tuple[List[Tuple[_MsgT, StreamReader]], bool, bytes]: + messages = [] + + if self._tail: + data, self._tail = self._tail + data, b"" + + data_len = len(data) + start_pos = 0 + loop = self.loop + + while start_pos < data_len: + # read HTTP message (request/response line + headers), \r\n\r\n + # and split by lines + if self._payload_parser is None and not self._upgraded: + pos = data.find(SEP, start_pos) + # consume \r\n + if pos == start_pos and not self._lines: + start_pos = pos + 2 + continue + + if pos >= start_pos: + # line found + self._lines.append(data[start_pos:pos]) + start_pos = pos + 2 + + # \r\n\r\n found + if self._lines[-1] == EMPTY: + try: + msg: _MsgT = self.parse_message(self._lines) + finally: + self._lines.clear() + + def get_content_length() -> Optional[int]: + # payload length + length_hdr = msg.headers.get(CONTENT_LENGTH) + if length_hdr is None: + return None + + try: + length = int(length_hdr) + except ValueError: + raise InvalidHeader(CONTENT_LENGTH) + + if length < 0: + raise InvalidHeader(CONTENT_LENGTH) + + return length + + length = get_content_length() + # do not support old websocket spec + if SEC_WEBSOCKET_KEY1 in msg.headers: + raise InvalidHeader(SEC_WEBSOCKET_KEY1) + + self._upgraded = msg.upgrade + + method = getattr(msg, "method", self.method) + + assert self.protocol is not None + # calculate payload + if ( + (length is not None and length > 0) + or msg.chunked + and not msg.upgrade + ): + payload = StreamReader( + self.protocol, + timer=self.timer, + loop=loop, + limit=self._limit, + ) + payload_parser = HttpPayloadParser( + payload, + length=length, + chunked=msg.chunked, + method=method, + compression=msg.compression, + code=self.code, + readall=self.readall, + response_with_body=self.response_with_body, + auto_decompress=self._auto_decompress, + ) + if not payload_parser.done: + self._payload_parser = payload_parser + elif method == METH_CONNECT: + assert isinstance(msg, RawRequestMessage) + payload = StreamReader( + self.protocol, + timer=self.timer, + loop=loop, + limit=self._limit, + ) + self._upgraded = True + self._payload_parser = HttpPayloadParser( + payload, + method=msg.method, + compression=msg.compression, + readall=True, + auto_decompress=self._auto_decompress, + ) + else: + if ( + getattr(msg, "code", 100) >= 199 + and length is None + and self.read_until_eof + ): + payload = StreamReader( + self.protocol, + timer=self.timer, + loop=loop, + limit=self._limit, + ) + payload_parser = HttpPayloadParser( + payload, + length=length, + chunked=msg.chunked, + method=method, + compression=msg.compression, + code=self.code, + readall=True, + response_with_body=self.response_with_body, + auto_decompress=self._auto_decompress, + ) + if not payload_parser.done: + self._payload_parser = payload_parser + else: + payload = EMPTY_PAYLOAD + + messages.append((msg, payload)) + else: + self._tail = data[start_pos:] + data = EMPTY + break + + # no parser, just store + elif self._payload_parser is None and self._upgraded: + assert not self._lines + break + + # feed payload + elif data and start_pos < data_len: + assert not self._lines + assert self._payload_parser is not None + try: + eof, data = self._payload_parser.feed_data(data[start_pos:]) + except BaseException as exc: + if self.payload_exception is not None: + self._payload_parser.payload.set_exception( + self.payload_exception(str(exc)) + ) + else: + self._payload_parser.payload.set_exception(exc) + + eof = True + data = b"" + + if eof: + start_pos = 0 + data_len = len(data) + self._payload_parser = None + continue + else: + break + + if data and start_pos < data_len: + data = data[start_pos:] + else: + data = EMPTY + + return messages, self._upgraded, data + + def parse_headers( + self, lines: List[bytes] + ) -> Tuple[ + "CIMultiDictProxy[str]", RawHeaders, Optional[bool], Optional[str], bool, bool + ]: + """Parses RFC 5322 headers from a stream. + + Line continuations are supported. Returns list of header name + and value pairs. Header name is in upper case. + """ + headers, raw_headers = self._headers_parser.parse_headers(lines) + close_conn = None + encoding = None + upgrade = False + chunked = False + + # keep-alive + conn = headers.get(hdrs.CONNECTION) + if conn: + v = conn.lower() + if v == "close": + close_conn = True + elif v == "keep-alive": + close_conn = False + elif v == "upgrade": + upgrade = True + + # encoding + enc = headers.get(hdrs.CONTENT_ENCODING) + if enc: + enc = enc.lower() + if enc in ("gzip", "deflate", "br"): + encoding = enc + + # chunking + te = headers.get(hdrs.TRANSFER_ENCODING) + if te is not None: + if "chunked" == te.lower(): + chunked = True + else: + raise BadHttpMessage("Request has invalid `Transfer-Encoding`") + + if hdrs.CONTENT_LENGTH in headers: + raise BadHttpMessage( + "Transfer-Encoding can't be present with Content-Length", + ) + + return (headers, raw_headers, close_conn, encoding, upgrade, chunked) + + def set_upgraded(self, val: bool) -> None: + """Set connection upgraded (to websocket) mode. + + :param bool val: new state. + """ + self._upgraded = val + + +class HttpRequestParser(HttpParser[RawRequestMessage]): + """Read request status line. + + Exception .http_exceptions.BadStatusLine + could be raised in case of any errors in status line. + Returns RawRequestMessage. + """ + + def parse_message(self, lines: List[bytes]) -> RawRequestMessage: + # request line + line = lines[0].decode("utf-8", "surrogateescape") + try: + method, path, version = line.split(None, 2) + except ValueError: + raise BadStatusLine(line) from None + + if len(path) > self.max_line_size: + raise LineTooLong( + "Status line is too long", str(self.max_line_size), str(len(path)) + ) + + # method + if not METHRE.match(method): + raise BadStatusLine(method) + + # version + try: + if version.startswith("HTTP/"): + n1, n2 = version[5:].split(".", 1) + version_o = HttpVersion(int(n1), int(n2)) + else: + raise BadStatusLine(version) + except Exception: + raise BadStatusLine(version) + + if method == "CONNECT": + # authority-form, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.3 + url = URL.build(authority=path, encoded=True) + elif path.startswith("/"): + # origin-form, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.1 + path_part, _hash_separator, url_fragment = path.partition("#") + path_part, _question_mark_separator, qs_part = path_part.partition("?") + + # NOTE: `yarl.URL.build()` is used to mimic what the Cython-based + # NOTE: parser does, otherwise it results into the same + # NOTE: HTTP Request-Line input producing different + # NOTE: `yarl.URL()` objects + url = URL.build( + path=path_part, + query_string=qs_part, + fragment=url_fragment, + encoded=True, + ) + else: + # absolute-form for proxy maybe, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.2 + url = URL(path, encoded=True) + + # read headers + ( + headers, + raw_headers, + close, + compression, + upgrade, + chunked, + ) = self.parse_headers(lines) + + if close is None: # then the headers weren't set in the request + if version_o <= HttpVersion10: # HTTP 1.0 must asks to not close + close = True + else: # HTTP 1.1 must ask to close. + close = False + + return RawRequestMessage( + method, + path, + version_o, + headers, + raw_headers, + close, + compression, + upgrade, + chunked, + url, + ) + + +class HttpResponseParser(HttpParser[RawResponseMessage]): + """Read response status line and headers. + + BadStatusLine could be raised in case of any errors in status line. + Returns RawResponseMessage. + """ + + def parse_message(self, lines: List[bytes]) -> RawResponseMessage: + line = lines[0].decode("utf-8", "surrogateescape") + try: + version, status = line.split(None, 1) + except ValueError: + raise BadStatusLine(line) from None + + try: + status, reason = status.split(None, 1) + except ValueError: + reason = "" + + if len(reason) > self.max_line_size: + raise LineTooLong( + "Status line is too long", str(self.max_line_size), str(len(reason)) + ) + + # version + match = VERSRE.match(version) + if match is None: + raise BadStatusLine(line) + version_o = HttpVersion(int(match.group(1)), int(match.group(2))) + + # The status code is a three-digit number + try: + status_i = int(status) + except ValueError: + raise BadStatusLine(line) from None + + if status_i > 999: + raise BadStatusLine(line) + + # read headers + ( + headers, + raw_headers, + close, + compression, + upgrade, + chunked, + ) = self.parse_headers(lines) + + if close is None: + close = version_o <= HttpVersion10 + + return RawResponseMessage( + version_o, + status_i, + reason.strip(), + headers, + raw_headers, + close, + compression, + upgrade, + chunked, + ) + + +class HttpPayloadParser: + def __init__( + self, + payload: StreamReader, + length: Optional[int] = None, + chunked: bool = False, + compression: Optional[str] = None, + code: Optional[int] = None, + method: Optional[str] = None, + readall: bool = False, + response_with_body: bool = True, + auto_decompress: bool = True, + ) -> None: + self._length = 0 + self._type = ParseState.PARSE_NONE + self._chunk = ChunkState.PARSE_CHUNKED_SIZE + self._chunk_size = 0 + self._chunk_tail = b"" + self._auto_decompress = auto_decompress + self.done = False + + # payload decompression wrapper + if response_with_body and compression and self._auto_decompress: + real_payload: Union[StreamReader, DeflateBuffer] = DeflateBuffer( + payload, compression + ) + else: + real_payload = payload + + # payload parser + if not response_with_body: + # don't parse payload if it's not expected to be received + self._type = ParseState.PARSE_NONE + real_payload.feed_eof() + self.done = True + + elif chunked: + self._type = ParseState.PARSE_CHUNKED + elif length is not None: + self._type = ParseState.PARSE_LENGTH + self._length = length + if self._length == 0: + real_payload.feed_eof() + self.done = True + else: + if readall and code != 204: + self._type = ParseState.PARSE_UNTIL_EOF + elif method in ("PUT", "POST"): + internal_logger.warning( # pragma: no cover + "Content-Length or Transfer-Encoding header is required" + ) + self._type = ParseState.PARSE_NONE + real_payload.feed_eof() + self.done = True + + self.payload = real_payload + + def feed_eof(self) -> None: + if self._type == ParseState.PARSE_UNTIL_EOF: + self.payload.feed_eof() + elif self._type == ParseState.PARSE_LENGTH: + raise ContentLengthError( + "Not enough data for satisfy content length header." + ) + elif self._type == ParseState.PARSE_CHUNKED: + raise TransferEncodingError( + "Not enough data for satisfy transfer length header." + ) + + def feed_data( + self, chunk: bytes, SEP: bytes = b"\r\n", CHUNK_EXT: bytes = b";" + ) -> Tuple[bool, bytes]: + # Read specified amount of bytes + if self._type == ParseState.PARSE_LENGTH: + required = self._length + chunk_len = len(chunk) + + if required >= chunk_len: + self._length = required - chunk_len + self.payload.feed_data(chunk, chunk_len) + if self._length == 0: + self.payload.feed_eof() + return True, b"" + else: + self._length = 0 + self.payload.feed_data(chunk[:required], required) + self.payload.feed_eof() + return True, chunk[required:] + + # Chunked transfer encoding parser + elif self._type == ParseState.PARSE_CHUNKED: + if self._chunk_tail: + chunk = self._chunk_tail + chunk + self._chunk_tail = b"" + + while chunk: + # read next chunk size + if self._chunk == ChunkState.PARSE_CHUNKED_SIZE: + pos = chunk.find(SEP) + if pos >= 0: + i = chunk.find(CHUNK_EXT, 0, pos) + if i >= 0: + size_b = chunk[:i] # strip chunk-extensions + else: + size_b = chunk[:pos] + + try: + size = int(bytes(size_b), 16) + except ValueError: + exc = TransferEncodingError( + chunk[:pos].decode("ascii", "surrogateescape") + ) + self.payload.set_exception(exc) + raise exc from None + + chunk = chunk[pos + 2 :] + if size == 0: # eof marker + self._chunk = ChunkState.PARSE_MAYBE_TRAILERS + else: + self._chunk = ChunkState.PARSE_CHUNKED_CHUNK + self._chunk_size = size + self.payload.begin_http_chunk_receiving() + else: + self._chunk_tail = chunk + return False, b"" + + # read chunk and feed buffer + if self._chunk == ChunkState.PARSE_CHUNKED_CHUNK: + required = self._chunk_size + chunk_len = len(chunk) + + if required > chunk_len: + self._chunk_size = required - chunk_len + self.payload.feed_data(chunk, chunk_len) + return False, b"" + else: + self._chunk_size = 0 + self.payload.feed_data(chunk[:required], required) + chunk = chunk[required:] + self._chunk = ChunkState.PARSE_CHUNKED_CHUNK_EOF + self.payload.end_http_chunk_receiving() + + # toss the CRLF at the end of the chunk + if self._chunk == ChunkState.PARSE_CHUNKED_CHUNK_EOF: + if chunk[:2] == SEP: + chunk = chunk[2:] + self._chunk = ChunkState.PARSE_CHUNKED_SIZE + else: + self._chunk_tail = chunk + return False, b"" + + # if stream does not contain trailer, after 0\r\n + # we should get another \r\n otherwise + # trailers needs to be skiped until \r\n\r\n + if self._chunk == ChunkState.PARSE_MAYBE_TRAILERS: + head = chunk[:2] + if head == SEP: + # end of stream + self.payload.feed_eof() + return True, chunk[2:] + # Both CR and LF, or only LF may not be received yet. It is + # expected that CRLF or LF will be shown at the very first + # byte next time, otherwise trailers should come. The last + # CRLF which marks the end of response might not be + # contained in the same TCP segment which delivered the + # size indicator. + if not head: + return False, b"" + if head == SEP[:1]: + self._chunk_tail = head + return False, b"" + self._chunk = ChunkState.PARSE_TRAILERS + + # read and discard trailer up to the CRLF terminator + if self._chunk == ChunkState.PARSE_TRAILERS: + pos = chunk.find(SEP) + if pos >= 0: + chunk = chunk[pos + 2 :] + self._chunk = ChunkState.PARSE_MAYBE_TRAILERS + else: + self._chunk_tail = chunk + return False, b"" + + # Read all bytes until eof + elif self._type == ParseState.PARSE_UNTIL_EOF: + self.payload.feed_data(chunk, len(chunk)) + + return False, b"" + + +class DeflateBuffer: + """DeflateStream decompress stream and feed data into specified stream.""" + + def __init__(self, out: StreamReader, encoding: Optional[str]) -> None: + self.out = out + self.size = 0 + self.encoding = encoding + self._started_decoding = False + + self.decompressor: Union[BrotliDecompressor, ZLibDecompressor] + if encoding == "br": + if not HAS_BROTLI: # pragma: no cover + raise ContentEncodingError( + "Can not decode content-encoding: brotli (br). " + "Please install `Brotli`" + ) + self.decompressor = BrotliDecompressor() + else: + self.decompressor = ZLibDecompressor(encoding=encoding) + + def set_exception(self, exc: BaseException) -> None: + self.out.set_exception(exc) + + def feed_data(self, chunk: bytes, size: int) -> None: + if not size: + return + + self.size += size + + # RFC1950 + # bits 0..3 = CM = 0b1000 = 8 = "deflate" + # bits 4..7 = CINFO = 1..7 = windows size. + if ( + not self._started_decoding + and self.encoding == "deflate" + and chunk[0] & 0xF != 8 + ): + # Change the decoder to decompress incorrectly compressed data + # Actually we should issue a warning about non-RFC-compliant data. + self.decompressor = ZLibDecompressor( + encoding=self.encoding, suppress_deflate_header=True + ) + + try: + chunk = self.decompressor.decompress_sync(chunk) + except Exception: + raise ContentEncodingError( + "Can not decode content-encoding: %s" % self.encoding + ) + + self._started_decoding = True + + if chunk: + self.out.feed_data(chunk, len(chunk)) + + def feed_eof(self) -> None: + chunk = self.decompressor.flush() + + if chunk or self.size > 0: + self.out.feed_data(chunk, len(chunk)) + # decompressor is not brotli unless encoding is "br" + if self.encoding == "deflate" and not self.decompressor.eof: # type: ignore[union-attr] + raise ContentEncodingError("deflate") + + self.out.feed_eof() + + def begin_http_chunk_receiving(self) -> None: + self.out.begin_http_chunk_receiving() + + def end_http_chunk_receiving(self) -> None: + self.out.end_http_chunk_receiving() + + +HttpRequestParserPy = HttpRequestParser +HttpResponseParserPy = HttpResponseParser +RawRequestMessagePy = RawRequestMessage +RawResponseMessagePy = RawResponseMessage + +try: + if not NO_EXTENSIONS: + from ._http_parser import ( # type: ignore[import,no-redef] + HttpRequestParser, + HttpResponseParser, + RawRequestMessage, + RawResponseMessage, + ) + + HttpRequestParserC = HttpRequestParser + HttpResponseParserC = HttpResponseParser + RawRequestMessageC = RawRequestMessage + RawResponseMessageC = RawResponseMessage +except ImportError: # pragma: no cover + pass diff --git a/aiohttp/http_websocket.py b/aiohttp/http_websocket.py new file mode 100644 index 0000000..deb8ab9 --- /dev/null +++ b/aiohttp/http_websocket.py @@ -0,0 +1,710 @@ +"""WebSocket protocol versions 13 and 8.""" + +import asyncio +import functools +import json +import random +import re +import sys +import zlib +from enum import IntEnum +from struct import Struct +from typing import ( + Any, + Callable, + Final, + List, + NamedTuple, + Optional, + Pattern, + Set, + Tuple, + Union, + cast, +) + +from .base_protocol import BaseProtocol +from .compression_utils import ZLibCompressor, ZLibDecompressor +from .helpers import NO_EXTENSIONS +from .streams import DataQueue + +__all__ = ( + "WS_CLOSED_MESSAGE", + "WS_CLOSING_MESSAGE", + "WS_KEY", + "WebSocketReader", + "WebSocketWriter", + "WSMessage", + "WebSocketError", + "WSMsgType", + "WSCloseCode", +) + + +class WSCloseCode(IntEnum): + OK = 1000 + GOING_AWAY = 1001 + PROTOCOL_ERROR = 1002 + UNSUPPORTED_DATA = 1003 + ABNORMAL_CLOSURE = 1006 + INVALID_TEXT = 1007 + POLICY_VIOLATION = 1008 + MESSAGE_TOO_BIG = 1009 + MANDATORY_EXTENSION = 1010 + INTERNAL_ERROR = 1011 + SERVICE_RESTART = 1012 + TRY_AGAIN_LATER = 1013 + BAD_GATEWAY = 1014 + + +ALLOWED_CLOSE_CODES: Final[Set[int]] = {int(i) for i in WSCloseCode} + + +class WSMsgType(IntEnum): + # websocket spec types + CONTINUATION = 0x0 + TEXT = 0x1 + BINARY = 0x2 + PING = 0x9 + PONG = 0xA + CLOSE = 0x8 + + # aiohttp specific types + CLOSING = 0x100 + CLOSED = 0x101 + ERROR = 0x102 + + +WS_KEY: Final[bytes] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + + +UNPACK_LEN2 = Struct("!H").unpack_from +UNPACK_LEN3 = Struct("!Q").unpack_from +UNPACK_CLOSE_CODE = Struct("!H").unpack +PACK_LEN1 = Struct("!BB").pack +PACK_LEN2 = Struct("!BBH").pack +PACK_LEN3 = Struct("!BBQ").pack +PACK_CLOSE_CODE = Struct("!H").pack +MSG_SIZE: Final[int] = 2**14 +DEFAULT_LIMIT: Final[int] = 2**16 + + +class WSMessage(NamedTuple): + type: WSMsgType + # To type correctly, this would need some kind of tagged union for each type. + data: Any + extra: Optional[str] + + def json(self, *, loads: Callable[[Any], Any] = json.loads) -> Any: + """Return parsed JSON data. + + .. versionadded:: 0.22 + """ + return loads(self.data) + + +WS_CLOSED_MESSAGE = WSMessage(WSMsgType.CLOSED, None, None) +WS_CLOSING_MESSAGE = WSMessage(WSMsgType.CLOSING, None, None) + + +class WebSocketError(Exception): + """WebSocket protocol parser error.""" + + def __init__(self, code: int, message: str) -> None: + self.code = code + super().__init__(code, message) + + def __str__(self) -> str: + return cast(str, self.args[1]) + + +class WSHandshakeError(Exception): + """WebSocket protocol handshake error.""" + + +native_byteorder: Final[str] = sys.byteorder + + +# Used by _websocket_mask_python +@functools.lru_cache() +def _xor_table() -> List[bytes]: + return [bytes(a ^ b for a in range(256)) for b in range(256)] + + +def _websocket_mask_python(mask: bytes, data: bytearray) -> None: + """Websocket masking function. + + `mask` is a `bytes` object of length 4; `data` is a `bytearray` + object of any length. The contents of `data` are masked with `mask`, + as specified in section 5.3 of RFC 6455. + + Note that this function mutates the `data` argument. + + This pure-python implementation may be replaced by an optimized + version when available. + + """ + assert isinstance(data, bytearray), data + assert len(mask) == 4, mask + + if data: + _XOR_TABLE = _xor_table() + a, b, c, d = (_XOR_TABLE[n] for n in mask) + data[::4] = data[::4].translate(a) + data[1::4] = data[1::4].translate(b) + data[2::4] = data[2::4].translate(c) + data[3::4] = data[3::4].translate(d) + + +if NO_EXTENSIONS: # pragma: no cover + _websocket_mask = _websocket_mask_python +else: + try: + from ._websocket import _websocket_mask_cython # type: ignore[import] + + _websocket_mask = _websocket_mask_cython + except ImportError: # pragma: no cover + _websocket_mask = _websocket_mask_python + +_WS_DEFLATE_TRAILING: Final[bytes] = bytes([0x00, 0x00, 0xFF, 0xFF]) + + +_WS_EXT_RE: Final[Pattern[str]] = re.compile( + r"^(?:;\s*(?:" + r"(server_no_context_takeover)|" + r"(client_no_context_takeover)|" + r"(server_max_window_bits(?:=(\d+))?)|" + r"(client_max_window_bits(?:=(\d+))?)))*$" +) + +_WS_EXT_RE_SPLIT: Final[Pattern[str]] = re.compile(r"permessage-deflate([^,]+)?") + + +def ws_ext_parse(extstr: Optional[str], isserver: bool = False) -> Tuple[int, bool]: + if not extstr: + return 0, False + + compress = 0 + notakeover = False + for ext in _WS_EXT_RE_SPLIT.finditer(extstr): + defext = ext.group(1) + # Return compress = 15 when get `permessage-deflate` + if not defext: + compress = 15 + break + match = _WS_EXT_RE.match(defext) + if match: + compress = 15 + if isserver: + # Server never fail to detect compress handshake. + # Server does not need to send max wbit to client + if match.group(4): + compress = int(match.group(4)) + # Group3 must match if group4 matches + # Compress wbit 8 does not support in zlib + # If compress level not support, + # CONTINUE to next extension + if compress > 15 or compress < 9: + compress = 0 + continue + if match.group(1): + notakeover = True + # Ignore regex group 5 & 6 for client_max_window_bits + break + else: + if match.group(6): + compress = int(match.group(6)) + # Group5 must match if group6 matches + # Compress wbit 8 does not support in zlib + # If compress level not support, + # FAIL the parse progress + if compress > 15 or compress < 9: + raise WSHandshakeError("Invalid window size") + if match.group(2): + notakeover = True + # Ignore regex group 5 & 6 for client_max_window_bits + break + # Return Fail if client side and not match + elif not isserver: + raise WSHandshakeError("Extension for deflate not supported" + ext.group(1)) + + return compress, notakeover + + +def ws_ext_gen( + compress: int = 15, isserver: bool = False, server_notakeover: bool = False +) -> str: + # client_notakeover=False not used for server + # compress wbit 8 does not support in zlib + if compress < 9 or compress > 15: + raise ValueError( + "Compress wbits must between 9 and 15, " "zlib does not support wbits=8" + ) + enabledext = ["permessage-deflate"] + if not isserver: + enabledext.append("client_max_window_bits") + + if compress < 15: + enabledext.append("server_max_window_bits=" + str(compress)) + if server_notakeover: + enabledext.append("server_no_context_takeover") + # if client_notakeover: + # enabledext.append('client_no_context_takeover') + return "; ".join(enabledext) + + +class WSParserState(IntEnum): + READ_HEADER = 1 + READ_PAYLOAD_LENGTH = 2 + READ_PAYLOAD_MASK = 3 + READ_PAYLOAD = 4 + + +class WebSocketReader: + def __init__( + self, queue: DataQueue[WSMessage], max_msg_size: int, compress: bool = True + ) -> None: + self.queue = queue + self._max_msg_size = max_msg_size + + self._exc: Optional[BaseException] = None + self._partial = bytearray() + self._state = WSParserState.READ_HEADER + + self._opcode: Optional[int] = None + self._frame_fin = False + self._frame_opcode: Optional[int] = None + self._frame_payload = bytearray() + + self._tail = b"" + self._has_mask = False + self._frame_mask: Optional[bytes] = None + self._payload_length = 0 + self._payload_length_flag = 0 + self._compressed: Optional[bool] = None + self._decompressobj: Optional[ZLibDecompressor] = None + self._compress = compress + + def feed_eof(self) -> None: + self.queue.feed_eof() + + def feed_data(self, data: bytes) -> Tuple[bool, bytes]: + if self._exc: + return True, data + + try: + return self._feed_data(data) + except Exception as exc: + self._exc = exc + self.queue.set_exception(exc) + return True, b"" + + def _feed_data(self, data: bytes) -> Tuple[bool, bytes]: + for fin, opcode, payload, compressed in self.parse_frame(data): + if compressed and not self._decompressobj: + self._decompressobj = ZLibDecompressor(suppress_deflate_header=True) + if opcode == WSMsgType.CLOSE: + if len(payload) >= 2: + close_code = UNPACK_CLOSE_CODE(payload[:2])[0] + if close_code < 3000 and close_code not in ALLOWED_CLOSE_CODES: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + f"Invalid close code: {close_code}", + ) + try: + close_message = payload[2:].decode("utf-8") + except UnicodeDecodeError as exc: + raise WebSocketError( + WSCloseCode.INVALID_TEXT, "Invalid UTF-8 text message" + ) from exc + msg = WSMessage(WSMsgType.CLOSE, close_code, close_message) + elif payload: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + f"Invalid close frame: {fin} {opcode} {payload!r}", + ) + else: + msg = WSMessage(WSMsgType.CLOSE, 0, "") + + self.queue.feed_data(msg, 0) + + elif opcode == WSMsgType.PING: + self.queue.feed_data( + WSMessage(WSMsgType.PING, payload, ""), len(payload) + ) + + elif opcode == WSMsgType.PONG: + self.queue.feed_data( + WSMessage(WSMsgType.PONG, payload, ""), len(payload) + ) + + elif ( + opcode not in (WSMsgType.TEXT, WSMsgType.BINARY) + and self._opcode is None + ): + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, f"Unexpected opcode={opcode!r}" + ) + else: + # load text/binary + if not fin: + # got partial frame payload + if opcode != WSMsgType.CONTINUATION: + self._opcode = opcode + self._partial.extend(payload) + if self._max_msg_size and len(self._partial) >= self._max_msg_size: + raise WebSocketError( + WSCloseCode.MESSAGE_TOO_BIG, + "Message size {} exceeds limit {}".format( + len(self._partial), self._max_msg_size + ), + ) + else: + # previous frame was non finished + # we should get continuation opcode + if self._partial: + if opcode != WSMsgType.CONTINUATION: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "The opcode in non-fin frame is expected " + "to be zero, got {!r}".format(opcode), + ) + + if opcode == WSMsgType.CONTINUATION: + assert self._opcode is not None + opcode = self._opcode + self._opcode = None + + self._partial.extend(payload) + if self._max_msg_size and len(self._partial) >= self._max_msg_size: + raise WebSocketError( + WSCloseCode.MESSAGE_TOO_BIG, + "Message size {} exceeds limit {}".format( + len(self._partial), self._max_msg_size + ), + ) + + # Decompress process must to be done after all packets + # received. + if compressed: + assert self._decompressobj is not None + self._partial.extend(_WS_DEFLATE_TRAILING) + payload_merged = self._decompressobj.decompress_sync( + self._partial, self._max_msg_size + ) + if self._decompressobj.unconsumed_tail: + left = len(self._decompressobj.unconsumed_tail) + raise WebSocketError( + WSCloseCode.MESSAGE_TOO_BIG, + "Decompressed message size {} exceeds limit {}".format( + self._max_msg_size + left, self._max_msg_size + ), + ) + else: + payload_merged = bytes(self._partial) + + self._partial.clear() + + if opcode == WSMsgType.TEXT: + try: + text = payload_merged.decode("utf-8") + self.queue.feed_data( + WSMessage(WSMsgType.TEXT, text, ""), len(text) + ) + except UnicodeDecodeError as exc: + raise WebSocketError( + WSCloseCode.INVALID_TEXT, "Invalid UTF-8 text message" + ) from exc + else: + self.queue.feed_data( + WSMessage(WSMsgType.BINARY, payload_merged, ""), + len(payload_merged), + ) + + return False, b"" + + def parse_frame( + self, buf: bytes + ) -> List[Tuple[bool, Optional[int], bytearray, Optional[bool]]]: + """Return the next frame from the socket.""" + frames = [] + if self._tail: + buf, self._tail = self._tail + buf, b"" + + start_pos = 0 + buf_length = len(buf) + + while True: + # read header + if self._state == WSParserState.READ_HEADER: + if buf_length - start_pos >= 2: + data = buf[start_pos : start_pos + 2] + start_pos += 2 + first_byte, second_byte = data + + fin = (first_byte >> 7) & 1 + rsv1 = (first_byte >> 6) & 1 + rsv2 = (first_byte >> 5) & 1 + rsv3 = (first_byte >> 4) & 1 + opcode = first_byte & 0xF + + # frame-fin = %x0 ; more frames of this message follow + # / %x1 ; final frame of this message + # frame-rsv1 = %x0 ; + # 1 bit, MUST be 0 unless negotiated otherwise + # frame-rsv2 = %x0 ; + # 1 bit, MUST be 0 unless negotiated otherwise + # frame-rsv3 = %x0 ; + # 1 bit, MUST be 0 unless negotiated otherwise + # + # Remove rsv1 from this test for deflate development + if rsv2 or rsv3 or (rsv1 and not self._compress): + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Received frame with non-zero reserved bits", + ) + + if opcode > 0x7 and fin == 0: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Received fragmented control frame", + ) + + has_mask = (second_byte >> 7) & 1 + length = second_byte & 0x7F + + # Control frames MUST have a payload + # length of 125 bytes or less + if opcode > 0x7 and length > 125: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Control frame payload cannot be " "larger than 125 bytes", + ) + + # Set compress status if last package is FIN + # OR set compress status if this is first fragment + # Raise error if not first fragment with rsv1 = 0x1 + if self._frame_fin or self._compressed is None: + self._compressed = True if rsv1 else False + elif rsv1: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Received frame with non-zero reserved bits", + ) + + self._frame_fin = bool(fin) + self._frame_opcode = opcode + self._has_mask = bool(has_mask) + self._payload_length_flag = length + self._state = WSParserState.READ_PAYLOAD_LENGTH + else: + break + + # read payload length + if self._state == WSParserState.READ_PAYLOAD_LENGTH: + length = self._payload_length_flag + if length == 126: + if buf_length - start_pos >= 2: + data = buf[start_pos : start_pos + 2] + start_pos += 2 + length = UNPACK_LEN2(data)[0] + self._payload_length = length + self._state = ( + WSParserState.READ_PAYLOAD_MASK + if self._has_mask + else WSParserState.READ_PAYLOAD + ) + else: + break + elif length > 126: + if buf_length - start_pos >= 8: + data = buf[start_pos : start_pos + 8] + start_pos += 8 + length = UNPACK_LEN3(data)[0] + self._payload_length = length + self._state = ( + WSParserState.READ_PAYLOAD_MASK + if self._has_mask + else WSParserState.READ_PAYLOAD + ) + else: + break + else: + self._payload_length = length + self._state = ( + WSParserState.READ_PAYLOAD_MASK + if self._has_mask + else WSParserState.READ_PAYLOAD + ) + + # read payload mask + if self._state == WSParserState.READ_PAYLOAD_MASK: + if buf_length - start_pos >= 4: + self._frame_mask = buf[start_pos : start_pos + 4] + start_pos += 4 + self._state = WSParserState.READ_PAYLOAD + else: + break + + if self._state == WSParserState.READ_PAYLOAD: + length = self._payload_length + payload = self._frame_payload + + chunk_len = buf_length - start_pos + if length >= chunk_len: + self._payload_length = length - chunk_len + payload.extend(buf[start_pos:]) + start_pos = buf_length + else: + self._payload_length = 0 + payload.extend(buf[start_pos : start_pos + length]) + start_pos = start_pos + length + + if self._payload_length == 0: + if self._has_mask: + assert self._frame_mask is not None + _websocket_mask(self._frame_mask, payload) + + frames.append( + (self._frame_fin, self._frame_opcode, payload, self._compressed) + ) + + self._frame_payload = bytearray() + self._state = WSParserState.READ_HEADER + else: + break + + self._tail = buf[start_pos:] + + return frames + + +class WebSocketWriter: + def __init__( + self, + protocol: BaseProtocol, + transport: asyncio.Transport, + *, + use_mask: bool = False, + limit: int = DEFAULT_LIMIT, + random: Any = random.Random(), + compress: int = 0, + notakeover: bool = False, + ) -> None: + self.protocol = protocol + self.transport = transport + self.use_mask = use_mask + self.randrange = random.randrange + self.compress = compress + self.notakeover = notakeover + self._closing = False + self._limit = limit + self._output_size = 0 + self._compressobj: Any = None # actually compressobj + + async def _send_frame( + self, message: bytes, opcode: int, compress: Optional[int] = None + ) -> None: + """Send a frame over the websocket with message as its payload.""" + if self._closing and not (opcode & WSMsgType.CLOSE): + raise ConnectionResetError("Cannot write to closing transport") + + rsv = 0 + + # Only compress larger packets (disabled) + # Does small packet needs to be compressed? + # if self.compress and opcode < 8 and len(message) > 124: + if (compress or self.compress) and opcode < 8: + if compress: + # Do not set self._compress if compressing is for this frame + compressobj = ZLibCompressor(level=zlib.Z_BEST_SPEED, wbits=-compress) + else: # self.compress + if not self._compressobj: + self._compressobj = ZLibCompressor( + level=zlib.Z_BEST_SPEED, wbits=-self.compress + ) + compressobj = self._compressobj + + message = await compressobj.compress(message) + message += compressobj.flush( + zlib.Z_FULL_FLUSH if self.notakeover else zlib.Z_SYNC_FLUSH + ) + if message.endswith(_WS_DEFLATE_TRAILING): + message = message[:-4] + rsv = rsv | 0x40 + + msg_length = len(message) + + use_mask = self.use_mask + if use_mask: + mask_bit = 0x80 + else: + mask_bit = 0 + + if msg_length < 126: + header = PACK_LEN1(0x80 | rsv | opcode, msg_length | mask_bit) + elif msg_length < (1 << 16): + header = PACK_LEN2(0x80 | rsv | opcode, 126 | mask_bit, msg_length) + else: + header = PACK_LEN3(0x80 | rsv | opcode, 127 | mask_bit, msg_length) + if use_mask: + mask = self.randrange(0, 0xFFFFFFFF) + mask = mask.to_bytes(4, "big") + message = bytearray(message) + _websocket_mask(mask, message) + self._write(header + mask + message) + self._output_size += len(header) + len(mask) + len(message) + else: + if len(message) > MSG_SIZE: + self._write(header) + self._write(message) + else: + self._write(header + message) + + self._output_size += len(header) + len(message) + + if self._output_size > self._limit: + self._output_size = 0 + await self.protocol._drain_helper() + + def _write(self, data: bytes) -> None: + if self.transport.is_closing(): + raise ConnectionResetError("Cannot write to closing transport") + self.transport.write(data) + + async def pong(self, message: Union[bytes, str] = b"") -> None: + """Send pong message.""" + if isinstance(message, str): + message = message.encode("utf-8") + await self._send_frame(message, WSMsgType.PONG) + + async def ping(self, message: Union[bytes, str] = b"") -> None: + """Send ping message.""" + if isinstance(message, str): + message = message.encode("utf-8") + await self._send_frame(message, WSMsgType.PING) + + async def send( + self, + message: Union[str, bytes], + binary: bool = False, + compress: Optional[int] = None, + ) -> None: + """Send a frame over the websocket with message as its payload.""" + if isinstance(message, str): + message = message.encode("utf-8") + if binary: + await self._send_frame(message, WSMsgType.BINARY, compress) + else: + await self._send_frame(message, WSMsgType.TEXT, compress) + + async def close(self, code: int = 1000, message: Union[bytes, str] = b"") -> None: + """Close the websocket, sending the specified code and message.""" + if isinstance(message, str): + message = message.encode("utf-8") + try: + await self._send_frame( + PACK_CLOSE_CODE(code) + message, opcode=WSMsgType.CLOSE + ) + finally: + self._closing = True diff --git a/aiohttp/http_writer.py b/aiohttp/http_writer.py new file mode 100644 index 0000000..8f2d908 --- /dev/null +++ b/aiohttp/http_writer.py @@ -0,0 +1,198 @@ +"""Http related parsers and protocol.""" + +import asyncio +import zlib +from typing import Any, Awaitable, Callable, NamedTuple, Optional, Union # noqa + +from multidict import CIMultiDict + +from .abc import AbstractStreamWriter +from .base_protocol import BaseProtocol +from .compression_utils import ZLibCompressor +from .helpers import NO_EXTENSIONS + +__all__ = ("StreamWriter", "HttpVersion", "HttpVersion10", "HttpVersion11") + + +class HttpVersion(NamedTuple): + major: int + minor: int + + +HttpVersion10 = HttpVersion(1, 0) +HttpVersion11 = HttpVersion(1, 1) + + +_T_OnChunkSent = Optional[Callable[[bytes], Awaitable[None]]] +_T_OnHeadersSent = Optional[Callable[["CIMultiDict[str]"], Awaitable[None]]] + + +class StreamWriter(AbstractStreamWriter): + def __init__( + self, + protocol: BaseProtocol, + loop: asyncio.AbstractEventLoop, + on_chunk_sent: _T_OnChunkSent = None, + on_headers_sent: _T_OnHeadersSent = None, + ) -> None: + self._protocol = protocol + + self.loop = loop + self.length = None + self.chunked = False + self.buffer_size = 0 + self.output_size = 0 + + self._eof = False + self._compress: Optional[ZLibCompressor] = None + self._drain_waiter = None + + self._on_chunk_sent: _T_OnChunkSent = on_chunk_sent + self._on_headers_sent: _T_OnHeadersSent = on_headers_sent + + @property + def transport(self) -> Optional[asyncio.Transport]: + return self._protocol.transport + + @property + def protocol(self) -> BaseProtocol: + return self._protocol + + def enable_chunking(self) -> None: + self.chunked = True + + def enable_compression( + self, encoding: str = "deflate", strategy: int = zlib.Z_DEFAULT_STRATEGY + ) -> None: + self._compress = ZLibCompressor(encoding=encoding, strategy=strategy) + + def _write(self, chunk: bytes) -> None: + size = len(chunk) + self.buffer_size += size + self.output_size += size + transport = self.transport + if not self._protocol.connected or transport is None or transport.is_closing(): + raise ConnectionResetError("Cannot write to closing transport") + transport.write(chunk) + + async def write( + self, chunk: bytes, *, drain: bool = True, LIMIT: int = 0x10000 + ) -> None: + """Writes chunk of data to a stream. + + write_eof() indicates end of stream. + writer can't be used after write_eof() method being called. + write() return drain future. + """ + if self._on_chunk_sent is not None: + await self._on_chunk_sent(chunk) + + if isinstance(chunk, memoryview): + if chunk.nbytes != len(chunk): + # just reshape it + chunk = chunk.cast("c") + + if self._compress is not None: + chunk = await self._compress.compress(chunk) + if not chunk: + return + + if self.length is not None: + chunk_len = len(chunk) + if self.length >= chunk_len: + self.length = self.length - chunk_len + else: + chunk = chunk[: self.length] + self.length = 0 + if not chunk: + return + + if chunk: + if self.chunked: + chunk_len_pre = ("%x\r\n" % len(chunk)).encode("ascii") + chunk = chunk_len_pre + chunk + b"\r\n" + + self._write(chunk) + + if self.buffer_size > LIMIT and drain: + self.buffer_size = 0 + await self.drain() + + async def write_headers( + self, status_line: str, headers: "CIMultiDict[str]" + ) -> None: + """Write request/response status and headers.""" + if self._on_headers_sent is not None: + await self._on_headers_sent(headers) + + # status + headers + buf = _serialize_headers(status_line, headers) + self._write(buf) + + async def write_eof(self, chunk: bytes = b"") -> None: + if self._eof: + return + + if chunk and self._on_chunk_sent is not None: + await self._on_chunk_sent(chunk) + + if self._compress: + if chunk: + chunk = await self._compress.compress(chunk) + + chunk += self._compress.flush() + if chunk and self.chunked: + chunk_len = ("%x\r\n" % len(chunk)).encode("ascii") + chunk = chunk_len + chunk + b"\r\n0\r\n\r\n" + else: + if self.chunked: + if chunk: + chunk_len = ("%x\r\n" % len(chunk)).encode("ascii") + chunk = chunk_len + chunk + b"\r\n0\r\n\r\n" + else: + chunk = b"0\r\n\r\n" + + if chunk: + self._write(chunk) + + await self.drain() + + self._eof = True + + async def drain(self) -> None: + """Flush the write buffer. + + The intended use is to write + + await w.write(data) + await w.drain() + """ + if self._protocol.transport is not None: + await self._protocol._drain_helper() + + +def _safe_header(string: str) -> str: + if "\r" in string or "\n" in string: + raise ValueError( + "Newline or carriage return detected in headers. " + "Potential header injection attack." + ) + return string + + +def _py_serialize_headers(status_line: str, headers: "CIMultiDict[str]") -> bytes: + headers_gen = (_safe_header(k) + ": " + _safe_header(v) for k, v in headers.items()) + line = status_line + "\r\n" + "\r\n".join(headers_gen) + "\r\n\r\n" + return line.encode("utf-8") + + +_serialize_headers = _py_serialize_headers + +try: + import aiohttp._http_writer as _http_writer # type: ignore[import] + + _c_serialize_headers = _http_writer._serialize_headers + if not NO_EXTENSIONS: + _serialize_headers = _c_serialize_headers +except ImportError: + pass diff --git a/aiohttp/locks.py b/aiohttp/locks.py new file mode 100644 index 0000000..de2dc83 --- /dev/null +++ b/aiohttp/locks.py @@ -0,0 +1,41 @@ +import asyncio +import collections +from typing import Any, Deque, Optional + + +class EventResultOrError: + """Event asyncio lock helper class. + + Wraps the Event asyncio lock allowing either to awake the + locked Tasks without any error or raising an exception. + + thanks to @vorpalsmith for the simple design. + """ + + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: + self._loop = loop + self._exc: Optional[BaseException] = None + self._event = asyncio.Event() + self._waiters: Deque[asyncio.Future[Any]] = collections.deque() + + def set(self, exc: Optional[BaseException] = None) -> None: + self._exc = exc + self._event.set() + + async def wait(self) -> Any: + waiter = self._loop.create_task(self._event.wait()) + self._waiters.append(waiter) + try: + val = await waiter + finally: + self._waiters.remove(waiter) + + if self._exc is not None: + raise self._exc + + return val + + def cancel(self) -> None: + """Cancel all waiters""" + for waiter in self._waiters: + waiter.cancel() diff --git a/aiohttp/log.py b/aiohttp/log.py new file mode 100644 index 0000000..3cecea2 --- /dev/null +++ b/aiohttp/log.py @@ -0,0 +1,8 @@ +import logging + +access_logger = logging.getLogger("aiohttp.access") +client_logger = logging.getLogger("aiohttp.client") +internal_logger = logging.getLogger("aiohttp.internal") +server_logger = logging.getLogger("aiohttp.server") +web_logger = logging.getLogger("aiohttp.web") +ws_logger = logging.getLogger("aiohttp.websocket") diff --git a/aiohttp/multipart.py b/aiohttp/multipart.py new file mode 100644 index 0000000..dd56ce0 --- /dev/null +++ b/aiohttp/multipart.py @@ -0,0 +1,1031 @@ +import base64 +import binascii +import json +import re +import uuid +import warnings +import zlib +from collections import deque +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + AsyncIterator, + Deque, + Dict, + Iterator, + List, + Mapping, + Optional, + Sequence, + Tuple, + Type, + Union, + cast, +) +from urllib.parse import parse_qsl, unquote, urlencode + +from multidict import CIMultiDict, CIMultiDictProxy, MultiMapping + +from .compression_utils import ZLibCompressor, ZLibDecompressor +from .hdrs import ( + CONTENT_DISPOSITION, + CONTENT_ENCODING, + CONTENT_LENGTH, + CONTENT_TRANSFER_ENCODING, + CONTENT_TYPE, +) +from .helpers import CHAR, TOKEN, parse_mimetype, reify +from .http import HeadersParser +from .payload import ( + JsonPayload, + LookupError, + Order, + Payload, + StringPayload, + get_payload, + payload_type, +) +from .streams import StreamReader + +__all__ = ( + "MultipartReader", + "MultipartWriter", + "BodyPartReader", + "BadContentDispositionHeader", + "BadContentDispositionParam", + "parse_content_disposition", + "content_disposition_filename", +) + + +if TYPE_CHECKING: # pragma: no cover + from .client_reqrep import ClientResponse + + +class BadContentDispositionHeader(RuntimeWarning): + pass + + +class BadContentDispositionParam(RuntimeWarning): + pass + + +def parse_content_disposition( + header: Optional[str], +) -> Tuple[Optional[str], Dict[str, str]]: + def is_token(string: str) -> bool: + return bool(string) and TOKEN >= set(string) + + def is_quoted(string: str) -> bool: + return string[0] == string[-1] == '"' + + def is_rfc5987(string: str) -> bool: + return is_token(string) and string.count("'") == 2 + + def is_extended_param(string: str) -> bool: + return string.endswith("*") + + def is_continuous_param(string: str) -> bool: + pos = string.find("*") + 1 + if not pos: + return False + substring = string[pos:-1] if string.endswith("*") else string[pos:] + return substring.isdigit() + + def unescape(text: str, *, chars: str = "".join(map(re.escape, CHAR))) -> str: + return re.sub(f"\\\\([{chars}])", "\\1", text) + + if not header: + return None, {} + + disptype, *parts = header.split(";") + if not is_token(disptype): + warnings.warn(BadContentDispositionHeader(header)) + return None, {} + + params: Dict[str, str] = {} + while parts: + item = parts.pop(0) + + if "=" not in item: + warnings.warn(BadContentDispositionHeader(header)) + return None, {} + + key, value = item.split("=", 1) + key = key.lower().strip() + value = value.lstrip() + + if key in params: + warnings.warn(BadContentDispositionHeader(header)) + return None, {} + + if not is_token(key): + warnings.warn(BadContentDispositionParam(item)) + continue + + elif is_continuous_param(key): + if is_quoted(value): + value = unescape(value[1:-1]) + elif not is_token(value): + warnings.warn(BadContentDispositionParam(item)) + continue + + elif is_extended_param(key): + if is_rfc5987(value): + encoding, _, value = value.split("'", 2) + encoding = encoding or "utf-8" + else: + warnings.warn(BadContentDispositionParam(item)) + continue + + try: + value = unquote(value, encoding, "strict") + except UnicodeDecodeError: # pragma: nocover + warnings.warn(BadContentDispositionParam(item)) + continue + + else: + failed = True + if is_quoted(value): + failed = False + value = unescape(value[1:-1].lstrip("\\/")) + elif is_token(value): + failed = False + elif parts: + # maybe just ; in filename, in any case this is just + # one case fix, for proper fix we need to redesign parser + _value = f"{value};{parts[0]}" + if is_quoted(_value): + parts.pop(0) + value = unescape(_value[1:-1].lstrip("\\/")) + failed = False + + if failed: + warnings.warn(BadContentDispositionHeader(header)) + return None, {} + + params[key] = value + + return disptype.lower(), params + + +def content_disposition_filename( + params: Mapping[str, str], name: str = "filename" +) -> Optional[str]: + name_suf = "%s*" % name + if not params: + return None + elif name_suf in params: + return params[name_suf] + elif name in params: + return params[name] + else: + parts = [] + fnparams = sorted( + (key, value) for key, value in params.items() if key.startswith(name_suf) + ) + for num, (key, value) in enumerate(fnparams): + _, tail = key.split("*", 1) + if tail.endswith("*"): + tail = tail[:-1] + if tail == str(num): + parts.append(value) + else: + break + if not parts: + return None + value = "".join(parts) + if "'" in value: + encoding, _, value = value.split("'", 2) + encoding = encoding or "utf-8" + return unquote(value, encoding, "strict") + return value + + +class MultipartResponseWrapper: + """Wrapper around the MultipartReader. + + It takes care about + underlying connection and close it when it needs in. + """ + + def __init__( + self, + resp: "ClientResponse", + stream: "MultipartReader", + ) -> None: + self.resp = resp + self.stream = stream + + def __aiter__(self) -> "MultipartResponseWrapper": + return self + + async def __anext__( + self, + ) -> Union["MultipartReader", "BodyPartReader"]: + part = await self.next() + if part is None: + raise StopAsyncIteration + return part + + def at_eof(self) -> bool: + """Returns True when all response data had been read.""" + return self.resp.content.at_eof() + + async def next( + self, + ) -> Optional[Union["MultipartReader", "BodyPartReader"]]: + """Emits next multipart reader object.""" + item = await self.stream.next() + if self.stream.at_eof(): + await self.release() + return item + + async def release(self) -> None: + """Release the connection gracefully. + + All remaining content is read to the void. + """ + await self.resp.release() + + +class BodyPartReader: + """Multipart reader for single body part.""" + + chunk_size = 8192 + + def __init__( + self, + boundary: bytes, + headers: "CIMultiDictProxy[str]", + content: StreamReader, + *, + _newline: bytes = b"\r\n", + ) -> None: + self.headers = headers + self._boundary = boundary + self._newline = _newline + self._content = content + self._at_eof = False + length = self.headers.get(CONTENT_LENGTH, None) + self._length = int(length) if length is not None else None + self._read_bytes = 0 + self._unread: Deque[bytes] = deque() + self._prev_chunk: Optional[bytes] = None + self._content_eof = 0 + self._cache: Dict[str, Any] = {} + + def __aiter__(self) -> AsyncIterator["BodyPartReader"]: + return self # type: ignore[return-value] + + async def __anext__(self) -> bytes: + part = await self.next() + if part is None: + raise StopAsyncIteration + return part + + async def next(self) -> Optional[bytes]: + item = await self.read() + if not item: + return None + return item + + async def read(self, *, decode: bool = False) -> bytes: + """Reads body part data. + + decode: Decodes data following by encoding + method from Content-Encoding header. If it missed + data remains untouched + """ + if self._at_eof: + return b"" + data = bytearray() + while not self._at_eof: + data.extend(await self.read_chunk(self.chunk_size)) + if decode: + return self.decode(data) + return data + + async def read_chunk(self, size: int = chunk_size) -> bytes: + """Reads body part content chunk of the specified size. + + size: chunk size + """ + if self._at_eof: + return b"" + if self._length: + chunk = await self._read_chunk_from_length(size) + else: + chunk = await self._read_chunk_from_stream(size) + + # For the case of base64 data, we must read a fragment of size with a + # remainder of 0 by dividing by 4 for string without symbols \n or \r + encoding = self.headers.get(CONTENT_TRANSFER_ENCODING) + if encoding and encoding.lower() == "base64": + stripped_chunk = b"".join(chunk.split()) + remainder = len(stripped_chunk) % 4 + + while remainder != 0 and not self.at_eof(): + over_chunk_size = 4 - remainder + over_chunk = b"" + + if self._prev_chunk: + over_chunk = self._prev_chunk[:over_chunk_size] + self._prev_chunk = self._prev_chunk[len(over_chunk) :] + + if len(over_chunk) != over_chunk_size: + over_chunk += await self._content.read(4 - len(over_chunk)) + + if not over_chunk: + self._at_eof = True + + stripped_chunk += b"".join(over_chunk.split()) + chunk += over_chunk + remainder = len(stripped_chunk) % 4 + + self._read_bytes += len(chunk) + if self._read_bytes == self._length: + self._at_eof = True + if self._at_eof: + newline = await self._content.readline() + assert ( + newline == self._newline + ), "reader did not read all the data or it is malformed" + return chunk + + async def _read_chunk_from_length(self, size: int) -> bytes: + # Reads body part content chunk of the specified size. + # The body part must has Content-Length header with proper value. + assert self._length is not None, "Content-Length required for chunked read" + chunk_size = min(size, self._length - self._read_bytes) + chunk = await self._content.read(chunk_size) + return chunk + + async def _read_chunk_from_stream(self, size: int) -> bytes: + # Reads content chunk of body part with unknown length. + # The Content-Length header for body part is not necessary. + assert ( + size >= len(self._boundary) + 2 + ), "Chunk size must be greater or equal than boundary length + 2" + first_chunk = self._prev_chunk is None + if first_chunk: + self._prev_chunk = await self._content.read(size) + + chunk = await self._content.read(size) + self._content_eof += int(self._content.at_eof()) + assert self._content_eof < 3, "Reading after EOF" + assert self._prev_chunk is not None + window = self._prev_chunk + chunk + + intermeditate_boundary = self._newline + self._boundary + + if first_chunk: + pos = 0 + else: + pos = max(0, len(self._prev_chunk) - len(intermeditate_boundary)) + + idx = window.find(intermeditate_boundary, pos) + if idx >= 0: + # pushing boundary back to content + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + self._content.unread_data(window[idx:]) + if size > idx: + self._prev_chunk = self._prev_chunk[:idx] + chunk = window[len(self._prev_chunk) : idx] + if not chunk: + self._at_eof = True + + result = self._prev_chunk + self._prev_chunk = chunk + return result + + async def readline(self) -> bytes: + """Reads body part by line by line.""" + if self._at_eof: + return b"" + + if self._unread: + line = self._unread.popleft() + else: + line = await self._content.readline() + + if line.startswith(self._boundary): + # the very last boundary may not come with \r\n, + # so set single rules for everyone + sline = line.rstrip(b"\r\n") + boundary = self._boundary + last_boundary = self._boundary + b"--" + # ensure that we read exactly the boundary, not something alike + if sline == boundary or sline == last_boundary: + self._at_eof = True + self._unread.append(line) + return b"" + else: + next_line = await self._content.readline() + if next_line.startswith(self._boundary): + # strip newline but only once + line = line[: -len(self._newline)] + self._unread.append(next_line) + + return line + + async def release(self) -> None: + """Like read(), but reads all the data to the void.""" + if self._at_eof: + return + while not self._at_eof: + await self.read_chunk(self.chunk_size) + + async def text(self, *, encoding: Optional[str] = None) -> str: + """Like read(), but assumes that body part contains text data.""" + data = await self.read(decode=True) + # see https://www.w3.org/TR/html5/forms.html#multipart/form-data-encoding-algorithm + # and https://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-send + encoding = encoding or self.get_charset(default="utf-8") + return data.decode(encoding) + + async def json(self, *, encoding: Optional[str] = None) -> Optional[Dict[str, Any]]: + """Like read(), but assumes that body parts contains JSON data.""" + data = await self.read(decode=True) + if not data: + return None + encoding = encoding or self.get_charset(default="utf-8") + return cast(Dict[str, Any], json.loads(data.decode(encoding))) + + async def form(self, *, encoding: Optional[str] = None) -> List[Tuple[str, str]]: + """Like read(), but assumes that body parts contain form urlencoded data.""" + data = await self.read(decode=True) + if not data: + return [] + if encoding is not None: + real_encoding = encoding + else: + real_encoding = self.get_charset(default="utf-8") + try: + decoded_data = data.rstrip().decode(real_encoding) + except UnicodeDecodeError: + raise ValueError("data cannot be decoded with %s encoding" % real_encoding) + + return parse_qsl( + decoded_data, + keep_blank_values=True, + encoding=real_encoding, + ) + + def at_eof(self) -> bool: + """Returns True if the boundary was reached or False otherwise.""" + return self._at_eof + + def decode(self, data: bytes) -> bytes: + """Decodes data. + + Decoding is done according the specified Content-Encoding + or Content-Transfer-Encoding headers value. + """ + if CONTENT_TRANSFER_ENCODING in self.headers: + data = self._decode_content_transfer(data) + if CONTENT_ENCODING in self.headers: + return self._decode_content(data) + return data + + def _decode_content(self, data: bytes) -> bytes: + encoding = self.headers.get(CONTENT_ENCODING, "").lower() + if encoding == "identity": + return data + if encoding in {"deflate", "gzip"}: + return ZLibDecompressor( + encoding=encoding, + suppress_deflate_header=True, + ).decompress_sync(data) + + raise RuntimeError(f"unknown content encoding: {encoding}") + + def _decode_content_transfer(self, data: bytes) -> bytes: + encoding = self.headers.get(CONTENT_TRANSFER_ENCODING, "").lower() + + if encoding == "base64": + return base64.b64decode(data) + elif encoding == "quoted-printable": + return binascii.a2b_qp(data) + elif encoding in ("binary", "8bit", "7bit"): + return data + else: + raise RuntimeError( + "unknown content transfer encoding: {}" "".format(encoding) + ) + + def get_charset(self, default: str) -> str: + """Returns charset parameter from Content-Type header or default.""" + ctype = self.headers.get(CONTENT_TYPE, "") + mimetype = parse_mimetype(ctype) + return mimetype.parameters.get("charset", default) + + @reify + def name(self) -> Optional[str]: + """Returns name specified in Content-Disposition header. + + If the header is missing or malformed, returns None. + """ + _, params = parse_content_disposition(self.headers.get(CONTENT_DISPOSITION)) + return content_disposition_filename(params, "name") + + @reify + def filename(self) -> Optional[str]: + """Returns filename specified in Content-Disposition header. + + Returns None if the header is missing or malformed. + """ + _, params = parse_content_disposition(self.headers.get(CONTENT_DISPOSITION)) + return content_disposition_filename(params, "filename") + + +@payload_type(BodyPartReader, order=Order.try_first) +class BodyPartReaderPayload(Payload): + def __init__(self, value: BodyPartReader, *args: Any, **kwargs: Any) -> None: + super().__init__(value, *args, **kwargs) + + params: Dict[str, str] = {} + if value.name is not None: + params["name"] = value.name + if value.filename is not None: + params["filename"] = value.filename + + if params: + self.set_content_disposition("attachment", True, **params) + + async def write(self, writer: Any) -> None: + field = self._value + chunk = await field.read_chunk(size=2**16) + while chunk: + await writer.write(field.decode(chunk)) + chunk = await field.read_chunk(size=2**16) + + +class MultipartReader: + """Multipart body reader.""" + + #: Response wrapper, used when multipart readers constructs from response. + response_wrapper_cls = MultipartResponseWrapper + #: Multipart reader class, used to handle multipart/* body parts. + #: None points to type(self) + multipart_reader_cls = None + #: Body part reader class for non multipart/* content types. + part_reader_cls = BodyPartReader + + def __init__( + self, + headers: Mapping[str, str], + content: StreamReader, + *, + _newline: bytes = b"\r\n", + ) -> None: + self.headers = headers + self._boundary = ("--" + self._get_boundary()).encode() + self._newline = _newline + self._content = content + self._last_part: Optional[Union["MultipartReader", BodyPartReader]] = None + self._at_eof = False + self._at_bof = True + self._unread: List[bytes] = [] + + def __aiter__( + self, + ) -> AsyncIterator["BodyPartReader"]: + return self # type: ignore[return-value] + + async def __anext__( + self, + ) -> Optional[Union["MultipartReader", BodyPartReader]]: + part = await self.next() + if part is None: + raise StopAsyncIteration + return part + + @classmethod + def from_response( + cls, + response: "ClientResponse", + ) -> MultipartResponseWrapper: + """Constructs reader instance from HTTP response. + + :param response: :class:`~aiohttp.client.ClientResponse` instance + """ + obj = cls.response_wrapper_cls( + response, cls(response.headers, response.content) + ) + return obj + + def at_eof(self) -> bool: + """Returns True if the final boundary was reached, false otherwise.""" + return self._at_eof + + async def next( + self, + ) -> Optional[Union["MultipartReader", BodyPartReader]]: + """Emits the next multipart body part.""" + # So, if we're at BOF, we need to skip till the boundary. + if self._at_eof: + return None + await self._maybe_release_last_part() + if self._at_bof: + await self._read_until_first_boundary() + self._at_bof = False + else: + await self._read_boundary() + if self._at_eof: # we just read the last boundary, nothing to do there + return None + self._last_part = await self.fetch_next_part() + return self._last_part + + async def release(self) -> None: + """Reads all the body parts to the void till the final boundary.""" + while not self._at_eof: + item = await self.next() + if item is None: + break + await item.release() + + async def fetch_next_part( + self, + ) -> Union["MultipartReader", BodyPartReader]: + """Returns the next body part reader.""" + headers = await self._read_headers() + return self._get_part_reader(headers) + + def _get_part_reader( + self, + headers: "CIMultiDictProxy[str]", + ) -> Union["MultipartReader", BodyPartReader]: + """Dispatches the response by the `Content-Type` header. + + Returns a suitable reader instance. + + :param dict headers: Response headers + """ + ctype = headers.get(CONTENT_TYPE, "") + mimetype = parse_mimetype(ctype) + + if mimetype.type == "multipart": + if self.multipart_reader_cls is None: + return type(self)(headers, self._content) + return self.multipart_reader_cls( + headers, self._content, _newline=self._newline + ) + else: + return self.part_reader_cls( + self._boundary, headers, self._content, _newline=self._newline + ) + + def _get_boundary(self) -> str: + mimetype = parse_mimetype(self.headers[CONTENT_TYPE]) + + assert mimetype.type == "multipart", "multipart/* content type expected" + + if "boundary" not in mimetype.parameters: + raise ValueError( + "boundary missed for Content-Type: %s" % self.headers[CONTENT_TYPE] + ) + + boundary = mimetype.parameters["boundary"] + if len(boundary) > 70: + raise ValueError("boundary %r is too long (70 chars max)" % boundary) + + return boundary + + async def _readline(self) -> bytes: + if self._unread: + return self._unread.pop() + return await self._content.readline() + + async def _read_until_first_boundary(self) -> None: + while True: + chunk = await self._readline() + if chunk == b"": + raise ValueError( + "Could not find starting boundary %r" % (self._boundary) + ) + newline = None + end_boundary = self._boundary + b"--" + if chunk.startswith(end_boundary): + _, newline = chunk.split(end_boundary, 1) + elif chunk.startswith(self._boundary): + _, newline = chunk.split(self._boundary, 1) + if newline is not None: + assert newline in (b"\r\n", b"\n"), (newline, chunk, self._boundary) + self._newline = newline + + chunk = chunk.rstrip() + if chunk == self._boundary: + return + elif chunk == end_boundary: + self._at_eof = True + return + + async def _read_boundary(self) -> None: + chunk = (await self._readline()).rstrip() + if chunk == self._boundary: + pass + elif chunk == self._boundary + b"--": + self._at_eof = True + epilogue = await self._readline() + next_line = await self._readline() + + # the epilogue is expected and then either the end of input or the + # parent multipart boundary, if the parent boundary is found then + # it should be marked as unread and handed to the parent for + # processing + if next_line[:2] == b"--": + self._unread.append(next_line) + # otherwise the request is likely missing an epilogue and both + # lines should be passed to the parent for processing + # (this handles the old behavior gracefully) + else: + self._unread.extend([next_line, epilogue]) + else: + raise ValueError(f"Invalid boundary {chunk!r}, expected {self._boundary!r}") + + async def _read_headers(self) -> "CIMultiDictProxy[str]": + lines = [b""] + while True: + chunk = await self._content.readline() + chunk = chunk.strip() + lines.append(chunk) + if not chunk: + break + parser = HeadersParser() + headers, raw_headers = parser.parse_headers(lines) + return headers + + async def _maybe_release_last_part(self) -> None: + """Ensures that the last read body part is read completely.""" + if self._last_part is not None: + if not self._last_part.at_eof(): + await self._last_part.release() + self._unread.extend(self._last_part._unread) + self._last_part = None + + +_Part = Tuple[Payload, str, str] + + +class MultipartWriter(Payload): + """Multipart body writer.""" + + def __init__(self, subtype: str = "mixed", boundary: Optional[str] = None) -> None: + boundary = boundary if boundary is not None else uuid.uuid4().hex + # The underlying Payload API demands a str (utf-8), not bytes, + # so we need to ensure we don't lose anything during conversion. + # As a result, require the boundary to be ASCII only. + # In both situations. + + try: + self._boundary = boundary.encode("ascii") + except UnicodeEncodeError: + raise ValueError("boundary should contain ASCII only chars") from None + + if len(boundary) > 70: + raise ValueError("boundary %r is too long (70 chars max)" % boundary) + + ctype = f"multipart/{subtype}; boundary={self._boundary_value}" + + super().__init__(None, content_type=ctype) + + self._parts: List[_Part] = [] + + def __enter__(self) -> "MultipartWriter": + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + pass + + def __iter__(self) -> Iterator[_Part]: + return iter(self._parts) + + def __len__(self) -> int: + return len(self._parts) + + def __bool__(self) -> bool: + return True + + _valid_tchar_regex = re.compile(rb"\A[!#$%&'*+\-.^_`|~\w]+\Z") + _invalid_qdtext_char_regex = re.compile(rb"[\x00-\x08\x0A-\x1F\x7F]") + + @property + def _boundary_value(self) -> str: + """Wrap boundary parameter value in quotes, if necessary. + + Reads self.boundary and returns a unicode string. + """ + # Refer to RFCs 7231, 7230, 5234. + # + # parameter = token "=" ( token / quoted-string ) + # token = 1*tchar + # quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE + # qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text + # obs-text = %x80-FF + # quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + # tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" + # / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" + # / DIGIT / ALPHA + # ; any VCHAR, except delimiters + # VCHAR = %x21-7E + value = self._boundary + if re.match(self._valid_tchar_regex, value): + return value.decode("ascii") # cannot fail + + if re.search(self._invalid_qdtext_char_regex, value): + raise ValueError("boundary value contains invalid characters") + + # escape %x5C and %x22 + quoted_value_content = value.replace(b"\\", b"\\\\") + quoted_value_content = quoted_value_content.replace(b'"', b'\\"') + + return '"' + quoted_value_content.decode("ascii") + '"' + + @property + def boundary(self) -> str: + return self._boundary.decode("ascii") + + def append(self, obj: Any, headers: Optional[MultiMapping[str]] = None) -> Payload: + if headers is None: + headers = CIMultiDict() + + if isinstance(obj, Payload): + obj.headers.update(headers) + return self.append_payload(obj) + else: + try: + payload = get_payload(obj, headers=headers) + except LookupError: + raise TypeError("Cannot create payload from %r" % obj) + else: + return self.append_payload(payload) + + def append_payload(self, payload: Payload) -> Payload: + """Adds a new body part to multipart writer.""" + # compression + encoding: Optional[str] = payload.headers.get( + CONTENT_ENCODING, + "", + ).lower() + if encoding and encoding not in ("deflate", "gzip", "identity"): + raise RuntimeError(f"unknown content encoding: {encoding}") + if encoding == "identity": + encoding = None + + # te encoding + te_encoding: Optional[str] = payload.headers.get( + CONTENT_TRANSFER_ENCODING, + "", + ).lower() + if te_encoding not in ("", "base64", "quoted-printable", "binary"): + raise RuntimeError( + "unknown content transfer encoding: {}" "".format(te_encoding) + ) + if te_encoding == "binary": + te_encoding = None + + # size + size = payload.size + if size is not None and not (encoding or te_encoding): + payload.headers[CONTENT_LENGTH] = str(size) + + self._parts.append((payload, encoding, te_encoding)) # type: ignore[arg-type] + return payload + + def append_json( + self, obj: Any, headers: Optional[MultiMapping[str]] = None + ) -> Payload: + """Helper to append JSON part.""" + if headers is None: + headers = CIMultiDict() + + return self.append_payload(JsonPayload(obj, headers=headers)) + + def append_form( + self, + obj: Union[Sequence[Tuple[str, str]], Mapping[str, str]], + headers: Optional[MultiMapping[str]] = None, + ) -> Payload: + """Helper to append form urlencoded part.""" + assert isinstance(obj, (Sequence, Mapping)) + + if headers is None: + headers = CIMultiDict() + + if isinstance(obj, Mapping): + obj = list(obj.items()) + data = urlencode(obj, doseq=True) + + return self.append_payload( + StringPayload( + data, headers=headers, content_type="application/x-www-form-urlencoded" + ) + ) + + @property + def size(self) -> Optional[int]: + """Size of the payload.""" + total = 0 + for part, encoding, te_encoding in self._parts: + if encoding or te_encoding or part.size is None: + return None + + total += int( + 2 + + len(self._boundary) + + 2 + + part.size # b'--'+self._boundary+b'\r\n' + + len(part._binary_headers) + + 2 # b'\r\n' + ) + + total += 2 + len(self._boundary) + 4 # b'--'+self._boundary+b'--\r\n' + return total + + async def write(self, writer: Any, close_boundary: bool = True) -> None: + """Write body.""" + for part, encoding, te_encoding in self._parts: + await writer.write(b"--" + self._boundary + b"\r\n") + await writer.write(part._binary_headers) + + if encoding or te_encoding: + w = MultipartPayloadWriter(writer) + if encoding: + w.enable_compression(encoding) + if te_encoding: + w.enable_encoding(te_encoding) + await part.write(w) # type: ignore[arg-type] + await w.write_eof() + else: + await part.write(writer) + + await writer.write(b"\r\n") + + if close_boundary: + await writer.write(b"--" + self._boundary + b"--\r\n") + + +class MultipartPayloadWriter: + def __init__(self, writer: Any) -> None: + self._writer = writer + self._encoding: Optional[str] = None + self._compress: Optional[ZLibCompressor] = None + self._encoding_buffer: Optional[bytearray] = None + + def enable_encoding(self, encoding: str) -> None: + if encoding == "base64": + self._encoding = encoding + self._encoding_buffer = bytearray() + elif encoding == "quoted-printable": + self._encoding = "quoted-printable" + + def enable_compression( + self, encoding: str = "deflate", strategy: int = zlib.Z_DEFAULT_STRATEGY + ) -> None: + self._compress = ZLibCompressor( + encoding=encoding, + suppress_deflate_header=True, + strategy=strategy, + ) + + async def write_eof(self) -> None: + if self._compress is not None: + chunk = self._compress.flush() + if chunk: + self._compress = None + await self.write(chunk) + + if self._encoding == "base64": + if self._encoding_buffer: + await self._writer.write(base64.b64encode(self._encoding_buffer)) + + async def write(self, chunk: bytes) -> None: + if self._compress is not None: + if chunk: + chunk = await self._compress.compress(chunk) + if not chunk: + return + + if self._encoding == "base64": + buf = self._encoding_buffer + assert buf is not None + buf.extend(chunk) + + if buf: + div, mod = divmod(len(buf), 3) + enc_chunk, self._encoding_buffer = (buf[: div * 3], buf[div * 3 :]) + if enc_chunk: + b64chunk = base64.b64encode(enc_chunk) + await self._writer.write(b64chunk) + elif self._encoding == "quoted-printable": + await self._writer.write(binascii.b2a_qp(chunk)) + else: + await self._writer.write(chunk) diff --git a/aiohttp/payload.py b/aiohttp/payload.py new file mode 100644 index 0000000..6b4a795 --- /dev/null +++ b/aiohttp/payload.py @@ -0,0 +1,458 @@ +import asyncio +import enum +import io +import json +import mimetypes +import os +import warnings +from abc import ABC, abstractmethod +from itertools import chain +from typing import ( + IO, + TYPE_CHECKING, + Any, + ByteString, + Dict, + Final, + Iterable, + Optional, + TextIO, + Tuple, + Type, + Union, +) + +from multidict import CIMultiDict + +from . import hdrs +from .abc import AbstractStreamWriter +from .helpers import ( + _SENTINEL, + content_disposition_header, + guess_filename, + parse_mimetype, + sentinel, +) +from .streams import StreamReader +from .typedefs import JSONEncoder, _CIMultiDict + +__all__ = ( + "PAYLOAD_REGISTRY", + "get_payload", + "payload_type", + "Payload", + "BytesPayload", + "StringPayload", + "IOBasePayload", + "BytesIOPayload", + "BufferedReaderPayload", + "TextIOPayload", + "StringIOPayload", + "JsonPayload", + "AsyncIterablePayload", +) + +TOO_LARGE_BYTES_BODY: Final[int] = 2**20 # 1 MB + +if TYPE_CHECKING: # pragma: no cover + from typing import List + + +class LookupError(Exception): + pass + + +class Order(str, enum.Enum): + normal = "normal" + try_first = "try_first" + try_last = "try_last" + + +def get_payload(data: Any, *args: Any, **kwargs: Any) -> "Payload": + return PAYLOAD_REGISTRY.get(data, *args, **kwargs) + + +def register_payload( + factory: Type["Payload"], type: Any, *, order: Order = Order.normal +) -> None: + PAYLOAD_REGISTRY.register(factory, type, order=order) + + +class payload_type: + def __init__(self, type: Any, *, order: Order = Order.normal) -> None: + self.type = type + self.order = order + + def __call__(self, factory: Type["Payload"]) -> Type["Payload"]: + register_payload(factory, self.type, order=self.order) + return factory + + +PayloadType = Type["Payload"] +_PayloadRegistryItem = Tuple[PayloadType, Any] + + +class PayloadRegistry: + """Payload registry. + + note: we need zope.interface for more efficient adapter search + """ + + def __init__(self) -> None: + self._first: List[_PayloadRegistryItem] = [] + self._normal: List[_PayloadRegistryItem] = [] + self._last: List[_PayloadRegistryItem] = [] + + def get( + self, + data: Any, + *args: Any, + _CHAIN: "Type[chain[_PayloadRegistryItem]]" = chain, + **kwargs: Any, + ) -> "Payload": + if isinstance(data, Payload): + return data + for factory, type in _CHAIN(self._first, self._normal, self._last): + if isinstance(data, type): + return factory(data, *args, **kwargs) + + raise LookupError() + + def register( + self, factory: PayloadType, type: Any, *, order: Order = Order.normal + ) -> None: + if order is Order.try_first: + self._first.append((factory, type)) + elif order is Order.normal: + self._normal.append((factory, type)) + elif order is Order.try_last: + self._last.append((factory, type)) + else: + raise ValueError(f"Unsupported order {order!r}") + + +class Payload(ABC): + _default_content_type: str = "application/octet-stream" + _size: Optional[int] = None + + def __init__( + self, + value: Any, + headers: Optional[ + Union[_CIMultiDict, Dict[str, str], Iterable[Tuple[str, str]]] + ] = None, + content_type: Union[None, str, _SENTINEL] = sentinel, + filename: Optional[str] = None, + encoding: Optional[str] = None, + **kwargs: Any, + ) -> None: + self._encoding = encoding + self._filename = filename + self._headers: _CIMultiDict = CIMultiDict() + self._value = value + if content_type is not sentinel and content_type is not None: + assert isinstance(content_type, str) + self._headers[hdrs.CONTENT_TYPE] = content_type + elif self._filename is not None: + content_type = mimetypes.guess_type(self._filename)[0] + if content_type is None: + content_type = self._default_content_type + self._headers[hdrs.CONTENT_TYPE] = content_type + else: + self._headers[hdrs.CONTENT_TYPE] = self._default_content_type + self._headers.update(headers or {}) + + @property + def size(self) -> Optional[int]: + """Size of the payload.""" + return self._size + + @property + def filename(self) -> Optional[str]: + """Filename of the payload.""" + return self._filename + + @property + def headers(self) -> _CIMultiDict: + """Custom item headers""" + return self._headers + + @property + def _binary_headers(self) -> bytes: + return ( + "".join([k + ": " + v + "\r\n" for k, v in self.headers.items()]).encode( + "utf-8" + ) + + b"\r\n" + ) + + @property + def encoding(self) -> Optional[str]: + """Payload encoding""" + return self._encoding + + @property + def content_type(self) -> str: + """Content type""" + return self._headers[hdrs.CONTENT_TYPE] + + def set_content_disposition( + self, + disptype: str, + quote_fields: bool = True, + _charset: str = "utf-8", + **params: Any, + ) -> None: + """Sets ``Content-Disposition`` header.""" + self._headers[hdrs.CONTENT_DISPOSITION] = content_disposition_header( + disptype, quote_fields=quote_fields, _charset=_charset, **params + ) + + @abstractmethod + async def write(self, writer: AbstractStreamWriter) -> None: + """Write payload. + + writer is an AbstractStreamWriter instance: + """ + + +class BytesPayload(Payload): + def __init__(self, value: ByteString, *args: Any, **kwargs: Any) -> None: + if not isinstance(value, (bytes, bytearray, memoryview)): + raise TypeError(f"value argument must be byte-ish, not {type(value)!r}") + + if "content_type" not in kwargs: + kwargs["content_type"] = "application/octet-stream" + + super().__init__(value, *args, **kwargs) + + if isinstance(value, memoryview): + self._size = value.nbytes + else: + self._size = len(value) + + if self._size > TOO_LARGE_BYTES_BODY: + warnings.warn( + "Sending a large body directly with raw bytes might" + " lock the event loop. You should probably pass an " + "io.BytesIO object instead", + ResourceWarning, + source=self, + ) + + async def write(self, writer: AbstractStreamWriter) -> None: + await writer.write(self._value) + + +class StringPayload(BytesPayload): + def __init__( + self, + value: str, + *args: Any, + encoding: Optional[str] = None, + content_type: Optional[str] = None, + **kwargs: Any, + ) -> None: + if encoding is None: + if content_type is None: + real_encoding = "utf-8" + content_type = "text/plain; charset=utf-8" + else: + mimetype = parse_mimetype(content_type) + real_encoding = mimetype.parameters.get("charset", "utf-8") + else: + if content_type is None: + content_type = "text/plain; charset=%s" % encoding + real_encoding = encoding + + super().__init__( + value.encode(real_encoding), + encoding=real_encoding, + content_type=content_type, + *args, + **kwargs, + ) + + +class StringIOPayload(StringPayload): + def __init__(self, value: IO[str], *args: Any, **kwargs: Any) -> None: + super().__init__(value.read(), *args, **kwargs) + + +class IOBasePayload(Payload): + _value: IO[Any] + + def __init__( + self, value: IO[Any], disposition: str = "attachment", *args: Any, **kwargs: Any + ) -> None: + if "filename" not in kwargs: + kwargs["filename"] = guess_filename(value) + + super().__init__(value, *args, **kwargs) + + if self._filename is not None and disposition is not None: + if hdrs.CONTENT_DISPOSITION not in self.headers: + self.set_content_disposition(disposition, filename=self._filename) + + async def write(self, writer: AbstractStreamWriter) -> None: + loop = asyncio.get_event_loop() + try: + chunk = await loop.run_in_executor(None, self._value.read, 2**16) + while chunk: + await writer.write(chunk) + chunk = await loop.run_in_executor(None, self._value.read, 2**16) + finally: + await loop.run_in_executor(None, self._value.close) + + +class TextIOPayload(IOBasePayload): + _value: TextIO + + def __init__( + self, + value: TextIO, + *args: Any, + encoding: Optional[str] = None, + content_type: Optional[str] = None, + **kwargs: Any, + ) -> None: + if encoding is None: + if content_type is None: + encoding = "utf-8" + content_type = "text/plain; charset=utf-8" + else: + mimetype = parse_mimetype(content_type) + encoding = mimetype.parameters.get("charset", "utf-8") + else: + if content_type is None: + content_type = "text/plain; charset=%s" % encoding + + super().__init__( + value, + content_type=content_type, + encoding=encoding, + *args, + **kwargs, + ) + + @property + def size(self) -> Optional[int]: + try: + return os.fstat(self._value.fileno()).st_size - self._value.tell() + except OSError: + return None + + async def write(self, writer: AbstractStreamWriter) -> None: + loop = asyncio.get_event_loop() + try: + chunk = await loop.run_in_executor(None, self._value.read, 2**16) + while chunk: + data = ( + chunk.encode(encoding=self._encoding) + if self._encoding + else chunk.encode() + ) + await writer.write(data) + chunk = await loop.run_in_executor(None, self._value.read, 2**16) + finally: + await loop.run_in_executor(None, self._value.close) + + +class BytesIOPayload(IOBasePayload): + @property + def size(self) -> int: + position = self._value.tell() + end = self._value.seek(0, os.SEEK_END) + self._value.seek(position) + return end - position + + +class BufferedReaderPayload(IOBasePayload): + @property + def size(self) -> Optional[int]: + try: + return os.fstat(self._value.fileno()).st_size - self._value.tell() + except OSError: + # data.fileno() is not supported, e.g. + # io.BufferedReader(io.BytesIO(b'data')) + return None + + +class JsonPayload(BytesPayload): + def __init__( + self, + value: Any, + encoding: str = "utf-8", + content_type: str = "application/json", + dumps: JSONEncoder = json.dumps, + *args: Any, + **kwargs: Any, + ) -> None: + super().__init__( + dumps(value).encode(encoding), + content_type=content_type, + encoding=encoding, + *args, + **kwargs, + ) + + +if TYPE_CHECKING: # pragma: no cover + from typing import AsyncIterable, AsyncIterator + + _AsyncIterator = AsyncIterator[bytes] + _AsyncIterable = AsyncIterable[bytes] +else: + from collections.abc import AsyncIterable, AsyncIterator + + _AsyncIterator = AsyncIterator + _AsyncIterable = AsyncIterable + + +class AsyncIterablePayload(Payload): + _iter: Optional[_AsyncIterator] = None + + def __init__(self, value: _AsyncIterable, *args: Any, **kwargs: Any) -> None: + if not isinstance(value, AsyncIterable): + raise TypeError( + "value argument must support " + "collections.abc.AsyncIterable interface, " + "got {!r}".format(type(value)) + ) + + if "content_type" not in kwargs: + kwargs["content_type"] = "application/octet-stream" + + super().__init__(value, *args, **kwargs) + + self._iter = value.__aiter__() + + async def write(self, writer: AbstractStreamWriter) -> None: + if self._iter: + try: + # iter is not None check prevents rare cases + # when the case iterable is used twice + while True: + chunk = await self._iter.__anext__() + await writer.write(chunk) + except StopAsyncIteration: + self._iter = None + + +class StreamReaderPayload(AsyncIterablePayload): + def __init__(self, value: StreamReader, *args: Any, **kwargs: Any) -> None: + super().__init__(value.iter_any(), *args, **kwargs) + + +PAYLOAD_REGISTRY = PayloadRegistry() +PAYLOAD_REGISTRY.register(BytesPayload, (bytes, bytearray, memoryview)) +PAYLOAD_REGISTRY.register(StringPayload, str) +PAYLOAD_REGISTRY.register(StringIOPayload, io.StringIO) +PAYLOAD_REGISTRY.register(TextIOPayload, io.TextIOBase) +PAYLOAD_REGISTRY.register(BytesIOPayload, io.BytesIO) +PAYLOAD_REGISTRY.register(BufferedReaderPayload, (io.BufferedReader, io.BufferedRandom)) +PAYLOAD_REGISTRY.register(IOBasePayload, io.IOBase) +PAYLOAD_REGISTRY.register(StreamReaderPayload, StreamReader) +# try_last for giving a chance to more specialized async interables like +# multidict.BodyPartReaderPayload override the default +PAYLOAD_REGISTRY.register(AsyncIterablePayload, AsyncIterable, order=Order.try_last) diff --git a/aiohttp/py.typed b/aiohttp/py.typed new file mode 100644 index 0000000..f5642f7 --- /dev/null +++ b/aiohttp/py.typed @@ -0,0 +1 @@ +Marker diff --git a/aiohttp/pytest_plugin.py b/aiohttp/pytest_plugin.py new file mode 100644 index 0000000..8bbe46f --- /dev/null +++ b/aiohttp/pytest_plugin.py @@ -0,0 +1,363 @@ +import asyncio +import contextlib +import inspect +import warnings +from typing import Any, Awaitable, Callable, Dict, Iterator, Optional, Type, Union + +import pytest + +from aiohttp.web import Application + +from .test_utils import ( + BaseTestServer, + RawTestServer, + TestClient, + TestServer, + loop_context, + setup_test_loop, + teardown_test_loop, + unused_port as _unused_port, +) + +try: + import uvloop +except ImportError: # pragma: no cover + uvloop = None # type: ignore[assignment] + +AiohttpClient = Callable[[Union[Application, BaseTestServer]], Awaitable[TestClient]] +AiohttpRawServer = Callable[[Application], Awaitable[RawTestServer]] +AiohttpServer = Callable[[Application], Awaitable[TestServer]] + + +def pytest_addoption(parser): # type: ignore[no-untyped-def] + parser.addoption( + "--aiohttp-fast", + action="store_true", + default=False, + help="run tests faster by disabling extra checks", + ) + parser.addoption( + "--aiohttp-loop", + action="store", + default="pyloop", + help="run tests with specific loop: pyloop, uvloop or all", + ) + parser.addoption( + "--aiohttp-enable-loop-debug", + action="store_true", + default=False, + help="enable event loop debug mode", + ) + + +def pytest_fixture_setup(fixturedef): # type: ignore[no-untyped-def] + """Set up pytest fixture. + + Allow fixtures to be coroutines. Run coroutine fixtures in an event loop. + """ + func = fixturedef.func + + if inspect.isasyncgenfunction(func): + # async generator fixture + is_async_gen = True + elif asyncio.iscoroutinefunction(func): + # regular async fixture + is_async_gen = False + else: + # not an async fixture, nothing to do + return + + strip_request = False + if "request" not in fixturedef.argnames: + fixturedef.argnames += ("request",) + strip_request = True + + def wrapper(*args, **kwargs): # type: ignore[no-untyped-def] + request = kwargs["request"] + if strip_request: + del kwargs["request"] + + # if neither the fixture nor the test use the 'loop' fixture, + # 'getfixturevalue' will fail because the test is not parameterized + # (this can be removed someday if 'loop' is no longer parameterized) + if "loop" not in request.fixturenames: + raise Exception( + "Asynchronous fixtures must depend on the 'loop' fixture or " + "be used in tests depending from it." + ) + + _loop = request.getfixturevalue("loop") + + if is_async_gen: + # for async generators, we need to advance the generator once, + # then advance it again in a finalizer + gen = func(*args, **kwargs) + + def finalizer(): # type: ignore[no-untyped-def] + try: + return _loop.run_until_complete(gen.__anext__()) + except StopAsyncIteration: + pass + + request.addfinalizer(finalizer) + return _loop.run_until_complete(gen.__anext__()) + else: + return _loop.run_until_complete(func(*args, **kwargs)) + + fixturedef.func = wrapper + + +@pytest.fixture +def fast(request): # type: ignore[no-untyped-def] + """--fast config option""" + return request.config.getoption("--aiohttp-fast") + + +@pytest.fixture +def loop_debug(request): # type: ignore[no-untyped-def] + """--enable-loop-debug config option""" + return request.config.getoption("--aiohttp-enable-loop-debug") + + +@contextlib.contextmanager +def _runtime_warning_context(): # type: ignore[no-untyped-def] + """Context manager which checks for RuntimeWarnings. + + This exists specifically to + avoid "coroutine 'X' was never awaited" warnings being missed. + + If RuntimeWarnings occur in the context a RuntimeError is raised. + """ + with warnings.catch_warnings(record=True) as _warnings: + yield + rw = [ + "{w.filename}:{w.lineno}:{w.message}".format(w=w) + for w in _warnings + if w.category == RuntimeWarning + ] + if rw: + raise RuntimeError( + "{} Runtime Warning{},\n{}".format( + len(rw), "" if len(rw) == 1 else "s", "\n".join(rw) + ) + ) + + # Propagate warnings to pytest + for msg in _warnings: + warnings.showwarning( + msg.message, msg.category, msg.filename, msg.lineno, msg.file, msg.line + ) + + +@contextlib.contextmanager +def _passthrough_loop_context(loop, fast=False): # type: ignore[no-untyped-def] + """Passthrough loop context. + + Sets up and tears down a loop unless one is passed in via the loop + argument when it's passed straight through. + """ + if loop: + # loop already exists, pass it straight through + yield loop + else: + # this shadows loop_context's standard behavior + loop = setup_test_loop() + yield loop + teardown_test_loop(loop, fast=fast) + + +def pytest_pycollect_makeitem(collector, name, obj): # type: ignore[no-untyped-def] + """Fix pytest collecting for coroutines.""" + if collector.funcnamefilter(name) and asyncio.iscoroutinefunction(obj): + return list(collector._genfunctions(name, obj)) + + +def pytest_pyfunc_call(pyfuncitem): # type: ignore[no-untyped-def] + """Run coroutines in an event loop instead of a normal function call.""" + fast = pyfuncitem.config.getoption("--aiohttp-fast") + if asyncio.iscoroutinefunction(pyfuncitem.function): + existing_loop = pyfuncitem.funcargs.get( + "proactor_loop" + ) or pyfuncitem.funcargs.get("loop", None) + with _runtime_warning_context(): + with _passthrough_loop_context(existing_loop, fast=fast) as _loop: + testargs = { + arg: pyfuncitem.funcargs[arg] + for arg in pyfuncitem._fixtureinfo.argnames + } + _loop.run_until_complete(pyfuncitem.obj(**testargs)) + + return True + + +def pytest_generate_tests(metafunc): # type: ignore[no-untyped-def] + if "loop_factory" not in metafunc.fixturenames: + return + + loops = metafunc.config.option.aiohttp_loop + avail_factories: Dict[str, Type[asyncio.AbstractEventLoopPolicy]] + avail_factories = {"pyloop": asyncio.DefaultEventLoopPolicy} + + if uvloop is not None: # pragma: no cover + avail_factories["uvloop"] = uvloop.EventLoopPolicy + + if loops == "all": + loops = "pyloop,uvloop?" + + factories = {} # type: ignore[var-annotated] + for name in loops.split(","): + required = not name.endswith("?") + name = name.strip(" ?") + if name not in avail_factories: # pragma: no cover + if required: + raise ValueError( + "Unknown loop '%s', available loops: %s" + % (name, list(factories.keys())) + ) + else: + continue + factories[name] = avail_factories[name] + metafunc.parametrize( + "loop_factory", list(factories.values()), ids=list(factories.keys()) + ) + + +@pytest.fixture +def loop(loop_factory, fast, loop_debug): # type: ignore[no-untyped-def] + """Return an instance of the event loop.""" + policy = loop_factory() + asyncio.set_event_loop_policy(policy) + with loop_context(fast=fast) as _loop: + if loop_debug: + _loop.set_debug(True) # pragma: no cover + asyncio.set_event_loop(_loop) + yield _loop + + +@pytest.fixture +def proactor_loop(): # type: ignore[no-untyped-def] + policy = asyncio.WindowsProactorEventLoopPolicy() # type: ignore[attr-defined] + asyncio.set_event_loop_policy(policy) + + with loop_context(policy.new_event_loop) as _loop: + asyncio.set_event_loop(_loop) + yield _loop + + +@pytest.fixture +def aiohttp_unused_port() -> Callable[[], int]: + """Return a port that is unused on the current host.""" + return _unused_port + + +@pytest.fixture +def aiohttp_server(loop: asyncio.AbstractEventLoop) -> Iterator[AiohttpServer]: + """Factory to create a TestServer instance, given an app. + + aiohttp_server(app, **kwargs) + """ + servers = [] + + async def go(app, *, port=None, **kwargs): # type: ignore[no-untyped-def] + server = TestServer(app, port=port) + await server.start_server(**kwargs) + servers.append(server) + return server + + yield go + + async def finalize() -> None: + while servers: + await servers.pop().close() + + loop.run_until_complete(finalize()) + + +@pytest.fixture +def aiohttp_raw_server(loop: asyncio.AbstractEventLoop) -> Iterator[AiohttpRawServer]: + """Factory to create a RawTestServer instance, given a web handler. + + aiohttp_raw_server(handler, **kwargs) + """ + servers = [] + + async def go(handler, *, port=None, **kwargs): # type: ignore[no-untyped-def] + server = RawTestServer(handler, port=port) + await server.start_server(**kwargs) + servers.append(server) + return server + + yield go + + async def finalize() -> None: + while servers: + await servers.pop().close() + + loop.run_until_complete(finalize()) + + +@pytest.fixture +def aiohttp_client_cls() -> Type[TestClient]: + """ + Client class to use in ``aiohttp_client`` factory. + + Use it for passing custom ``TestClient`` implementations. + + Example:: + + class MyClient(TestClient): + async def login(self, *, user, pw): + payload = {"username": user, "password": pw} + return await self.post("/login", json=payload) + + @pytest.fixture + def aiohttp_client_cls(): + return MyClient + + def test_login(aiohttp_client): + app = web.Application() + client = await aiohttp_client(app) + await client.login(user="admin", pw="s3cr3t") + + """ + return TestClient + + +@pytest.fixture +def aiohttp_client( + loop: asyncio.AbstractEventLoop, aiohttp_client_cls: Type[TestClient] +) -> Iterator[AiohttpClient]: + """Factory to create a TestClient instance. + + aiohttp_client(app, **kwargs) + aiohttp_client(server, **kwargs) + aiohttp_client(raw_server, **kwargs) + """ + clients = [] + + async def go( + __param: Union[Application, BaseTestServer], + *, + server_kwargs: Optional[Dict[str, Any]] = None, + **kwargs: Any + ) -> TestClient: + if isinstance(__param, Application): + server_kwargs = server_kwargs or {} + server = TestServer(__param, **server_kwargs) + client = aiohttp_client_cls(server, **kwargs) + elif isinstance(__param, BaseTestServer): + client = aiohttp_client_cls(__param, **kwargs) + else: + raise ValueError("Unknown argument type: %r" % type(__param)) + + await client.start_server() + clients.append(client) + return client + + yield go + + async def finalize() -> None: + while clients: + await clients.pop().close() + + loop.run_until_complete(finalize()) diff --git a/aiohttp/resolver.py b/aiohttp/resolver.py new file mode 100644 index 0000000..d20c919 --- /dev/null +++ b/aiohttp/resolver.py @@ -0,0 +1,118 @@ +import asyncio +import socket +from typing import Any, Dict, List, Type, Union + +from .abc import AbstractResolver + +__all__ = ("ThreadedResolver", "AsyncResolver", "DefaultResolver") + +try: + import aiodns + + # aiodns_default = hasattr(aiodns.DNSResolver, 'gethostbyname') +except ImportError: # pragma: no cover + aiodns = None + +aiodns_default = False + + +class ThreadedResolver(AbstractResolver): + """Threaded resolver. + + Uses an Executor for synchronous getaddrinfo() calls. + concurrent.futures.ThreadPoolExecutor is used by default. + """ + + def __init__(self) -> None: + self._loop = asyncio.get_running_loop() + + async def resolve( + self, hostname: str, port: int = 0, family: int = socket.AF_INET + ) -> List[Dict[str, Any]]: + infos = await self._loop.getaddrinfo( + hostname, + port, + type=socket.SOCK_STREAM, + family=family, + flags=socket.AI_ADDRCONFIG, + ) + + hosts = [] + for family, _, proto, _, address in infos: + if family == socket.AF_INET6: + if len(address) < 3: + # IPv6 is not supported by Python build, + # or IPv6 is not enabled in the host + continue + if address[3]: # type: ignore[misc] + # This is essential for link-local IPv6 addresses. + # LL IPv6 is a VERY rare case. Strictly speaking, we should use + # getnameinfo() unconditionally, but performance makes sense. + host, _port = socket.getnameinfo( + address, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV + ) + port = int(_port) + else: + host, port = address[:2] + else: # IPv4 + assert family == socket.AF_INET + host, port = address # type: ignore[misc] + hosts.append( + { + "hostname": hostname, + "host": host, + "port": port, + "family": family, + "proto": proto, + "flags": socket.AI_NUMERICHOST | socket.AI_NUMERICSERV, + } + ) + + return hosts + + async def close(self) -> None: + pass + + +class AsyncResolver(AbstractResolver): + """Use the `aiodns` package to make asynchronous DNS lookups""" + + def __init__(self, *args: Any, **kwargs: Any) -> None: + if aiodns is None: + raise RuntimeError("Resolver requires aiodns library") + + self._loop = asyncio.get_running_loop() + self._resolver = aiodns.DNSResolver(*args, loop=self._loop, **kwargs) + + async def resolve( + self, host: str, port: int = 0, family: int = socket.AF_INET + ) -> List[Dict[str, Any]]: + try: + resp = await self._resolver.gethostbyname(host, family) + except aiodns.error.DNSError as exc: + msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed" + raise OSError(msg) from exc + hosts = [] + for address in resp.addresses: + hosts.append( + { + "hostname": host, + "host": address, + "port": port, + "family": family, + "proto": 0, + "flags": socket.AI_NUMERICHOST | socket.AI_NUMERICSERV, + } + ) + + if not hosts: + raise OSError("DNS lookup failed") + + return hosts + + async def close(self) -> None: + self._resolver.cancel() + + +_DefaultType = Type[Union[AsyncResolver, ThreadedResolver]] +DefaultResolver: _DefaultType = AsyncResolver if aiodns_default else ThreadedResolver diff --git a/aiohttp/streams.py b/aiohttp/streams.py new file mode 100644 index 0000000..8ee088d --- /dev/null +++ b/aiohttp/streams.py @@ -0,0 +1,652 @@ +import asyncio +import collections +import warnings +from typing import ( + Awaitable, + Callable, + Deque, + Final, + Generic, + List, + Optional, + Tuple, + TypeVar, +) + +from .base_protocol import BaseProtocol +from .helpers import BaseTimerContext, TimerNoop, set_exception, set_result +from .log import internal_logger + +__all__ = ( + "EMPTY_PAYLOAD", + "EofStream", + "StreamReader", + "DataQueue", + "FlowControlDataQueue", +) + +_T = TypeVar("_T") + + +class EofStream(Exception): + """eof stream indication.""" + + +class AsyncStreamIterator(Generic[_T]): + def __init__(self, read_func: Callable[[], Awaitable[_T]]) -> None: + self.read_func = read_func + + def __aiter__(self) -> "AsyncStreamIterator[_T]": + return self + + async def __anext__(self) -> _T: + try: + rv = await self.read_func() + except EofStream: + raise StopAsyncIteration + if rv == b"": + raise StopAsyncIteration + return rv + + +class ChunkTupleAsyncStreamIterator: + def __init__(self, stream: "StreamReader") -> None: + self._stream = stream + + def __aiter__(self) -> "ChunkTupleAsyncStreamIterator": + return self + + async def __anext__(self) -> Tuple[bytes, bool]: + rv = await self._stream.readchunk() + if rv == (b"", False): + raise StopAsyncIteration + return rv + + +class AsyncStreamReaderMixin: + def __aiter__(self) -> AsyncStreamIterator[bytes]: + return AsyncStreamIterator(self.readline) # type: ignore[attr-defined] + + def iter_chunked(self, n: int) -> AsyncStreamIterator[bytes]: + """Returns an asynchronous iterator that yields chunks of size n.""" + return AsyncStreamIterator(lambda: self.read(n)) # type: ignore[attr-defined] + + def iter_any(self) -> AsyncStreamIterator[bytes]: + """Yield all available data as soon as it is received.""" + return AsyncStreamIterator(self.readany) # type: ignore[attr-defined] + + def iter_chunks(self) -> ChunkTupleAsyncStreamIterator: + """Yield chunks of data as they are received by the server. + + The yielded objects are tuples + of (bytes, bool) as returned by the StreamReader.readchunk method. + """ + return ChunkTupleAsyncStreamIterator(self) # type: ignore[arg-type] + + +class StreamReader(AsyncStreamReaderMixin): + """An enhancement of asyncio.StreamReader. + + Supports asynchronous iteration by line, chunk or as available:: + + async for line in reader: + ... + async for chunk in reader.iter_chunked(1024): + ... + async for slice in reader.iter_any(): + ... + + """ + + total_bytes = 0 + + def __init__( + self, + protocol: BaseProtocol, + limit: int, + *, + timer: Optional[BaseTimerContext] = None, + loop: asyncio.AbstractEventLoop, + ) -> None: + self._protocol = protocol + self._low_water = limit + self._high_water = limit * 2 + if loop is None: + loop = asyncio.get_event_loop() + self._loop = loop + self._size = 0 + self._cursor = 0 + self._http_chunk_splits: Optional[List[int]] = None + self._buffer: Deque[bytes] = collections.deque() + self._buffer_offset = 0 + self._eof = False + self._waiter: Optional[asyncio.Future[None]] = None + self._eof_waiter: Optional[asyncio.Future[None]] = None + self._exception: Optional[BaseException] = None + self._timer = TimerNoop() if timer is None else timer + self._eof_callbacks: List[Callable[[], None]] = [] + + def __repr__(self) -> str: + info = [self.__class__.__name__] + if self._size: + info.append("%d bytes" % self._size) + if self._eof: + info.append("eof") + if self._low_water != 2**16: # default limit + info.append("low=%d high=%d" % (self._low_water, self._high_water)) + if self._waiter: + info.append("w=%r" % self._waiter) + if self._exception: + info.append("e=%r" % self._exception) + return "<%s>" % " ".join(info) + + def get_read_buffer_limits(self) -> Tuple[int, int]: + return (self._low_water, self._high_water) + + def exception(self) -> Optional[BaseException]: + return self._exception + + def set_exception(self, exc: BaseException) -> None: + self._exception = exc + self._eof_callbacks.clear() + + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_exception(waiter, exc) + + waiter = self._eof_waiter + if waiter is not None: + self._eof_waiter = None + set_exception(waiter, exc) + + def on_eof(self, callback: Callable[[], None]) -> None: + if self._eof: + try: + callback() + except Exception: + internal_logger.exception("Exception in eof callback") + else: + self._eof_callbacks.append(callback) + + def feed_eof(self) -> None: + self._eof = True + + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_result(waiter, None) + + waiter = self._eof_waiter + if waiter is not None: + self._eof_waiter = None + set_result(waiter, None) + + for cb in self._eof_callbacks: + try: + cb() + except Exception: + internal_logger.exception("Exception in eof callback") + + self._eof_callbacks.clear() + + def is_eof(self) -> bool: + """Return True if 'feed_eof' was called.""" + return self._eof + + def at_eof(self) -> bool: + """Return True if the buffer is empty and 'feed_eof' was called.""" + return self._eof and not self._buffer + + async def wait_eof(self) -> None: + if self._eof: + return + + assert self._eof_waiter is None + self._eof_waiter = self._loop.create_future() + try: + await self._eof_waiter + finally: + self._eof_waiter = None + + def unread_data(self, data: bytes) -> None: + """rollback reading some data from stream, inserting it to buffer head.""" + warnings.warn( + "unread_data() is deprecated " + "and will be removed in future releases (#3260)", + DeprecationWarning, + stacklevel=2, + ) + if not data: + return + + if self._buffer_offset: + self._buffer[0] = self._buffer[0][self._buffer_offset :] + self._buffer_offset = 0 + self._size += len(data) + self._cursor -= len(data) + self._buffer.appendleft(data) + self._eof_counter = 0 + + # TODO: size is ignored, remove the param later + def feed_data(self, data: bytes, size: int = 0) -> None: + assert not self._eof, "feed_data after feed_eof" + + if not data: + return + + self._size += len(data) + self._buffer.append(data) + self.total_bytes += len(data) + + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_result(waiter, None) + + if self._size > self._high_water and not self._protocol._reading_paused: + self._protocol.pause_reading() + + def begin_http_chunk_receiving(self) -> None: + if self._http_chunk_splits is None: + if self.total_bytes: + raise RuntimeError( + "Called begin_http_chunk_receiving when" "some data was already fed" + ) + self._http_chunk_splits = [] + + def end_http_chunk_receiving(self) -> None: + if self._http_chunk_splits is None: + raise RuntimeError( + "Called end_chunk_receiving without calling " + "begin_chunk_receiving first" + ) + + # self._http_chunk_splits contains logical byte offsets from start of + # the body transfer. Each offset is the offset of the end of a chunk. + # "Logical" means bytes, accessible for a user. + # If no chunks containing logical data were received, current position + # is difinitely zero. + pos = self._http_chunk_splits[-1] if self._http_chunk_splits else 0 + + if self.total_bytes == pos: + # We should not add empty chunks here. So we check for that. + # Note, when chunked + gzip is used, we can receive a chunk + # of compressed data, but that data may not be enough for gzip FSM + # to yield any uncompressed data. That's why current position may + # not change after receiving a chunk. + return + + self._http_chunk_splits.append(self.total_bytes) + + # wake up readchunk when end of http chunk received + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_result(waiter, None) + + async def _wait(self, func_name: str) -> None: + # StreamReader uses a future to link the protocol feed_data() method + # to a read coroutine. Running two read coroutines at the same time + # would have an unexpected behaviour. It would not possible to know + # which coroutine would get the next data. + if self._waiter is not None: + raise RuntimeError( + "%s() called while another coroutine is " + "already waiting for incoming data" % func_name + ) + + waiter = self._waiter = self._loop.create_future() + try: + with self._timer: + await waiter + finally: + self._waiter = None + + async def readline(self) -> bytes: + return await self.readuntil() + + async def readuntil(self, separator: bytes = b"\n") -> bytes: + seplen = len(separator) + if seplen == 0: + raise ValueError("Separator should be at least one-byte string") + + if self._exception is not None: + raise self._exception + + chunk = b"" + chunk_size = 0 + not_enough = True + + while not_enough: + while self._buffer and not_enough: + offset = self._buffer_offset + ichar = self._buffer[0].find(separator, offset) + 1 + # Read from current offset to found separator or to the end. + data = self._read_nowait_chunk( + ichar - offset + seplen - 1 if ichar else -1 + ) + chunk += data + chunk_size += len(data) + if ichar: + not_enough = False + + if chunk_size > self._high_water: + raise ValueError("Chunk too big") + + if self._eof: + break + + if not_enough: + await self._wait("readuntil") + + return chunk + + async def read(self, n: int = -1) -> bytes: + if self._exception is not None: + raise self._exception + + if not n: + return b"" + + if n < 0: + # This used to just loop creating a new waiter hoping to + # collect everything in self._buffer, but that would + # deadlock if the subprocess sends more than self.limit + # bytes. So just call self.readany() until EOF. + blocks = [] + while True: + block = await self.readany() + if not block: + break + blocks.append(block) + return b"".join(blocks) + + # TODO: should be `if` instead of `while` + # because waiter maybe triggered on chunk end, + # without feeding any data + while not self._buffer and not self._eof: + await self._wait("read") + + return self._read_nowait(n) + + async def readany(self) -> bytes: + if self._exception is not None: + raise self._exception + + # TODO: should be `if` instead of `while` + # because waiter maybe triggered on chunk end, + # without feeding any data + while not self._buffer and not self._eof: + await self._wait("readany") + + return self._read_nowait(-1) + + async def readchunk(self) -> Tuple[bytes, bool]: + """Returns a tuple of (data, end_of_http_chunk). + + When chunked transfer + encoding is used, end_of_http_chunk is a boolean indicating if the end + of the data corresponds to the end of a HTTP chunk , otherwise it is + always False. + """ + while True: + if self._exception is not None: + raise self._exception + + while self._http_chunk_splits: + pos = self._http_chunk_splits.pop(0) + if pos == self._cursor: + return (b"", True) + if pos > self._cursor: + return (self._read_nowait(pos - self._cursor), True) + internal_logger.warning( + "Skipping HTTP chunk end due to data " + "consumption beyond chunk boundary" + ) + + if self._buffer: + return (self._read_nowait_chunk(-1), False) + # return (self._read_nowait(-1), False) + + if self._eof: + # Special case for signifying EOF. + # (b'', True) is not a final return value actually. + return (b"", False) + + await self._wait("readchunk") + + async def readexactly(self, n: int) -> bytes: + if self._exception is not None: + raise self._exception + + blocks: List[bytes] = [] + while n > 0: + block = await self.read(n) + if not block: + partial = b"".join(blocks) + raise asyncio.IncompleteReadError(partial, len(partial) + n) + blocks.append(block) + n -= len(block) + + return b"".join(blocks) + + def read_nowait(self, n: int = -1) -> bytes: + # default was changed to be consistent with .read(-1) + # + # I believe the most users don't know about the method and + # they are not affected. + if self._exception is not None: + raise self._exception + + if self._waiter and not self._waiter.done(): + raise RuntimeError( + "Called while some coroutine is waiting for incoming data." + ) + + return self._read_nowait(n) + + def _read_nowait_chunk(self, n: int) -> bytes: + first_buffer = self._buffer[0] + offset = self._buffer_offset + if n != -1 and len(first_buffer) - offset > n: + data = first_buffer[offset : offset + n] + self._buffer_offset += n + + elif offset: + self._buffer.popleft() + data = first_buffer[offset:] + self._buffer_offset = 0 + + else: + data = self._buffer.popleft() + + self._size -= len(data) + self._cursor += len(data) + + chunk_splits = self._http_chunk_splits + # Prevent memory leak: drop useless chunk splits + while chunk_splits and chunk_splits[0] < self._cursor: + chunk_splits.pop(0) + + if self._size < self._low_water and self._protocol._reading_paused: + self._protocol.resume_reading() + return data + + def _read_nowait(self, n: int) -> bytes: + """Read not more than n bytes, or whole buffer if n == -1""" + self._timer.assert_timeout() + + chunks = [] + while self._buffer: + chunk = self._read_nowait_chunk(n) + chunks.append(chunk) + if n != -1: + n -= len(chunk) + if n == 0: + break + + return b"".join(chunks) if chunks else b"" + + +class EmptyStreamReader(StreamReader): # lgtm [py/missing-call-to-init] + def __init__(self) -> None: + self._read_eof_chunk = False + + def __repr__(self) -> str: + return "<%s>" % self.__class__.__name__ + + def exception(self) -> Optional[BaseException]: + return None + + def set_exception(self, exc: BaseException) -> None: + pass + + def on_eof(self, callback: Callable[[], None]) -> None: + try: + callback() + except Exception: + internal_logger.exception("Exception in eof callback") + + def feed_eof(self) -> None: + pass + + def is_eof(self) -> bool: + return True + + def at_eof(self) -> bool: + return True + + async def wait_eof(self) -> None: + return + + def feed_data(self, data: bytes, n: int = 0) -> None: + pass + + async def readline(self) -> bytes: + return b"" + + async def read(self, n: int = -1) -> bytes: + return b"" + + # TODO add async def readuntil + + async def readany(self) -> bytes: + return b"" + + async def readchunk(self) -> Tuple[bytes, bool]: + if not self._read_eof_chunk: + self._read_eof_chunk = True + return (b"", False) + + return (b"", True) + + async def readexactly(self, n: int) -> bytes: + raise asyncio.IncompleteReadError(b"", n) + + def read_nowait(self, n: int = -1) -> bytes: + return b"" + + +EMPTY_PAYLOAD: Final[StreamReader] = EmptyStreamReader() + + +class DataQueue(Generic[_T]): + """DataQueue is a general-purpose blocking queue with one reader.""" + + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: + self._loop = loop + self._eof = False + self._waiter: Optional[asyncio.Future[None]] = None + self._exception: Optional[BaseException] = None + self._size = 0 + self._buffer: Deque[Tuple[_T, int]] = collections.deque() + + def __len__(self) -> int: + return len(self._buffer) + + def is_eof(self) -> bool: + return self._eof + + def at_eof(self) -> bool: + return self._eof and not self._buffer + + def exception(self) -> Optional[BaseException]: + return self._exception + + def set_exception(self, exc: BaseException) -> None: + self._eof = True + self._exception = exc + + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_exception(waiter, exc) + + def feed_data(self, data: _T, size: int = 0) -> None: + self._size += size + self._buffer.append((data, size)) + + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_result(waiter, None) + + def feed_eof(self) -> None: + self._eof = True + + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_result(waiter, None) + + async def read(self) -> _T: + if not self._buffer and not self._eof: + assert not self._waiter + self._waiter = self._loop.create_future() + try: + await self._waiter + except (asyncio.CancelledError, asyncio.TimeoutError): + self._waiter = None + raise + + if self._buffer: + data, size = self._buffer.popleft() + self._size -= size + return data + else: + if self._exception is not None: + raise self._exception + else: + raise EofStream + + def __aiter__(self) -> AsyncStreamIterator[_T]: + return AsyncStreamIterator(self.read) + + +class FlowControlDataQueue(DataQueue[_T]): + """FlowControlDataQueue resumes and pauses an underlying stream. + + It is a destination for parsed data. + """ + + def __init__( + self, protocol: BaseProtocol, limit: int, *, loop: asyncio.AbstractEventLoop + ) -> None: + super().__init__(loop=loop) + + self._protocol = protocol + self._limit = limit * 2 + + def feed_data(self, data: _T, size: int = 0) -> None: + super().feed_data(data, size) + + if self._size > self._limit and not self._protocol._reading_paused: + self._protocol.pause_reading() + + async def read(self) -> _T: + try: + return await super().read() + finally: + if self._size < self._limit and self._protocol._reading_paused: + self._protocol.resume_reading() diff --git a/aiohttp/tcp_helpers.py b/aiohttp/tcp_helpers.py new file mode 100644 index 0000000..88b2442 --- /dev/null +++ b/aiohttp/tcp_helpers.py @@ -0,0 +1,37 @@ +"""Helper methods to tune a TCP connection""" + +import asyncio +import socket +from contextlib import suppress +from typing import Optional # noqa + +__all__ = ("tcp_keepalive", "tcp_nodelay") + + +if hasattr(socket, "SO_KEEPALIVE"): + + def tcp_keepalive(transport: asyncio.Transport) -> None: + sock = transport.get_extra_info("socket") + if sock is not None: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + +else: + + def tcp_keepalive(transport: asyncio.Transport) -> None: # pragma: no cover + pass + + +def tcp_nodelay(transport: asyncio.Transport, value: bool) -> None: + sock = transport.get_extra_info("socket") + + if sock is None: + return + + if sock.family not in (socket.AF_INET, socket.AF_INET6): + return + + value = bool(value) + + # socket may be closed already, on windows OSError get raised + with suppress(OSError): + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, value) diff --git a/aiohttp/test_utils.py b/aiohttp/test_utils.py new file mode 100644 index 0000000..414ae11 --- /dev/null +++ b/aiohttp/test_utils.py @@ -0,0 +1,618 @@ +"""Utilities shared by tests.""" + +import asyncio +import contextlib +import gc +import inspect +import ipaddress +import os +import socket +import sys +from abc import ABC, abstractmethod +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Iterator, + List, + Optional, + Type, + Union, + cast, +) +from unittest import IsolatedAsyncioTestCase, mock + +from aiosignal import Signal +from multidict import CIMultiDict, CIMultiDictProxy +from yarl import URL + +import aiohttp +from aiohttp.client import _RequestContextManager, _WSRequestContextManager + +from . import ClientSession, hdrs +from .abc import AbstractCookieJar +from .client_reqrep import ClientResponse +from .client_ws import ClientWebSocketResponse +from .helpers import _SENTINEL, sentinel +from .http import HttpVersion, RawRequestMessage +from .typedefs import StrOrURL +from .web import ( + Application, + AppRunner, + BaseRunner, + Request, + Server, + ServerRunner, + SockSite, + UrlMappingMatchInfo, +) +from .web_protocol import _RequestHandler + +if TYPE_CHECKING: # pragma: no cover + from ssl import SSLContext +else: + SSLContext = None + +REUSE_ADDRESS = os.name == "posix" and sys.platform != "cygwin" + + +def get_unused_port_socket( + host: str, family: socket.AddressFamily = socket.AF_INET +) -> socket.socket: + return get_port_socket(host, 0, family) + + +def get_port_socket( + host: str, port: int, family: socket.AddressFamily = socket.AF_INET +) -> socket.socket: + s = socket.socket(family, socket.SOCK_STREAM) + if REUSE_ADDRESS: + # Windows has different semantics for SO_REUSEADDR, + # so don't set it. Ref: + # https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind((host, port)) + return s + + +def unused_port() -> int: + """Return a port that is unused on the current host.""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("127.0.0.1", 0)) + return cast(int, s.getsockname()[1]) + + +class BaseTestServer(ABC): + __test__ = False + + def __init__( + self, + *, + scheme: Union[str, _SENTINEL] = sentinel, + host: str = "127.0.0.1", + port: Optional[int] = None, + skip_url_asserts: bool = False, + socket_factory: Callable[ + [str, int, socket.AddressFamily], socket.socket + ] = get_port_socket, + **kwargs: Any, + ) -> None: + self.runner: Optional[BaseRunner] = None + self._root: Optional[URL] = None + self.host = host + self.port = port + self._closed = False + self.scheme = scheme + self.skip_url_asserts = skip_url_asserts + self.socket_factory = socket_factory + + async def start_server(self, **kwargs: Any) -> None: + if self.runner: + return + self._ssl = kwargs.pop("ssl", None) + self.runner = await self._make_runner(handler_cancellation=True, **kwargs) + await self.runner.setup() + if not self.port: + self.port = 0 + absolute_host = self.host + try: + version = ipaddress.ip_address(self.host).version + except ValueError: + version = 4 + if version == 6: + absolute_host = f"[{self.host}]" + family = socket.AF_INET6 if version == 6 else socket.AF_INET + _sock = self.socket_factory(self.host, self.port, family) + self.host, self.port = _sock.getsockname()[:2] + site = SockSite(self.runner, sock=_sock, ssl_context=self._ssl) + await site.start() + server = site._server + assert server is not None + sockets = server.sockets # type: ignore[attr-defined] + assert sockets is not None + self.port = sockets[0].getsockname()[1] + if self.scheme is sentinel: + if self._ssl: + scheme = "https" + else: + scheme = "http" + self.scheme = scheme + self._root = URL(f"{self.scheme}://{absolute_host}:{self.port}") + + @abstractmethod # pragma: no cover + async def _make_runner(self, **kwargs: Any) -> BaseRunner: + pass + + def make_url(self, path: StrOrURL) -> URL: + assert self._root is not None + url = URL(path) + if not self.skip_url_asserts: + assert not url.is_absolute() + return self._root.join(url) + else: + return URL(str(self._root) + str(path)) + + @property + def started(self) -> bool: + return self.runner is not None + + @property + def closed(self) -> bool: + return self._closed + + @property + def handler(self) -> Server: + # for backward compatibility + # web.Server instance + runner = self.runner + assert runner is not None + assert runner.server is not None + return runner.server + + async def close(self) -> None: + """Close all fixtures created by the test client. + + After that point, the TestClient is no longer usable. + + This is an idempotent function: running close multiple times + will not have any additional effects. + + close is also run when the object is garbage collected, and on + exit when used as a context manager. + + """ + if self.started and not self.closed: + assert self.runner is not None + await self.runner.cleanup() + self._root = None + self.port = None + self._closed = True + + async def __aenter__(self) -> "BaseTestServer": + await self.start_server() + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + await self.close() + + +class TestServer(BaseTestServer): + def __init__( + self, + app: Application, + *, + scheme: Union[str, _SENTINEL] = sentinel, + host: str = "127.0.0.1", + port: Optional[int] = None, + **kwargs: Any, + ): + self.app = app + super().__init__(scheme=scheme, host=host, port=port, **kwargs) + + async def _make_runner(self, **kwargs: Any) -> BaseRunner: + return AppRunner(self.app, **kwargs) + + +class RawTestServer(BaseTestServer): + def __init__( + self, + handler: _RequestHandler, + *, + scheme: Union[str, _SENTINEL] = sentinel, + host: str = "127.0.0.1", + port: Optional[int] = None, + **kwargs: Any, + ) -> None: + self._handler = handler + super().__init__(scheme=scheme, host=host, port=port, **kwargs) + + async def _make_runner(self, **kwargs: Any) -> ServerRunner: + srv = Server(self._handler, **kwargs) + return ServerRunner(srv, **kwargs) + + +class TestClient: + """ + A test client implementation. + + To write functional tests for aiohttp based servers. + + """ + + __test__ = False + + def __init__( + self, + server: BaseTestServer, + *, + cookie_jar: Optional[AbstractCookieJar] = None, + **kwargs: Any, + ) -> None: + if not isinstance(server, BaseTestServer): + raise TypeError( + "server must be TestServer " "instance, found type: %r" % type(server) + ) + self._server = server + if cookie_jar is None: + cookie_jar = aiohttp.CookieJar(unsafe=True) + self._session = ClientSession(cookie_jar=cookie_jar, **kwargs) + self._closed = False + self._responses: List[ClientResponse] = [] + self._websockets: List[ClientWebSocketResponse] = [] + + async def start_server(self) -> None: + await self._server.start_server() + + @property + def scheme(self) -> Union[str, object]: + return self._server.scheme + + @property + def host(self) -> str: + return self._server.host + + @property + def port(self) -> Optional[int]: + return self._server.port + + @property + def server(self) -> BaseTestServer: + return self._server + + @property + def app(self) -> Optional[Application]: + return cast(Optional[Application], getattr(self._server, "app", None)) + + @property + def session(self) -> ClientSession: + """An internal aiohttp.ClientSession. + + Unlike the methods on the TestClient, client session requests + do not automatically include the host in the url queried, and + will require an absolute path to the resource. + + """ + return self._session + + def make_url(self, path: StrOrURL) -> URL: + return self._server.make_url(path) + + async def _request( + self, method: str, path: StrOrURL, **kwargs: Any + ) -> ClientResponse: + resp = await self._session.request(method, self.make_url(path), **kwargs) + # save it to close later + self._responses.append(resp) + return resp + + def request( + self, method: str, path: StrOrURL, **kwargs: Any + ) -> _RequestContextManager: + """Routes a request to tested http server. + + The interface is identical to aiohttp.ClientSession.request, + except the loop kwarg is overridden by the instance used by the + test server. + + """ + return _RequestContextManager(self._request(method, path, **kwargs)) + + def get(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP GET request.""" + return _RequestContextManager(self._request(hdrs.METH_GET, path, **kwargs)) + + def post(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP POST request.""" + return _RequestContextManager(self._request(hdrs.METH_POST, path, **kwargs)) + + def options(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP OPTIONS request.""" + return _RequestContextManager(self._request(hdrs.METH_OPTIONS, path, **kwargs)) + + def head(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP HEAD request.""" + return _RequestContextManager(self._request(hdrs.METH_HEAD, path, **kwargs)) + + def put(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP PUT request.""" + return _RequestContextManager(self._request(hdrs.METH_PUT, path, **kwargs)) + + def patch(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP PATCH request.""" + return _RequestContextManager(self._request(hdrs.METH_PATCH, path, **kwargs)) + + def delete(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP PATCH request.""" + return _RequestContextManager(self._request(hdrs.METH_DELETE, path, **kwargs)) + + def ws_connect(self, path: StrOrURL, **kwargs: Any) -> _WSRequestContextManager: + """Initiate websocket connection. + + The api corresponds to aiohttp.ClientSession.ws_connect. + + """ + return _WSRequestContextManager(self._ws_connect(path, **kwargs)) + + async def _ws_connect( + self, path: StrOrURL, **kwargs: Any + ) -> ClientWebSocketResponse: + ws = await self._session.ws_connect(self.make_url(path), **kwargs) + self._websockets.append(ws) + return ws + + async def close(self) -> None: + """Close all fixtures created by the test client. + + After that point, the TestClient is no longer usable. + + This is an idempotent function: running close multiple times + will not have any additional effects. + + close is also run on exit when used as a(n) (asynchronous) + context manager. + + """ + if not self._closed: + for resp in self._responses: + resp.close() + for ws in self._websockets: + await ws.close() + await self._session.close() + await self._server.close() + self._closed = True + + async def __aenter__(self) -> "TestClient": + await self.start_server() + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + tb: Optional[TracebackType], + ) -> None: + await self.close() + + +class AioHTTPTestCase(IsolatedAsyncioTestCase, ABC): + """A base class to allow for unittest web applications using aiohttp. + + Provides the following: + + * self.client (aiohttp.test_utils.TestClient): an aiohttp test client. + * self.app (aiohttp.web.Application): the application returned by + self.get_application() + + Note that the TestClient's methods are asynchronous: you have to + execute function on the test client using asynchronous methods. + """ + + @abstractmethod + async def get_application(self) -> Application: + """Get application. + + This method should be overridden to return the aiohttp.web.Application + object to test. + """ + + async def asyncSetUp(self) -> None: + self.app = await self.get_application() + self.server = await self.get_server(self.app) + self.client = await self.get_client(self.server) + + await self.client.start_server() + + async def asyncTearDown(self) -> None: + await self.client.close() + + async def get_server(self, app: Application) -> TestServer: + """Return a TestServer instance.""" + return TestServer(app) + + async def get_client(self, server: TestServer) -> TestClient: + """Return a TestClient instance.""" + return TestClient(server) + + +_LOOP_FACTORY = Callable[[], asyncio.AbstractEventLoop] + + +@contextlib.contextmanager +def loop_context( + loop_factory: _LOOP_FACTORY = asyncio.new_event_loop, fast: bool = False +) -> Iterator[asyncio.AbstractEventLoop]: + """A contextmanager that creates an event_loop, for test purposes. + + Handles the creation and cleanup of a test loop. + """ + loop = setup_test_loop(loop_factory) + yield loop + teardown_test_loop(loop, fast=fast) + + +def setup_test_loop( + loop_factory: _LOOP_FACTORY = asyncio.new_event_loop, +) -> asyncio.AbstractEventLoop: + """Create and return an asyncio.BaseEventLoop instance. + + The caller should also call teardown_test_loop, + once they are done with the loop. + """ + loop = loop_factory() + asyncio.set_event_loop(loop) + return loop + + +def teardown_test_loop(loop: asyncio.AbstractEventLoop, fast: bool = False) -> None: + """Teardown and cleanup an event_loop created by setup_test_loop.""" + closed = loop.is_closed() + if not closed: + loop.call_soon(loop.stop) + loop.run_forever() + loop.close() + + if not fast: + gc.collect() + + asyncio.set_event_loop(None) + + +def _create_app_mock() -> mock.MagicMock: + def get_dict(app: Any, key: str) -> Any: + return app.__app_dict[key] + + def set_dict(app: Any, key: str, value: Any) -> None: + app.__app_dict[key] = value + + app = mock.MagicMock(spec=Application) + app.__app_dict = {} + app.__getitem__ = get_dict + app.__setitem__ = set_dict + + app.on_response_prepare = Signal(app) + app.on_response_prepare.freeze() + return app + + +def _create_transport(sslcontext: Optional[SSLContext] = None) -> mock.Mock: + transport = mock.Mock() + + def get_extra_info(key: str) -> Optional[SSLContext]: + if key == "sslcontext": + return sslcontext + else: + return None + + transport.get_extra_info.side_effect = get_extra_info + return transport + + +def make_mocked_request( + method: str, + path: str, + headers: Any = None, + *, + match_info: Any = sentinel, + version: HttpVersion = HttpVersion(1, 1), + closing: bool = False, + app: Any = None, + writer: Any = sentinel, + protocol: Any = sentinel, + transport: Any = sentinel, + payload: Any = sentinel, + sslcontext: Optional[SSLContext] = None, + client_max_size: int = 1024**2, + loop: Any = ..., +) -> Request: + """Creates mocked web.Request testing purposes. + + Useful in unit tests, when spinning full web server is overkill or + specific conditions and errors are hard to trigger. + """ + task = mock.Mock() + if loop is ...: + loop = mock.Mock() + loop.create_future.return_value = () + + if version < HttpVersion(1, 1): + closing = True + + if headers: + headers = CIMultiDictProxy(CIMultiDict(headers)) + raw_hdrs = tuple( + (k.encode("utf-8"), v.encode("utf-8")) for k, v in headers.items() + ) + else: + headers = CIMultiDictProxy(CIMultiDict()) + raw_hdrs = () + + chunked = "chunked" in headers.get(hdrs.TRANSFER_ENCODING, "").lower() + + message = RawRequestMessage( + method, + path, + version, + headers, + raw_hdrs, + closing, + None, + False, + chunked, + URL(path), + ) + if app is None: + app = _create_app_mock() + + if transport is sentinel: + transport = _create_transport(sslcontext) + + if protocol is sentinel: + protocol = mock.Mock() + protocol.transport = transport + + if writer is sentinel: + writer = mock.Mock() + writer.write_headers = make_mocked_coro(None) + writer.write = make_mocked_coro(None) + writer.write_eof = make_mocked_coro(None) + writer.drain = make_mocked_coro(None) + writer.transport = transport + + protocol.transport = transport + protocol.writer = writer + + if payload is sentinel: + payload = mock.Mock() + + req = Request( + message, payload, protocol, writer, task, loop, client_max_size=client_max_size + ) + + match_info = UrlMappingMatchInfo( + {} if match_info is sentinel else match_info, mock.Mock() + ) + match_info.add_app(app) + req._match_info = match_info + + return req + + +def make_mocked_coro( + return_value: Any = sentinel, raise_exception: Any = sentinel +) -> Any: + """Creates a coroutine mock.""" + + async def mock_coro(*args: Any, **kwargs: Any) -> Any: + if raise_exception is not sentinel: + raise raise_exception + if not inspect.isawaitable(return_value): + return return_value + await return_value + + return mock.Mock(wraps=mock_coro) diff --git a/aiohttp/tracing.py b/aiohttp/tracing.py new file mode 100644 index 0000000..37f0481 --- /dev/null +++ b/aiohttp/tracing.py @@ -0,0 +1,471 @@ +import dataclasses +from types import SimpleNamespace +from typing import TYPE_CHECKING, Awaitable, Optional, Protocol, Type, TypeVar + +from aiosignal import Signal +from multidict import CIMultiDict +from yarl import URL + +from .client_reqrep import ClientResponse + +if TYPE_CHECKING: # pragma: no cover + from .client import ClientSession + + _ParamT_contra = TypeVar("_ParamT_contra", contravariant=True) + + class _SignalCallback(Protocol[_ParamT_contra]): + def __call__( + self, + __client_session: ClientSession, + __trace_config_ctx: SimpleNamespace, + __params: _ParamT_contra, + ) -> Awaitable[None]: + ... + + +__all__ = ( + "TraceConfig", + "TraceRequestStartParams", + "TraceRequestEndParams", + "TraceRequestExceptionParams", + "TraceConnectionQueuedStartParams", + "TraceConnectionQueuedEndParams", + "TraceConnectionCreateStartParams", + "TraceConnectionCreateEndParams", + "TraceConnectionReuseconnParams", + "TraceDnsResolveHostStartParams", + "TraceDnsResolveHostEndParams", + "TraceDnsCacheHitParams", + "TraceDnsCacheMissParams", + "TraceRequestRedirectParams", + "TraceRequestChunkSentParams", + "TraceResponseChunkReceivedParams", + "TraceRequestHeadersSentParams", +) + + +class TraceConfig: + """First-class used to trace requests launched via ClientSession objects.""" + + def __init__( + self, trace_config_ctx_factory: Type[SimpleNamespace] = SimpleNamespace + ) -> None: + self._on_request_start: Signal[ + _SignalCallback[TraceRequestStartParams] + ] = Signal(self) + self._on_request_chunk_sent: Signal[ + _SignalCallback[TraceRequestChunkSentParams] + ] = Signal(self) + self._on_response_chunk_received: Signal[ + _SignalCallback[TraceResponseChunkReceivedParams] + ] = Signal(self) + self._on_request_end: Signal[_SignalCallback[TraceRequestEndParams]] = Signal( + self + ) + self._on_request_exception: Signal[ + _SignalCallback[TraceRequestExceptionParams] + ] = Signal(self) + self._on_request_redirect: Signal[ + _SignalCallback[TraceRequestRedirectParams] + ] = Signal(self) + self._on_connection_queued_start: Signal[ + _SignalCallback[TraceConnectionQueuedStartParams] + ] = Signal(self) + self._on_connection_queued_end: Signal[ + _SignalCallback[TraceConnectionQueuedEndParams] + ] = Signal(self) + self._on_connection_create_start: Signal[ + _SignalCallback[TraceConnectionCreateStartParams] + ] = Signal(self) + self._on_connection_create_end: Signal[ + _SignalCallback[TraceConnectionCreateEndParams] + ] = Signal(self) + self._on_connection_reuseconn: Signal[ + _SignalCallback[TraceConnectionReuseconnParams] + ] = Signal(self) + self._on_dns_resolvehost_start: Signal[ + _SignalCallback[TraceDnsResolveHostStartParams] + ] = Signal(self) + self._on_dns_resolvehost_end: Signal[ + _SignalCallback[TraceDnsResolveHostEndParams] + ] = Signal(self) + self._on_dns_cache_hit: Signal[ + _SignalCallback[TraceDnsCacheHitParams] + ] = Signal(self) + self._on_dns_cache_miss: Signal[ + _SignalCallback[TraceDnsCacheMissParams] + ] = Signal(self) + self._on_request_headers_sent: Signal[ + _SignalCallback[TraceRequestHeadersSentParams] + ] = Signal(self) + + self._trace_config_ctx_factory = trace_config_ctx_factory + + def trace_config_ctx( + self, trace_request_ctx: Optional[SimpleNamespace] = None + ) -> SimpleNamespace: + """Return a new trace_config_ctx instance""" + return self._trace_config_ctx_factory(trace_request_ctx=trace_request_ctx) + + def freeze(self) -> None: + self._on_request_start.freeze() + self._on_request_chunk_sent.freeze() + self._on_response_chunk_received.freeze() + self._on_request_end.freeze() + self._on_request_exception.freeze() + self._on_request_redirect.freeze() + self._on_connection_queued_start.freeze() + self._on_connection_queued_end.freeze() + self._on_connection_create_start.freeze() + self._on_connection_create_end.freeze() + self._on_connection_reuseconn.freeze() + self._on_dns_resolvehost_start.freeze() + self._on_dns_resolvehost_end.freeze() + self._on_dns_cache_hit.freeze() + self._on_dns_cache_miss.freeze() + self._on_request_headers_sent.freeze() + + @property + def on_request_start(self) -> "Signal[_SignalCallback[TraceRequestStartParams]]": + return self._on_request_start + + @property + def on_request_chunk_sent( + self, + ) -> "Signal[_SignalCallback[TraceRequestChunkSentParams]]": + return self._on_request_chunk_sent + + @property + def on_response_chunk_received( + self, + ) -> "Signal[_SignalCallback[TraceResponseChunkReceivedParams]]": + return self._on_response_chunk_received + + @property + def on_request_end(self) -> "Signal[_SignalCallback[TraceRequestEndParams]]": + return self._on_request_end + + @property + def on_request_exception( + self, + ) -> "Signal[_SignalCallback[TraceRequestExceptionParams]]": + return self._on_request_exception + + @property + def on_request_redirect( + self, + ) -> "Signal[_SignalCallback[TraceRequestRedirectParams]]": + return self._on_request_redirect + + @property + def on_connection_queued_start( + self, + ) -> "Signal[_SignalCallback[TraceConnectionQueuedStartParams]]": + return self._on_connection_queued_start + + @property + def on_connection_queued_end( + self, + ) -> "Signal[_SignalCallback[TraceConnectionQueuedEndParams]]": + return self._on_connection_queued_end + + @property + def on_connection_create_start( + self, + ) -> "Signal[_SignalCallback[TraceConnectionCreateStartParams]]": + return self._on_connection_create_start + + @property + def on_connection_create_end( + self, + ) -> "Signal[_SignalCallback[TraceConnectionCreateEndParams]]": + return self._on_connection_create_end + + @property + def on_connection_reuseconn( + self, + ) -> "Signal[_SignalCallback[TraceConnectionReuseconnParams]]": + return self._on_connection_reuseconn + + @property + def on_dns_resolvehost_start( + self, + ) -> "Signal[_SignalCallback[TraceDnsResolveHostStartParams]]": + return self._on_dns_resolvehost_start + + @property + def on_dns_resolvehost_end( + self, + ) -> "Signal[_SignalCallback[TraceDnsResolveHostEndParams]]": + return self._on_dns_resolvehost_end + + @property + def on_dns_cache_hit(self) -> "Signal[_SignalCallback[TraceDnsCacheHitParams]]": + return self._on_dns_cache_hit + + @property + def on_dns_cache_miss(self) -> "Signal[_SignalCallback[TraceDnsCacheMissParams]]": + return self._on_dns_cache_miss + + @property + def on_request_headers_sent( + self, + ) -> "Signal[_SignalCallback[TraceRequestHeadersSentParams]]": + return self._on_request_headers_sent + + +@dataclasses.dataclass(frozen=True) +class TraceRequestStartParams: + """Parameters sent by the `on_request_start` signal""" + + method: str + url: URL + headers: "CIMultiDict[str]" + + +@dataclasses.dataclass(frozen=True) +class TraceRequestChunkSentParams: + """Parameters sent by the `on_request_chunk_sent` signal""" + + method: str + url: URL + chunk: bytes + + +@dataclasses.dataclass(frozen=True) +class TraceResponseChunkReceivedParams: + """Parameters sent by the `on_response_chunk_received` signal""" + + method: str + url: URL + chunk: bytes + + +@dataclasses.dataclass(frozen=True) +class TraceRequestEndParams: + """Parameters sent by the `on_request_end` signal""" + + method: str + url: URL + headers: "CIMultiDict[str]" + response: ClientResponse + + +@dataclasses.dataclass(frozen=True) +class TraceRequestExceptionParams: + """Parameters sent by the `on_request_exception` signal""" + + method: str + url: URL + headers: "CIMultiDict[str]" + exception: BaseException + + +@dataclasses.dataclass(frozen=True) +class TraceRequestRedirectParams: + """Parameters sent by the `on_request_redirect` signal""" + + method: str + url: URL + headers: "CIMultiDict[str]" + response: ClientResponse + + +@dataclasses.dataclass(frozen=True) +class TraceConnectionQueuedStartParams: + """Parameters sent by the `on_connection_queued_start` signal""" + + +@dataclasses.dataclass(frozen=True) +class TraceConnectionQueuedEndParams: + """Parameters sent by the `on_connection_queued_end` signal""" + + +@dataclasses.dataclass(frozen=True) +class TraceConnectionCreateStartParams: + """Parameters sent by the `on_connection_create_start` signal""" + + +@dataclasses.dataclass(frozen=True) +class TraceConnectionCreateEndParams: + """Parameters sent by the `on_connection_create_end` signal""" + + +@dataclasses.dataclass(frozen=True) +class TraceConnectionReuseconnParams: + """Parameters sent by the `on_connection_reuseconn` signal""" + + +@dataclasses.dataclass(frozen=True) +class TraceDnsResolveHostStartParams: + """Parameters sent by the `on_dns_resolvehost_start` signal""" + + host: str + + +@dataclasses.dataclass(frozen=True) +class TraceDnsResolveHostEndParams: + """Parameters sent by the `on_dns_resolvehost_end` signal""" + + host: str + + +@dataclasses.dataclass(frozen=True) +class TraceDnsCacheHitParams: + """Parameters sent by the `on_dns_cache_hit` signal""" + + host: str + + +@dataclasses.dataclass(frozen=True) +class TraceDnsCacheMissParams: + """Parameters sent by the `on_dns_cache_miss` signal""" + + host: str + + +@dataclasses.dataclass(frozen=True) +class TraceRequestHeadersSentParams: + """Parameters sent by the `on_request_headers_sent` signal""" + + method: str + url: URL + headers: "CIMultiDict[str]" + + +class Trace: + """Internal dependency holder class. + + Used to keep together the main dependencies used + at the moment of send a signal. + """ + + def __init__( + self, + session: "ClientSession", + trace_config: TraceConfig, + trace_config_ctx: SimpleNamespace, + ) -> None: + self._trace_config = trace_config + self._trace_config_ctx = trace_config_ctx + self._session = session + + async def send_request_start( + self, method: str, url: URL, headers: "CIMultiDict[str]" + ) -> None: + return await self._trace_config.on_request_start.send( + self._session, + self._trace_config_ctx, + TraceRequestStartParams(method, url, headers), + ) + + async def send_request_chunk_sent( + self, method: str, url: URL, chunk: bytes + ) -> None: + return await self._trace_config.on_request_chunk_sent.send( + self._session, + self._trace_config_ctx, + TraceRequestChunkSentParams(method, url, chunk), + ) + + async def send_response_chunk_received( + self, method: str, url: URL, chunk: bytes + ) -> None: + return await self._trace_config.on_response_chunk_received.send( + self._session, + self._trace_config_ctx, + TraceResponseChunkReceivedParams(method, url, chunk), + ) + + async def send_request_end( + self, + method: str, + url: URL, + headers: "CIMultiDict[str]", + response: ClientResponse, + ) -> None: + return await self._trace_config.on_request_end.send( + self._session, + self._trace_config_ctx, + TraceRequestEndParams(method, url, headers, response), + ) + + async def send_request_exception( + self, + method: str, + url: URL, + headers: "CIMultiDict[str]", + exception: BaseException, + ) -> None: + return await self._trace_config.on_request_exception.send( + self._session, + self._trace_config_ctx, + TraceRequestExceptionParams(method, url, headers, exception), + ) + + async def send_request_redirect( + self, + method: str, + url: URL, + headers: "CIMultiDict[str]", + response: ClientResponse, + ) -> None: + return await self._trace_config._on_request_redirect.send( + self._session, + self._trace_config_ctx, + TraceRequestRedirectParams(method, url, headers, response), + ) + + async def send_connection_queued_start(self) -> None: + return await self._trace_config.on_connection_queued_start.send( + self._session, self._trace_config_ctx, TraceConnectionQueuedStartParams() + ) + + async def send_connection_queued_end(self) -> None: + return await self._trace_config.on_connection_queued_end.send( + self._session, self._trace_config_ctx, TraceConnectionQueuedEndParams() + ) + + async def send_connection_create_start(self) -> None: + return await self._trace_config.on_connection_create_start.send( + self._session, self._trace_config_ctx, TraceConnectionCreateStartParams() + ) + + async def send_connection_create_end(self) -> None: + return await self._trace_config.on_connection_create_end.send( + self._session, self._trace_config_ctx, TraceConnectionCreateEndParams() + ) + + async def send_connection_reuseconn(self) -> None: + return await self._trace_config.on_connection_reuseconn.send( + self._session, self._trace_config_ctx, TraceConnectionReuseconnParams() + ) + + async def send_dns_resolvehost_start(self, host: str) -> None: + return await self._trace_config.on_dns_resolvehost_start.send( + self._session, self._trace_config_ctx, TraceDnsResolveHostStartParams(host) + ) + + async def send_dns_resolvehost_end(self, host: str) -> None: + return await self._trace_config.on_dns_resolvehost_end.send( + self._session, self._trace_config_ctx, TraceDnsResolveHostEndParams(host) + ) + + async def send_dns_cache_hit(self, host: str) -> None: + return await self._trace_config.on_dns_cache_hit.send( + self._session, self._trace_config_ctx, TraceDnsCacheHitParams(host) + ) + + async def send_dns_cache_miss(self, host: str) -> None: + return await self._trace_config.on_dns_cache_miss.send( + self._session, self._trace_config_ctx, TraceDnsCacheMissParams(host) + ) + + async def send_request_headers( + self, method: str, url: URL, headers: "CIMultiDict[str]" + ) -> None: + return await self._trace_config._on_request_headers_sent.send( + self._session, + self._trace_config_ctx, + TraceRequestHeadersSentParams(method, url, headers), + ) diff --git a/aiohttp/typedefs.py b/aiohttp/typedefs.py new file mode 100644 index 0000000..57d95b3 --- /dev/null +++ b/aiohttp/typedefs.py @@ -0,0 +1,54 @@ +import json +import os +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Iterable, + Mapping, + Tuple, + Union, +) + +from multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy, istr +from yarl import URL + +DEFAULT_JSON_ENCODER = json.dumps +DEFAULT_JSON_DECODER = json.loads + +if TYPE_CHECKING: # pragma: no cover + _CIMultiDict = CIMultiDict[str] + _CIMultiDictProxy = CIMultiDictProxy[str] + _MultiDict = MultiDict[str] + _MultiDictProxy = MultiDictProxy[str] + from http.cookies import BaseCookie, Morsel + + from .web import Request, StreamResponse +else: + _CIMultiDict = CIMultiDict + _CIMultiDictProxy = CIMultiDictProxy + _MultiDict = MultiDict + _MultiDictProxy = MultiDictProxy + +Byteish = Union[bytes, bytearray, memoryview] +JSONEncoder = Callable[[Any], str] +JSONDecoder = Callable[[str], Any] +LooseHeaders = Union[Mapping[Union[str, istr], str], _CIMultiDict, _CIMultiDictProxy] +RawHeaders = Tuple[Tuple[bytes, bytes], ...] +StrOrURL = Union[str, URL] + +LooseCookiesMappings = Mapping[str, Union[str, "BaseCookie[str]", "Morsel[Any]"]] +LooseCookiesIterables = Iterable[ + Tuple[str, Union[str, "BaseCookie[str]", "Morsel[Any]"]] +] +LooseCookies = Union[ + LooseCookiesMappings, + LooseCookiesIterables, + "BaseCookie[str]", +] + +Handler = Callable[["Request"], Awaitable["StreamResponse"]] +Middleware = Callable[["Request", Handler], Awaitable["StreamResponse"]] + +PathLike = Union[str, "os.PathLike[str]"] diff --git a/aiohttp/web.py b/aiohttp/web.py new file mode 100644 index 0000000..f87d579 --- /dev/null +++ b/aiohttp/web.py @@ -0,0 +1,573 @@ +import asyncio +import logging +import os +import socket +import sys +from argparse import ArgumentParser +from collections.abc import Iterable +from importlib import import_module +from typing import ( + Any, + Awaitable, + Callable, + Iterable as TypingIterable, + List, + Optional, + Set, + Type, + Union, + cast, +) + +from .abc import AbstractAccessLogger +from .helpers import AppKey +from .log import access_logger +from .typedefs import PathLike +from .web_app import Application, CleanupError +from .web_exceptions import ( + HTTPAccepted, + HTTPBadGateway, + HTTPBadRequest, + HTTPClientError, + HTTPConflict, + HTTPCreated, + HTTPError, + HTTPException, + HTTPExpectationFailed, + HTTPFailedDependency, + HTTPForbidden, + HTTPFound, + HTTPGatewayTimeout, + HTTPGone, + HTTPInsufficientStorage, + HTTPInternalServerError, + HTTPLengthRequired, + HTTPMethodNotAllowed, + HTTPMisdirectedRequest, + HTTPMove, + HTTPMovedPermanently, + HTTPMultipleChoices, + HTTPNetworkAuthenticationRequired, + HTTPNoContent, + HTTPNonAuthoritativeInformation, + HTTPNotAcceptable, + HTTPNotExtended, + HTTPNotFound, + HTTPNotImplemented, + HTTPNotModified, + HTTPOk, + HTTPPartialContent, + HTTPPaymentRequired, + HTTPPermanentRedirect, + HTTPPreconditionFailed, + HTTPPreconditionRequired, + HTTPProxyAuthenticationRequired, + HTTPRedirection, + HTTPRequestEntityTooLarge, + HTTPRequestHeaderFieldsTooLarge, + HTTPRequestRangeNotSatisfiable, + HTTPRequestTimeout, + HTTPRequestURITooLong, + HTTPResetContent, + HTTPSeeOther, + HTTPServerError, + HTTPServiceUnavailable, + HTTPSuccessful, + HTTPTemporaryRedirect, + HTTPTooManyRequests, + HTTPUnauthorized, + HTTPUnavailableForLegalReasons, + HTTPUnprocessableEntity, + HTTPUnsupportedMediaType, + HTTPUpgradeRequired, + HTTPUseProxy, + HTTPVariantAlsoNegotiates, + HTTPVersionNotSupported, +) +from .web_fileresponse import FileResponse +from .web_log import AccessLogger +from .web_middlewares import middleware, normalize_path_middleware +from .web_protocol import PayloadAccessError, RequestHandler, RequestPayloadError +from .web_request import BaseRequest, FileField, Request +from .web_response import ContentCoding, Response, StreamResponse, json_response +from .web_routedef import ( + AbstractRouteDef, + RouteDef, + RouteTableDef, + StaticDef, + delete, + get, + head, + options, + patch, + post, + put, + route, + static, + view, +) +from .web_runner import ( + AppRunner, + BaseRunner, + BaseSite, + GracefulExit, + NamedPipeSite, + ServerRunner, + SockSite, + TCPSite, + UnixSite, +) +from .web_server import Server +from .web_urldispatcher import ( + AbstractResource, + AbstractRoute, + DynamicResource, + PlainResource, + PrefixedSubAppResource, + Resource, + ResourceRoute, + StaticResource, + UrlDispatcher, + UrlMappingMatchInfo, + View, +) +from .web_ws import WebSocketReady, WebSocketResponse, WSMsgType + +__all__ = ( + # web_app + "AppKey", + "Application", + "CleanupError", + # web_exceptions + "HTTPAccepted", + "HTTPBadGateway", + "HTTPBadRequest", + "HTTPClientError", + "HTTPConflict", + "HTTPCreated", + "HTTPError", + "HTTPException", + "HTTPExpectationFailed", + "HTTPFailedDependency", + "HTTPForbidden", + "HTTPFound", + "HTTPGatewayTimeout", + "HTTPGone", + "HTTPInsufficientStorage", + "HTTPInternalServerError", + "HTTPLengthRequired", + "HTTPMethodNotAllowed", + "HTTPMisdirectedRequest", + "HTTPMove", + "HTTPMovedPermanently", + "HTTPMultipleChoices", + "HTTPNetworkAuthenticationRequired", + "HTTPNoContent", + "HTTPNonAuthoritativeInformation", + "HTTPNotAcceptable", + "HTTPNotExtended", + "HTTPNotFound", + "HTTPNotImplemented", + "HTTPNotModified", + "HTTPOk", + "HTTPPartialContent", + "HTTPPaymentRequired", + "HTTPPermanentRedirect", + "HTTPPreconditionFailed", + "HTTPPreconditionRequired", + "HTTPProxyAuthenticationRequired", + "HTTPRedirection", + "HTTPRequestEntityTooLarge", + "HTTPRequestHeaderFieldsTooLarge", + "HTTPRequestRangeNotSatisfiable", + "HTTPRequestTimeout", + "HTTPRequestURITooLong", + "HTTPResetContent", + "HTTPSeeOther", + "HTTPServerError", + "HTTPServiceUnavailable", + "HTTPSuccessful", + "HTTPTemporaryRedirect", + "HTTPTooManyRequests", + "HTTPUnauthorized", + "HTTPUnavailableForLegalReasons", + "HTTPUnprocessableEntity", + "HTTPUnsupportedMediaType", + "HTTPUpgradeRequired", + "HTTPUseProxy", + "HTTPVariantAlsoNegotiates", + "HTTPVersionNotSupported", + # web_fileresponse + "FileResponse", + # web_middlewares + "middleware", + "normalize_path_middleware", + # web_protocol + "PayloadAccessError", + "RequestHandler", + "RequestPayloadError", + # web_request + "BaseRequest", + "FileField", + "Request", + # web_response + "ContentCoding", + "Response", + "StreamResponse", + "json_response", + # web_routedef + "AbstractRouteDef", + "RouteDef", + "RouteTableDef", + "StaticDef", + "delete", + "get", + "head", + "options", + "patch", + "post", + "put", + "route", + "static", + "view", + # web_runner + "AppRunner", + "BaseRunner", + "BaseSite", + "GracefulExit", + "ServerRunner", + "SockSite", + "TCPSite", + "UnixSite", + "NamedPipeSite", + # web_server + "Server", + # web_urldispatcher + "AbstractResource", + "AbstractRoute", + "DynamicResource", + "PlainResource", + "PrefixedSubAppResource", + "Resource", + "ResourceRoute", + "StaticResource", + "UrlDispatcher", + "UrlMappingMatchInfo", + "View", + # web_ws + "WebSocketReady", + "WebSocketResponse", + "WSMsgType", + # web + "run_app", +) + + +try: + from ssl import SSLContext +except ImportError: # pragma: no cover + SSLContext = Any # type: ignore[misc,assignment] + +HostSequence = TypingIterable[str] + + +async def _run_app( + app: Union[Application, Awaitable[Application]], + *, + host: Optional[Union[str, HostSequence]] = None, + port: Optional[int] = None, + path: Union[PathLike, TypingIterable[PathLike], None] = None, + sock: Optional[Union[socket.socket, TypingIterable[socket.socket]]] = None, + shutdown_timeout: float = 60.0, + keepalive_timeout: float = 75.0, + ssl_context: Optional[SSLContext] = None, + print: Optional[Callable[..., None]] = print, + backlog: int = 128, + access_log_class: Type[AbstractAccessLogger] = AccessLogger, + access_log_format: str = AccessLogger.LOG_FORMAT, + access_log: Optional[logging.Logger] = access_logger, + handle_signals: bool = True, + reuse_address: Optional[bool] = None, + reuse_port: Optional[bool] = None, + handler_cancellation: bool = False, +) -> None: + # An internal function to actually do all dirty job for application running + if asyncio.iscoroutine(app): + app = await app + + app = cast(Application, app) + + runner = AppRunner( + app, + handle_signals=handle_signals, + access_log_class=access_log_class, + access_log_format=access_log_format, + access_log=access_log, + keepalive_timeout=keepalive_timeout, + handler_cancellation=handler_cancellation, + ) + + await runner.setup() + + sites: List[BaseSite] = [] + + try: + if host is not None: + if isinstance(host, (str, bytes, bytearray, memoryview)): + sites.append( + TCPSite( + runner, + host, + port, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + reuse_address=reuse_address, + reuse_port=reuse_port, + ) + ) + else: + for h in host: + sites.append( + TCPSite( + runner, + h, + port, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + reuse_address=reuse_address, + reuse_port=reuse_port, + ) + ) + elif path is None and sock is None or port is not None: + sites.append( + TCPSite( + runner, + port=port, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + reuse_address=reuse_address, + reuse_port=reuse_port, + ) + ) + + if path is not None: + if isinstance(path, (str, os.PathLike)): + sites.append( + UnixSite( + runner, + path, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + ) + else: + for p in path: + sites.append( + UnixSite( + runner, + p, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + ) + + if sock is not None: + if not isinstance(sock, Iterable): + sites.append( + SockSite( + runner, + sock, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + ) + else: + for s in sock: + sites.append( + SockSite( + runner, + s, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + ) + for site in sites: + await site.start() + + if print: # pragma: no branch + names = sorted(str(s.name) for s in runner.sites) + print( + "======== Running on {} ========\n" + "(Press CTRL+C to quit)".format(", ".join(names)) + ) + + # sleep forever by 1 hour intervals, + while True: + await asyncio.sleep(3600) + finally: + await runner.cleanup() + + +def _cancel_tasks( + to_cancel: Set["asyncio.Task[Any]"], loop: asyncio.AbstractEventLoop +) -> None: + if not to_cancel: + return + + for task in to_cancel: + task.cancel() + + loop.run_until_complete(asyncio.gather(*to_cancel, return_exceptions=True)) + + for task in to_cancel: + if task.cancelled(): + continue + if task.exception() is not None: + loop.call_exception_handler( + { + "message": "unhandled exception during asyncio.run() shutdown", + "exception": task.exception(), + "task": task, + } + ) + + +def run_app( + app: Union[Application, Awaitable[Application]], + *, + debug: bool = False, + host: Optional[Union[str, HostSequence]] = None, + port: Optional[int] = None, + path: Union[PathLike, TypingIterable[PathLike], None] = None, + sock: Optional[Union[socket.socket, TypingIterable[socket.socket]]] = None, + shutdown_timeout: float = 60.0, + keepalive_timeout: float = 75.0, + ssl_context: Optional[SSLContext] = None, + print: Optional[Callable[..., None]] = print, + backlog: int = 128, + access_log_class: Type[AbstractAccessLogger] = AccessLogger, + access_log_format: str = AccessLogger.LOG_FORMAT, + access_log: Optional[logging.Logger] = access_logger, + handle_signals: bool = True, + reuse_address: Optional[bool] = None, + reuse_port: Optional[bool] = None, + handler_cancellation: bool = False, + loop: Optional[asyncio.AbstractEventLoop] = None, +) -> None: + """Run an app locally""" + if loop is None: + loop = asyncio.new_event_loop() + loop.set_debug(debug) + + # Configure if and only if in debugging mode and using the default logger + if loop.get_debug() and access_log and access_log.name == "aiohttp.access": + if access_log.level == logging.NOTSET: + access_log.setLevel(logging.DEBUG) + if not access_log.hasHandlers(): + access_log.addHandler(logging.StreamHandler()) + + main_task = loop.create_task( + _run_app( + app, + host=host, + port=port, + path=path, + sock=sock, + shutdown_timeout=shutdown_timeout, + keepalive_timeout=keepalive_timeout, + ssl_context=ssl_context, + print=print, + backlog=backlog, + access_log_class=access_log_class, + access_log_format=access_log_format, + access_log=access_log, + handle_signals=handle_signals, + reuse_address=reuse_address, + reuse_port=reuse_port, + handler_cancellation=handler_cancellation, + ) + ) + + try: + asyncio.set_event_loop(loop) + loop.run_until_complete(main_task) + except (GracefulExit, KeyboardInterrupt): # pragma: no cover + pass + finally: + _cancel_tasks({main_task}, loop) + _cancel_tasks(asyncio.all_tasks(loop), loop) + loop.run_until_complete(loop.shutdown_asyncgens()) + loop.close() + asyncio.set_event_loop(None) + + +def main(argv: List[str]) -> None: + arg_parser = ArgumentParser( + description="aiohttp.web Application server", prog="aiohttp.web" + ) + arg_parser.add_argument( + "entry_func", + help=( + "Callable returning the `aiohttp.web.Application` instance to " + "run. Should be specified in the 'module:function' syntax." + ), + metavar="entry-func", + ) + arg_parser.add_argument( + "-H", + "--hostname", + help="TCP/IP hostname to serve on (default: %(default)r)", + default="localhost", + ) + arg_parser.add_argument( + "-P", + "--port", + help="TCP/IP port to serve on (default: %(default)r)", + type=int, + default="8080", + ) + arg_parser.add_argument( + "-U", + "--path", + help="Unix file system path to serve on. Specifying a path will cause " + "hostname and port arguments to be ignored.", + ) + args, extra_argv = arg_parser.parse_known_args(argv) + + # Import logic + mod_str, _, func_str = args.entry_func.partition(":") + if not func_str or not mod_str: + arg_parser.error("'entry-func' not in 'module:function' syntax") + if mod_str.startswith("."): + arg_parser.error("relative module names not supported") + try: + module = import_module(mod_str) + except ImportError as ex: + arg_parser.error(f"unable to import {mod_str}: {ex}") + try: + func = getattr(module, func_str) + except AttributeError: + arg_parser.error(f"module {mod_str!r} has no attribute {func_str!r}") + + # Compatibility logic + if args.path is not None and not hasattr(socket, "AF_UNIX"): + arg_parser.error( + "file system paths not supported by your operating" " environment" + ) + + logging.basicConfig(level=logging.DEBUG) + + app = func(extra_argv) + run_app(app, host=args.hostname, port=args.port, path=args.path) + arg_parser.exit(message="Stopped\n") + + +if __name__ == "__main__": # pragma: no branch + main(sys.argv[1:]) # pragma: no cover diff --git a/aiohttp/web_app.py b/aiohttp/web_app.py new file mode 100644 index 0000000..5be9608 --- /dev/null +++ b/aiohttp/web_app.py @@ -0,0 +1,445 @@ +import asyncio +import logging +import warnings +from functools import partial, update_wrapper +from typing import ( + TYPE_CHECKING, + Any, + AsyncIterator, + Awaitable, + Callable, + Dict, + Iterable, + Iterator, + List, + Mapping, + MutableMapping, + Optional, + Sequence, + Type, + TypeVar, + Union, + cast, + final, + overload, +) + +from aiosignal import Signal +from frozenlist import FrozenList + +from . import hdrs +from .helpers import AppKey +from .log import web_logger +from .typedefs import Middleware +from .web_middlewares import _fix_request_current_app +from .web_request import Request +from .web_response import StreamResponse +from .web_routedef import AbstractRouteDef +from .web_urldispatcher import ( + AbstractResource, + AbstractRoute, + Domain, + MaskDomain, + MatchedSubAppResource, + PrefixedSubAppResource, + UrlDispatcher, +) + +__all__ = ("Application", "CleanupError") + + +if TYPE_CHECKING: # pragma: no cover + _AppSignal = Signal[Callable[["Application"], Awaitable[None]]] + _RespPrepareSignal = Signal[Callable[[Request, StreamResponse], Awaitable[None]]] + _Middlewares = FrozenList[Middleware] + _MiddlewaresHandlers = Sequence[Middleware] + _Subapps = List["Application"] +else: + # No type checker mode, skip types + _AppSignal = Signal + _RespPrepareSignal = Signal + _Handler = Callable + _Middlewares = FrozenList + _MiddlewaresHandlers = Sequence + _Subapps = List + +_T = TypeVar("_T") +_U = TypeVar("_U") + + +@final +class Application(MutableMapping[Union[str, AppKey[Any]], Any]): + __slots__ = ( + "logger", + "_debug", + "_router", + "_loop", + "_handler_args", + "_middlewares", + "_middlewares_handlers", + "_run_middlewares", + "_state", + "_frozen", + "_pre_frozen", + "_subapps", + "_on_response_prepare", + "_on_startup", + "_on_shutdown", + "_on_cleanup", + "_client_max_size", + "_cleanup_ctx", + ) + + def __init__( + self, + *, + logger: logging.Logger = web_logger, + middlewares: Iterable[Middleware] = (), + handler_args: Optional[Mapping[str, Any]] = None, + client_max_size: int = 1024**2, + debug: Any = ..., # mypy doesn't support ellipsis + ) -> None: + if debug is not ...: + warnings.warn( + "debug argument is no-op since 4.0 " "and scheduled for removal in 5.0", + DeprecationWarning, + stacklevel=2, + ) + self._router = UrlDispatcher() + self._handler_args = handler_args + self.logger = logger + + self._middlewares: _Middlewares = FrozenList(middlewares) + + # initialized on freezing + self._middlewares_handlers: _MiddlewaresHandlers = tuple() + # initialized on freezing + self._run_middlewares: Optional[bool] = None + + self._state: Dict[Union[AppKey[Any], str], object] = {} + self._frozen = False + self._pre_frozen = False + self._subapps: _Subapps = [] + + self._on_response_prepare: _RespPrepareSignal = Signal(self) + self._on_startup: _AppSignal = Signal(self) + self._on_shutdown: _AppSignal = Signal(self) + self._on_cleanup: _AppSignal = Signal(self) + self._cleanup_ctx = CleanupContext() + self._on_startup.append(self._cleanup_ctx._on_startup) + self._on_cleanup.append(self._cleanup_ctx._on_cleanup) + self._client_max_size = client_max_size + + def __init_subclass__(cls: Type["Application"]) -> None: + raise TypeError( + "Inheritance class {} from web.Application " + "is forbidden".format(cls.__name__) + ) + + # MutableMapping API + + def __eq__(self, other: object) -> bool: + return self is other + + @overload # type: ignore[override] + def __getitem__(self, key: AppKey[_T]) -> _T: + ... + + @overload + def __getitem__(self, key: str) -> Any: + ... + + def __getitem__(self, key: Union[str, AppKey[_T]]) -> Any: + return self._state[key] + + def _check_frozen(self) -> None: + if self._frozen: + raise RuntimeError( + "Changing state of started or joined " "application is forbidden" + ) + + @overload # type: ignore[override] + def __setitem__(self, key: AppKey[_T], value: _T) -> None: + ... + + @overload + def __setitem__(self, key: str, value: Any) -> None: + ... + + def __setitem__(self, key: Union[str, AppKey[_T]], value: Any) -> None: + self._check_frozen() + if not isinstance(key, AppKey): + warnings.warn( + "It is recommended to use web.AppKey instances for keys.\n" + + "https://docs.aiohttp.org/en/stable/web_advanced.html" + + "#application-s-config", + stacklevel=2, + ) + self._state[key] = value + + def __delitem__(self, key: Union[str, AppKey[_T]]) -> None: + self._check_frozen() + del self._state[key] + + def __len__(self) -> int: + return len(self._state) + + def __iter__(self) -> Iterator[Union[str, AppKey[Any]]]: + return iter(self._state) + + @overload # type: ignore[override] + def get(self, key: AppKey[_T], default: None = ...) -> Optional[_T]: + ... + + @overload + def get(self, key: AppKey[_T], default: _U) -> Union[_T, _U]: + ... + + @overload + def get(self, key: str, default: Any = ...) -> Any: + ... + + def get(self, key: Union[str, AppKey[_T]], default: Any = None) -> Any: + return self._state.get(key, default) + + ######## + def _set_loop(self, loop: Optional[asyncio.AbstractEventLoop]) -> None: + warnings.warn( + "_set_loop() is no-op since 4.0 " "and scheduled for removal in 5.0", + DeprecationWarning, + stacklevel=2, + ) + + @property + def pre_frozen(self) -> bool: + return self._pre_frozen + + def pre_freeze(self) -> None: + if self._pre_frozen: + return + + self._pre_frozen = True + self._middlewares.freeze() + self._router.freeze() + self._on_response_prepare.freeze() + self._cleanup_ctx.freeze() + self._on_startup.freeze() + self._on_shutdown.freeze() + self._on_cleanup.freeze() + self._middlewares_handlers = tuple(self._prepare_middleware()) + + # If current app and any subapp do not have middlewares avoid run all + # of the code footprint that it implies, which have a middleware + # hardcoded per app that sets up the current_app attribute. If no + # middlewares are configured the handler will receive the proper + # current_app without needing all of this code. + self._run_middlewares = True if self.middlewares else False + + for subapp in self._subapps: + subapp.pre_freeze() + self._run_middlewares = self._run_middlewares or subapp._run_middlewares + + @property + def frozen(self) -> bool: + return self._frozen + + def freeze(self) -> None: + if self._frozen: + return + + self.pre_freeze() + self._frozen = True + for subapp in self._subapps: + subapp.freeze() + + @property + def debug(self) -> bool: + warnings.warn( + "debug property is deprecated since 4.0" "and scheduled for removal in 5.0", + DeprecationWarning, + stacklevel=2, + ) + return asyncio.get_event_loop().get_debug() + + def _reg_subapp_signals(self, subapp: "Application") -> None: + def reg_handler(signame: str) -> None: + subsig = getattr(subapp, signame) + + async def handler(app: "Application") -> None: + await subsig.send(subapp) + + appsig = getattr(self, signame) + appsig.append(handler) + + reg_handler("on_startup") + reg_handler("on_shutdown") + reg_handler("on_cleanup") + + def add_subapp(self, prefix: str, subapp: "Application") -> AbstractResource: + if not isinstance(prefix, str): + raise TypeError("Prefix must be str") + prefix = prefix.rstrip("/") + if not prefix: + raise ValueError("Prefix cannot be empty") + factory = partial(PrefixedSubAppResource, prefix, subapp) + return self._add_subapp(factory, subapp) + + def _add_subapp( + self, resource_factory: Callable[[], AbstractResource], subapp: "Application" + ) -> AbstractResource: + if self.frozen: + raise RuntimeError("Cannot add sub application to frozen application") + if subapp.frozen: + raise RuntimeError("Cannot add frozen application") + resource = resource_factory() + self.router.register_resource(resource) + self._reg_subapp_signals(subapp) + self._subapps.append(subapp) + subapp.pre_freeze() + return resource + + def add_domain(self, domain: str, subapp: "Application") -> AbstractResource: + if not isinstance(domain, str): + raise TypeError("Domain must be str") + elif "*" in domain: + rule: Domain = MaskDomain(domain) + else: + rule = Domain(domain) + factory = partial(MatchedSubAppResource, rule, subapp) + return self._add_subapp(factory, subapp) + + def add_routes(self, routes: Iterable[AbstractRouteDef]) -> List[AbstractRoute]: + return self.router.add_routes(routes) + + @property + def on_response_prepare(self) -> _RespPrepareSignal: + return self._on_response_prepare + + @property + def on_startup(self) -> _AppSignal: + return self._on_startup + + @property + def on_shutdown(self) -> _AppSignal: + return self._on_shutdown + + @property + def on_cleanup(self) -> _AppSignal: + return self._on_cleanup + + @property + def cleanup_ctx(self) -> "CleanupContext": + return self._cleanup_ctx + + @property + def router(self) -> UrlDispatcher: + return self._router + + @property + def middlewares(self) -> _Middlewares: + return self._middlewares + + async def startup(self) -> None: + """Causes on_startup signal + + Should be called in the event loop along with the request handler. + """ + await self.on_startup.send(self) + + async def shutdown(self) -> None: + """Causes on_shutdown signal + + Should be called before cleanup() + """ + await self.on_shutdown.send(self) + + async def cleanup(self) -> None: + """Causes on_cleanup signal + + Should be called after shutdown() + """ + if self.on_cleanup.frozen: + await self.on_cleanup.send(self) + else: + # If an exception occurs in startup, ensure cleanup contexts are completed. + await self._cleanup_ctx._on_cleanup(self) + + def _prepare_middleware(self) -> Iterator[Middleware]: + yield from reversed(self._middlewares) + yield _fix_request_current_app(self) + + async def _handle(self, request: Request) -> StreamResponse: + match_info = await self._router.resolve(request) + match_info.add_app(self) + match_info.freeze() + + resp = None + request._match_info = match_info + expect = request.headers.get(hdrs.EXPECT) + if expect: + resp = await match_info.expect_handler(request) + await request.writer.drain() + + if resp is None: + handler = match_info.handler + + if self._run_middlewares: + for app in match_info.apps[::-1]: + assert app.pre_frozen, "middleware handlers are not ready" + for m in app._middlewares_handlers: + handler = update_wrapper(partial(m, handler=handler), handler) + + resp = await handler(request) + + return resp + + def __call__(self) -> "Application": + """gunicorn compatibility""" + return self + + def __repr__(self) -> str: + return f"" + + def __bool__(self) -> bool: + return True + + +class CleanupError(RuntimeError): + @property + def exceptions(self) -> List[BaseException]: + return cast(List[BaseException], self.args[1]) + + +if TYPE_CHECKING: # pragma: no cover + _CleanupContextBase = FrozenList[Callable[[Application], AsyncIterator[None]]] +else: + _CleanupContextBase = FrozenList + + +class CleanupContext(_CleanupContextBase): + def __init__(self) -> None: + super().__init__() + self._exits: List[AsyncIterator[None]] = [] + + async def _on_startup(self, app: Application) -> None: + for cb in self: + it = cb(app).__aiter__() + await it.__anext__() + self._exits.append(it) + + async def _on_cleanup(self, app: Application) -> None: + errors = [] + for it in reversed(self._exits): + try: + await it.__anext__() + except StopAsyncIteration: + pass + except Exception as exc: + errors.append(exc) + else: + errors.append(RuntimeError(f"{it!r} has more than one 'yield'")) + if errors: + if len(errors) == 1: + raise errors[0] + else: + raise CleanupError("Multiple errors on cleanup stage", errors) diff --git a/aiohttp/web_exceptions.py b/aiohttp/web_exceptions.py new file mode 100644 index 0000000..332ca9f --- /dev/null +++ b/aiohttp/web_exceptions.py @@ -0,0 +1,509 @@ +import warnings +from http import HTTPStatus +from typing import Any, Iterable, Optional, Set, Tuple + +from multidict import CIMultiDict +from yarl import URL + +from . import hdrs +from .helpers import CookieMixin +from .typedefs import LooseHeaders, StrOrURL + +__all__ = ( + "HTTPException", + "HTTPError", + "HTTPRedirection", + "HTTPSuccessful", + "HTTPOk", + "HTTPCreated", + "HTTPAccepted", + "HTTPNonAuthoritativeInformation", + "HTTPNoContent", + "HTTPResetContent", + "HTTPPartialContent", + "HTTPMove", + "HTTPMultipleChoices", + "HTTPMovedPermanently", + "HTTPFound", + "HTTPSeeOther", + "HTTPNotModified", + "HTTPUseProxy", + "HTTPTemporaryRedirect", + "HTTPPermanentRedirect", + "HTTPClientError", + "HTTPBadRequest", + "HTTPUnauthorized", + "HTTPPaymentRequired", + "HTTPForbidden", + "HTTPNotFound", + "HTTPMethodNotAllowed", + "HTTPNotAcceptable", + "HTTPProxyAuthenticationRequired", + "HTTPRequestTimeout", + "HTTPConflict", + "HTTPGone", + "HTTPLengthRequired", + "HTTPPreconditionFailed", + "HTTPRequestEntityTooLarge", + "HTTPRequestURITooLong", + "HTTPUnsupportedMediaType", + "HTTPRequestRangeNotSatisfiable", + "HTTPExpectationFailed", + "HTTPMisdirectedRequest", + "HTTPUnprocessableEntity", + "HTTPFailedDependency", + "HTTPUpgradeRequired", + "HTTPPreconditionRequired", + "HTTPTooManyRequests", + "HTTPRequestHeaderFieldsTooLarge", + "HTTPUnavailableForLegalReasons", + "HTTPServerError", + "HTTPInternalServerError", + "HTTPNotImplemented", + "HTTPBadGateway", + "HTTPServiceUnavailable", + "HTTPGatewayTimeout", + "HTTPVersionNotSupported", + "HTTPVariantAlsoNegotiates", + "HTTPInsufficientStorage", + "HTTPNotExtended", + "HTTPNetworkAuthenticationRequired", +) + + +############################################################ +# HTTP Exceptions +############################################################ + + +class HTTPException(CookieMixin, Exception): + # You should set in subclasses: + # status = 200 + + status_code = -1 + empty_body = False + default_reason = "" # Initialized at the end of the module + + def __init__( + self, + *, + headers: Optional[LooseHeaders] = None, + reason: Optional[str] = None, + text: Optional[str] = None, + content_type: Optional[str] = None, + ) -> None: + super().__init__() + if reason is None: + reason = self.default_reason + + if text is None: + if not self.empty_body: + text = f"{self.status_code}: {reason}" + else: + if self.empty_body: + warnings.warn( + "text argument is deprecated for HTTP status {} " + "since 4.0 and scheduled for removal in 5.0 (#3462)," + "the response should be provided without a body".format( + self.status_code + ), + DeprecationWarning, + stacklevel=2, + ) + + if headers is not None: + real_headers = CIMultiDict(headers) + else: + real_headers = CIMultiDict() + + if content_type is not None: + if not text: + warnings.warn( + "content_type without text is deprecated " + "since 4.0 and scheduled for removal in 5.0 " + "(#3462)", + DeprecationWarning, + stacklevel=2, + ) + real_headers[hdrs.CONTENT_TYPE] = content_type + elif hdrs.CONTENT_TYPE not in real_headers and text: + real_headers[hdrs.CONTENT_TYPE] = "text/plain" + + self._reason = reason + self._text = text + self._headers = real_headers + self.args = () + + def __bool__(self) -> bool: + return True + + @property + def status(self) -> int: + return self.status_code + + @property + def reason(self) -> str: + return self._reason + + @property + def text(self) -> Optional[str]: + return self._text + + @property + def headers(self) -> "CIMultiDict[str]": + return self._headers + + def __str__(self) -> str: + return self.reason + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}: {self.reason}>" + + __reduce__ = object.__reduce__ + + def __getnewargs__(self) -> Tuple[Any, ...]: + return self.args + + +class HTTPError(HTTPException): + """Base class for exceptions with status codes in the 400s and 500s.""" + + +class HTTPRedirection(HTTPException): + """Base class for exceptions with status codes in the 300s.""" + + +class HTTPSuccessful(HTTPException): + """Base class for exceptions with status codes in the 200s.""" + + +class HTTPOk(HTTPSuccessful): + status_code = 200 + + +class HTTPCreated(HTTPSuccessful): + status_code = 201 + + +class HTTPAccepted(HTTPSuccessful): + status_code = 202 + + +class HTTPNonAuthoritativeInformation(HTTPSuccessful): + status_code = 203 + + +class HTTPNoContent(HTTPSuccessful): + status_code = 204 + empty_body = True + + +class HTTPResetContent(HTTPSuccessful): + status_code = 205 + empty_body = True + + +class HTTPPartialContent(HTTPSuccessful): + status_code = 206 + + +############################################################ +# 3xx redirection +############################################################ + + +class HTTPMove(HTTPRedirection): + def __init__( + self, + location: StrOrURL, + *, + headers: Optional[LooseHeaders] = None, + reason: Optional[str] = None, + text: Optional[str] = None, + content_type: Optional[str] = None, + ) -> None: + if not location: + raise ValueError("HTTP redirects need a location to redirect to.") + super().__init__( + headers=headers, reason=reason, text=text, content_type=content_type + ) + self._location = URL(location) + self.headers["Location"] = str(self.location) + + @property + def location(self) -> URL: + return self._location + + +class HTTPMultipleChoices(HTTPMove): + status_code = 300 + + +class HTTPMovedPermanently(HTTPMove): + status_code = 301 + + +class HTTPFound(HTTPMove): + status_code = 302 + + +# This one is safe after a POST (the redirected location will be +# retrieved with GET): +class HTTPSeeOther(HTTPMove): + status_code = 303 + + +class HTTPNotModified(HTTPRedirection): + # FIXME: this should include a date or etag header + status_code = 304 + empty_body = True + + +class HTTPUseProxy(HTTPMove): + # Not a move, but looks a little like one + status_code = 305 + + +class HTTPTemporaryRedirect(HTTPMove): + status_code = 307 + + +class HTTPPermanentRedirect(HTTPMove): + status_code = 308 + + +############################################################ +# 4xx client error +############################################################ + + +class HTTPClientError(HTTPError): + pass + + +class HTTPBadRequest(HTTPClientError): + status_code = 400 + + +class HTTPUnauthorized(HTTPClientError): + status_code = 401 + + +class HTTPPaymentRequired(HTTPClientError): + status_code = 402 + + +class HTTPForbidden(HTTPClientError): + status_code = 403 + + +class HTTPNotFound(HTTPClientError): + status_code = 404 + + +class HTTPMethodNotAllowed(HTTPClientError): + status_code = 405 + + def __init__( + self, + method: str, + allowed_methods: Iterable[str], + *, + headers: Optional[LooseHeaders] = None, + reason: Optional[str] = None, + text: Optional[str] = None, + content_type: Optional[str] = None, + ) -> None: + allow = ",".join(sorted(allowed_methods)) + super().__init__( + headers=headers, reason=reason, text=text, content_type=content_type + ) + self.headers["Allow"] = allow + self._allowed: Set[str] = set(allowed_methods) + self._method = method + + @property + def allowed_methods(self) -> Set[str]: + return self._allowed + + @property + def method(self) -> str: + return self._method + + +class HTTPNotAcceptable(HTTPClientError): + status_code = 406 + + +class HTTPProxyAuthenticationRequired(HTTPClientError): + status_code = 407 + + +class HTTPRequestTimeout(HTTPClientError): + status_code = 408 + + +class HTTPConflict(HTTPClientError): + status_code = 409 + + +class HTTPGone(HTTPClientError): + status_code = 410 + + +class HTTPLengthRequired(HTTPClientError): + status_code = 411 + + +class HTTPPreconditionFailed(HTTPClientError): + status_code = 412 + + +class HTTPRequestEntityTooLarge(HTTPClientError): + status_code = 413 + + def __init__(self, max_size: int, actual_size: int, **kwargs: Any) -> None: + kwargs.setdefault( + "text", + "Maximum request body size {} exceeded, " + "actual body size {}".format(max_size, actual_size), + ) + super().__init__(**kwargs) + + +class HTTPRequestURITooLong(HTTPClientError): + status_code = 414 + + +class HTTPUnsupportedMediaType(HTTPClientError): + status_code = 415 + + +class HTTPRequestRangeNotSatisfiable(HTTPClientError): + status_code = 416 + + +class HTTPExpectationFailed(HTTPClientError): + status_code = 417 + + +class HTTPMisdirectedRequest(HTTPClientError): + status_code = 421 + + +class HTTPUnprocessableEntity(HTTPClientError): + status_code = 422 + + +class HTTPFailedDependency(HTTPClientError): + status_code = 424 + + +class HTTPUpgradeRequired(HTTPClientError): + status_code = 426 + + +class HTTPPreconditionRequired(HTTPClientError): + status_code = 428 + + +class HTTPTooManyRequests(HTTPClientError): + status_code = 429 + + +class HTTPRequestHeaderFieldsTooLarge(HTTPClientError): + status_code = 431 + + +class HTTPUnavailableForLegalReasons(HTTPClientError): + status_code = 451 + + def __init__( + self, + link: StrOrURL, + *, + headers: Optional[LooseHeaders] = None, + reason: Optional[str] = None, + text: Optional[str] = None, + content_type: Optional[str] = None, + ) -> None: + super().__init__( + headers=headers, reason=reason, text=text, content_type=content_type + ) + self.headers["Link"] = f'<{str(link)}>; rel="blocked-by"' + self._link = URL(link) + + @property + def link(self) -> URL: + return self._link + + +############################################################ +# 5xx Server Error +############################################################ +# Response status codes beginning with the digit "5" indicate cases in +# which the server is aware that it has erred or is incapable of +# performing the request. Except when responding to a HEAD request, the +# server SHOULD include an entity containing an explanation of the error +# situation, and whether it is a temporary or permanent condition. User +# agents SHOULD display any included entity to the user. These response +# codes are applicable to any request method. + + +class HTTPServerError(HTTPError): + pass + + +class HTTPInternalServerError(HTTPServerError): + status_code = 500 + + +class HTTPNotImplemented(HTTPServerError): + status_code = 501 + + +class HTTPBadGateway(HTTPServerError): + status_code = 502 + + +class HTTPServiceUnavailable(HTTPServerError): + status_code = 503 + + +class HTTPGatewayTimeout(HTTPServerError): + status_code = 504 + + +class HTTPVersionNotSupported(HTTPServerError): + status_code = 505 + + +class HTTPVariantAlsoNegotiates(HTTPServerError): + status_code = 506 + + +class HTTPInsufficientStorage(HTTPServerError): + status_code = 507 + + +class HTTPNotExtended(HTTPServerError): + status_code = 510 + + +class HTTPNetworkAuthenticationRequired(HTTPServerError): + status_code = 511 + + +def _initialize_default_reason() -> None: + for obj in globals().values(): + if isinstance(obj, type) and issubclass(obj, HTTPException): + if obj.status_code >= 0: + try: + status = HTTPStatus(obj.status_code) + obj.default_reason = status.phrase + except ValueError: + pass + + +_initialize_default_reason() +del _initialize_default_reason diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py new file mode 100644 index 0000000..0fbdeab --- /dev/null +++ b/aiohttp/web_fileresponse.py @@ -0,0 +1,281 @@ +import asyncio +import mimetypes +import os +import pathlib +from typing import ( + IO, + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Final, + Optional, + Tuple, + cast, +) + +from . import hdrs +from .abc import AbstractStreamWriter +from .helpers import ETAG_ANY, ETag +from .typedefs import LooseHeaders, PathLike +from .web_exceptions import ( + HTTPNotModified, + HTTPPartialContent, + HTTPPreconditionFailed, + HTTPRequestRangeNotSatisfiable, +) +from .web_response import StreamResponse + +__all__ = ("FileResponse",) + +if TYPE_CHECKING: # pragma: no cover + from .web_request import BaseRequest + + +_T_OnChunkSent = Optional[Callable[[bytes], Awaitable[None]]] + + +NOSENDFILE: Final[bool] = bool(os.environ.get("AIOHTTP_NOSENDFILE")) + + +class FileResponse(StreamResponse): + """A response object can be used to send files.""" + + def __init__( + self, + path: PathLike, + chunk_size: int = 256 * 1024, + status: int = 200, + reason: Optional[str] = None, + headers: Optional[LooseHeaders] = None, + ) -> None: + super().__init__(status=status, reason=reason, headers=headers) + + self._path = pathlib.Path(path) + self._chunk_size = chunk_size + + async def _sendfile_fallback( + self, writer: AbstractStreamWriter, fobj: IO[Any], offset: int, count: int + ) -> AbstractStreamWriter: + # To keep memory usage low,fobj is transferred in chunks + # controlled by the constructor's chunk_size argument. + + chunk_size = self._chunk_size + loop = asyncio.get_event_loop() + + await loop.run_in_executor(None, fobj.seek, offset) + + chunk = await loop.run_in_executor(None, fobj.read, chunk_size) + while chunk: + await writer.write(chunk) + count = count - chunk_size + if count <= 0: + break + chunk = await loop.run_in_executor(None, fobj.read, min(chunk_size, count)) + + await writer.drain() + return writer + + async def _sendfile( + self, request: "BaseRequest", fobj: IO[Any], offset: int, count: int + ) -> AbstractStreamWriter: + writer = await super().prepare(request) + assert writer is not None + + if NOSENDFILE or self.compression: + return await self._sendfile_fallback(writer, fobj, offset, count) + + loop = request._loop + transport = request.transport + assert transport is not None + + try: + await loop.sendfile(transport, fobj, offset, count) + except NotImplementedError: + return await self._sendfile_fallback(writer, fobj, offset, count) + + await super().write_eof() + return writer + + @staticmethod + def _strong_etag_match(etag_value: str, etags: Tuple[ETag, ...]) -> bool: + if len(etags) == 1 and etags[0].value == ETAG_ANY: + return True + return any(etag.value == etag_value for etag in etags if not etag.is_weak) + + async def _not_modified( + self, request: "BaseRequest", etag_value: str, last_modified: float + ) -> Optional[AbstractStreamWriter]: + self.set_status(HTTPNotModified.status_code) + self._length_check = False + self.etag = etag_value # type: ignore[assignment] + self.last_modified = last_modified # type: ignore[assignment] + # Delete any Content-Length headers provided by user. HTTP 304 + # should always have empty response body + return await super().prepare(request) + + async def _precondition_failed( + self, request: "BaseRequest" + ) -> Optional[AbstractStreamWriter]: + self.set_status(HTTPPreconditionFailed.status_code) + self.content_length = 0 + return await super().prepare(request) + + async def prepare(self, request: "BaseRequest") -> Optional[AbstractStreamWriter]: + filepath = self._path + + gzip = False + if "gzip" in request.headers.get(hdrs.ACCEPT_ENCODING, ""): + gzip_path = filepath.with_name(filepath.name + ".gz") + + if gzip_path.is_file(): + filepath = gzip_path + gzip = True + + loop = asyncio.get_event_loop() + st: os.stat_result = await loop.run_in_executor(None, filepath.stat) + + etag_value = f"{st.st_mtime_ns:x}-{st.st_size:x}" + last_modified = st.st_mtime + + # https://tools.ietf.org/html/rfc7232#section-6 + ifmatch = request.if_match + if ifmatch is not None and not self._strong_etag_match(etag_value, ifmatch): + return await self._precondition_failed(request) + + unmodsince = request.if_unmodified_since + if ( + unmodsince is not None + and ifmatch is None + and st.st_mtime > unmodsince.timestamp() + ): + return await self._precondition_failed(request) + + ifnonematch = request.if_none_match + if ifnonematch is not None and self._strong_etag_match(etag_value, ifnonematch): + return await self._not_modified(request, etag_value, last_modified) + + modsince = request.if_modified_since + if ( + modsince is not None + and ifnonematch is None + and st.st_mtime <= modsince.timestamp() + ): + return await self._not_modified(request, etag_value, last_modified) + + ct = None + if hdrs.CONTENT_TYPE not in self.headers: + ct, encoding = mimetypes.guess_type(str(filepath)) + if not ct: + ct = "application/octet-stream" + else: + encoding = "gzip" if gzip else None + + status = self._status + file_size = st.st_size + count = file_size + + start = None + + ifrange = request.if_range + if ifrange is None or st.st_mtime <= ifrange.timestamp(): + # If-Range header check: + # condition = cached date >= last modification date + # return 206 if True else 200. + # if False: + # Range header would not be processed, return 200 + # if True but Range header missing + # return 200 + try: + rng = request.http_range + start = rng.start + end = rng.stop + except ValueError: + # https://tools.ietf.org/html/rfc7233: + # A server generating a 416 (Range Not Satisfiable) response to + # a byte-range request SHOULD send a Content-Range header field + # with an unsatisfied-range value. + # The complete-length in a 416 response indicates the current + # length of the selected representation. + # + # Will do the same below. Many servers ignore this and do not + # send a Content-Range header with HTTP 416 + self.headers[hdrs.CONTENT_RANGE] = f"bytes */{file_size}" + self.set_status(HTTPRequestRangeNotSatisfiable.status_code) + return await super().prepare(request) + + # If a range request has been made, convert start, end slice + # notation into file pointer offset and count + if start is not None or end is not None: + if start < 0 and end is None: # return tail of file + start += file_size + if start < 0: + # if Range:bytes=-1000 in request header but file size + # is only 200, there would be trouble without this + start = 0 + count = file_size - start + else: + # rfc7233:If the last-byte-pos value is + # absent, or if the value is greater than or equal to + # the current length of the representation data, + # the byte range is interpreted as the remainder + # of the representation (i.e., the server replaces the + # value of last-byte-pos with a value that is one less than + # the current length of the selected representation). + count = ( + min(end if end is not None else file_size, file_size) - start + ) + + if start >= file_size: + # HTTP 416 should be returned in this case. + # + # According to https://tools.ietf.org/html/rfc7233: + # If a valid byte-range-set includes at least one + # byte-range-spec with a first-byte-pos that is less than + # the current length of the representation, or at least one + # suffix-byte-range-spec with a non-zero suffix-length, + # then the byte-range-set is satisfiable. Otherwise, the + # byte-range-set is unsatisfiable. + self.headers[hdrs.CONTENT_RANGE] = f"bytes */{file_size}" + self.set_status(HTTPRequestRangeNotSatisfiable.status_code) + return await super().prepare(request) + + status = HTTPPartialContent.status_code + # Even though you are sending the whole file, you should still + # return a HTTP 206 for a Range request. + self.set_status(status) + + if ct: + self.content_type = ct + if encoding: + self.headers[hdrs.CONTENT_ENCODING] = encoding + if gzip: + self.headers[hdrs.VARY] = hdrs.ACCEPT_ENCODING + + self.etag = etag_value # type: ignore[assignment] + self.last_modified = st.st_mtime # type: ignore[assignment] + self.content_length = count + + self.headers[hdrs.ACCEPT_RANGES] = "bytes" + + real_start = cast(int, start) + + if status == HTTPPartialContent.status_code: + self.headers[hdrs.CONTENT_RANGE] = "bytes {}-{}/{}".format( + real_start, real_start + count - 1, file_size + ) + + # If we are sending 0 bytes calling sendfile() will throw a ValueError + if count == 0 or request.method == hdrs.METH_HEAD or self.status in [204, 304]: + return await super().prepare(request) + + fobj = await loop.run_in_executor(None, filepath.open, "rb") + if start: # be aware that start could be None or int=0 here. + offset = start + else: + offset = 0 + + try: + return await self._sendfile(request, fobj, offset, count) + finally: + await asyncio.shield(loop.run_in_executor(None, fobj.close)) diff --git a/aiohttp/web_log.py b/aiohttp/web_log.py new file mode 100644 index 0000000..633e9e3 --- /dev/null +++ b/aiohttp/web_log.py @@ -0,0 +1,213 @@ +import datetime +import functools +import logging +import os +import re +import time as time_mod +from collections import namedtuple +from typing import Any, Callable, Dict, Iterable, List, Tuple # noqa + +from .abc import AbstractAccessLogger +from .web_request import BaseRequest +from .web_response import StreamResponse + +KeyMethod = namedtuple("KeyMethod", "key method") + + +class AccessLogger(AbstractAccessLogger): + """Helper object to log access. + + Usage: + log = logging.getLogger("spam") + log_format = "%a %{User-Agent}i" + access_logger = AccessLogger(log, log_format) + access_logger.log(request, response, time) + + Format: + %% The percent sign + %a Remote IP-address (IP-address of proxy if using reverse proxy) + %t Time when the request was started to process + %P The process ID of the child that serviced the request + %r First line of request + %s Response status code + %b Size of response in bytes, including HTTP headers + %T Time taken to serve the request, in seconds + %Tf Time taken to serve the request, in seconds with floating fraction + in .06f format + %D Time taken to serve the request, in microseconds + %{FOO}i request.headers['FOO'] + %{FOO}o response.headers['FOO'] + %{FOO}e os.environ['FOO'] + + """ + + LOG_FORMAT_MAP = { + "a": "remote_address", + "t": "request_start_time", + "P": "process_id", + "r": "first_request_line", + "s": "response_status", + "b": "response_size", + "T": "request_time", + "Tf": "request_time_frac", + "D": "request_time_micro", + "i": "request_header", + "o": "response_header", + } + + LOG_FORMAT = '%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"' + FORMAT_RE = re.compile(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrsbOD]|Tf?)") + CLEANUP_RE = re.compile(r"(%[^s])") + _FORMAT_CACHE: Dict[str, Tuple[str, List[KeyMethod]]] = {} + + def __init__(self, logger: logging.Logger, log_format: str = LOG_FORMAT) -> None: + """Initialise the logger. + + logger is a logger object to be used for logging. + log_format is a string with apache compatible log format description. + + """ + super().__init__(logger, log_format=log_format) + + _compiled_format = AccessLogger._FORMAT_CACHE.get(log_format) + if not _compiled_format: + _compiled_format = self.compile_format(log_format) + AccessLogger._FORMAT_CACHE[log_format] = _compiled_format + + self._log_format, self._methods = _compiled_format + + def compile_format(self, log_format: str) -> Tuple[str, List[KeyMethod]]: + """Translate log_format into form usable by modulo formatting + + All known atoms will be replaced with %s + Also methods for formatting of those atoms will be added to + _methods in appropriate order + + For example we have log_format = "%a %t" + This format will be translated to "%s %s" + Also contents of _methods will be + [self._format_a, self._format_t] + These method will be called and results will be passed + to translated string format. + + Each _format_* method receive 'args' which is list of arguments + given to self.log + + Exceptions are _format_e, _format_i and _format_o methods which + also receive key name (by functools.partial) + + """ + # list of (key, method) tuples, we don't use an OrderedDict as users + # can repeat the same key more than once + methods = list() + + for atom in self.FORMAT_RE.findall(log_format): + if atom[1] == "": + format_key1 = self.LOG_FORMAT_MAP[atom[0]] + m = getattr(AccessLogger, "_format_%s" % atom[0]) + key_method = KeyMethod(format_key1, m) + else: + format_key2 = (self.LOG_FORMAT_MAP[atom[2]], atom[1]) + m = getattr(AccessLogger, "_format_%s" % atom[2]) + key_method = KeyMethod(format_key2, functools.partial(m, atom[1])) + + methods.append(key_method) + + log_format = self.FORMAT_RE.sub(r"%s", log_format) + log_format = self.CLEANUP_RE.sub(r"%\1", log_format) + return log_format, methods + + @staticmethod + def _format_i( + key: str, request: BaseRequest, response: StreamResponse, time: float + ) -> str: + if request is None: + return "(no headers)" + + # suboptimal, make istr(key) once + return request.headers.get(key, "-") + + @staticmethod + def _format_o( + key: str, request: BaseRequest, response: StreamResponse, time: float + ) -> str: + # suboptimal, make istr(key) once + return response.headers.get(key, "-") + + @staticmethod + def _format_a(request: BaseRequest, response: StreamResponse, time: float) -> str: + if request is None: + return "-" + ip = request.remote + return ip if ip is not None else "-" + + @staticmethod + def _format_t(request: BaseRequest, response: StreamResponse, time: float) -> str: + tz = datetime.timezone(datetime.timedelta(seconds=-time_mod.timezone)) + now = datetime.datetime.now(tz) + start_time = now - datetime.timedelta(seconds=time) + return start_time.strftime("[%d/%b/%Y:%H:%M:%S %z]") + + @staticmethod + def _format_P(request: BaseRequest, response: StreamResponse, time: float) -> str: + return "<%s>" % os.getpid() + + @staticmethod + def _format_r(request: BaseRequest, response: StreamResponse, time: float) -> str: + if request is None: + return "-" + return "{} {} HTTP/{}.{}".format( + request.method, + request.path_qs, + request.version.major, + request.version.minor, + ) + + @staticmethod + def _format_s(request: BaseRequest, response: StreamResponse, time: float) -> int: + return response.status + + @staticmethod + def _format_b(request: BaseRequest, response: StreamResponse, time: float) -> int: + return response.body_length + + @staticmethod + def _format_T(request: BaseRequest, response: StreamResponse, time: float) -> str: + return str(round(time)) + + @staticmethod + def _format_Tf(request: BaseRequest, response: StreamResponse, time: float) -> str: + return "%06f" % time + + @staticmethod + def _format_D(request: BaseRequest, response: StreamResponse, time: float) -> str: + return str(round(time * 1000000)) + + def _format_line( + self, request: BaseRequest, response: StreamResponse, time: float + ) -> Iterable[Tuple[str, Callable[[BaseRequest, StreamResponse, float], str]]]: + return [(key, method(request, response, time)) for key, method in self._methods] + + def log(self, request: BaseRequest, response: StreamResponse, time: float) -> None: + if not self.logger.isEnabledFor(logging.INFO): + # Avoid formatting the log line if it will not be emitted. + return + try: + fmt_info = self._format_line(request, response, time) + + values = list() + extra = dict() + for key, value in fmt_info: + values.append(value) + + if key.__class__ is str: + extra[key] = value + else: + k1, k2 = key # type: ignore[misc] + dct = extra.get(k1, {}) # type: ignore[var-annotated,has-type] + dct[k2] = value # type: ignore[index,has-type] + extra[k1] = dct # type: ignore[has-type,assignment] + + self.logger.info(self._log_format % tuple(values), extra=extra) + except Exception: + self.logger.exception("Error in logging") diff --git a/aiohttp/web_middlewares.py b/aiohttp/web_middlewares.py new file mode 100644 index 0000000..4583824 --- /dev/null +++ b/aiohttp/web_middlewares.py @@ -0,0 +1,121 @@ +import re +import warnings +from typing import TYPE_CHECKING, Tuple, Type, TypeVar + +from .typedefs import Handler, Middleware +from .web_exceptions import HTTPMove, HTTPPermanentRedirect +from .web_request import Request +from .web_response import StreamResponse +from .web_urldispatcher import SystemRoute + +__all__ = ( + "middleware", + "normalize_path_middleware", +) + +if TYPE_CHECKING: # pragma: no cover + from .web_app import Application + +_Func = TypeVar("_Func") + + +async def _check_request_resolves(request: Request, path: str) -> Tuple[bool, Request]: + alt_request = request.clone(rel_url=path) + + match_info = await request.app.router.resolve(alt_request) + alt_request._match_info = match_info + + if match_info.http_exception is None: + return True, alt_request + + return False, request + + +def middleware(f: _Func) -> _Func: + warnings.warn( + "Middleware decorator is deprecated since 4.0 " + "and its behaviour is default, " + "you can simply remove this decorator.", + DeprecationWarning, + stacklevel=2, + ) + return f + + +def normalize_path_middleware( + *, + append_slash: bool = True, + remove_slash: bool = False, + merge_slashes: bool = True, + redirect_class: Type[HTTPMove] = HTTPPermanentRedirect, +) -> Middleware: + """Factory for producing a middleware that normalizes the path of a request. + + Normalizing means: + - Add or remove a trailing slash to the path. + - Double slashes are replaced by one. + + The middleware returns as soon as it finds a path that resolves + correctly. The order if both merge and append/remove are enabled is + 1) merge slashes + 2) append/remove slash + 3) both merge slashes and append/remove slash. + If the path resolves with at least one of those conditions, it will + redirect to the new path. + + Only one of `append_slash` and `remove_slash` can be enabled. If both + are `True` the factory will raise an assertion error + + If `append_slash` is `True` the middleware will append a slash when + needed. If a resource is defined with trailing slash and the request + comes without it, it will append it automatically. + + If `remove_slash` is `True`, `append_slash` must be `False`. When enabled + the middleware will remove trailing slashes and redirect if the resource + is defined + + If merge_slashes is True, merge multiple consecutive slashes in the + path into one. + """ + correct_configuration = not (append_slash and remove_slash) + assert correct_configuration, "Cannot both remove and append slash" + + async def impl(request: Request, handler: Handler) -> StreamResponse: + if isinstance(request.match_info.route, SystemRoute): + paths_to_check = [] + if "?" in request.raw_path: + path, query = request.raw_path.split("?", 1) + query = "?" + query + else: + query = "" + path = request.raw_path + + if merge_slashes: + paths_to_check.append(re.sub("//+", "/", path)) + if append_slash and not request.path.endswith("/"): + paths_to_check.append(path + "/") + if remove_slash and request.path.endswith("/"): + paths_to_check.append(path[:-1]) + if merge_slashes and append_slash: + paths_to_check.append(re.sub("//+", "/", path + "/")) + if merge_slashes and remove_slash and path.endswith("/"): + merged_slashes = re.sub("//+", "/", path) + paths_to_check.append(merged_slashes[:-1]) + + for path in paths_to_check: + path = re.sub("^//+", "/", path) # SECURITY: GHSA-v6wp-4m6f-gcjg + resolves, request = await _check_request_resolves(request, path) + if resolves: + raise redirect_class(request.raw_path + query) + + return await handler(request) + + return impl + + +def _fix_request_current_app(app: "Application") -> Middleware: + async def impl(request: Request, handler: Handler) -> StreamResponse: + with request.match_info.set_current_app(app): + return await handler(request) + + return impl diff --git a/aiohttp/web_protocol.py b/aiohttp/web_protocol.py new file mode 100644 index 0000000..ae0aaa8 --- /dev/null +++ b/aiohttp/web_protocol.py @@ -0,0 +1,711 @@ +import asyncio +import asyncio.streams +import dataclasses +import traceback +from collections import deque +from contextlib import suppress +from html import escape as html_escape +from http import HTTPStatus +from logging import Logger +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Deque, + Optional, + Sequence, + Tuple, + Type, + Union, + cast, +) + +import yarl + +from .abc import AbstractAccessLogger, AbstractAsyncAccessLogger, AbstractStreamWriter +from .base_protocol import BaseProtocol +from .helpers import ceil_timeout +from .http import ( + HttpProcessingError, + HttpRequestParser, + HttpVersion10, + RawRequestMessage, + StreamWriter, +) +from .log import access_logger, server_logger +from .streams import EMPTY_PAYLOAD, StreamReader +from .tcp_helpers import tcp_keepalive +from .web_exceptions import HTTPException +from .web_log import AccessLogger +from .web_request import BaseRequest +from .web_response import Response, StreamResponse + +__all__ = ("RequestHandler", "RequestPayloadError", "PayloadAccessError") + +if TYPE_CHECKING: # pragma: no cover + from .web_server import Server + + +_RequestFactory = Callable[ + [ + RawRequestMessage, + StreamReader, + "RequestHandler", + AbstractStreamWriter, + "asyncio.Task[None]", + ], + BaseRequest, +] + +_RequestHandler = Callable[[BaseRequest], Awaitable[StreamResponse]] +_AnyAbstractAccessLogger = Union[ + Type[AbstractAsyncAccessLogger], + Type[AbstractAccessLogger], +] + +ERROR = RawRequestMessage( + "UNKNOWN", + "/", + HttpVersion10, + {}, # type: ignore[arg-type] + {}, # type: ignore[arg-type] + True, + None, + False, + False, + yarl.URL("/"), +) + + +class RequestPayloadError(Exception): + """Payload parsing error.""" + + +class PayloadAccessError(Exception): + """Payload was accessed after response was sent.""" + + +class AccessLoggerWrapper(AbstractAsyncAccessLogger): + """Wrap an AbstractAccessLogger so it behaves like an AbstractAsyncAccessLogger.""" + + def __init__( + self, access_logger: AbstractAccessLogger, loop: asyncio.AbstractEventLoop + ) -> None: + self.access_logger = access_logger + self._loop = loop + super().__init__() + + async def log( + self, request: BaseRequest, response: StreamResponse, request_start: float + ) -> None: + self.access_logger.log(request, response, self._loop.time() - request_start) + + +@dataclasses.dataclass(frozen=True) +class _ErrInfo: + status: int + exc: BaseException + message: str + + +_MsgType = Tuple[Union[RawRequestMessage, _ErrInfo], StreamReader] + + +class RequestHandler(BaseProtocol): + """HTTP protocol implementation. + + RequestHandler handles incoming HTTP request. It reads request line, + request headers and request payload and calls handle_request() method. + By default it always returns with 404 response. + + RequestHandler handles errors in incoming request, like bad + status line, bad headers or incomplete payload. If any error occurs, + connection gets closed. + + keepalive_timeout -- number of seconds before closing + keep-alive connection + + tcp_keepalive -- TCP keep-alive is on, default is on + + logger -- custom logger object + + access_log_class -- custom class for access_logger + + access_log -- custom logging object + + access_log_format -- access log format string + + loop -- Optional event loop + + max_line_size -- Optional maximum header line size + + max_field_size -- Optional maximum header field size + + timeout_ceil_threshold -- Optional value to specify + threshold to ceil() timeout + values + + """ + + KEEPALIVE_RESCHEDULE_DELAY = 1 + + __slots__ = ( + "_request_count", + "_keepalive", + "_manager", + "_request_handler", + "_request_factory", + "_tcp_keepalive", + "_keepalive_time", + "_keepalive_handle", + "_keepalive_timeout", + "_lingering_time", + "_messages", + "_message_tail", + "_waiter", + "_task_handler", + "_upgrade", + "_payload_parser", + "_request_parser", + "logger", + "access_log", + "access_logger", + "_close", + "_force_close", + "_current_request", + "_timeout_ceil_threshold", + ) + + def __init__( + self, + manager: "Server", + *, + loop: asyncio.AbstractEventLoop, + keepalive_timeout: float = 75.0, # NGINX default is 75 secs + tcp_keepalive: bool = True, + logger: Logger = server_logger, + access_log_class: _AnyAbstractAccessLogger = AccessLogger, + access_log: Optional[Logger] = access_logger, + access_log_format: str = AccessLogger.LOG_FORMAT, + max_line_size: int = 8190, + max_field_size: int = 8190, + lingering_time: float = 10.0, + read_bufsize: int = 2**16, + auto_decompress: bool = True, + timeout_ceil_threshold: float = 5, + ): + super().__init__(loop) + + self._request_count = 0 + self._keepalive = False + self._current_request: Optional[BaseRequest] = None + self._manager: Optional[Server] = manager + self._request_handler: Optional[_RequestHandler] = manager.request_handler + self._request_factory: Optional[_RequestFactory] = manager.request_factory + + self._tcp_keepalive = tcp_keepalive + # placeholder to be replaced on keepalive timeout setup + self._keepalive_time = 0.0 + self._keepalive_handle: Optional[asyncio.Handle] = None + self._keepalive_timeout = keepalive_timeout + self._lingering_time = float(lingering_time) + + self._messages: Deque[_MsgType] = deque() + self._message_tail = b"" + + self._waiter: Optional[asyncio.Future[None]] = None + self._task_handler: Optional[asyncio.Task[None]] = None + + self._upgrade = False + self._payload_parser: Any = None + self._request_parser: Optional[HttpRequestParser] = HttpRequestParser( + self, + loop, + read_bufsize, + max_line_size=max_line_size, + max_field_size=max_field_size, + payload_exception=RequestPayloadError, + auto_decompress=auto_decompress, + ) + + self._timeout_ceil_threshold: float = 5 + try: + self._timeout_ceil_threshold = float(timeout_ceil_threshold) + except (TypeError, ValueError): + pass + + self.logger = logger + self.access_log = access_log + if access_log: + if issubclass(access_log_class, AbstractAsyncAccessLogger): + self.access_logger: Optional[ + AbstractAsyncAccessLogger + ] = access_log_class() + else: + access_logger = access_log_class(access_log, access_log_format) + self.access_logger = AccessLoggerWrapper( + access_logger, + self._loop, + ) + else: + self.access_logger = None + + self._close = False + self._force_close = False + + def __repr__(self) -> str: + return "<{} {}>".format( + self.__class__.__name__, + "connected" if self.transport is not None else "disconnected", + ) + + @property + def keepalive_timeout(self) -> float: + return self._keepalive_timeout + + async def shutdown(self, timeout: Optional[float] = 15.0) -> None: + """Do worker process exit preparations. + + We need to clean up everything and stop accepting requests. + It is especially important for keep-alive connections. + """ + self._force_close = True + + if self._keepalive_handle is not None: + self._keepalive_handle.cancel() + + if self._waiter: + self._waiter.cancel() + + # wait for handlers + with suppress(asyncio.CancelledError, asyncio.TimeoutError): + async with ceil_timeout(timeout): + if self._current_request is not None: + self._current_request._cancel(asyncio.CancelledError()) + + if self._task_handler is not None and not self._task_handler.done(): + await self._task_handler + + # force-close non-idle handler + if self._task_handler is not None: + self._task_handler.cancel() + + if self.transport is not None: + self.transport.close() + self.transport = None + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + super().connection_made(transport) + + real_transport = cast(asyncio.Transport, transport) + if self._tcp_keepalive: + tcp_keepalive(real_transport) + + self._task_handler = self._loop.create_task(self.start()) + assert self._manager is not None + self._manager.connection_made(self, real_transport) + + def connection_lost(self, exc: Optional[BaseException]) -> None: + if self._manager is None: + return + self._manager.connection_lost(self, exc) + + super().connection_lost(exc) + + # Grab value before setting _manager to None. + handler_cancellation = self._manager.handler_cancellation + + self._manager = None + self._force_close = True + self._request_factory = None + self._request_handler = None + self._request_parser = None + + if self._keepalive_handle is not None: + self._keepalive_handle.cancel() + + if self._current_request is not None: + if exc is None: + exc = ConnectionResetError("Connection lost") + self._current_request._cancel(exc) + + if self._waiter is not None: + self._waiter.cancel() + + if handler_cancellation and self._task_handler is not None: + self._task_handler.cancel() + + self._task_handler = None + + if self._payload_parser is not None: + self._payload_parser.feed_eof() + self._payload_parser = None + + def set_parser(self, parser: Any) -> None: + # Actual type is WebReader + assert self._payload_parser is None + + self._payload_parser = parser + + if self._message_tail: + self._payload_parser.feed_data(self._message_tail) + self._message_tail = b"" + + def eof_received(self) -> None: + pass + + def data_received(self, data: bytes) -> None: + if self._force_close or self._close: + return + # parse http messages + messages: Sequence[_MsgType] + if self._payload_parser is None and not self._upgrade: + assert self._request_parser is not None + try: + messages, upgraded, tail = self._request_parser.feed_data(data) + except HttpProcessingError as exc: + messages = [ + (_ErrInfo(status=400, exc=exc, message=exc.message), EMPTY_PAYLOAD) + ] + upgraded = False + tail = b"" + + for msg, payload in messages or (): + self._request_count += 1 + self._messages.append((msg, payload)) + + waiter = self._waiter + if messages and waiter is not None and not waiter.done(): + # don't set result twice + waiter.set_result(None) + + self._upgrade = upgraded + if upgraded and tail: + self._message_tail = tail + + # no parser, just store + elif self._payload_parser is None and self._upgrade and data: + self._message_tail += data + + # feed payload + elif data: + eof, tail = self._payload_parser.feed_data(data) + if eof: + self.close() + + def keep_alive(self, val: bool) -> None: + """Set keep-alive connection mode. + + :param bool val: new state. + """ + self._keepalive = val + if self._keepalive_handle: + self._keepalive_handle.cancel() + self._keepalive_handle = None + + def close(self) -> None: + """Close connection. + + Stop accepting new pipelining messages and close + connection when handlers done processing messages. + """ + self._close = True + if self._waiter: + self._waiter.cancel() + + def force_close(self) -> None: + """Forcefully close connection.""" + self._force_close = True + if self._waiter: + self._waiter.cancel() + if self.transport is not None: + self.transport.close() + self.transport = None + + async def log_access( + self, request: BaseRequest, response: StreamResponse, request_start: float + ) -> None: + if self.access_logger is not None: + await self.access_logger.log(request, response, request_start) + + def log_debug(self, *args: Any, **kw: Any) -> None: + if self._loop.get_debug(): + self.logger.debug(*args, **kw) + + def log_exception(self, *args: Any, **kw: Any) -> None: + self.logger.exception(*args, **kw) + + def _process_keepalive(self) -> None: + if self._force_close or not self._keepalive: + return + + next = self._keepalive_time + self._keepalive_timeout + + # handler in idle state + if self._waiter: + if self._loop.time() > next: + self.force_close() + return + + # not all request handlers are done, + # reschedule itself to next second + self._keepalive_handle = self._loop.call_later( + self.KEEPALIVE_RESCHEDULE_DELAY, + self._process_keepalive, + ) + + async def _handle_request( + self, + request: BaseRequest, + start_time: float, + request_handler: Callable[[BaseRequest], Awaitable[StreamResponse]], + ) -> Tuple[StreamResponse, bool]: + assert self._request_handler is not None + try: + try: + self._current_request = request + resp = await request_handler(request) + finally: + self._current_request = None + except HTTPException as exc: + resp = Response( + status=exc.status, reason=exc.reason, text=exc.text, headers=exc.headers + ) + resp._cookies = exc._cookies + reset = await self.finish_response(request, resp, start_time) + except asyncio.CancelledError: + raise + except asyncio.TimeoutError as exc: + self.log_debug("Request handler timed out.", exc_info=exc) + resp = self.handle_error(request, 504) + reset = await self.finish_response(request, resp, start_time) + except Exception as exc: + resp = self.handle_error(request, 500, exc) + reset = await self.finish_response(request, resp, start_time) + else: + reset = await self.finish_response(request, resp, start_time) + + return resp, reset + + async def start(self) -> None: + """Process incoming request. + + It reads request line, request headers and request payload, then + calls handle_request() method. Subclass has to override + handle_request(). start() handles various exceptions in request + or response handling. Connection is being closed always unless + keep_alive(True) specified. + """ + loop = self._loop + handler = self._task_handler + assert handler is not None + manager = self._manager + assert manager is not None + keepalive_timeout = self._keepalive_timeout + resp = None + assert self._request_factory is not None + assert self._request_handler is not None + + while not self._force_close: + if not self._messages: + try: + # wait for next request + self._waiter = loop.create_future() + await self._waiter + except asyncio.CancelledError: + break + finally: + self._waiter = None + + message, payload = self._messages.popleft() + + start = loop.time() + + manager.requests_count += 1 + writer = StreamWriter(self, loop) + if isinstance(message, _ErrInfo): + # make request_factory work + request_handler = self._make_error_handler(message) + message = ERROR + else: + request_handler = self._request_handler + + request = self._request_factory(message, payload, self, writer, handler) + try: + # a new task is used for copy context vars (#3406) + task = self._loop.create_task( + self._handle_request(request, start, request_handler) + ) + try: + resp, reset = await task + except (asyncio.CancelledError, ConnectionError): + self.log_debug("Ignored premature client disconnection") + break + + # Drop the processed task from asyncio.Task.all_tasks() early + del task + # https://github.com/python/mypy/issues/14309 + if reset: # type: ignore[possibly-undefined] + self.log_debug("Ignored premature client disconnection 2") + break + + # notify server about keep-alive + self._keepalive = bool(resp.keep_alive) + + # check payload + if not payload.is_eof(): + lingering_time = self._lingering_time + # Could be force closed while awaiting above tasks. + if not self._force_close and lingering_time: # type: ignore[redundant-expr] + self.log_debug( + "Start lingering close timer for %s sec.", lingering_time + ) + + now = loop.time() + end_t = now + lingering_time + + with suppress(asyncio.TimeoutError, asyncio.CancelledError): + while not payload.is_eof() and now < end_t: + async with ceil_timeout(end_t - now): + # read and ignore + await payload.readany() + now = loop.time() + + # if payload still uncompleted + if not payload.is_eof() and not self._force_close: + self.log_debug("Uncompleted request.") + self.close() + + payload.set_exception(PayloadAccessError()) + + except asyncio.CancelledError: + self.log_debug("Ignored premature client disconnection ") + break + except RuntimeError as exc: + if self._loop.get_debug(): + self.log_exception("Unhandled runtime exception", exc_info=exc) + self.force_close() + except Exception as exc: + self.log_exception("Unhandled exception", exc_info=exc) + self.force_close() + finally: + if self.transport is None and resp is not None: + self.log_debug("Ignored premature client disconnection.") + elif not self._force_close: + if self._keepalive and not self._close: + # start keep-alive timer + if keepalive_timeout is not None: + now = self._loop.time() + self._keepalive_time = now + if self._keepalive_handle is None: + self._keepalive_handle = loop.call_at( + now + keepalive_timeout, self._process_keepalive + ) + else: + break + + # remove handler, close transport if no handlers left + if not self._force_close: + self._task_handler = None + if self.transport is not None: + self.transport.close() + + async def finish_response( + self, request: BaseRequest, resp: StreamResponse, start_time: float + ) -> bool: + """Prepare the response and write_eof, then log access. + + This has to + be called within the context of any exception so the access logger + can get exception information. Returns True if the client disconnects + prematurely. + """ + request._finish() + if self._request_parser is not None: + self._request_parser.set_upgraded(False) + self._upgrade = False + if self._message_tail: + self._request_parser.feed_data(self._message_tail) + self._message_tail = b"" + try: + prepare_meth = resp.prepare + except AttributeError: + if resp is None: + raise RuntimeError("Missing return " "statement on request handler") + else: + raise RuntimeError( + "Web-handler should return " + "a response instance, " + "got {!r}".format(resp) + ) + try: + await prepare_meth(request) + await resp.write_eof() + except ConnectionError: + await self.log_access(request, resp, start_time) + return True + else: + await self.log_access(request, resp, start_time) + return False + + def handle_error( + self, + request: BaseRequest, + status: int = 500, + exc: Optional[BaseException] = None, + message: Optional[str] = None, + ) -> StreamResponse: + """Handle errors. + + Returns HTTP response with specific status code. Logs additional + information. It always closes current connection. + """ + self.log_exception("Error handling request", exc_info=exc) + + # some data already got sent, connection is broken + if request.writer.output_size > 0: + raise ConnectionError( + "Response is sent already, cannot send another response " + "with the error message" + ) + + ct = "text/plain" + if status == HTTPStatus.INTERNAL_SERVER_ERROR: + title = "{0.value} {0.phrase}".format(HTTPStatus.INTERNAL_SERVER_ERROR) + msg = HTTPStatus.INTERNAL_SERVER_ERROR.description + tb = None + if self._loop.get_debug(): + with suppress(Exception): + tb = traceback.format_exc() + + if "text/html" in request.headers.get("Accept", ""): + if tb: + tb = html_escape(tb) + msg = f"

Traceback:

\n
{tb}
" + message = ( + "" + "{title}" + "\n

{title}

" + "\n{msg}\n\n" + ).format(title=title, msg=msg) + ct = "text/html" + else: + if tb: + msg = tb + message = title + "\n\n" + msg + + resp = Response(status=status, text=message, content_type=ct) + resp.force_close() + + return resp + + def _make_error_handler( + self, err_info: _ErrInfo + ) -> Callable[[BaseRequest], Awaitable[StreamResponse]]: + async def handler(request: BaseRequest) -> StreamResponse: + return self.handle_error( + request, err_info.status, err_info.exc, err_info.message + ) + + return handler diff --git a/aiohttp/web_request.py b/aiohttp/web_request.py new file mode 100644 index 0000000..5181bd1 --- /dev/null +++ b/aiohttp/web_request.py @@ -0,0 +1,912 @@ +import asyncio +import dataclasses +import datetime +import io +import re +import socket +import string +import tempfile +import types +from http.cookies import SimpleCookie +from types import MappingProxyType +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Final, + Iterator, + Mapping, + MutableMapping, + Optional, + Pattern, + Set, + Tuple, + Union, + cast, +) +from urllib.parse import parse_qsl + +from multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy +from yarl import URL + +from . import hdrs +from .abc import AbstractStreamWriter +from .helpers import ( + _SENTINEL, + ETAG_ANY, + LIST_QUOTED_ETAG_RE, + ChainMapProxy, + ETag, + HeadersMixin, + is_expected_content_type, + parse_http_date, + reify, + sentinel, + set_result, +) +from .http_parser import RawRequestMessage +from .http_writer import HttpVersion +from .multipart import BodyPartReader, MultipartReader +from .streams import EmptyStreamReader, StreamReader +from .typedefs import ( + DEFAULT_JSON_DECODER, + JSONDecoder, + LooseHeaders, + RawHeaders, + StrOrURL, +) +from .web_exceptions import ( + HTTPBadRequest, + HTTPRequestEntityTooLarge, + HTTPUnsupportedMediaType, +) +from .web_response import StreamResponse + +__all__ = ("BaseRequest", "FileField", "Request") + + +if TYPE_CHECKING: # pragma: no cover + from .web_app import Application + from .web_protocol import RequestHandler + from .web_urldispatcher import UrlMappingMatchInfo + + +@dataclasses.dataclass(frozen=True) +class FileField: + name: str + filename: str + file: io.BufferedReader + content_type: str + headers: "CIMultiDictProxy[str]" + + +_TCHAR: Final[str] = string.digits + string.ascii_letters + r"!#$%&'*+.^_`|~-" +# '-' at the end to prevent interpretation as range in a char class + +_TOKEN: Final[str] = rf"[{_TCHAR}]+" + +_QDTEXT: Final[str] = r"[{}]".format( + r"".join(chr(c) for c in (0x09, 0x20, 0x21) + tuple(range(0x23, 0x7F))) +) +# qdtext includes 0x5C to escape 0x5D ('\]') +# qdtext excludes obs-text (because obsoleted, and encoding not specified) + +_QUOTED_PAIR: Final[str] = r"\\[\t !-~]" + +_QUOTED_STRING: Final[str] = r'"(?:{quoted_pair}|{qdtext})*"'.format( + qdtext=_QDTEXT, quoted_pair=_QUOTED_PAIR +) + +_FORWARDED_PAIR: Final[ + str +] = r"({token})=({token}|{quoted_string})(:\d{{1,4}})?".format( + token=_TOKEN, quoted_string=_QUOTED_STRING +) + +_QUOTED_PAIR_REPLACE_RE: Final[Pattern[str]] = re.compile(r"\\([\t !-~])") +# same pattern as _QUOTED_PAIR but contains a capture group + +_FORWARDED_PAIR_RE: Final[Pattern[str]] = re.compile(_FORWARDED_PAIR) + +############################################################ +# HTTP Request +############################################################ + + +class BaseRequest(MutableMapping[str, Any], HeadersMixin): + POST_METHODS = { + hdrs.METH_PATCH, + hdrs.METH_POST, + hdrs.METH_PUT, + hdrs.METH_TRACE, + hdrs.METH_DELETE, + } + + __slots__ = ( + "_message", + "_protocol", + "_payload_writer", + "_payload", + "_headers", + "_method", + "_version", + "_rel_url", + "_post", + "_read_bytes", + "_state", + "_cache", + "_task", + "_client_max_size", + "_loop", + "_transport_sslcontext", + "_transport_peername", + "_disconnection_waiters", + "__weakref__", + ) + + def __init__( + self, + message: RawRequestMessage, + payload: StreamReader, + protocol: "RequestHandler", + payload_writer: AbstractStreamWriter, + task: "asyncio.Task[None]", + loop: asyncio.AbstractEventLoop, + *, + client_max_size: int = 1024**2, + state: Optional[Dict[str, Any]] = None, + scheme: Optional[str] = None, + host: Optional[str] = None, + remote: Optional[str] = None, + ) -> None: + super().__init__() + if state is None: + state = {} + self._message = message + self._protocol = protocol + self._payload_writer = payload_writer + + self._payload = payload + self._headers = message.headers + self._method = message.method + self._version = message.version + self._cache: Dict[str, Any] = {} + url = message.url + if url.is_absolute(): + # absolute URL is given, + # override auto-calculating url, host, and scheme + # all other properties should be good + self._cache["url"] = url + self._cache["host"] = url.host + self._cache["scheme"] = url.scheme + self._rel_url = url.relative() + else: + self._rel_url = message.url + self._post: Optional[MultiDictProxy[Union[str, bytes, FileField]]] = None + self._read_bytes: Optional[bytes] = None + + self._state = state + self._task = task + self._client_max_size = client_max_size + self._loop = loop + self._disconnection_waiters: Set[asyncio.Future[None]] = set() + + transport = self._protocol.transport + assert transport is not None + self._transport_sslcontext = transport.get_extra_info("sslcontext") + self._transport_peername = transport.get_extra_info("peername") + + if scheme is not None: + self._cache["scheme"] = scheme + if host is not None: + self._cache["host"] = host + if remote is not None: + self._cache["remote"] = remote + + def clone( + self, + *, + method: Union[str, _SENTINEL] = sentinel, + rel_url: Union[StrOrURL, _SENTINEL] = sentinel, + headers: Union[LooseHeaders, _SENTINEL] = sentinel, + scheme: Union[str, _SENTINEL] = sentinel, + host: Union[str, _SENTINEL] = sentinel, + remote: Union[str, _SENTINEL] = sentinel, + client_max_size: Union[int, _SENTINEL] = sentinel, + ) -> "BaseRequest": + """Clone itself with replacement some attributes. + + Creates and returns a new instance of Request object. If no parameters + are given, an exact copy is returned. If a parameter is not passed, it + will reuse the one from the current request object. + """ + if self._read_bytes: + raise RuntimeError("Cannot clone request " "after reading its content") + + dct: Dict[str, Any] = {} + if method is not sentinel: + dct["method"] = method + if rel_url is not sentinel: + new_url: URL = URL(rel_url) + dct["url"] = new_url + dct["path"] = str(new_url) + if headers is not sentinel: + # a copy semantic + new_headers = CIMultiDictProxy(CIMultiDict(headers)) + dct["headers"] = new_headers + dct["raw_headers"] = tuple( + (k.encode("utf-8"), v.encode("utf-8")) for k, v in new_headers.items() + ) + + message = self._message._replace(**dct) + + kwargs: Dict[str, str] = {} + if scheme is not sentinel: + kwargs["scheme"] = scheme + if host is not sentinel: + kwargs["host"] = host + if remote is not sentinel: + kwargs["remote"] = remote + if client_max_size is sentinel: + client_max_size = self._client_max_size + + return self.__class__( + message, + self._payload, + self._protocol, + self._payload_writer, + self._task, + self._loop, + client_max_size=client_max_size, + state=self._state.copy(), + **kwargs, + ) + + @property + def task(self) -> "asyncio.Task[None]": + return self._task + + @property + def protocol(self) -> "RequestHandler": + return self._protocol + + @property + def transport(self) -> Optional[asyncio.Transport]: + if self._protocol is None: + return None + return self._protocol.transport + + @property + def writer(self) -> AbstractStreamWriter: + return self._payload_writer + + @property + def client_max_size(self) -> int: + return self._client_max_size + + @reify + def rel_url(self) -> URL: + return self._rel_url + + # MutableMapping API + + def __getitem__(self, key: str) -> Any: + return self._state[key] + + def __setitem__(self, key: str, value: Any) -> None: + self._state[key] = value + + def __delitem__(self, key: str) -> None: + del self._state[key] + + def __len__(self) -> int: + return len(self._state) + + def __iter__(self) -> Iterator[str]: + return iter(self._state) + + ######## + + @reify + def secure(self) -> bool: + """A bool indicating if the request is handled with SSL.""" + return self.scheme == "https" + + @reify + def forwarded(self) -> Tuple[Mapping[str, str], ...]: + """A tuple containing all parsed Forwarded header(s). + + Makes an effort to parse Forwarded headers as specified by RFC 7239: + + - It adds one (immutable) dictionary per Forwarded 'field-value', ie + per proxy. The element corresponds to the data in the Forwarded + field-value added by the first proxy encountered by the client. Each + subsequent item corresponds to those added by later proxies. + - It checks that every value has valid syntax in general as specified + in section 4: either a 'token' or a 'quoted-string'. + - It un-escapes found escape sequences. + - It does NOT validate 'by' and 'for' contents as specified in section + 6. + - It does NOT validate 'host' contents (Host ABNF). + - It does NOT validate 'proto' contents for valid URI scheme names. + + Returns a tuple containing one or more immutable dicts + """ + elems = [] + for field_value in self._message.headers.getall(hdrs.FORWARDED, ()): + length = len(field_value) + pos = 0 + need_separator = False + elem: Dict[str, str] = {} + elems.append(types.MappingProxyType(elem)) + while 0 <= pos < length: + match = _FORWARDED_PAIR_RE.match(field_value, pos) + if match is not None: # got a valid forwarded-pair + if need_separator: + # bad syntax here, skip to next comma + pos = field_value.find(",", pos) + else: + name, value, port = match.groups() + if value[0] == '"': + # quoted string: remove quotes and unescape + value = _QUOTED_PAIR_REPLACE_RE.sub(r"\1", value[1:-1]) + if port: + value += port + elem[name.lower()] = value + pos += len(match.group(0)) + need_separator = True + elif field_value[pos] == ",": # next forwarded-element + need_separator = False + elem = {} + elems.append(types.MappingProxyType(elem)) + pos += 1 + elif field_value[pos] == ";": # next forwarded-pair + need_separator = False + pos += 1 + elif field_value[pos] in " \t": + # Allow whitespace even between forwarded-pairs, though + # RFC 7239 doesn't. This simplifies code and is in line + # with Postel's law. + pos += 1 + else: + # bad syntax here, skip to next comma + pos = field_value.find(",", pos) + return tuple(elems) + + @reify + def scheme(self) -> str: + """A string representing the scheme of the request. + + Hostname is resolved in this order: + + - overridden value by .clone(scheme=new_scheme) call. + - type of connection to peer: HTTPS if socket is SSL, HTTP otherwise. + + 'http' or 'https'. + """ + if self._transport_sslcontext: + return "https" + else: + return "http" + + @reify + def method(self) -> str: + """Read only property for getting HTTP method. + + The value is upper-cased str like 'GET', 'POST', 'PUT' etc. + """ + return self._method + + @reify + def version(self) -> HttpVersion: + """Read only property for getting HTTP version of request. + + Returns aiohttp.protocol.HttpVersion instance. + """ + return self._version + + @reify + def host(self) -> str: + """Hostname of the request. + + Hostname is resolved in this order: + + - overridden value by .clone(host=new_host) call. + - HOST HTTP header + - socket.getfqdn() value + """ + host = self._message.headers.get(hdrs.HOST) + if host is not None: + return host + return socket.getfqdn() + + @reify + def remote(self) -> Optional[str]: + """Remote IP of client initiated HTTP request. + + The IP is resolved in this order: + + - overridden value by .clone(remote=new_remote) call. + - peername of opened socket + """ + if self._transport_peername is None: + return None + if isinstance(self._transport_peername, (list, tuple)): + return str(self._transport_peername[0]) + return str(self._transport_peername) + + @reify + def url(self) -> URL: + url = URL.build(scheme=self.scheme, host=self.host) + return url.join(self._rel_url) + + @reify + def path(self) -> str: + """The URL including *PATH INFO* without the host or scheme. + + E.g., ``/app/blog`` + """ + return self._rel_url.path + + @reify + def path_qs(self) -> str: + """The URL including PATH_INFO and the query string. + + E.g, /app/blog?id=10 + """ + return str(self._rel_url) + + @reify + def raw_path(self) -> str: + """The URL including raw *PATH INFO* without the host or scheme. + + Warning, the path is unquoted and may contains non valid URL characters + + E.g., ``/my%2Fpath%7Cwith%21some%25strange%24characters`` + """ + return self._message.path + + @reify + def query(self) -> MultiDictProxy[str]: + """A multidict with all the variables in the query string.""" + return MultiDictProxy(self._rel_url.query) + + @reify + def query_string(self) -> str: + """The query string in the URL. + + E.g., id=10 + """ + return self._rel_url.query_string + + @reify + def headers(self) -> "CIMultiDictProxy[str]": + """A case-insensitive multidict proxy with all headers.""" + return self._headers + + @reify + def raw_headers(self) -> RawHeaders: + """A sequence of pairs for all headers.""" + return self._message.raw_headers + + @reify + def if_modified_since(self) -> Optional[datetime.datetime]: + """The value of If-Modified-Since HTTP header, or None. + + This header is represented as a `datetime` object. + """ + return parse_http_date(self.headers.get(hdrs.IF_MODIFIED_SINCE)) + + @reify + def if_unmodified_since(self) -> Optional[datetime.datetime]: + """The value of If-Unmodified-Since HTTP header, or None. + + This header is represented as a `datetime` object. + """ + return parse_http_date(self.headers.get(hdrs.IF_UNMODIFIED_SINCE)) + + @staticmethod + def _etag_values(etag_header: str) -> Iterator[ETag]: + """Extract `ETag` objects from raw header.""" + if etag_header == ETAG_ANY: + yield ETag( + is_weak=False, + value=ETAG_ANY, + ) + else: + for match in LIST_QUOTED_ETAG_RE.finditer(etag_header): + is_weak, value, garbage = match.group(2, 3, 4) + # Any symbol captured by 4th group means + # that the following sequence is invalid. + if garbage: + break + + yield ETag( + is_weak=bool(is_weak), + value=value, + ) + + @classmethod + def _if_match_or_none_impl( + cls, header_value: Optional[str] + ) -> Optional[Tuple[ETag, ...]]: + if not header_value: + return None + + return tuple(cls._etag_values(header_value)) + + @reify + def if_match(self) -> Optional[Tuple[ETag, ...]]: + """The value of If-Match HTTP header, or None. + + This header is represented as a `tuple` of `ETag` objects. + """ + return self._if_match_or_none_impl(self.headers.get(hdrs.IF_MATCH)) + + @reify + def if_none_match(self) -> Optional[Tuple[ETag, ...]]: + """The value of If-None-Match HTTP header, or None. + + This header is represented as a `tuple` of `ETag` objects. + """ + return self._if_match_or_none_impl(self.headers.get(hdrs.IF_NONE_MATCH)) + + @reify + def if_range(self) -> Optional[datetime.datetime]: + """The value of If-Range HTTP header, or None. + + This header is represented as a `datetime` object. + """ + return parse_http_date(self.headers.get(hdrs.IF_RANGE)) + + @reify + def keep_alive(self) -> bool: + """Is keepalive enabled by client?""" + return not self._message.should_close + + @reify + def cookies(self) -> Mapping[str, str]: + """Return request cookies. + + A read-only dictionary-like object. + """ + raw = self.headers.get(hdrs.COOKIE, "") + parsed: SimpleCookie[str] = SimpleCookie(raw) + return MappingProxyType({key: val.value for key, val in parsed.items()}) + + @reify + def http_range(self) -> slice: + """The content of Range HTTP header. + + Return a slice instance. + + """ + rng = self._headers.get(hdrs.RANGE) + start, end = None, None + if rng is not None: + try: + pattern = r"^bytes=(\d*)-(\d*)$" + start, end = re.findall(pattern, rng)[0] + except IndexError: # pattern was not found in header + raise ValueError("range not in acceptable format") + + end = int(end) if end else None + start = int(start) if start else None + + if start is None and end is not None: + # end with no start is to return tail of content + start = -end + end = None + + if start is not None and end is not None: + # end is inclusive in range header, exclusive for slice + end += 1 + + if start >= end: + raise ValueError("start cannot be after end") + + if start is end is None: # No valid range supplied + raise ValueError("No start or end of range specified") + + return slice(start, end, 1) + + @reify + def content(self) -> StreamReader: + """Return raw payload stream.""" + return self._payload + + @property + def can_read_body(self) -> bool: + """Return True if request's HTTP BODY can be read, False otherwise.""" + return not self._payload.at_eof() + + @reify + def body_exists(self) -> bool: + """Return True if request has HTTP BODY, False otherwise.""" + return type(self._payload) is not EmptyStreamReader + + async def release(self) -> None: + """Release request. + + Eat unread part of HTTP BODY if present. + """ + while not self._payload.at_eof(): + await self._payload.readany() + + async def read(self) -> bytes: + """Read request body if present. + + Returns bytes object with full request content. + """ + if self._read_bytes is None: + body = bytearray() + while True: + chunk = await self._payload.readany() + body.extend(chunk) + if self._client_max_size: + body_size = len(body) + if body_size > self._client_max_size: + raise HTTPRequestEntityTooLarge( + max_size=self._client_max_size, actual_size=body_size + ) + if not chunk: + break + self._read_bytes = bytes(body) + return self._read_bytes + + async def text(self) -> str: + """Return BODY as text using encoding from .charset.""" + bytes_body = await self.read() + encoding = self.charset or "utf-8" + try: + return bytes_body.decode(encoding) + except LookupError: + raise HTTPUnsupportedMediaType() + + async def json( + self, + *, + loads: JSONDecoder = DEFAULT_JSON_DECODER, + content_type: Optional[str] = "application/json", + ) -> Any: + """Return BODY as JSON.""" + body = await self.text() + if content_type: + if not is_expected_content_type(self.content_type, content_type): + raise HTTPBadRequest( + text=( + "Attempt to decode JSON with " + "unexpected mimetype: %s" % self.content_type + ) + ) + + return loads(body) + + async def multipart(self) -> MultipartReader: + """Return async iterator to process BODY as multipart.""" + return MultipartReader(self._headers, self._payload) + + async def post(self) -> "MultiDictProxy[Union[str, bytes, FileField]]": + """Return POST parameters.""" + if self._post is not None: + return self._post + if self._method not in self.POST_METHODS: + self._post = MultiDictProxy(MultiDict()) + return self._post + + content_type = self.content_type + if content_type not in ( + "", + "application/x-www-form-urlencoded", + "multipart/form-data", + ): + self._post = MultiDictProxy(MultiDict()) + return self._post + + out: MultiDict[Union[str, bytes, FileField]] = MultiDict() + + if content_type == "multipart/form-data": + multipart = await self.multipart() + max_size = self._client_max_size + + field = await multipart.next() + while field is not None: + size = 0 + field_ct = field.headers.get(hdrs.CONTENT_TYPE) + + if isinstance(field, BodyPartReader): + assert field.name is not None + + # Note that according to RFC 7578, the Content-Type header + # is optional, even for files, so we can't assume it's + # present. + # https://tools.ietf.org/html/rfc7578#section-4.4 + if field.filename: + # store file in temp file + tmp = tempfile.TemporaryFile() + chunk = await field.read_chunk(size=2**16) + while chunk: + chunk = field.decode(chunk) + tmp.write(chunk) + size += len(chunk) + if 0 < max_size < size: + tmp.close() + raise HTTPRequestEntityTooLarge( + max_size=max_size, actual_size=size + ) + chunk = await field.read_chunk(size=2**16) + tmp.seek(0) + + if field_ct is None: + field_ct = "application/octet-stream" + + ff = FileField( + field.name, + field.filename, + cast(io.BufferedReader, tmp), + field_ct, + field.headers, + ) + out.add(field.name, ff) + else: + # deal with ordinary data + value = await field.read(decode=True) + if field_ct is None or field_ct.startswith("text/"): + charset = field.get_charset(default="utf-8") + out.add(field.name, value.decode(charset)) + else: + out.add(field.name, value) + size += len(value) + if 0 < max_size < size: + raise HTTPRequestEntityTooLarge( + max_size=max_size, actual_size=size + ) + else: + raise ValueError( + "To decode nested multipart you need " "to use custom reader", + ) + + field = await multipart.next() + else: + data = await self.read() + if data: + charset = self.charset or "utf-8" + bytes_query = data.rstrip() + try: + query = bytes_query.decode(charset) + except LookupError: + raise HTTPUnsupportedMediaType() + out.extend( + parse_qsl(qs=query, keep_blank_values=True, encoding=charset) + ) + + self._post = MultiDictProxy(out) + return self._post + + def get_extra_info(self, name: str, default: Any = None) -> Any: + """Extra info from protocol transport""" + protocol = self._protocol + if protocol is None: + return default + + transport = protocol.transport + if transport is None: + return default + + return transport.get_extra_info(name, default) + + def __repr__(self) -> str: + ascii_encodable_path = self.path.encode("ascii", "backslashreplace").decode( + "ascii" + ) + return "<{} {} {} >".format( + self.__class__.__name__, self._method, ascii_encodable_path + ) + + def __eq__(self, other: object) -> bool: + return id(self) == id(other) + + def __bool__(self) -> bool: + return True + + async def _prepare_hook(self, response: StreamResponse) -> None: + return + + def _cancel(self, exc: BaseException) -> None: + self._payload.set_exception(exc) + for fut in self._disconnection_waiters: + set_result(fut, None) + + def _finish(self) -> None: + for fut in self._disconnection_waiters: + fut.cancel() + + if self._post is None or self.content_type != "multipart/form-data": + return + + # NOTE: Release file descriptors for the + # NOTE: `tempfile.Temporaryfile`-created `_io.BufferedRandom` + # NOTE: instances of files sent within multipart request body + # NOTE: via HTTP POST request. + for file_name, file_field_object in self._post.items(): + if not isinstance(file_field_object, FileField): + continue + + file_field_object.file.close() + + async def wait_for_disconnection(self) -> None: + loop = asyncio.get_event_loop() + fut: asyncio.Future[None] = loop.create_future() + self._disconnection_waiters.add(fut) + try: + await fut + finally: + self._disconnection_waiters.remove(fut) + + +class Request(BaseRequest): + __slots__ = ("_match_info",) + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + # matchdict, route_name, handler + # or information about traversal lookup + + # initialized after route resolving + self._match_info: Optional[UrlMappingMatchInfo] = None + + def clone( + self, + *, + method: Union[str, _SENTINEL] = sentinel, + rel_url: Union[StrOrURL, _SENTINEL] = sentinel, + headers: Union[LooseHeaders, _SENTINEL] = sentinel, + scheme: Union[str, _SENTINEL] = sentinel, + host: Union[str, _SENTINEL] = sentinel, + remote: Union[str, _SENTINEL] = sentinel, + client_max_size: Union[int, _SENTINEL] = sentinel, + ) -> "Request": + ret = super().clone( + method=method, + rel_url=rel_url, + headers=headers, + scheme=scheme, + host=host, + remote=remote, + client_max_size=client_max_size, + ) + new_ret = cast(Request, ret) + new_ret._match_info = self._match_info + return new_ret + + @reify + def match_info(self) -> "UrlMappingMatchInfo": + """Result of route resolving.""" + match_info = self._match_info + assert match_info is not None + return match_info + + @property + def app(self) -> "Application": + """Application instance.""" + match_info = self._match_info + assert match_info is not None + return match_info.current_app + + @property + def config_dict(self) -> ChainMapProxy: + match_info = self._match_info + assert match_info is not None + lst = match_info.apps + app = self.app + idx = lst.index(app) + sublist = list(reversed(lst[: idx + 1])) + return ChainMapProxy(sublist) + + async def _prepare_hook(self, response: StreamResponse) -> None: + match_info = self._match_info + if match_info is None: + return + for app in match_info._apps: + await app.on_response_prepare.send(self, response) diff --git a/aiohttp/web_response.py b/aiohttp/web_response.py new file mode 100644 index 0000000..1f2d623 --- /dev/null +++ b/aiohttp/web_response.py @@ -0,0 +1,728 @@ +import asyncio +import collections.abc +import datetime +import enum +import json +import math +import time +import warnings +from concurrent.futures import Executor +from http import HTTPStatus +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Iterator, + MutableMapping, + Optional, + Union, + cast, +) + +from multidict import CIMultiDict, istr + +from . import hdrs, payload +from .abc import AbstractStreamWriter +from .compression_utils import ZLibCompressor +from .helpers import ( + ETAG_ANY, + QUOTED_ETAG_RE, + CookieMixin, + ETag, + HeadersMixin, + parse_http_date, + populate_with_cookies, + rfc822_formatted_time, + sentinel, + validate_etag_value, +) +from .http import SERVER_SOFTWARE, HttpVersion10, HttpVersion11 +from .payload import Payload +from .typedefs import JSONEncoder, LooseHeaders + +__all__ = ("ContentCoding", "StreamResponse", "Response", "json_response") + + +if TYPE_CHECKING: # pragma: no cover + from .web_request import BaseRequest + + BaseClass = MutableMapping[str, Any] +else: + BaseClass = collections.abc.MutableMapping + + +class ContentCoding(enum.Enum): + # The content codings that we have support for. + # + # Additional registered codings are listed at: + # https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding + deflate = "deflate" + gzip = "gzip" + identity = "identity" + + +############################################################ +# HTTP Response classes +############################################################ + + +class StreamResponse(BaseClass, HeadersMixin, CookieMixin): + __slots__ = ( + "_length_check", + "_body", + "_keep_alive", + "_chunked", + "_compression", + "_compression_force", + "_req", + "_payload_writer", + "_eof_sent", + "_body_length", + "_state", + "_headers", + "_status", + "_reason", + "_cookies", + "__weakref__", + ) + + def __init__( + self, + *, + status: int = 200, + reason: Optional[str] = None, + headers: Optional[LooseHeaders] = None, + ) -> None: + super().__init__() + self._length_check = True + self._body = None + self._keep_alive: Optional[bool] = None + self._chunked = False + self._compression = False + self._compression_force: Optional[ContentCoding] = None + + self._req: Optional[BaseRequest] = None + self._payload_writer: Optional[AbstractStreamWriter] = None + self._eof_sent = False + self._body_length = 0 + self._state: Dict[str, Any] = {} + + if headers is not None: + self._headers: CIMultiDict[str] = CIMultiDict(headers) + else: + self._headers = CIMultiDict() + + self.set_status(status, reason) + + @property + def prepared(self) -> bool: + return self._payload_writer is not None + + @property + def task(self) -> "Optional[asyncio.Task[None]]": + if self._req: + return self._req.task + else: + return None + + @property + def status(self) -> int: + return self._status + + @property + def chunked(self) -> bool: + return self._chunked + + @property + def compression(self) -> bool: + return self._compression + + @property + def reason(self) -> str: + return self._reason + + def set_status( + self, + status: int, + reason: Optional[str] = None, + ) -> None: + assert not self.prepared, ( + "Cannot change the response status code after " "the headers have been sent" + ) + self._status = int(status) + if reason is None: + try: + reason = HTTPStatus(self._status).phrase + except ValueError: + reason = "" + self._reason = reason + + @property + def keep_alive(self) -> Optional[bool]: + return self._keep_alive + + def force_close(self) -> None: + self._keep_alive = False + + @property + def body_length(self) -> int: + return self._body_length + + def enable_chunked_encoding(self) -> None: + """Enables automatic chunked transfer encoding.""" + self._chunked = True + + if hdrs.CONTENT_LENGTH in self._headers: + raise RuntimeError( + "You can't enable chunked encoding when " "a content length is set" + ) + + def enable_compression(self, force: Optional[ContentCoding] = None) -> None: + """Enables response compression encoding.""" + # Backwards compatibility for when force was a bool <0.17. + self._compression = True + self._compression_force = force + + @property + def headers(self) -> "CIMultiDict[str]": + return self._headers + + @property + def content_length(self) -> Optional[int]: + # Just a placeholder for adding setter + return super().content_length + + @content_length.setter + def content_length(self, value: Optional[int]) -> None: + if value is not None: + value = int(value) + if self._chunked: + raise RuntimeError( + "You can't set content length when " "chunked encoding is enable" + ) + self._headers[hdrs.CONTENT_LENGTH] = str(value) + else: + self._headers.pop(hdrs.CONTENT_LENGTH, None) + + @property + def content_type(self) -> str: + # Just a placeholder for adding setter + return super().content_type + + @content_type.setter + def content_type(self, value: str) -> None: + self.content_type # read header values if needed + self._content_type = str(value) + self._generate_content_type_header() + + @property + def charset(self) -> Optional[str]: + # Just a placeholder for adding setter + return super().charset + + @charset.setter + def charset(self, value: Optional[str]) -> None: + ctype = self.content_type # read header values if needed + if ctype == "application/octet-stream": + raise RuntimeError( + "Setting charset for application/octet-stream " + "doesn't make sense, setup content_type first" + ) + assert self._content_dict is not None + if value is None: + self._content_dict.pop("charset", None) + else: + self._content_dict["charset"] = str(value).lower() + self._generate_content_type_header() + + @property + def last_modified(self) -> Optional[datetime.datetime]: + """The value of Last-Modified HTTP header, or None. + + This header is represented as a `datetime` object. + """ + return parse_http_date(self._headers.get(hdrs.LAST_MODIFIED)) + + @last_modified.setter + def last_modified( + self, value: Optional[Union[int, float, datetime.datetime, str]] + ) -> None: + if value is None: + self._headers.pop(hdrs.LAST_MODIFIED, None) + elif isinstance(value, (int, float)): + self._headers[hdrs.LAST_MODIFIED] = time.strftime( + "%a, %d %b %Y %H:%M:%S GMT", time.gmtime(math.ceil(value)) + ) + elif isinstance(value, datetime.datetime): + self._headers[hdrs.LAST_MODIFIED] = time.strftime( + "%a, %d %b %Y %H:%M:%S GMT", value.utctimetuple() + ) + elif isinstance(value, str): + self._headers[hdrs.LAST_MODIFIED] = value + + @property + def etag(self) -> Optional[ETag]: + quoted_value = self._headers.get(hdrs.ETAG) + if not quoted_value: + return None + elif quoted_value == ETAG_ANY: + return ETag(value=ETAG_ANY) + match = QUOTED_ETAG_RE.fullmatch(quoted_value) + if not match: + return None + is_weak, value = match.group(1, 2) + return ETag( + is_weak=bool(is_weak), + value=value, + ) + + @etag.setter + def etag(self, value: Optional[Union[ETag, str]]) -> None: + if value is None: + self._headers.pop(hdrs.ETAG, None) + elif (isinstance(value, str) and value == ETAG_ANY) or ( + isinstance(value, ETag) and value.value == ETAG_ANY + ): + self._headers[hdrs.ETAG] = ETAG_ANY + elif isinstance(value, str): + validate_etag_value(value) + self._headers[hdrs.ETAG] = f'"{value}"' + elif isinstance(value, ETag) and isinstance(value.value, str): # type: ignore[redundant-expr] + validate_etag_value(value.value) + hdr_value = f'W/"{value.value}"' if value.is_weak else f'"{value.value}"' + self._headers[hdrs.ETAG] = hdr_value + else: + raise ValueError( + f"Unsupported etag type: {type(value)}. " + f"etag must be str, ETag or None" + ) + + def _generate_content_type_header( + self, CONTENT_TYPE: istr = hdrs.CONTENT_TYPE + ) -> None: + assert self._content_dict is not None + assert self._content_type is not None + params = "; ".join(f"{k}={v}" for k, v in self._content_dict.items()) + if params: + ctype = self._content_type + "; " + params + else: + ctype = self._content_type + self._headers[CONTENT_TYPE] = ctype + + async def _do_start_compression(self, coding: ContentCoding) -> None: + if coding != ContentCoding.identity: + assert self._payload_writer is not None + self._headers[hdrs.CONTENT_ENCODING] = coding.value + self._payload_writer.enable_compression(coding.value) + # Compressed payload may have different content length, + # remove the header + self._headers.popall(hdrs.CONTENT_LENGTH, None) + + async def _start_compression(self, request: "BaseRequest") -> None: + if self._compression_force: + await self._do_start_compression(self._compression_force) + else: + accept_encoding = request.headers.get(hdrs.ACCEPT_ENCODING, "").lower() + for coding in ContentCoding: + if coding.value in accept_encoding: + await self._do_start_compression(coding) + return + + async def prepare(self, request: "BaseRequest") -> Optional[AbstractStreamWriter]: + if self._eof_sent: + return None + if self._payload_writer is not None: + return self._payload_writer + + return await self._start(request) + + async def _start(self, request: "BaseRequest") -> AbstractStreamWriter: + self._req = request + writer = self._payload_writer = request._payload_writer + + await self._prepare_headers() + await request._prepare_hook(self) + await self._write_headers() + + return writer + + async def _prepare_headers(self) -> None: + request = self._req + assert request is not None + writer = self._payload_writer + assert writer is not None + keep_alive = self._keep_alive + if keep_alive is None: + keep_alive = request.keep_alive + self._keep_alive = keep_alive + + version = request.version + + headers = self._headers + populate_with_cookies(headers, self.cookies) + + if self._compression: + await self._start_compression(request) + + if self._chunked: + if version != HttpVersion11: + raise RuntimeError( + "Using chunked encoding is forbidden " + "for HTTP/{0.major}.{0.minor}".format(request.version) + ) + writer.enable_chunking() + headers[hdrs.TRANSFER_ENCODING] = "chunked" + if hdrs.CONTENT_LENGTH in headers: + del headers[hdrs.CONTENT_LENGTH] + elif self._length_check: + writer.length = self.content_length + if writer.length is None: + if version >= HttpVersion11 and self.status != 204: + writer.enable_chunking() + headers[hdrs.TRANSFER_ENCODING] = "chunked" + if hdrs.CONTENT_LENGTH in headers: + del headers[hdrs.CONTENT_LENGTH] + else: + keep_alive = False + # HTTP 1.1: https://tools.ietf.org/html/rfc7230#section-3.3.2 + # HTTP 1.0: https://tools.ietf.org/html/rfc1945#section-10.4 + elif version >= HttpVersion11 and self.status in (100, 101, 102, 103, 204): + del headers[hdrs.CONTENT_LENGTH] + + if self.status not in (204, 304): + headers.setdefault(hdrs.CONTENT_TYPE, "application/octet-stream") + headers.setdefault(hdrs.DATE, rfc822_formatted_time()) + headers.setdefault(hdrs.SERVER, SERVER_SOFTWARE) + + # connection header + if hdrs.CONNECTION not in headers: + if keep_alive: + if version == HttpVersion10: + headers[hdrs.CONNECTION] = "keep-alive" + else: + if version == HttpVersion11: + headers[hdrs.CONNECTION] = "close" + + async def _write_headers(self) -> None: + request = self._req + assert request is not None + writer = self._payload_writer + assert writer is not None + # status line + version = request.version + status_line = "HTTP/{}.{} {} {}".format( + version[0], version[1], self._status, self._reason + ) + await writer.write_headers(status_line, self._headers) + + async def write(self, data: bytes) -> None: + assert isinstance( + data, (bytes, bytearray, memoryview) + ), "data argument must be byte-ish (%r)" % type(data) + + if self._eof_sent: + raise RuntimeError("Cannot call write() after write_eof()") + if self._payload_writer is None: + raise RuntimeError("Cannot call write() before prepare()") + + await self._payload_writer.write(data) + + async def drain(self) -> None: + assert not self._eof_sent, "EOF has already been sent" + assert self._payload_writer is not None, "Response has not been started" + warnings.warn( + "drain method is deprecated, use await resp.write()", + DeprecationWarning, + stacklevel=2, + ) + await self._payload_writer.drain() + + async def write_eof(self, data: bytes = b"") -> None: + assert isinstance( + data, (bytes, bytearray, memoryview) + ), "data argument must be byte-ish (%r)" % type(data) + + if self._eof_sent: + return + + assert self._payload_writer is not None, "Response has not been started" + + await self._payload_writer.write_eof(data) + self._eof_sent = True + self._req = None + self._body_length = self._payload_writer.output_size + self._payload_writer = None + + def __repr__(self) -> str: + if self._eof_sent: + info = "eof" + elif self.prepared: + assert self._req is not None + info = f"{self._req.method} {self._req.path} " + else: + info = "not prepared" + return f"<{self.__class__.__name__} {self.reason} {info}>" + + def __getitem__(self, key: str) -> Any: + return self._state[key] + + def __setitem__(self, key: str, value: Any) -> None: + self._state[key] = value + + def __delitem__(self, key: str) -> None: + del self._state[key] + + def __len__(self) -> int: + return len(self._state) + + def __iter__(self) -> Iterator[str]: + return iter(self._state) + + def __hash__(self) -> int: + return hash(id(self)) + + def __eq__(self, other: object) -> bool: + return self is other + + +class Response(StreamResponse): + __slots__ = ( + "_body_payload", + "_compressed_body", + "_zlib_executor_size", + "_zlib_executor", + ) + + def __init__( + self, + *, + body: Any = None, + status: int = 200, + reason: Optional[str] = None, + text: Optional[str] = None, + headers: Optional[LooseHeaders] = None, + content_type: Optional[str] = None, + charset: Optional[str] = None, + zlib_executor_size: Optional[int] = None, + zlib_executor: Optional[Executor] = None, + ) -> None: + if body is not None and text is not None: + raise ValueError("body and text are not allowed together") + + if headers is None: + real_headers: CIMultiDict[str] = CIMultiDict() + elif not isinstance(headers, CIMultiDict): + real_headers = CIMultiDict(headers) + else: + real_headers = headers # = cast('CIMultiDict[str]', headers) + + if content_type is not None and "charset" in content_type: + raise ValueError("charset must not be in content_type " "argument") + + if text is not None: + if hdrs.CONTENT_TYPE in real_headers: + if content_type or charset: + raise ValueError( + "passing both Content-Type header and " + "content_type or charset params " + "is forbidden" + ) + else: + # fast path for filling headers + if not isinstance(text, str): + raise TypeError("text argument must be str (%r)" % type(text)) + if content_type is None: + content_type = "text/plain" + if charset is None: + charset = "utf-8" + real_headers[hdrs.CONTENT_TYPE] = content_type + "; charset=" + charset + body = text.encode(charset) + text = None + else: + if hdrs.CONTENT_TYPE in real_headers: + if content_type is not None or charset is not None: + raise ValueError( + "passing both Content-Type header and " + "content_type or charset params " + "is forbidden" + ) + else: + if content_type is not None: + if charset is not None: + content_type += "; charset=" + charset + real_headers[hdrs.CONTENT_TYPE] = content_type + + super().__init__(status=status, reason=reason, headers=real_headers) + + if text is not None: + self.text = text + else: + self.body = body + + self._compressed_body: Optional[bytes] = None + self._zlib_executor_size = zlib_executor_size + self._zlib_executor = zlib_executor + + @property + def body(self) -> Optional[Union[bytes, Payload]]: + return self._body + + @body.setter + def body(self, body: bytes) -> None: + if body is None: + self._body: Optional[bytes] = None + self._body_payload: bool = False + elif isinstance(body, (bytes, bytearray)): + self._body = body + self._body_payload = False + else: + try: + self._body = body = payload.PAYLOAD_REGISTRY.get(body) + except payload.LookupError: + raise ValueError("Unsupported body type %r" % type(body)) + + self._body_payload = True + + headers = self._headers + + # set content-type + if hdrs.CONTENT_TYPE not in headers: + headers[hdrs.CONTENT_TYPE] = body.content_type + + # copy payload headers + if body.headers: + for key, value in body.headers.items(): + if key not in headers: + headers[key] = value + + self._compressed_body = None + + @property + def text(self) -> Optional[str]: + if self._body is None: + return None + return self._body.decode(self.charset or "utf-8") + + @text.setter + def text(self, text: str) -> None: + assert isinstance(text, str), "text argument must be str (%r)" % type(text) + + if self.content_type == "application/octet-stream": + self.content_type = "text/plain" + if self.charset is None: + self.charset = "utf-8" + + self._body = text.encode(self.charset) + self._body_payload = False + self._compressed_body = None + + @property + def content_length(self) -> Optional[int]: + if self._chunked: + return None + + if hdrs.CONTENT_LENGTH in self._headers: + return super().content_length + + if self._compressed_body is not None: + # Return length of the compressed body + return len(self._compressed_body) + elif self._body_payload: + # A payload without content length, or a compressed payload + return None + elif self._body is not None: + return len(self._body) + else: + return 0 + + @content_length.setter + def content_length(self, value: Optional[int]) -> None: + raise RuntimeError("Content length is set automatically") + + async def write_eof(self, data: bytes = b"") -> None: + if self._eof_sent: + return + if self._compressed_body is None: + body: Optional[Union[bytes, Payload]] = self._body + else: + body = self._compressed_body + assert not data, f"data arg is not supported, got {data!r}" + assert self._req is not None + assert self._payload_writer is not None + if body is not None: + if self._req._method == hdrs.METH_HEAD or self._status in [204, 304]: + await super().write_eof() + elif self._body_payload: + payload = cast(Payload, body) + await payload.write(self._payload_writer) + await super().write_eof() + else: + await super().write_eof(cast(bytes, body)) + else: + await super().write_eof() + + async def _start(self, request: "BaseRequest") -> AbstractStreamWriter: + if not self._chunked and hdrs.CONTENT_LENGTH not in self._headers: + if self._body_payload: + size = cast(Payload, self._body).size + if size is not None: + self._headers[hdrs.CONTENT_LENGTH] = str(size) + else: + body_len = len(self._body) if self._body else "0" + self._headers[hdrs.CONTENT_LENGTH] = str(body_len) + + return await super()._start(request) + + async def _do_start_compression(self, coding: ContentCoding) -> None: + if self._body_payload or self._chunked: + return await super()._do_start_compression(coding) + + if coding != ContentCoding.identity: + # Instead of using _payload_writer.enable_compression, + # compress the whole body + compressor = ZLibCompressor( + encoding=str(coding.value), + max_sync_chunk_size=self._zlib_executor_size, + executor=self._zlib_executor, + ) + assert self._body is not None + if self._zlib_executor_size is None and len(self._body) > 1024 * 1024: + warnings.warn( + "Synchronous compression of large response bodies " + f"({len(self._body)} bytes) might block the async event loop. " + "Consider providing a custom value to zlib_executor_size/" + "zlib_executor response properties or disabling compression on it." + ) + self._compressed_body = ( + await compressor.compress(self._body) + compressor.flush() + ) + assert self._compressed_body is not None + + self._headers[hdrs.CONTENT_ENCODING] = coding.value + self._headers[hdrs.CONTENT_LENGTH] = str(len(self._compressed_body)) + + +def json_response( + data: Any = sentinel, + *, + text: Optional[str] = None, + body: Optional[bytes] = None, + status: int = 200, + reason: Optional[str] = None, + headers: Optional[LooseHeaders] = None, + content_type: str = "application/json", + dumps: JSONEncoder = json.dumps, +) -> Response: + if data is not sentinel: + if text or body: + raise ValueError("only one of data, text, or body should be specified") + else: + text = dumps(data) + return Response( + text=text, + body=body, + status=status, + reason=reason, + headers=headers, + content_type=content_type, + ) diff --git a/aiohttp/web_routedef.py b/aiohttp/web_routedef.py new file mode 100644 index 0000000..e1ea3c1 --- /dev/null +++ b/aiohttp/web_routedef.py @@ -0,0 +1,215 @@ +import abc +import dataclasses +import os # noqa +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Iterator, + List, + Optional, + Sequence, + Type, + Union, + overload, +) + +from . import hdrs +from .abc import AbstractView +from .typedefs import Handler, PathLike + +if TYPE_CHECKING: # pragma: no cover + from .web_request import Request + from .web_response import StreamResponse + from .web_urldispatcher import AbstractRoute, UrlDispatcher +else: + Request = StreamResponse = UrlDispatcher = AbstractRoute = None + + +__all__ = ( + "AbstractRouteDef", + "RouteDef", + "StaticDef", + "RouteTableDef", + "head", + "options", + "get", + "post", + "patch", + "put", + "delete", + "route", + "view", + "static", +) + + +class AbstractRouteDef(abc.ABC): + @abc.abstractmethod + def register(self, router: UrlDispatcher) -> List[AbstractRoute]: + pass # pragma: no cover + + +_HandlerType = Union[Type[AbstractView], Handler] + + +@dataclasses.dataclass(frozen=True, repr=False) +class RouteDef(AbstractRouteDef): + method: str + path: str + handler: _HandlerType + kwargs: Dict[str, Any] + + def __repr__(self) -> str: + info = [] + for name, value in sorted(self.kwargs.items()): + info.append(f", {name}={value!r}") + return " {handler.__name__!r}" "{info}>".format( + method=self.method, path=self.path, handler=self.handler, info="".join(info) + ) + + def register(self, router: UrlDispatcher) -> List[AbstractRoute]: + if self.method in hdrs.METH_ALL: + reg = getattr(router, "add_" + self.method.lower()) + return [reg(self.path, self.handler, **self.kwargs)] + else: + return [ + router.add_route(self.method, self.path, self.handler, **self.kwargs) + ] + + +@dataclasses.dataclass(frozen=True, repr=False) +class StaticDef(AbstractRouteDef): + prefix: str + path: PathLike + kwargs: Dict[str, Any] + + def __repr__(self) -> str: + info = [] + for name, value in sorted(self.kwargs.items()): + info.append(f", {name}={value!r}") + return " {path}" "{info}>".format( + prefix=self.prefix, path=self.path, info="".join(info) + ) + + def register(self, router: UrlDispatcher) -> List[AbstractRoute]: + resource = router.add_static(self.prefix, self.path, **self.kwargs) + routes = resource.get_info().get("routes", {}) + return list(routes.values()) + + +def route(method: str, path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return RouteDef(method, path, handler, kwargs) + + +def head(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_HEAD, path, handler, **kwargs) + + +def options(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_OPTIONS, path, handler, **kwargs) + + +def get( + path: str, + handler: _HandlerType, + *, + name: Optional[str] = None, + allow_head: bool = True, + **kwargs: Any, +) -> RouteDef: + return route( + hdrs.METH_GET, path, handler, name=name, allow_head=allow_head, **kwargs + ) + + +def post(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_POST, path, handler, **kwargs) + + +def put(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_PUT, path, handler, **kwargs) + + +def patch(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_PATCH, path, handler, **kwargs) + + +def delete(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_DELETE, path, handler, **kwargs) + + +def view(path: str, handler: Type[AbstractView], **kwargs: Any) -> RouteDef: + return route(hdrs.METH_ANY, path, handler, **kwargs) + + +def static(prefix: str, path: PathLike, **kwargs: Any) -> StaticDef: + return StaticDef(prefix, path, kwargs) + + +_Deco = Callable[[_HandlerType], _HandlerType] + + +class RouteTableDef(Sequence[AbstractRouteDef]): + """Route definition table""" + + def __init__(self) -> None: + self._items: List[AbstractRouteDef] = [] + + def __repr__(self) -> str: + return f"" + + @overload + def __getitem__(self, index: int) -> AbstractRouteDef: + ... + + @overload + def __getitem__(self, index: slice) -> List[AbstractRouteDef]: + ... + + def __getitem__(self, index): # type: ignore[no-untyped-def] + return self._items[index] + + def __iter__(self) -> Iterator[AbstractRouteDef]: + return iter(self._items) + + def __len__(self) -> int: + return len(self._items) + + def __contains__(self, item: object) -> bool: + return item in self._items + + def route(self, method: str, path: str, **kwargs: Any) -> _Deco: + def inner(handler: _HandlerType) -> _HandlerType: + self._items.append(RouteDef(method, path, handler, kwargs)) + return handler + + return inner + + def head(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_HEAD, path, **kwargs) + + def get(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_GET, path, **kwargs) + + def post(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_POST, path, **kwargs) + + def put(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_PUT, path, **kwargs) + + def patch(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_PATCH, path, **kwargs) + + def delete(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_DELETE, path, **kwargs) + + def options(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_OPTIONS, path, **kwargs) + + def view(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_ANY, path, **kwargs) + + def static(self, prefix: str, path: PathLike, **kwargs: Any) -> None: + self._items.append(StaticDef(prefix, path, kwargs)) diff --git a/aiohttp/web_runner.py b/aiohttp/web_runner.py new file mode 100644 index 0000000..3063dce --- /dev/null +++ b/aiohttp/web_runner.py @@ -0,0 +1,450 @@ +import asyncio +import signal +import socket +from abc import ABC, abstractmethod +from contextlib import suppress +from typing import Any, List, Optional, Set, Type + +from yarl import URL + +from .abc import AbstractAccessLogger, AbstractStreamWriter +from .http_parser import RawRequestMessage +from .streams import StreamReader +from .typedefs import PathLike +from .web_app import Application +from .web_log import AccessLogger +from .web_protocol import RequestHandler +from .web_request import Request +from .web_server import Server + +try: + from ssl import SSLContext +except ImportError: + SSLContext = object # type: ignore[misc,assignment] + + +__all__ = ( + "BaseSite", + "TCPSite", + "UnixSite", + "NamedPipeSite", + "SockSite", + "BaseRunner", + "AppRunner", + "ServerRunner", + "GracefulExit", +) + + +class GracefulExit(SystemExit): + code = 1 + + +def _raise_graceful_exit() -> None: + raise GracefulExit() + + +class BaseSite(ABC): + __slots__ = ("_runner", "_shutdown_timeout", "_ssl_context", "_backlog", "_server") + + def __init__( + self, + runner: "BaseRunner", + *, + shutdown_timeout: float = 60.0, + ssl_context: Optional[SSLContext] = None, + backlog: int = 128, + ) -> None: + if runner.server is None: + raise RuntimeError("Call runner.setup() before making a site") + self._runner = runner + self._shutdown_timeout = shutdown_timeout + self._ssl_context = ssl_context + self._backlog = backlog + self._server: Optional[asyncio.AbstractServer] = None + + @property + @abstractmethod + def name(self) -> str: + pass # pragma: no cover + + @abstractmethod + async def start(self) -> None: + self._runner._reg_site(self) + + async def stop(self) -> None: + self._runner._check_site(self) + if self._server is None: + self._runner._unreg_site(self) + return # not started yet + self._server.close() + # named pipes do not have wait_closed property + if hasattr(self._server, "wait_closed"): + await self._server.wait_closed() + + # Wait for pending tasks for a given time limit. + with suppress(asyncio.TimeoutError): + await asyncio.wait_for( + self._wait(asyncio.current_task()), timeout=self._shutdown_timeout + ) + + await self._runner.shutdown() + assert self._runner.server + await self._runner.server.shutdown(self._shutdown_timeout) + self._runner._unreg_site(self) + + async def _wait(self, parent_task: Optional["asyncio.Task[object]"]) -> None: + exclude = self._runner.starting_tasks | {asyncio.current_task(), parent_task} + while tasks := asyncio.all_tasks() - exclude: + await asyncio.wait(tasks) + + +class TCPSite(BaseSite): + __slots__ = ("_host", "_port", "_reuse_address", "_reuse_port") + + def __init__( + self, + runner: "BaseRunner", + host: Optional[str] = None, + port: Optional[int] = None, + *, + shutdown_timeout: float = 60.0, + ssl_context: Optional[SSLContext] = None, + backlog: int = 128, + reuse_address: Optional[bool] = None, + reuse_port: Optional[bool] = None, + ) -> None: + super().__init__( + runner, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + self._host = host + if port is None: + port = 8443 if self._ssl_context else 8080 + self._port = port + self._reuse_address = reuse_address + self._reuse_port = reuse_port + + @property + def name(self) -> str: + scheme = "https" if self._ssl_context else "http" + host = "0.0.0.0" if self._host is None else self._host + return str(URL.build(scheme=scheme, host=host, port=self._port)) + + async def start(self) -> None: + await super().start() + loop = asyncio.get_event_loop() + server = self._runner.server + assert server is not None + self._server = await loop.create_server( + server, + self._host, + self._port, + ssl=self._ssl_context, + backlog=self._backlog, + reuse_address=self._reuse_address, + reuse_port=self._reuse_port, + ) + + +class UnixSite(BaseSite): + __slots__ = ("_path",) + + def __init__( + self, + runner: "BaseRunner", + path: PathLike, + *, + shutdown_timeout: float = 60.0, + ssl_context: Optional[SSLContext] = None, + backlog: int = 128, + ) -> None: + super().__init__( + runner, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + self._path = path + + @property + def name(self) -> str: + scheme = "https" if self._ssl_context else "http" + return f"{scheme}://unix:{self._path}:" + + async def start(self) -> None: + await super().start() + loop = asyncio.get_event_loop() + server = self._runner.server + assert server is not None + self._server = await loop.create_unix_server( + server, + self._path, + ssl=self._ssl_context, + backlog=self._backlog, + ) + + +class NamedPipeSite(BaseSite): + __slots__ = ("_path",) + + def __init__( + self, runner: "BaseRunner", path: str, *, shutdown_timeout: float = 60.0 + ) -> None: + loop = asyncio.get_event_loop() + if not isinstance( + loop, asyncio.ProactorEventLoop # type: ignore[attr-defined] + ): + raise RuntimeError( + "Named Pipes only available in proactor" "loop under windows" + ) + super().__init__(runner, shutdown_timeout=shutdown_timeout) + self._path = path + + @property + def name(self) -> str: + return self._path + + async def start(self) -> None: + await super().start() + loop = asyncio.get_event_loop() + server = self._runner.server + assert server is not None + _server = await loop.start_serving_pipe( # type: ignore[attr-defined] + server, self._path + ) + self._server = _server[0] + + +class SockSite(BaseSite): + __slots__ = ("_sock", "_name") + + def __init__( + self, + runner: "BaseRunner", + sock: socket.socket, + *, + shutdown_timeout: float = 60.0, + ssl_context: Optional[SSLContext] = None, + backlog: int = 128, + ) -> None: + super().__init__( + runner, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + self._sock = sock + scheme = "https" if self._ssl_context else "http" + if hasattr(socket, "AF_UNIX") and sock.family == socket.AF_UNIX: + name = f"{scheme}://unix:{sock.getsockname()}:" + else: + host, port = sock.getsockname()[:2] + name = str(URL.build(scheme=scheme, host=host, port=port)) + self._name = name + + @property + def name(self) -> str: + return self._name + + async def start(self) -> None: + await super().start() + loop = asyncio.get_event_loop() + server = self._runner.server + assert server is not None + self._server = await loop.create_server( + server, sock=self._sock, ssl=self._ssl_context, backlog=self._backlog + ) + + +class BaseRunner(ABC): + __slots__ = ("starting_tasks", "_handle_signals", "_kwargs", "_server", "_sites") + + def __init__(self, *, handle_signals: bool = False, **kwargs: Any) -> None: + self._handle_signals = handle_signals + self._kwargs = kwargs + self._server: Optional[Server] = None + self._sites: List[BaseSite] = [] + + @property + def server(self) -> Optional[Server]: + return self._server + + @property + def addresses(self) -> List[Any]: + ret: List[Any] = [] + for site in self._sites: + server = site._server + if server is not None: + sockets = server.sockets # type: ignore[attr-defined] + if sockets is not None: + for sock in sockets: + ret.append(sock.getsockname()) + return ret + + @property + def sites(self) -> Set[BaseSite]: + return set(self._sites) + + async def setup(self) -> None: + loop = asyncio.get_event_loop() + + if self._handle_signals: + try: + loop.add_signal_handler(signal.SIGINT, _raise_graceful_exit) + loop.add_signal_handler(signal.SIGTERM, _raise_graceful_exit) + except NotImplementedError: # pragma: no cover + # add_signal_handler is not implemented on Windows + pass + + self._server = await self._make_server() + # On shutdown we want to avoid waiting on tasks which run forever. + # It's very likely that all tasks which run forever will have been created by + # the time we have completed the application startup (in self._make_server()), + # so we just record all running tasks here and exclude them later. + self.starting_tasks = asyncio.all_tasks() + + @abstractmethod + async def shutdown(self) -> None: + pass # pragma: no cover + + async def cleanup(self) -> None: + loop = asyncio.get_event_loop() + + # The loop over sites is intentional, an exception on gather() + # leaves self._sites in unpredictable state. + # The loop guarantees that a site is either deleted on success or + # still present on failure + for site in list(self._sites): + await site.stop() + await self._cleanup_server() + self._server = None + if self._handle_signals: + try: + loop.remove_signal_handler(signal.SIGINT) + loop.remove_signal_handler(signal.SIGTERM) + except NotImplementedError: # pragma: no cover + # remove_signal_handler is not implemented on Windows + pass + + @abstractmethod + async def _make_server(self) -> Server: + pass # pragma: no cover + + @abstractmethod + async def _cleanup_server(self) -> None: + pass # pragma: no cover + + def _reg_site(self, site: BaseSite) -> None: + if site in self._sites: + raise RuntimeError(f"Site {site} is already registered in runner {self}") + self._sites.append(site) + + def _check_site(self, site: BaseSite) -> None: + if site not in self._sites: + raise RuntimeError(f"Site {site} is not registered in runner {self}") + + def _unreg_site(self, site: BaseSite) -> None: + if site not in self._sites: + raise RuntimeError(f"Site {site} is not registered in runner {self}") + self._sites.remove(site) + + +class ServerRunner(BaseRunner): + """Low-level web server runner""" + + __slots__ = ("_web_server",) + + def __init__( + self, web_server: Server, *, handle_signals: bool = False, **kwargs: Any + ) -> None: + super().__init__(handle_signals=handle_signals, **kwargs) + self._web_server = web_server + + async def shutdown(self) -> None: + pass + + async def _make_server(self) -> Server: + return self._web_server + + async def _cleanup_server(self) -> None: + pass + + +class AppRunner(BaseRunner): + """Web Application runner""" + + __slots__ = ("_app",) + + def __init__( + self, + app: Application, + *, + handle_signals: bool = False, + access_log_class: Type[AbstractAccessLogger] = AccessLogger, + **kwargs: Any, + ) -> None: + if not isinstance(app, Application): + raise TypeError( + "The first argument should be web.Application " + "instance, got {!r}".format(app) + ) + kwargs["access_log_class"] = access_log_class + + if app._handler_args: + for k, v in app._handler_args.items(): + kwargs[k] = v + + if not issubclass(kwargs["access_log_class"], AbstractAccessLogger): + raise TypeError( + "access_log_class must be subclass of " + "aiohttp.abc.AbstractAccessLogger, got {}".format( + kwargs["access_log_class"] + ) + ) + + super().__init__(handle_signals=handle_signals, **kwargs) + self._app = app + + @property + def app(self) -> Application: + return self._app + + async def shutdown(self) -> None: + await self._app.shutdown() + + async def _make_server(self) -> Server: + self._app.on_startup.freeze() + await self._app.startup() + self._app.freeze() + + return Server( + self._app._handle, # type: ignore[arg-type] + request_factory=self._make_request, + **self._kwargs, + ) + + def _make_request( + self, + message: RawRequestMessage, + payload: StreamReader, + protocol: RequestHandler, + writer: AbstractStreamWriter, + task: "asyncio.Task[None]", + _cls: Type[Request] = Request, + ) -> Request: + loop = asyncio.get_running_loop() + return _cls( + message, + payload, + protocol, + writer, + task, + loop, + client_max_size=self.app._client_max_size, + ) + + async def _cleanup_server(self) -> None: + await self._app.cleanup() diff --git a/aiohttp/web_server.py b/aiohttp/web_server.py new file mode 100644 index 0000000..a3d658a --- /dev/null +++ b/aiohttp/web_server.py @@ -0,0 +1,79 @@ +"""Low level HTTP server.""" +import asyncio +import warnings +from typing import Any, Awaitable, Callable, Dict, List, Optional # noqa + +from .abc import AbstractStreamWriter +from .http_parser import RawRequestMessage +from .streams import StreamReader +from .web_protocol import RequestHandler, _RequestFactory, _RequestHandler +from .web_request import BaseRequest + +__all__ = ("Server",) + + +class Server: + def __init__( + self, + handler: _RequestHandler, + *, + request_factory: Optional[_RequestFactory] = None, + debug: Optional[bool] = None, + handler_cancellation: bool = False, + **kwargs: Any, + ) -> None: + if debug is not None: + warnings.warn( + "debug argument is no-op since 4.0 " "and scheduled for removal in 5.0", + DeprecationWarning, + stacklevel=2, + ) + self._loop = asyncio.get_running_loop() + self._connections: Dict[RequestHandler, asyncio.Transport] = {} + self._kwargs = kwargs + self.requests_count = 0 + self.request_handler = handler + self.request_factory = request_factory or self._make_request + self.handler_cancellation = handler_cancellation + + @property + def connections(self) -> List[RequestHandler]: + return list(self._connections.keys()) + + def connection_made( + self, handler: RequestHandler, transport: asyncio.Transport + ) -> None: + self._connections[handler] = transport + + def connection_lost( + self, handler: RequestHandler, exc: Optional[BaseException] = None + ) -> None: + if handler in self._connections: + del self._connections[handler] + + def _make_request( + self, + message: RawRequestMessage, + payload: StreamReader, + protocol: RequestHandler, + writer: AbstractStreamWriter, + task: "asyncio.Task[None]", + ) -> BaseRequest: + return BaseRequest(message, payload, protocol, writer, task, self._loop) + + async def shutdown(self, timeout: Optional[float] = None) -> None: + coros = [conn.shutdown(timeout) for conn in self._connections] + await asyncio.gather(*coros) + self._connections.clear() + + def __call__(self) -> RequestHandler: + try: + return RequestHandler(self, loop=self._loop, **self._kwargs) + except TypeError: + # Failsafe creation: remove all custom handler_args + kwargs = { + k: v + for k, v in self._kwargs.items() + if k in ["debug", "access_log_class"] + } + return RequestHandler(self, loop=self._loop, **kwargs) diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py new file mode 100644 index 0000000..5c80323 --- /dev/null +++ b/aiohttp/web_urldispatcher.py @@ -0,0 +1,1198 @@ +import abc +import asyncio +import base64 +import hashlib +import keyword +import os +import re +from contextlib import contextmanager +from pathlib import Path +from types import MappingProxyType +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Container, + Dict, + Final, + Generator, + Iterable, + Iterator, + List, + Mapping, + NoReturn, + Optional, + Pattern, + Set, + Sized, + Tuple, + Type, + TypedDict, + Union, + cast, +) + +from yarl import URL, __version__ as yarl_version # type: ignore[attr-defined] + +from . import hdrs +from .abc import AbstractMatchInfo, AbstractRouter, AbstractView +from .helpers import DEBUG +from .http import HttpVersion11 +from .typedefs import Handler, PathLike +from .web_exceptions import ( + HTTPException, + HTTPExpectationFailed, + HTTPForbidden, + HTTPMethodNotAllowed, + HTTPNotFound, +) +from .web_fileresponse import FileResponse +from .web_request import Request +from .web_response import Response, StreamResponse +from .web_routedef import AbstractRouteDef + +__all__ = ( + "UrlDispatcher", + "UrlMappingMatchInfo", + "AbstractResource", + "Resource", + "PlainResource", + "DynamicResource", + "AbstractRoute", + "ResourceRoute", + "StaticResource", + "View", +) + + +if TYPE_CHECKING: # pragma: no cover + from .web_app import Application + + BaseDict = Dict[str, str] +else: + BaseDict = dict + +YARL_VERSION: Final[Tuple[int, ...]] = tuple(map(int, yarl_version.split(".")[:2])) + +HTTP_METHOD_RE: Final[Pattern[str]] = re.compile( + r"^[0-9A-Za-z!#\$%&'\*\+\-\.\^_`\|~]+$" +) +ROUTE_RE: Final[Pattern[str]] = re.compile( + r"(\{[_a-zA-Z][^{}]*(?:\{[^{}]*\}[^{}]*)*\})" +) +PATH_SEP: Final[str] = re.escape("/") + + +_ExpectHandler = Callable[[Request], Awaitable[Optional[StreamResponse]]] +_Resolve = Tuple[Optional["UrlMappingMatchInfo"], Set[str]] + + +class _InfoDict(TypedDict, total=False): + path: str + + formatter: str + pattern: Pattern[str] + + directory: Path + prefix: str + routes: Mapping[str, "AbstractRoute"] + + app: "Application" + + domain: str + + rule: "AbstractRuleMatching" + + http_exception: HTTPException + + +class AbstractResource(Sized, Iterable["AbstractRoute"]): + def __init__(self, *, name: Optional[str] = None) -> None: + self._name = name + + @property + def name(self) -> Optional[str]: + return self._name + + @property + @abc.abstractmethod + def canonical(self) -> str: + """Exposes the resource's canonical path. + + For example '/foo/bar/{name}' + + """ + + @abc.abstractmethod # pragma: no branch + def url_for(self, **kwargs: str) -> URL: + """Construct url for resource with additional params.""" + + @abc.abstractmethod # pragma: no branch + async def resolve(self, request: Request) -> _Resolve: + """Resolve resource. + + Return (UrlMappingMatchInfo, allowed_methods) pair. + """ + + @abc.abstractmethod + def add_prefix(self, prefix: str) -> None: + """Add a prefix to processed URLs. + + Required for subapplications support. + """ + + @abc.abstractmethod + def get_info(self) -> _InfoDict: + """Return a dict with additional info useful for introspection""" + + def freeze(self) -> None: + pass + + @abc.abstractmethod + def raw_match(self, path: str) -> bool: + """Perform a raw match against path""" + + +class AbstractRoute(abc.ABC): + def __init__( + self, + method: str, + handler: Union[Handler, Type[AbstractView]], + *, + expect_handler: Optional[_ExpectHandler] = None, + resource: Optional[AbstractResource] = None, + ) -> None: + if expect_handler is None: + expect_handler = _default_expect_handler + + assert asyncio.iscoroutinefunction( + expect_handler + ), f"Coroutine is expected, got {expect_handler!r}" + + method = method.upper() + if not HTTP_METHOD_RE.match(method): + raise ValueError(f"{method} is not allowed HTTP method") + + if asyncio.iscoroutinefunction(handler): + pass + elif isinstance(handler, type) and issubclass(handler, AbstractView): + pass + else: + raise TypeError( + "Only async functions are allowed as web-handlers " + ", got {!r}".format(handler) + ) + + self._method = method + self._handler = handler + self._expect_handler = expect_handler + self._resource = resource + + @property + def method(self) -> str: + return self._method + + @property + def handler(self) -> Handler: + return self._handler + + @property + @abc.abstractmethod + def name(self) -> Optional[str]: + """Optional route's name, always equals to resource's name.""" + + @property + def resource(self) -> Optional[AbstractResource]: + return self._resource + + @abc.abstractmethod + def get_info(self) -> _InfoDict: + """Return a dict with additional info useful for introspection""" + + @abc.abstractmethod # pragma: no branch + def url_for(self, *args: str, **kwargs: str) -> URL: + """Construct url for route with additional params.""" + + async def handle_expect_header(self, request: Request) -> Optional[StreamResponse]: + return await self._expect_handler(request) + + +class UrlMappingMatchInfo(BaseDict, AbstractMatchInfo): + def __init__(self, match_dict: Dict[str, str], route: AbstractRoute): + super().__init__(match_dict) + self._route = route + self._apps: List[Application] = [] + self._current_app: Optional[Application] = None + self._frozen = False + + @property + def handler(self) -> Handler: + return self._route.handler + + @property + def route(self) -> AbstractRoute: + return self._route + + @property + def expect_handler(self) -> _ExpectHandler: + return self._route.handle_expect_header + + @property + def http_exception(self) -> Optional[HTTPException]: + return None + + def get_info(self) -> _InfoDict: # type: ignore[override] + return self._route.get_info() + + @property + def apps(self) -> Tuple["Application", ...]: + return tuple(self._apps) + + def add_app(self, app: "Application") -> None: + if self._frozen: + raise RuntimeError("Cannot change apps stack after .freeze() call") + if self._current_app is None: + self._current_app = app + self._apps.insert(0, app) + + @property + def current_app(self) -> "Application": + app = self._current_app + assert app is not None + return app + + @contextmanager + def set_current_app(self, app: "Application") -> Generator[None, None, None]: + if DEBUG: # pragma: no cover + if app not in self._apps: + raise RuntimeError( + "Expected one of the following apps {!r}, got {!r}".format( + self._apps, app + ) + ) + prev = self._current_app + self._current_app = app + try: + yield + finally: + self._current_app = prev + + def freeze(self) -> None: + self._frozen = True + + def __repr__(self) -> str: + return f"" + + +class MatchInfoError(UrlMappingMatchInfo): + def __init__(self, http_exception: HTTPException) -> None: + self._exception = http_exception + super().__init__({}, SystemRoute(self._exception)) + + @property + def http_exception(self) -> HTTPException: + return self._exception + + def __repr__(self) -> str: + return "".format( + self._exception.status, self._exception.reason + ) + + +async def _default_expect_handler(request: Request) -> None: + """Default handler for Expect header. + + Just send "100 Continue" to client. + raise HTTPExpectationFailed if value of header is not "100-continue" + """ + expect = request.headers.get(hdrs.EXPECT, "") + if request.version == HttpVersion11: + if expect.lower() == "100-continue": + await request.writer.write(b"HTTP/1.1 100 Continue\r\n\r\n") + else: + raise HTTPExpectationFailed(text="Unknown Expect: %s" % expect) + + +class Resource(AbstractResource): + def __init__(self, *, name: Optional[str] = None) -> None: + super().__init__(name=name) + self._routes: List[ResourceRoute] = [] + + def add_route( + self, + method: str, + handler: Union[Type[AbstractView], Handler], + *, + expect_handler: Optional[_ExpectHandler] = None, + ) -> "ResourceRoute": + for route_obj in self._routes: + if route_obj.method == method or route_obj.method == hdrs.METH_ANY: + raise RuntimeError( + "Added route will never be executed, " + "method {route.method} is already " + "registered".format(route=route_obj) + ) + + route_obj = ResourceRoute(method, handler, self, expect_handler=expect_handler) + self.register_route(route_obj) + return route_obj + + def register_route(self, route: "ResourceRoute") -> None: + assert isinstance( + route, ResourceRoute + ), f"Instance of Route class is required, got {route!r}" + self._routes.append(route) + + async def resolve(self, request: Request) -> _Resolve: + allowed_methods: Set[str] = set() + + match_dict = self._match(request.rel_url.raw_path) + if match_dict is None: + return None, allowed_methods + + for route_obj in self._routes: + route_method = route_obj.method + allowed_methods.add(route_method) + + if route_method == request.method or route_method == hdrs.METH_ANY: + return (UrlMappingMatchInfo(match_dict, route_obj), allowed_methods) + else: + return None, allowed_methods + + @abc.abstractmethod + def _match(self, path: str) -> Optional[Dict[str, str]]: + pass # pragma: no cover + + def __len__(self) -> int: + return len(self._routes) + + def __iter__(self) -> Iterator["ResourceRoute"]: + return iter(self._routes) + + # TODO: implement all abstract methods + + +class PlainResource(Resource): + def __init__(self, path: str, *, name: Optional[str] = None) -> None: + super().__init__(name=name) + assert not path or path.startswith("/") + self._path = path + + @property + def canonical(self) -> str: + return self._path + + def freeze(self) -> None: + if not self._path: + self._path = "/" + + def add_prefix(self, prefix: str) -> None: + assert prefix.startswith("/") + assert not prefix.endswith("/") + assert len(prefix) > 1 + self._path = prefix + self._path + + def _match(self, path: str) -> Optional[Dict[str, str]]: + # string comparison is about 10 times faster than regexp matching + if self._path == path: + return {} + else: + return None + + def raw_match(self, path: str) -> bool: + return self._path == path + + def get_info(self) -> _InfoDict: + return {"path": self._path} + + def url_for(self) -> URL: # type: ignore[override] + return URL.build(path=self._path, encoded=True) + + def __repr__(self) -> str: + name = "'" + self.name + "' " if self.name is not None else "" + return f"" + + +class DynamicResource(Resource): + DYN = re.compile(r"\{(?P[_a-zA-Z][_a-zA-Z0-9]*)\}") + DYN_WITH_RE = re.compile(r"\{(?P[_a-zA-Z][_a-zA-Z0-9]*):(?P.+)\}") + GOOD = r"[^{}/]+" + + def __init__(self, path: str, *, name: Optional[str] = None) -> None: + super().__init__(name=name) + pattern = "" + formatter = "" + for part in ROUTE_RE.split(path): + match = self.DYN.fullmatch(part) + if match: + pattern += "(?P<{}>{})".format(match.group("var"), self.GOOD) + formatter += "{" + match.group("var") + "}" + continue + + match = self.DYN_WITH_RE.fullmatch(part) + if match: + pattern += "(?P<{var}>{re})".format(**match.groupdict()) + formatter += "{" + match.group("var") + "}" + continue + + if "{" in part or "}" in part: + raise ValueError(f"Invalid path '{path}'['{part}']") + + part = _requote_path(part) + formatter += part + pattern += re.escape(part) + + try: + compiled = re.compile(pattern) + except re.error as exc: + raise ValueError(f"Bad pattern '{pattern}': {exc}") from None + assert compiled.pattern.startswith(PATH_SEP) + assert formatter.startswith("/") + self._pattern = compiled + self._formatter = formatter + + @property + def canonical(self) -> str: + return self._formatter + + def add_prefix(self, prefix: str) -> None: + assert prefix.startswith("/") + assert not prefix.endswith("/") + assert len(prefix) > 1 + self._pattern = re.compile(re.escape(prefix) + self._pattern.pattern) + self._formatter = prefix + self._formatter + + def _match(self, path: str) -> Optional[Dict[str, str]]: + match = self._pattern.fullmatch(path) + if match is None: + return None + else: + return { + key: _unquote_path(value) for key, value in match.groupdict().items() + } + + def raw_match(self, path: str) -> bool: + return self._formatter == path + + def get_info(self) -> _InfoDict: + return {"formatter": self._formatter, "pattern": self._pattern} + + def url_for(self, **parts: str) -> URL: + url = self._formatter.format_map({k: _quote_path(v) for k, v in parts.items()}) + return URL.build(path=url, encoded=True) + + def __repr__(self) -> str: + name = "'" + self.name + "' " if self.name is not None else "" + return "".format( + name=name, formatter=self._formatter + ) + + +class PrefixResource(AbstractResource): + def __init__(self, prefix: str, *, name: Optional[str] = None) -> None: + assert not prefix or prefix.startswith("/"), prefix + assert prefix in ("", "/") or not prefix.endswith("/"), prefix + super().__init__(name=name) + self._prefix = _requote_path(prefix) + self._prefix2 = self._prefix + "/" + + @property + def canonical(self) -> str: + return self._prefix + + def add_prefix(self, prefix: str) -> None: + assert prefix.startswith("/") + assert not prefix.endswith("/") + assert len(prefix) > 1 + self._prefix = prefix + self._prefix + self._prefix2 = self._prefix + "/" + + def raw_match(self, prefix: str) -> bool: + return False + + # TODO: impl missing abstract methods + + +class StaticResource(PrefixResource): + VERSION_KEY = "v" + + def __init__( + self, + prefix: str, + directory: PathLike, + *, + name: Optional[str] = None, + expect_handler: Optional[_ExpectHandler] = None, + chunk_size: int = 256 * 1024, + show_index: bool = False, + follow_symlinks: bool = False, + append_version: bool = False, + ) -> None: + super().__init__(prefix, name=name) + try: + directory = Path(directory) + if str(directory).startswith("~"): + directory = Path(os.path.expanduser(str(directory))) + directory = directory.resolve() + if not directory.is_dir(): + raise ValueError("Not a directory") + except (FileNotFoundError, ValueError) as error: + raise ValueError(f"No directory exists at '{directory}'") from error + self._directory = directory + self._show_index = show_index + self._chunk_size = chunk_size + self._follow_symlinks = follow_symlinks + self._expect_handler = expect_handler + self._append_version = append_version + + self._routes = { + "GET": ResourceRoute( + "GET", self._handle, self, expect_handler=expect_handler + ), + "HEAD": ResourceRoute( + "HEAD", self._handle, self, expect_handler=expect_handler + ), + } + + def url_for( # type: ignore[override] + self, + *, + filename: PathLike, + append_version: Optional[bool] = None, + ) -> URL: + if append_version is None: + append_version = self._append_version + filename = str(filename).lstrip("/") + + url = URL.build(path=self._prefix, encoded=True) + # filename is not encoded + if YARL_VERSION < (1, 6): + url = url / filename.replace("%", "%25") + else: + url = url / filename + + if append_version: + try: + filepath = self._directory.joinpath(filename).resolve() + if not self._follow_symlinks: + filepath.relative_to(self._directory) + except (ValueError, FileNotFoundError): + # ValueError for case when path point to symlink + # with follow_symlinks is False + return url # relatively safe + if filepath.is_file(): + # TODO cache file content + # with file watcher for cache invalidation + with filepath.open("rb") as f: + file_bytes = f.read() + h = self._get_file_hash(file_bytes) + url = url.with_query({self.VERSION_KEY: h}) + return url + return url + + @staticmethod + def _get_file_hash(byte_array: bytes) -> str: + m = hashlib.sha256() # todo sha256 can be configurable param + m.update(byte_array) + b64 = base64.urlsafe_b64encode(m.digest()) + return b64.decode("ascii") + + def get_info(self) -> _InfoDict: + return { + "directory": self._directory, + "prefix": self._prefix, + "routes": self._routes, + } + + def set_options_route(self, handler: Handler) -> None: + if "OPTIONS" in self._routes: + raise RuntimeError("OPTIONS route was set already") + self._routes["OPTIONS"] = ResourceRoute( + "OPTIONS", handler, self, expect_handler=self._expect_handler + ) + + async def resolve(self, request: Request) -> _Resolve: + path = request.rel_url.raw_path + method = request.method + allowed_methods = set(self._routes) + if not path.startswith(self._prefix2) and path != self._prefix: + return None, set() + + if method not in allowed_methods: + return None, allowed_methods + + match_dict = {"filename": _unquote_path(path[len(self._prefix) + 1 :])} + return (UrlMappingMatchInfo(match_dict, self._routes[method]), allowed_methods) + + def __len__(self) -> int: + return len(self._routes) + + def __iter__(self) -> Iterator[AbstractRoute]: + return iter(self._routes.values()) + + async def _handle(self, request: Request) -> StreamResponse: + rel_url = request.match_info["filename"] + try: + filename = Path(rel_url) + if filename.anchor: + # rel_url is an absolute name like + # /static/\\machine_name\c$ or /static/D:\path + # where the static dir is totally different + raise HTTPForbidden() + filepath = self._directory.joinpath(filename).resolve() + if not self._follow_symlinks: + filepath.relative_to(self._directory) + except (ValueError, FileNotFoundError) as error: + # relatively safe + raise HTTPNotFound() from error + except HTTPForbidden: + raise + except Exception as error: + # perm error or other kind! + request.app.logger.exception(error) + raise HTTPNotFound() from error + + # on opening a dir, load its contents if allowed + if filepath.is_dir(): + if self._show_index: + try: + return Response( + text=self._directory_as_html(filepath), content_type="text/html" + ) + except PermissionError: + raise HTTPForbidden() + else: + raise HTTPForbidden() + elif filepath.is_file(): + return FileResponse(filepath, chunk_size=self._chunk_size) + else: + raise HTTPNotFound + + def _directory_as_html(self, filepath: Path) -> str: + # returns directory's index as html + + # sanity check + assert filepath.is_dir() + + relative_path_to_dir = filepath.relative_to(self._directory).as_posix() + index_of = f"Index of /{relative_path_to_dir}" + h1 = f"

{index_of}

" + + index_list = [] + dir_index = filepath.iterdir() + for _file in sorted(dir_index): + # show file url as relative to static path + rel_path = _file.relative_to(self._directory).as_posix() + file_url = self._prefix + "/" + rel_path + + # if file is a directory, add '/' to the end of the name + if _file.is_dir(): + file_name = f"{_file.name}/" + else: + file_name = _file.name + + index_list.append( + '
  • {name}
  • '.format( + url=file_url, name=file_name + ) + ) + ul = "
      \n{}\n
    ".format("\n".join(index_list)) + body = f"\n{h1}\n{ul}\n" + + head_str = f"\n{index_of}\n" + html = f"\n{head_str}\n{body}\n" + + return html + + def __repr__(self) -> str: + name = "'" + self.name + "'" if self.name is not None else "" + return " {directory!r}>".format( + name=name, path=self._prefix, directory=self._directory + ) + + +class PrefixedSubAppResource(PrefixResource): + def __init__(self, prefix: str, app: "Application") -> None: + super().__init__(prefix) + self._app = app + for resource in app.router.resources(): + resource.add_prefix(prefix) + + def add_prefix(self, prefix: str) -> None: + super().add_prefix(prefix) + for resource in self._app.router.resources(): + resource.add_prefix(prefix) + + def url_for(self, *args: str, **kwargs: str) -> URL: + raise RuntimeError(".url_for() is not supported " "by sub-application root") + + def get_info(self) -> _InfoDict: + return {"app": self._app, "prefix": self._prefix} + + async def resolve(self, request: Request) -> _Resolve: + if ( + not request.url.raw_path.startswith(self._prefix2) + and request.url.raw_path != self._prefix + ): + return None, set() + match_info = await self._app.router.resolve(request) + match_info.add_app(self._app) + if isinstance(match_info.http_exception, HTTPMethodNotAllowed): + methods = match_info.http_exception.allowed_methods + else: + methods = set() + return match_info, methods + + def __len__(self) -> int: + return len(self._app.router.routes()) + + def __iter__(self) -> Iterator[AbstractRoute]: + return iter(self._app.router.routes()) + + def __repr__(self) -> str: + return " {app!r}>".format( + prefix=self._prefix, app=self._app + ) + + +class AbstractRuleMatching(abc.ABC): + @abc.abstractmethod # pragma: no branch + async def match(self, request: Request) -> bool: + """Return bool if the request satisfies the criteria""" + + @abc.abstractmethod # pragma: no branch + def get_info(self) -> _InfoDict: + """Return a dict with additional info useful for introspection""" + + @property + @abc.abstractmethod # pragma: no branch + def canonical(self) -> str: + """Return a str""" + + +class Domain(AbstractRuleMatching): + re_part = re.compile(r"(?!-)[a-z\d-]{1,63}(? None: + super().__init__() + self._domain = self.validation(domain) + + @property + def canonical(self) -> str: + return self._domain + + def validation(self, domain: str) -> str: + if not isinstance(domain, str): + raise TypeError("Domain must be str") + domain = domain.rstrip(".").lower() + if not domain: + raise ValueError("Domain cannot be empty") + elif "://" in domain: + raise ValueError("Scheme not supported") + url = URL("http://" + domain) + assert url.raw_host is not None + if not all(self.re_part.fullmatch(x) for x in url.raw_host.split(".")): + raise ValueError("Domain not valid") + if url.port == 80: + return url.raw_host + return f"{url.raw_host}:{url.port}" + + async def match(self, request: Request) -> bool: + host = request.headers.get(hdrs.HOST) + if not host: + return False + return self.match_domain(host) + + def match_domain(self, host: str) -> bool: + return host.lower() == self._domain + + def get_info(self) -> _InfoDict: + return {"domain": self._domain} + + +class MaskDomain(Domain): + re_part = re.compile(r"(?!-)[a-z\d\*-]{1,63}(? None: + super().__init__(domain) + mask = self._domain.replace(".", r"\.").replace("*", ".*") + self._mask = re.compile(mask) + + @property + def canonical(self) -> str: + return self._mask.pattern + + def match_domain(self, host: str) -> bool: + return self._mask.fullmatch(host) is not None + + +class MatchedSubAppResource(PrefixedSubAppResource): + def __init__(self, rule: AbstractRuleMatching, app: "Application") -> None: + AbstractResource.__init__(self) + self._prefix = "" + self._app = app + self._rule = rule + + @property + def canonical(self) -> str: + return self._rule.canonical + + def get_info(self) -> _InfoDict: + return {"app": self._app, "rule": self._rule} + + async def resolve(self, request: Request) -> _Resolve: + if not await self._rule.match(request): + return None, set() + match_info = await self._app.router.resolve(request) + match_info.add_app(self._app) + if isinstance(match_info.http_exception, HTTPMethodNotAllowed): + methods = match_info.http_exception.allowed_methods + else: + methods = set() + return match_info, methods + + def __repr__(self) -> str: + return " {app!r}>" "".format(app=self._app) + + +class ResourceRoute(AbstractRoute): + """A route with resource""" + + def __init__( + self, + method: str, + handler: Union[Handler, Type[AbstractView]], + resource: AbstractResource, + *, + expect_handler: Optional[_ExpectHandler] = None, + ) -> None: + super().__init__( + method, handler, expect_handler=expect_handler, resource=resource + ) + + def __repr__(self) -> str: + return " {handler!r}".format( + method=self.method, resource=self._resource, handler=self.handler + ) + + @property + def name(self) -> Optional[str]: + if self._resource is None: + return None + return self._resource.name + + def url_for(self, *args: str, **kwargs: str) -> URL: + """Construct url for route with additional params.""" + assert self._resource is not None + return self._resource.url_for(*args, **kwargs) + + def get_info(self) -> _InfoDict: + assert self._resource is not None + return self._resource.get_info() + + +class SystemRoute(AbstractRoute): + def __init__(self, http_exception: HTTPException) -> None: + super().__init__(hdrs.METH_ANY, self._handle) + self._http_exception = http_exception + + def url_for(self, *args: str, **kwargs: str) -> URL: + raise RuntimeError(".url_for() is not allowed for SystemRoute") + + @property + def name(self) -> Optional[str]: + return None + + def get_info(self) -> _InfoDict: + return {"http_exception": self._http_exception} + + async def _handle(self, request: Request) -> StreamResponse: + raise self._http_exception + + @property + def status(self) -> int: + return self._http_exception.status + + @property + def reason(self) -> str: + return self._http_exception.reason + + def __repr__(self) -> str: + return "".format(self=self) + + +class View(AbstractView): + async def _iter(self) -> StreamResponse: + if self.request.method not in hdrs.METH_ALL: + self._raise_allowed_methods() + method: Optional[Callable[[], Awaitable[StreamResponse]]] = getattr( + self, self.request.method.lower(), None + ) + if method is None: + self._raise_allowed_methods() + return await method() + + def __await__(self) -> Generator[Any, None, StreamResponse]: + return self._iter().__await__() + + def _raise_allowed_methods(self) -> NoReturn: + allowed_methods = {m for m in hdrs.METH_ALL if hasattr(self, m.lower())} + raise HTTPMethodNotAllowed(self.request.method, allowed_methods) + + +class ResourcesView(Sized, Iterable[AbstractResource], Container[AbstractResource]): + def __init__(self, resources: List[AbstractResource]) -> None: + self._resources = resources + + def __len__(self) -> int: + return len(self._resources) + + def __iter__(self) -> Iterator[AbstractResource]: + yield from self._resources + + def __contains__(self, resource: object) -> bool: + return resource in self._resources + + +class RoutesView(Sized, Iterable[AbstractRoute], Container[AbstractRoute]): + def __init__(self, resources: List[AbstractResource]): + self._routes: List[AbstractRoute] = [] + for resource in resources: + for route in resource: + self._routes.append(route) + + def __len__(self) -> int: + return len(self._routes) + + def __iter__(self) -> Iterator[AbstractRoute]: + yield from self._routes + + def __contains__(self, route: object) -> bool: + return route in self._routes + + +class UrlDispatcher(AbstractRouter, Mapping[str, AbstractResource]): + NAME_SPLIT_RE = re.compile(r"[.:-]") + + def __init__(self) -> None: + super().__init__() + self._resources: List[AbstractResource] = [] + self._named_resources: Dict[str, AbstractResource] = {} + + async def resolve(self, request: Request) -> UrlMappingMatchInfo: + method = request.method + allowed_methods: Set[str] = set() + + for resource in self._resources: + match_dict, allowed = await resource.resolve(request) + if match_dict is not None: + return match_dict + else: + allowed_methods |= allowed + + if allowed_methods: + return MatchInfoError(HTTPMethodNotAllowed(method, allowed_methods)) + else: + return MatchInfoError(HTTPNotFound()) + + def __iter__(self) -> Iterator[str]: + return iter(self._named_resources) + + def __len__(self) -> int: + return len(self._named_resources) + + def __contains__(self, resource: object) -> bool: + return resource in self._named_resources + + def __getitem__(self, name: str) -> AbstractResource: + return self._named_resources[name] + + def resources(self) -> ResourcesView: + return ResourcesView(self._resources) + + def routes(self) -> RoutesView: + return RoutesView(self._resources) + + def named_resources(self) -> Mapping[str, AbstractResource]: + return MappingProxyType(self._named_resources) + + def register_resource(self, resource: AbstractResource) -> None: + assert isinstance( + resource, AbstractResource + ), f"Instance of AbstractResource class is required, got {resource!r}" + if self.frozen: + raise RuntimeError("Cannot register a resource into frozen router.") + + name = resource.name + + if name is not None: + parts = self.NAME_SPLIT_RE.split(name) + for part in parts: + if keyword.iskeyword(part): + raise ValueError( + f"Incorrect route name {name!r}, " + "python keywords cannot be used " + "for route name" + ) + if not part.isidentifier(): + raise ValueError( + "Incorrect route name {!r}, " + "the name should be a sequence of " + "python identifiers separated " + "by dash, dot or column".format(name) + ) + if name in self._named_resources: + raise ValueError( + "Duplicate {!r}, " + "already handled by {!r}".format(name, self._named_resources[name]) + ) + self._named_resources[name] = resource + self._resources.append(resource) + + def add_resource(self, path: str, *, name: Optional[str] = None) -> Resource: + if path and not path.startswith("/"): + raise ValueError("path should be started with / or be empty") + # Reuse last added resource if path and name are the same + if self._resources: + resource = self._resources[-1] + if resource.name == name and resource.raw_match(path): + return cast(Resource, resource) + if not ("{" in path or "}" in path or ROUTE_RE.search(path)): + resource = PlainResource(_requote_path(path), name=name) + self.register_resource(resource) + return resource + resource = DynamicResource(path, name=name) + self.register_resource(resource) + return resource + + def add_route( + self, + method: str, + path: str, + handler: Union[Handler, Type[AbstractView]], + *, + name: Optional[str] = None, + expect_handler: Optional[_ExpectHandler] = None, + ) -> AbstractRoute: + resource = self.add_resource(path, name=name) + return resource.add_route(method, handler, expect_handler=expect_handler) + + def add_static( + self, + prefix: str, + path: PathLike, + *, + name: Optional[str] = None, + expect_handler: Optional[_ExpectHandler] = None, + chunk_size: int = 256 * 1024, + show_index: bool = False, + follow_symlinks: bool = False, + append_version: bool = False, + ) -> AbstractResource: + """Add static files view. + + prefix - url prefix + path - folder with files + + """ + assert prefix.startswith("/") + if prefix.endswith("/"): + prefix = prefix[:-1] + resource = StaticResource( + prefix, + path, + name=name, + expect_handler=expect_handler, + chunk_size=chunk_size, + show_index=show_index, + follow_symlinks=follow_symlinks, + append_version=append_version, + ) + self.register_resource(resource) + return resource + + def add_head(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method HEAD.""" + return self.add_route(hdrs.METH_HEAD, path, handler, **kwargs) + + def add_options(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method OPTIONS.""" + return self.add_route(hdrs.METH_OPTIONS, path, handler, **kwargs) + + def add_get( + self, + path: str, + handler: Handler, + *, + name: Optional[str] = None, + allow_head: bool = True, + **kwargs: Any, + ) -> AbstractRoute: + """Shortcut for add_route with method GET. + + If allow_head is true, another + route is added allowing head requests to the same endpoint. + """ + resource = self.add_resource(path, name=name) + if allow_head: + resource.add_route(hdrs.METH_HEAD, handler, **kwargs) + return resource.add_route(hdrs.METH_GET, handler, **kwargs) + + def add_post(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method POST.""" + return self.add_route(hdrs.METH_POST, path, handler, **kwargs) + + def add_put(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method PUT.""" + return self.add_route(hdrs.METH_PUT, path, handler, **kwargs) + + def add_patch(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method PATCH.""" + return self.add_route(hdrs.METH_PATCH, path, handler, **kwargs) + + def add_delete(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method DELETE.""" + return self.add_route(hdrs.METH_DELETE, path, handler, **kwargs) + + def add_view( + self, path: str, handler: Type[AbstractView], **kwargs: Any + ) -> AbstractRoute: + """Shortcut for add_route with ANY methods for a class-based view.""" + return self.add_route(hdrs.METH_ANY, path, handler, **kwargs) + + def freeze(self) -> None: + super().freeze() + for resource in self._resources: + resource.freeze() + + def add_routes(self, routes: Iterable[AbstractRouteDef]) -> List[AbstractRoute]: + """Append routes to route table. + + Parameter should be a sequence of RouteDef objects. + + Returns a list of registered AbstractRoute instances. + """ + registered_routes = [] + for route_def in routes: + registered_routes.extend(route_def.register(self)) + return registered_routes + + +def _quote_path(value: str) -> str: + if YARL_VERSION < (1, 6): + value = value.replace("%", "%25") + return URL.build(path=value, encoded=False).raw_path + + +def _unquote_path(value: str) -> str: + return URL.build(path=value, encoded=True).path + + +def _requote_path(value: str) -> str: + # Quote non-ascii characters and other characters which must be quoted, + # but preserve existing %-sequences. + result = _quote_path(value) + if "%" in value: + result = result.replace("%25", "%") + return result diff --git a/aiohttp/web_ws.py b/aiohttp/web_ws.py new file mode 100644 index 0000000..9fcdc4b --- /dev/null +++ b/aiohttp/web_ws.py @@ -0,0 +1,525 @@ +import asyncio +import base64 +import binascii +import dataclasses +import hashlib +import json +import sys +from typing import Any, Final, Iterable, Optional, Tuple, cast + +from multidict import CIMultiDict + +from . import hdrs +from .abc import AbstractStreamWriter +from .helpers import call_later, set_result +from .http import ( + WS_CLOSED_MESSAGE, + WS_CLOSING_MESSAGE, + WS_KEY, + WebSocketError, + WebSocketReader, + WebSocketWriter, + WSCloseCode, + WSMessage, + WSMsgType as WSMsgType, + ws_ext_gen, + ws_ext_parse, +) +from .log import ws_logger +from .streams import EofStream, FlowControlDataQueue +from .typedefs import JSONDecoder, JSONEncoder +from .web_exceptions import HTTPBadRequest, HTTPException +from .web_request import BaseRequest +from .web_response import StreamResponse + +if sys.version_info >= (3, 11): + import asyncio as async_timeout +else: + import async_timeout + +__all__ = ( + "WebSocketResponse", + "WebSocketReady", + "WSMsgType", +) + +THRESHOLD_CONNLOST_ACCESS: Final[int] = 5 + + +@dataclasses.dataclass(frozen=True) +class WebSocketReady: + ok: bool + protocol: Optional[str] + + def __bool__(self) -> bool: + return self.ok + + +class WebSocketResponse(StreamResponse): + __slots__ = ( + "_protocols", + "_ws_protocol", + "_writer", + "_reader", + "_closed", + "_closing", + "_conn_lost", + "_close_code", + "_loop", + "_waiting", + "_exception", + "_timeout", + "_receive_timeout", + "_autoclose", + "_autoping", + "_heartbeat", + "_heartbeat_cb", + "_pong_heartbeat", + "_pong_response_cb", + "_compress", + "_max_msg_size", + ) + + def __init__( + self, + *, + timeout: float = 10.0, + receive_timeout: Optional[float] = None, + autoclose: bool = True, + autoping: bool = True, + heartbeat: Optional[float] = None, + protocols: Iterable[str] = (), + compress: bool = True, + max_msg_size: int = 4 * 1024 * 1024, + ) -> None: + super().__init__(status=101) + self._length_check = False + self._protocols = protocols + self._ws_protocol: Optional[str] = None + self._writer: Optional[WebSocketWriter] = None + self._reader: Optional[FlowControlDataQueue[WSMessage]] = None + self._closed = False + self._closing = False + self._conn_lost = 0 + self._close_code: Optional[int] = None + self._loop: Optional[asyncio.AbstractEventLoop] = None + self._waiting: Optional[asyncio.Future[bool]] = None + self._exception: Optional[BaseException] = None + self._timeout = timeout + self._receive_timeout = receive_timeout + self._autoclose = autoclose + self._autoping = autoping + self._heartbeat = heartbeat + self._heartbeat_cb: Optional[asyncio.TimerHandle] = None + if heartbeat is not None: + self._pong_heartbeat = heartbeat / 2.0 + self._pong_response_cb: Optional[asyncio.TimerHandle] = None + self._compress = compress + self._max_msg_size = max_msg_size + + def _cancel_heartbeat(self) -> None: + if self._pong_response_cb is not None: + self._pong_response_cb.cancel() + self._pong_response_cb = None + + if self._heartbeat_cb is not None: + self._heartbeat_cb.cancel() + self._heartbeat_cb = None + + def _reset_heartbeat(self) -> None: + self._cancel_heartbeat() + + if self._heartbeat is not None: + assert self._loop is not None + self._heartbeat_cb = call_later( + self._send_heartbeat, + self._heartbeat, + self._loop, + timeout_ceil_threshold=self._req._protocol._timeout_ceil_threshold + if self._req is not None + else 5, + ) + + def _send_heartbeat(self) -> None: + if self._heartbeat is not None and not self._closed: + assert self._loop is not None and self._writer is not None + # fire-and-forget a task is not perfect but maybe ok for + # sending ping. Otherwise we need a long-living heartbeat + # task in the class. + self._loop.create_task(self._writer.ping()) # type: ignore[unused-awaitable] + + if self._pong_response_cb is not None: + self._pong_response_cb.cancel() + self._pong_response_cb = call_later( + self._pong_not_received, + self._pong_heartbeat, + self._loop, + timeout_ceil_threshold=self._req._protocol._timeout_ceil_threshold + if self._req is not None + else 5, + ) + + def _pong_not_received(self) -> None: + if self._req is not None and self._req.transport is not None: + self._closed = True + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = asyncio.TimeoutError() + self._req.transport.close() + + async def prepare(self, request: BaseRequest) -> AbstractStreamWriter: + # make pre-check to don't hide it by do_handshake() exceptions + if self._payload_writer is not None: + return self._payload_writer + + protocol, writer = self._pre_start(request) + payload_writer = await super().prepare(request) + assert payload_writer is not None + self._post_start(request, protocol, writer) + await payload_writer.drain() + return payload_writer + + def _handshake( + self, request: BaseRequest + ) -> Tuple["CIMultiDict[str]", str, bool, bool]: + headers = request.headers + if "websocket" != headers.get(hdrs.UPGRADE, "").lower().strip(): + raise HTTPBadRequest( + text=( + "No WebSocket UPGRADE hdr: {}\n Can " + '"Upgrade" only to "WebSocket".' + ).format(headers.get(hdrs.UPGRADE)) + ) + + if "upgrade" not in headers.get(hdrs.CONNECTION, "").lower(): + raise HTTPBadRequest( + text="No CONNECTION upgrade hdr: {}".format( + headers.get(hdrs.CONNECTION) + ) + ) + + # find common sub-protocol between client and server + protocol = None + if hdrs.SEC_WEBSOCKET_PROTOCOL in headers: + req_protocols = [ + str(proto.strip()) + for proto in headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",") + ] + + for proto in req_protocols: + if proto in self._protocols: + protocol = proto + break + else: + # No overlap found: Return no protocol as per spec + ws_logger.warning( + "Client protocols %r don’t overlap server-known ones %r", + req_protocols, + self._protocols, + ) + + # check supported version + version = headers.get(hdrs.SEC_WEBSOCKET_VERSION, "") + if version not in ("13", "8", "7"): + raise HTTPBadRequest(text=f"Unsupported version: {version}") + + # check client handshake for validity + key = headers.get(hdrs.SEC_WEBSOCKET_KEY) + try: + if not key or len(base64.b64decode(key)) != 16: + raise HTTPBadRequest(text=f"Handshake error: {key!r}") + except binascii.Error: + raise HTTPBadRequest(text=f"Handshake error: {key!r}") from None + + accept_val = base64.b64encode( + hashlib.sha1(key.encode() + WS_KEY).digest() + ).decode() + response_headers = CIMultiDict( + { + hdrs.UPGRADE: "websocket", + hdrs.CONNECTION: "upgrade", + hdrs.SEC_WEBSOCKET_ACCEPT: accept_val, + } + ) + + notakeover = False + compress = 0 + if self._compress: + extensions = headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS) + # Server side always get return with no exception. + # If something happened, just drop compress extension + compress, notakeover = ws_ext_parse(extensions, isserver=True) + if compress: + enabledext = ws_ext_gen( + compress=compress, isserver=True, server_notakeover=notakeover + ) + response_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = enabledext + + if protocol: + response_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = protocol + return ( + response_headers, + protocol, + compress, + notakeover, + ) # type: ignore[return-value] + + def _pre_start(self, request: BaseRequest) -> Tuple[str, WebSocketWriter]: + self._loop = request._loop + + headers, protocol, compress, notakeover = self._handshake(request) + + self.set_status(101) + self.headers.update(headers) + self.force_close() + self._compress = compress + transport = request._protocol.transport + assert transport is not None + writer = WebSocketWriter( + request._protocol, transport, compress=compress, notakeover=notakeover + ) + + return protocol, writer + + def _post_start( + self, request: BaseRequest, protocol: str, writer: WebSocketWriter + ) -> None: + self._ws_protocol = protocol + self._writer = writer + + self._reset_heartbeat() + + loop = self._loop + assert loop is not None + self._reader = FlowControlDataQueue(request._protocol, 2**16, loop=loop) + request.protocol.set_parser( + WebSocketReader(self._reader, self._max_msg_size, compress=self._compress) + ) + # disable HTTP keepalive for WebSocket + request.protocol.keep_alive(False) + + def can_prepare(self, request: BaseRequest) -> WebSocketReady: + if self._writer is not None: + raise RuntimeError("Already started") + try: + _, protocol, _, _ = self._handshake(request) + except HTTPException: + return WebSocketReady(False, None) + else: + return WebSocketReady(True, protocol) + + @property + def closed(self) -> bool: + return self._closed + + @property + def close_code(self) -> Optional[int]: + return self._close_code + + @property + def ws_protocol(self) -> Optional[str]: + return self._ws_protocol + + @property + def compress(self) -> bool: + return self._compress + + def exception(self) -> Optional[BaseException]: + return self._exception + + async def ping(self, message: bytes = b"") -> None: + if self._writer is None: + raise RuntimeError("Call .prepare() first") + await self._writer.ping(message) + + async def pong(self, message: bytes = b"") -> None: + # unsolicited pong + if self._writer is None: + raise RuntimeError("Call .prepare() first") + await self._writer.pong(message) + + async def send_str(self, data: str, compress: Optional[bool] = None) -> None: + if self._writer is None: + raise RuntimeError("Call .prepare() first") + if not isinstance(data, str): + raise TypeError("data argument must be str (%r)" % type(data)) + await self._writer.send(data, binary=False, compress=compress) + + async def send_bytes(self, data: bytes, compress: Optional[bool] = None) -> None: + if self._writer is None: + raise RuntimeError("Call .prepare() first") + if not isinstance(data, (bytes, bytearray, memoryview)): + raise TypeError("data argument must be byte-ish (%r)" % type(data)) + await self._writer.send(data, binary=True, compress=compress) + + async def send_json( + self, + data: Any, + compress: Optional[bool] = None, + *, + dumps: JSONEncoder = json.dumps, + ) -> None: + await self.send_str(dumps(data), compress=compress) + + async def write_eof(self) -> None: # type: ignore[override] + if self._eof_sent: + return + if self._payload_writer is None: + raise RuntimeError("Response has not been started") + + await self.close() + self._eof_sent = True + + async def close(self, *, code: int = WSCloseCode.OK, message: bytes = b"") -> bool: + if self._writer is None: + raise RuntimeError("Call .prepare() first") + + self._cancel_heartbeat() + reader = self._reader + assert reader is not None + + # we need to break `receive()` cycle first, + # `close()` may be called from different task + if self._waiting is not None and not self._closed: + reader.feed_data(WS_CLOSING_MESSAGE, 0) + await self._waiting + + if not self._closed: + self._closed = True + try: + await self._writer.close(code, message) + writer = self._payload_writer + assert writer is not None + await writer.drain() + except (asyncio.CancelledError, asyncio.TimeoutError): + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + raise + except Exception as exc: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = exc + return True + + if self._closing: + return True + + reader = self._reader + assert reader is not None + try: + async with async_timeout.timeout(self._timeout): + msg = await reader.read() + except asyncio.CancelledError: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + raise + except Exception as exc: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = exc + return True + + if msg.type == WSMsgType.CLOSE: + self._close_code = msg.data + return True + + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = asyncio.TimeoutError() + return True + else: + return False + + async def receive(self, timeout: Optional[float] = None) -> WSMessage: + if self._reader is None: + raise RuntimeError("Call .prepare() first") + + loop = self._loop + assert loop is not None + while True: + if self._waiting is not None: + raise RuntimeError("Concurrent call to receive() is not allowed") + + if self._closed: + self._conn_lost += 1 + if self._conn_lost >= THRESHOLD_CONNLOST_ACCESS: + raise RuntimeError("WebSocket connection is closed.") + return WS_CLOSED_MESSAGE + elif self._closing: + return WS_CLOSING_MESSAGE + + try: + self._waiting = loop.create_future() + try: + async with async_timeout.timeout(timeout or self._receive_timeout): + msg = await self._reader.read() + self._reset_heartbeat() + finally: + waiter = self._waiting + set_result(waiter, True) + self._waiting = None + except (asyncio.CancelledError, asyncio.TimeoutError): + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + raise + except EofStream: + self._close_code = WSCloseCode.OK + await self.close() + return WSMessage(WSMsgType.CLOSED, None, None) + except WebSocketError as exc: + self._close_code = exc.code + await self.close(code=exc.code) + return WSMessage(WSMsgType.ERROR, exc, None) + except Exception as exc: + self._exception = exc + self._closing = True + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + await self.close() + return WSMessage(WSMsgType.ERROR, exc, None) + + if msg.type == WSMsgType.CLOSE: + self._closing = True + self._close_code = msg.data + # Could be closed while awaiting reader. + if not self._closed and self._autoclose: # type: ignore[redundant-expr] + await self.close() + elif msg.type == WSMsgType.CLOSING: + self._closing = True + elif msg.type == WSMsgType.PING and self._autoping: + await self.pong(msg.data) + continue + elif msg.type == WSMsgType.PONG and self._autoping: + continue + + return msg + + async def receive_str(self, *, timeout: Optional[float] = None) -> str: + msg = await self.receive(timeout) + if msg.type != WSMsgType.TEXT: + raise TypeError( + "Received message {}:{!r} is not WSMsgType.TEXT".format( + msg.type, msg.data + ) + ) + return cast(str, msg.data) + + async def receive_bytes(self, *, timeout: Optional[float] = None) -> bytes: + msg = await self.receive(timeout) + if msg.type != WSMsgType.BINARY: + raise TypeError(f"Received message {msg.type}:{msg.data!r} is not bytes") + return cast(bytes, msg.data) + + async def receive_json( + self, *, loads: JSONDecoder = json.loads, timeout: Optional[float] = None + ) -> Any: + data = await self.receive_str(timeout=timeout) + return loads(data) + + async def write(self, data: bytes) -> None: + raise RuntimeError("Cannot call .write() for websocket") + + def __aiter__(self) -> "WebSocketResponse": + return self + + async def __anext__(self) -> WSMessage: + msg = await self.receive() + if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED): + raise StopAsyncIteration + return msg + + def _cancel(self, exc: BaseException) -> None: + if self._reader is not None: + self._reader.set_exception(exc) diff --git a/aiohttp/worker.py b/aiohttp/worker.py new file mode 100644 index 0000000..c1c45f1 --- /dev/null +++ b/aiohttp/worker.py @@ -0,0 +1,239 @@ +"""Async gunicorn worker for aiohttp.web""" + +import asyncio +import os +import re +import signal +import sys +from types import FrameType +from typing import Any, Awaitable, Callable, Optional, Union # noqa + +from gunicorn.config import AccessLogFormat as GunicornAccessLogFormat +from gunicorn.workers import base + +from aiohttp import web + +from .helpers import set_result +from .web_app import Application +from .web_log import AccessLogger + +try: + import ssl + + SSLContext = ssl.SSLContext +except ImportError: # pragma: no cover + ssl = None # type: ignore[assignment] + SSLContext = object # type: ignore[misc,assignment] + + +__all__ = ("GunicornWebWorker", "GunicornUVLoopWebWorker") + + +class GunicornWebWorker(base.Worker): # type: ignore[misc,no-any-unimported] + DEFAULT_AIOHTTP_LOG_FORMAT = AccessLogger.LOG_FORMAT + DEFAULT_GUNICORN_LOG_FORMAT = GunicornAccessLogFormat.default + + def __init__(self, *args: Any, **kw: Any) -> None: # pragma: no cover + super().__init__(*args, **kw) + + self._task: Optional[asyncio.Task[None]] = None + self.exit_code = 0 + self._notify_waiter: Optional[asyncio.Future[bool]] = None + + def init_process(self) -> None: + # create new event_loop after fork + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + + super().init_process() + + def run(self) -> None: + self._task = self.loop.create_task(self._run()) + + try: # ignore all finalization problems + self.loop.run_until_complete(self._task) + except Exception: + self.log.exception("Exception in gunicorn worker") + self.loop.run_until_complete(self.loop.shutdown_asyncgens()) + self.loop.close() + + sys.exit(self.exit_code) + + async def _run(self) -> None: + runner = None + if isinstance(self.wsgi, Application): + app = self.wsgi + elif asyncio.iscoroutinefunction(self.wsgi): + wsgi = await self.wsgi() + if isinstance(wsgi, web.AppRunner): + runner = wsgi + app = runner.app + else: + app = wsgi + else: + raise RuntimeError( + "wsgi app should be either Application or " + "async function returning Application, got {}".format(self.wsgi) + ) + + if runner is None: + access_log = self.log.access_log if self.cfg.accesslog else None + runner = web.AppRunner( + app, + logger=self.log, + keepalive_timeout=self.cfg.keepalive, + access_log=access_log, + access_log_format=self._get_valid_log_format( + self.cfg.access_log_format + ), + ) + await runner.setup() + + ctx = self._create_ssl_context(self.cfg) if self.cfg.is_ssl else None + + assert runner is not None + server = runner.server + assert server is not None + for sock in self.sockets: + site = web.SockSite( + runner, + sock, + ssl_context=ctx, + shutdown_timeout=self.cfg.graceful_timeout / 100 * 95, + ) + await site.start() + + # If our parent changed then we shut down. + pid = os.getpid() + try: + while self.alive: # type: ignore[has-type] + self.notify() + + cnt = server.requests_count + if self.max_requests and cnt > self.max_requests: + self.alive = False + self.log.info("Max requests, shutting down: %s", self) + + elif pid == os.getpid() and self.ppid != os.getppid(): + self.alive = False + self.log.info("Parent changed, shutting down: %s", self) + else: + await self._wait_next_notify() + except BaseException: + pass + + await runner.cleanup() + + def _wait_next_notify(self) -> "asyncio.Future[bool]": + self._notify_waiter_done() + + loop = self.loop + assert loop is not None + self._notify_waiter = waiter = loop.create_future() + self.loop.call_later(1.0, self._notify_waiter_done, waiter) + + return waiter + + def _notify_waiter_done( + self, waiter: Optional["asyncio.Future[bool]"] = None + ) -> None: + if waiter is None: + waiter = self._notify_waiter + if waiter is not None: + set_result(waiter, True) + + if waiter is self._notify_waiter: + self._notify_waiter = None + + def init_signals(self) -> None: + # Set up signals through the event loop API. + + self.loop.add_signal_handler( + signal.SIGQUIT, self.handle_quit, signal.SIGQUIT, None + ) + + self.loop.add_signal_handler( + signal.SIGTERM, self.handle_exit, signal.SIGTERM, None + ) + + self.loop.add_signal_handler( + signal.SIGINT, self.handle_quit, signal.SIGINT, None + ) + + self.loop.add_signal_handler( + signal.SIGWINCH, self.handle_winch, signal.SIGWINCH, None + ) + + self.loop.add_signal_handler( + signal.SIGUSR1, self.handle_usr1, signal.SIGUSR1, None + ) + + self.loop.add_signal_handler( + signal.SIGABRT, self.handle_abort, signal.SIGABRT, None + ) + + # Don't let SIGTERM and SIGUSR1 disturb active requests + # by interrupting system calls + signal.siginterrupt(signal.SIGTERM, False) + signal.siginterrupt(signal.SIGUSR1, False) + # Reset signals so Gunicorn doesn't swallow subprocess return codes + # See: https://github.com/aio-libs/aiohttp/issues/6130 + + def handle_quit(self, sig: int, frame: Optional[FrameType]) -> None: + self.alive = False + + # worker_int callback + self.cfg.worker_int(self) + + # wakeup closing process + self._notify_waiter_done() + + def handle_abort(self, sig: int, frame: Optional[FrameType]) -> None: + self.alive = False + self.exit_code = 1 + self.cfg.worker_abort(self) + sys.exit(1) + + @staticmethod + def _create_ssl_context(cfg: Any) -> "SSLContext": + """Creates SSLContext instance for usage in asyncio.create_server. + + See ssl.SSLSocket.__init__ for more details. + """ + if ssl is None: # pragma: no cover + raise RuntimeError("SSL is not supported.") + + ctx = ssl.SSLContext(cfg.ssl_version) + ctx.load_cert_chain(cfg.certfile, cfg.keyfile) + ctx.verify_mode = cfg.cert_reqs + if cfg.ca_certs: + ctx.load_verify_locations(cfg.ca_certs) + if cfg.ciphers: + ctx.set_ciphers(cfg.ciphers) + return ctx + + def _get_valid_log_format(self, source_format: str) -> str: + if source_format == self.DEFAULT_GUNICORN_LOG_FORMAT: + return self.DEFAULT_AIOHTTP_LOG_FORMAT + elif re.search(r"%\([^\)]+\)", source_format): + raise ValueError( + "Gunicorn's style options in form of `%(name)s` are not " + "supported for the log formatting. Please use aiohttp's " + "format specification to configure access log formatting: " + "http://docs.aiohttp.org/en/stable/logging.html" + "#format-specification" + ) + else: + return source_format + + +class GunicornUVLoopWebWorker(GunicornWebWorker): + def init_process(self) -> None: + import uvloop + + # Setup uvloop policy, so that every + # asyncio.get_event_loop() will create an instance + # of uvloop event loop. + asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) + + super().init_process() diff --git a/aiosignal/__init__.py b/aiosignal/__init__.py new file mode 100644 index 0000000..3d288e6 --- /dev/null +++ b/aiosignal/__init__.py @@ -0,0 +1,36 @@ +from frozenlist import FrozenList + +__version__ = "1.3.1" + +__all__ = ("Signal",) + + +class Signal(FrozenList): + """Coroutine-based signal implementation. + + To connect a callback to a signal, use any list method. + + Signals are fired using the send() coroutine, which takes named + arguments. + """ + + __slots__ = ("_owner",) + + def __init__(self, owner): + super().__init__() + self._owner = owner + + def __repr__(self): + return "".format( + self._owner, self.frozen, list(self) + ) + + async def send(self, *args, **kwargs): + """ + Sends data to all registered receivers. + """ + if not self.frozen: + raise RuntimeError("Cannot send non-frozen signal.") + + for receiver in self: + await receiver(*args, **kwargs) # type: ignore diff --git a/aiosignal/__init__.pyi b/aiosignal/__init__.pyi new file mode 100644 index 0000000..d4e3416 --- /dev/null +++ b/aiosignal/__init__.pyi @@ -0,0 +1,12 @@ +from typing import Any, Generic, TypeVar + +from frozenlist import FrozenList + +__all__ = ("Signal",) + +_T = TypeVar("_T") + +class Signal(FrozenList[_T], Generic[_T]): + def __init__(self, owner: Any) -> None: ... + def __repr__(self) -> str: ... + async def send(self, *args: Any, **kwargs: Any) -> None: ... diff --git a/aiosignal/py.typed b/aiosignal/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/async_timeout/__init__.py b/async_timeout/__init__.py new file mode 100644 index 0000000..1ffb069 --- /dev/null +++ b/async_timeout/__init__.py @@ -0,0 +1,239 @@ +import asyncio +import enum +import sys +import warnings +from types import TracebackType +from typing import Optional, Type + + +if sys.version_info >= (3, 8): + from typing import final +else: + from typing_extensions import final + + +if sys.version_info >= (3, 11): + + def _uncancel_task(task: "asyncio.Task[object]") -> None: + task.uncancel() + +else: + + def _uncancel_task(task: "asyncio.Task[object]") -> None: + pass + + +__version__ = "4.0.3" + + +__all__ = ("timeout", "timeout_at", "Timeout") + + +def timeout(delay: Optional[float]) -> "Timeout": + """timeout context manager. + + Useful in cases when you want to apply timeout logic around block + of code or in cases when asyncio.wait_for is not suitable. For example: + + >>> async with timeout(0.001): + ... async with aiohttp.get('https://github.com') as r: + ... await r.text() + + + delay - value in seconds or None to disable timeout logic + """ + loop = asyncio.get_running_loop() + if delay is not None: + deadline = loop.time() + delay # type: Optional[float] + else: + deadline = None + return Timeout(deadline, loop) + + +def timeout_at(deadline: Optional[float]) -> "Timeout": + """Schedule the timeout at absolute time. + + deadline argument points on the time in the same clock system + as loop.time(). + + Please note: it is not POSIX time but a time with + undefined starting base, e.g. the time of the system power on. + + >>> async with timeout_at(loop.time() + 10): + ... async with aiohttp.get('https://github.com') as r: + ... await r.text() + + + """ + loop = asyncio.get_running_loop() + return Timeout(deadline, loop) + + +class _State(enum.Enum): + INIT = "INIT" + ENTER = "ENTER" + TIMEOUT = "TIMEOUT" + EXIT = "EXIT" + + +@final +class Timeout: + # Internal class, please don't instantiate it directly + # Use timeout() and timeout_at() public factories instead. + # + # Implementation note: `async with timeout()` is preferred + # over `with timeout()`. + # While technically the Timeout class implementation + # doesn't need to be async at all, + # the `async with` statement explicitly points that + # the context manager should be used from async function context. + # + # This design allows to avoid many silly misusages. + # + # TimeoutError is raised immediately when scheduled + # if the deadline is passed. + # The purpose is to time out as soon as possible + # without waiting for the next await expression. + + __slots__ = ("_deadline", "_loop", "_state", "_timeout_handler", "_task") + + def __init__( + self, deadline: Optional[float], loop: asyncio.AbstractEventLoop + ) -> None: + self._loop = loop + self._state = _State.INIT + + self._task: Optional["asyncio.Task[object]"] = None + self._timeout_handler = None # type: Optional[asyncio.Handle] + if deadline is None: + self._deadline = None # type: Optional[float] + else: + self.update(deadline) + + def __enter__(self) -> "Timeout": + warnings.warn( + "with timeout() is deprecated, use async with timeout() instead", + DeprecationWarning, + stacklevel=2, + ) + self._do_enter() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> Optional[bool]: + self._do_exit(exc_type) + return None + + async def __aenter__(self) -> "Timeout": + self._do_enter() + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> Optional[bool]: + self._do_exit(exc_type) + return None + + @property + def expired(self) -> bool: + """Is timeout expired during execution?""" + return self._state == _State.TIMEOUT + + @property + def deadline(self) -> Optional[float]: + return self._deadline + + def reject(self) -> None: + """Reject scheduled timeout if any.""" + # cancel is maybe better name but + # task.cancel() raises CancelledError in asyncio world. + if self._state not in (_State.INIT, _State.ENTER): + raise RuntimeError(f"invalid state {self._state.value}") + self._reject() + + def _reject(self) -> None: + self._task = None + if self._timeout_handler is not None: + self._timeout_handler.cancel() + self._timeout_handler = None + + def shift(self, delay: float) -> None: + """Advance timeout on delay seconds. + + The delay can be negative. + + Raise RuntimeError if shift is called when deadline is not scheduled + """ + deadline = self._deadline + if deadline is None: + raise RuntimeError("cannot shift timeout if deadline is not scheduled") + self.update(deadline + delay) + + def update(self, deadline: float) -> None: + """Set deadline to absolute value. + + deadline argument points on the time in the same clock system + as loop.time(). + + If new deadline is in the past the timeout is raised immediately. + + Please note: it is not POSIX time but a time with + undefined starting base, e.g. the time of the system power on. + """ + if self._state == _State.EXIT: + raise RuntimeError("cannot reschedule after exit from context manager") + if self._state == _State.TIMEOUT: + raise RuntimeError("cannot reschedule expired timeout") + if self._timeout_handler is not None: + self._timeout_handler.cancel() + self._deadline = deadline + if self._state != _State.INIT: + self._reschedule() + + def _reschedule(self) -> None: + assert self._state == _State.ENTER + deadline = self._deadline + if deadline is None: + return + + now = self._loop.time() + if self._timeout_handler is not None: + self._timeout_handler.cancel() + + self._task = asyncio.current_task() + if deadline <= now: + self._timeout_handler = self._loop.call_soon(self._on_timeout) + else: + self._timeout_handler = self._loop.call_at(deadline, self._on_timeout) + + def _do_enter(self) -> None: + if self._state != _State.INIT: + raise RuntimeError(f"invalid state {self._state.value}") + self._state = _State.ENTER + self._reschedule() + + def _do_exit(self, exc_type: Optional[Type[BaseException]]) -> None: + if exc_type is asyncio.CancelledError and self._state == _State.TIMEOUT: + assert self._task is not None + _uncancel_task(self._task) + self._timeout_handler = None + self._task = None + raise asyncio.TimeoutError + # timeout has not expired + self._state = _State.EXIT + self._reject() + return None + + def _on_timeout(self) -> None: + assert self._task is not None + self._task.cancel() + self._state = _State.TIMEOUT + # drop the reference early + self._timeout_handler = None diff --git a/async_timeout/py.typed b/async_timeout/py.typed new file mode 100644 index 0000000..3b94f91 --- /dev/null +++ b/async_timeout/py.typed @@ -0,0 +1 @@ +Placeholder diff --git a/autoActions/README.md b/autoActions/README.md new file mode 100644 index 0000000..9e819e0 --- /dev/null +++ b/autoActions/README.md @@ -0,0 +1,272 @@ +# AutoActions + +Automatiken, die im Web-UI unter „Automatismen" angelegt werden und hier +ausgeführt werden: *wenn Bedingung, dann Kommando*. + +``` +Browser Datenbank homeMesh Runner +────────────────────── ───────────────────────── ──────────────────── +Karte „Automatismen" ──▶ automations ──▶ autoaction_runner.py +ajax/AutoAction.php automation_conditions MQTT / HTTP / Tahoma +restricted/automations.php automation_actions ──▶ Geräte +js/solar/autoActionFuncs.js automation_action_params + automation_log + calendar_days ◀── fetch_calendar.py +``` + +## Wie eine Automatik aufgebaut ist + +Eine Automatik hat **Auslöser**, **Rahmenbedingungen** und **Aktionen**. + +Ein Auslöser vergleicht einen Messwert (`actor_states`) mit einer Schwelle. +Mehrere Auslöser werden über `group_no` verknüpft: gleiche Nummer heißt UND, +verschiedene Nummern heißen ODER — ausgewertet wird `any(all(gruppe))`. Im +Editor ist eine Gruppe ein gerahmter Block mit eigenem „+ Bedingung", zwischen +den Blöcken steht ein ODER. Die Klammerung ist damit gezeichnet und nicht bloß +vereinbart, und darunter steht derselbe Ausdruck noch einmal als Satz. + +Eine Aktion ist ein Kommando (`actor_commands`) mit einem Wert je Parameter +(`command_parameters`) — nicht vier feste Spalten „Wert 1" bis „Wert 4", +sondern so viele Zeilen, wie das Gerät Parameter hat. + +Die Rahmenbedingungen (Wochentage, Zeitfenster, Ferien, Feiertage) sagen, wann +die Automatik überhaupt hinsehen darf. + +## Warum ein Dauerläufer und kein Cronjob + +Zwei Gründe: + +* Schwellwert-Auslöser sollen greifen, wenn die MQTT-Nachricht hereinkommt, + nicht erst im nächsten Minutenraster. +* `actor_states.current_value` wird sonst von niemandem fortgeschrieben — es + wird beim Geräte-Discovery einmal gesetzt und danach nie wieder. Ein + zustandsloser Cronjob hätte gar nichts, womit er vergleichen könnte. Der + Runner pflegt den Wert nebenbei mit — nur wenn er sich geändert hat und + höchstens einmal je Minute —, wovon auch der Editor profitiert: er zeigt + neben jedem Messwert den aktuellen Stand. + +## Was worauf wartet + +Die Hauptschleife wartet auf nichts. Alles, was ein Netz braucht, läuft +daneben: + +| | wo | Takt | +|---|---|---| +| MQTT-Nachrichten | kommen von selbst, Zwischenspeicher im Transport | sofort | +| Uhrzeit, Datum | im Runner gerechnet | jeder Takt | +| Sonnenauf-/-untergang | `solarLog.daylight` | einmal je Tag | +| HTTP- und WLED-Geräte | `Sammler`, eigener Faden | `poll_http`, `poll_wled` | +| Tahoma | `Sammler`, eigener Faden | `poll_tahoma`, Vorgabe 5 Minuten | +| Kommandos senden | `Versand`, ein Faden je Gerät | wenn etwas ansteht | + +Das war nicht immer so. Vorher wurden die Geräte mitten in der Schleife +abgefragt — neunzehn Tahoma-Geräte nacheinander, jedes mit bis zu zehn +Sekunden Zeitlimit. Eine Runde dauerte dadurch rund fünfundvierzig statt +dreißig Sekunden, gepollt wurde erst jede zweite Runde, also alle neunzig +Sekunden. Und weil die Uhr an derselben Abfrage hing, kam jede dritte Minute +nie vor: `um 18:26` wurde nie wahr. + +Die Fäden fassen die Datenbank nicht an. Sie legen ihre Ergebnisse in eine +Queue, geschrieben wird im Hauptfaden — eine pymysql-Verbindung gehört einem +Faden. + +## Nur die steigende Flanke + +`automations.cond_met` hält fest, ob die Bedingung beim letzten Durchlauf schon +erfüllt war. Ohne das würde „Temperatur über 22 Grad" bei jedem Takt erneut +feuern. Verlässt die Automatik ihr Zeitfenster, wird die Flanke +zurückgesetzt, damit sie im nächsten Fenster wieder steigen kann. + +Zeit-Auslöser gibt es in drei Formen: + +| | wahr, wenn | +|---|---| +| `um 16:30` | ab dieser Minute, noch `catchup_minutes` lang | +| `ab 16:30` | von da an bis Mitternacht | +| `vor 16:30` | bis dahin | + +Ausgelöst wird in allen drei Fällen nur einmal, eben wegen der Flanke. + +Das Nachholfenster bei `um` ist der Ersatz für die früher verlangte +Punktgenauigkeit. Solange `um 16:30` nur in genau dieser Minute wahr war, +kostete jeder Aussetzer die Automatik für den ganzen Tag — und Aussetzer gab +es reichlich, weil die Uhr am Geräte-Poll hing und jede dritte Minute +übersprang. Jetzt gilt die Bedingung fünf Minuten lang (einstellbar), die +Flanke sorgt weiterhin für genau einen Lauf, und ein Neustart mitten im +Fenster holt den Lauf nach. Die breiten Zeitfenster in `auto_watering.py` +folgen derselben Überlegung. + +Beim Sonnenauf- und -untergang ist der Wert ein Versatz, und der kann davor +oder danach liegen — deshalb dieselben drei Fälle mal zwei: `+ 00:30` eine +halbe Stunde nach Sonnenaufgang, `ab - 00:30` ab einer halben Stunde davor, +`vor + 00:30` bis eine halbe Stunde danach. + +Über den Tagesrand wird gerechnet, nicht abgeschnitten: „sechs Stunden vor +Sonnenaufgang" landet am Vorabend, und das ist so gewollt — abgeschnitten +wären solche Angaben gar nicht mehr formulierbar. Verglichen wird die Uhrzeit +innerhalb des Tages; ein Ziel jenseits von Mitternacht gilt als diese Uhrzeit +am selben Tag. Bei `+` und `-` ist das genau der gemeinte Zeitpunkt, bei `ab` +und `vor` verschiebt sich der wahre Bereich entsprechend mit. + +`force_once` („am Ende des Zeitraums auf jeden Fall ausführen") greift, wenn +das Fenster zugeht und in diesem Fenster noch nichts passiert ist. + +## Sperrzeit + +Die Flanke allein schützt nicht gegen einen Messwert, der um die Schwelle +**pendelt**: „Temperatur > 22" bei 22,1 / 21,9 / 22,1 °C ist jedes Mal eine +echte steigende Flanke, und über MQTT können die Werte im Sekundentakt +hereinkommen. `automations.lockout_secs` sagt, wie lange nach einer Auslösung +nicht wieder geschaltet wird. Der Editor bietet drei Stufen an: + +| | | gedacht für | +|---|---|---| +| Ohne | 0 s | volle Geschwindigkeit, jede Flanke schaltet | +| Kurz | 60 s | Licht, Farbe, Dimmwert | +| Lang | 900 s | Rollläden, Ventile, alles mit Motor | + +Gespeichert werden Sekunden, angeboten werden nur die drei Stufen — eine +vierte ist damit eine Zeile in `lockoutChoices()` und keine Wanderung durch +die Datenbank. + +Eine Flanke innerhalb der Sperrzeit wird **verworfen, nicht aufgehoben**. Ein +Rollladen, der eine Viertelstunde später doch noch losfährt, weil vor langer +Zeit einmal eine Schwelle gestreift wurde, wäre unangenehmer als einer, der +gar nicht fährt — und der nächste echte Anlass nach Ablauf der Sperre kommt +ohnehin durch. Verworfene Flanken stehen im Log auf `DEBUG`, nicht in +`automation_log`; bei einem zappelnden Sensor wäre die Tabelle sonst voll +davon. + +`force_once` ist von der Sperre nicht betroffen: es greift nur, wenn im +Fenster gar nichts gelaufen ist — dann ist auch keine Sperre aktiv. + +## Transporte + +Welcher Weg zum Gerät führt, entscheidet die URL des Aktors in `actors`: + +| URL | Messwert (`actor_states.url`) | Kommando (`actor_commands.command_url`) | +|---|---|---| +| `mqtt://…` | vollständiges Topic, abonniert; bei mehreren Messwerten je Topic zusätzlich `value_path` | Nutzlast auf das Parameter-Topic | +| `http://…` | Feldname in der JSON-Antwort, gepollt | Abfrageargumente an die Geräte-URL (`turn=on`) | +| `wled://…` | Pfad in `/json/state` (`seg[0].col[0]`), gepollt | JSON-Vorlage mit Platzhaltern, als Ganzes gesendet | +| Tahoma | Statusname (`core:ClosureState`), gepollt | `exec/apply` an die Box | +| `Logic` | gerechnet: Uhrzeit, Datum, Sonne | – | + +Alle fünf stehen in `transports.py`. Eine sechste Geräteart kommt als weitere +Klasse dazu; sie braucht `passt()`, `zustaende_lesen()` und `senden()`. + +Tahoma ist der einzige, der nicht am URL-Schema erkannt wird, sondern an der +**Box-Kennung** in der URL. Das Schema beschreibt dort die Funkart, und +dieselbe Box liefert `io://` für die Jalousien, `rts://` für die Dachfenster +und `internal://` für die Alarmanlage. Ohne `pin` in der `config.ini` ist +niemand zuständig — dann meldet der Runner beim Auslösen „kein Transport", +statt still nichts zu tun. + +Mehrere Messwerte teilen sich oft **ein Topic**: der go-eCharger schickt +sechzehn Zahlen als JSON-Feld auf `…/nrg`, und erst das `value_template` der +Home-Assistant-Discovery sagt, dass „Strom L1" das fünfte Element ist. Diese +Angabe steht in `actor_states.value_path` — in derselben Schreibweise, die +auch WLED benutzt: `[4]`, `ssid`, `seg[0].col[0]`. Ohne Pfad gilt die ganze +Nutzlast. + +Gelesen wird nur der einfache Fall aus dem Template: ein Zugriff auf +`value_json` und was danach an Punkten und Klammern folgt. + +### Werttabellen + +Manche Geräte schicken eine Zahl und meinen einen Zustand: + +``` +{{ ['Unknown','Idle','Charging','WaitCar','Complete','Error'][value_json|int] }} +{{ ['Default','Eco','NextTrip'][value_json|int-3] }} +``` + +Das ist kein Pfad, sondern eine Übersetzung von Zahl nach Text. Sie landet in +`possible_values` — in der Schreibweise, die WLED für seine Effektliste schon +benutzt: eine Liste aus `{Wert: Bezeichnung}`. Ein Versatz im Ausdruck wandert +dabei in die Schlüssel, aus `[value_json|int-3]` wird also `{"3":"Default"}`. + +Der Runner übersetzt beim Lesen: aus der gesendeten `2` wird `Charging`. Eine +Bedingung vergleicht damit genau den Klartext, den der Editor zur Auswahl +stellt. Steht die Zahl nicht in der Tabelle, bleibt sie stehen — ein +erfundener Name wäre schlimmer als ein roher Wert. + +Auf beiden Seiten des Editors steckt dieselbe Tabelle, aber der gespeicherte +Wert ist ein anderer: + +| | angezeigt | gespeichert | +|---|---|---| +| **Messwert** (Bedingung) | `Charging` | `Charging` — der Runner hat schon übersetzt | +| **Parameter** (Aktion) | `Blink` | `1` — das Gerät will die Zahl | + +Bei WLED trägt die Kommando-Vorlage alles: `{"seg":[{"col":[[%red%,%green%,%blue%]]}]}` +wird mit den Parameterwerten gefüllt und am Stück geschickt. Deshalb haben die +Parameter dort keine eigene URL — ihr Name *ist* der Platzhalter. + +## Voraussetzungen + +* Python 3 mit `pymysql`, `requests`, `paho-mqtt` +* `homeMesh_automations.sql` einmal eingespielt +* Im Web-Verzeichnis muss in `restricted/deviceDiscovery/config.ini` + **`clear_tables = false`** stehen. + Discovery schreibt mit `ON DUPLICATE KEY UPDATE` auf den URLs, das Leeren ist + unnötig — ein `TRUNCATE` würde dagegen die Geräte-IDs neu vergeben, und die + Automatiken zeigen per Fremdschlüssel genau auf diese IDs. + +## Einrichten + +```bash +cp config.ini.example config.ini # ausfüllen: Datenbank, MQTT, Tahoma +python3 fetch_calendar.py # Feiertage und Ferien holen +python3 autoaction_runner.py --once --dry-run --verbose # Probelauf +``` + +`--dry-run` schaltet nichts, protokolliert aber jedes Kommando, das geschickt +würde. `--once` macht einen einzigen Durchlauf. + +## Wo das läuft + +Der Runner ist ein Hintergrundprozess und wohnt deshalb beim SolarManager, +nicht im Web-Verzeichnis: + +``` +/volume1/homes/wagner/SolarManager/ +├── solarManager.py +├── startSolarServer.sh startet beide, siehe unten +└── autoActions/ + ├── autoaction_runner.py + ├── transports.py + ├── fetch_calendar.py + └── config.ini Zugangsdaten, nicht im Git +``` + +Das Web-UI kennt diesen Pfad nicht — Browser und Runner reden ausschließlich +über die Datenbank `homeMesh` miteinander. Der Runner lädt das Regelwerk nach, +sobald im Browser etwas gespeichert wurde; ein Neustart nach jeder Änderung ist +nicht nötig. + +`startSolarServer.sh` startet `solarManager.py` und den Runner gemeinsam und +beendet vorher, was schon läuft. Aufgerufen wird es beim Booten (auf der +Synology über den Aufgabenplaner, Ereignis „Hochfahren", als root); dasselbe +Skript von Hand aufzurufen ist der normale Weg, den Runner neu zu starten. +Zwei Instanzen dürfen nie gleichzeitig laufen — sie würden jedes Kommando +doppelt schicken und sich gegenseitig vom MQTT-Broker werfen, weil beide +dieselbe Client-Kennung benutzen. Genau davor schützt das Beenden am Anfang. + +Ausgabe landet in `autoActions.log` neben `solarOutput.log`. `SIGTERM` fängt +der Runner ab und fährt geordnet herunter. + +`fetch_calendar.py` gehört einmal jährlich in den Cron: + +``` +0 4 1 1 * /usr/bin/python3 /volume1/homes/wagner/SolarManager/autoActions/fetch_calendar.py +``` + +Ein zusätzlicher Lauf im Herbst schadet nicht — die Ferientermine des +übernächsten Schuljahres stehen erst später fest. + +## Nachsehen, was passiert ist + +`automation_log` hält je Auslösung fest, ob sie durchlief (`fired`), wegen +`force_once` nachgeholt wurde (`forced`) oder scheiterte (`error`, mit Grund in +`detail`). Einträge älter als 30 Tage räumt der Runner selbst weg. diff --git a/autoActions/autoaction_runner.py b/autoActions/autoaction_runner.py new file mode 100644 index 0000000..3533884 --- /dev/null +++ b/autoActions/autoaction_runner.py @@ -0,0 +1,1022 @@ +#!/usr/bin/env python3 +""" +AutoAction-Runner - fuehrt die im Web-UI angelegten Automatiken aus. + +Laeuft als Dauerprozess, nicht als Cronjob. Zwei Gruende: + + * Schwellwert-Ausloeser ("Temperatur ueber 22 Grad") sollen sofort greifen, + wenn die Nachricht hereinkommt, und nicht bis zum naechsten Minutenraster + warten. + * `actor_states.current_value` wird sonst von niemandem fortgeschrieben - + beim Geraete-Discovery einmal gesetzt und danach nie wieder. Ein + zustandsloser Cronjob haette also gar nichts, womit er vergleichen + koennte. Der Runner pflegt den Wert nebenbei mit, wodurch auch der Editor + im Browser aktuelle Zahlen anzeigt. + +Ablauf: + + Start Regelwerk und Geraetemodell laden, MQTT-Topics abonnieren, + Sammlerfaeden fuer die gepollten Geraete starten + Ereignis MQTT-Nachricht -> Wert merken -> im naechsten Takt auswerten + Hintergrund je Transport ein Faden: HTTP und WLED im Minutentakt, Tahoma + alle fuenf Minuten. Die Werte landen in einer Queue. + Takt alle tick_seconds: Queue leeren, auswerten, Regelwerk auf + Aenderung pruefen. Nichts davon wartet auf ein Netz. + Pruefen aktiv? (enabled, Wochentag, Ferien/Feiertag, Zeitfenster) + -> Bedingungen auswerten -> steigende Flanke -> Aktionen + +Die Uhr gehoert ausdruecklich nicht zu den abgefragten Geraeten. Sie stand +frueher mit im Geraete-Poll, und weil neunzehn Tahoma-Geraete nacheinander +laenger als eine Minute brauchten, kam jede dritte Minute nie vor: ein +Ausloeser "um 18:26" wurde nie wahr. Verglichen wird jetzt direkt gegen +datetime.now() - siehe uhrzeit_erfuellt(). + +Nur die steigende Flanke loest aus: `automations.cond_met` haelt fest, ob die +Bedingung beim letzten Durchlauf schon erfuellt war. Ohne das wuerde +"Temperatur ueber 22 Grad" bei jedem Takt erneut feuern. + +Zeit-Ausloeser gibt es in drei Formen: "um 16:30" gilt ab dieser Minute noch +catchup_minutes lang, "ab 16:30" von da an bis Mitternacht, "vor 16:30" bis +dahin. Ausgeloest wird in allen drei Faellen nur einmal, eben wegen der +Flanke. Das Nachholfenster bei "um" ist der Ersatz fuer die frueher +verlangte Punktgenauigkeit: ein Neustart, ein haengendes Geraet oder ein +langsamer Durchlauf kosten die Automatik nicht mehr den ganzen Tag, und weil +nur die Flanke zaehlt, laeuft sie trotzdem hoechstens einmal. Dieselbe +Ueberlegung steht hinter den breiten Zeitfenstern in auto_watering.py. + +Beim Sonnenauf- und -untergang traegt der Operator zusaetzlich das +Vorzeichen des Versatzes: "+ 00:30" eine halbe Stunde danach, ">=- 00:30" +ab einer halben Stunde davor, "<+ 00:30" bis eine halbe Stunde danach. + +Tabellen siehe homeMesh_automations.sql, Konfiguration siehe config.ini.example. +""" + +import argparse +import configparser +import json +import logging +import os +import queue +import signal +import sys +import threading +import time +from datetime import date, datetime, timedelta + +import pymysql +import requests +import paho.mqtt.client as mqtt + +from transports import (HTTPTransport, LogicTransport, MQTTTransport, + TahomaTransport, WLEDTransport) + +logger = logging.getLogger("autoaction") + +# Wie lange ein Messwert in der Datenbank stehen bleiben darf, bevor er +# aufgefrischt wird. Der Wert dient nur der Anzeige im Editor; jede Nachricht +# sofort zu schreiben waere bei einem gespraechigen Sensor sinnlose Last. +SCHREIB_ABSTAND = timedelta(seconds=60) + +# Aelteres im Protokoll interessiert niemanden mehr. +LOG_AUFBEWAHRUNG_TAGE = 30 + + +# =========================================================================== +# Konfiguration +# =========================================================================== + +class Config: + def __init__(self, dateiname="config.ini"): + pfad = os.path.join(os.path.dirname(os.path.abspath(__file__)), dateiname) + if not os.path.exists(pfad): + raise FileNotFoundError( + "%s fehlt - config.ini.example kopieren und ausfuellen." % pfad) + self.cfg = configparser.ConfigParser() + self.cfg.read(pfad, encoding="utf-8") + + def text(self, sektion, schluessel, vorgabe=""): + return self.cfg.get(sektion, schluessel, fallback=vorgabe).strip() + + def zahl(self, sektion, schluessel, vorgabe=0): + try: + return self.cfg.getint(sektion, schluessel, fallback=vorgabe) + except ValueError: + return vorgabe + + def ja(self, sektion, schluessel, vorgabe=False): + return self.text(sektion, schluessel, str(vorgabe)).lower() in ("true", "1", "yes", "on") + + +# =========================================================================== +# Datenbank +# =========================================================================== + +def verbinden(config, sektion="database"): + return pymysql.connect( + host=config.text("database", "host", "localhost"), + port=config.zahl("database", "port", 3306), + user=config.text(sektion, "user") or config.text("database", "user"), + password=config.text(sektion, "password") or config.text("database", "password"), + database=config.text(sektion, "database"), + charset="utf8mb4", + cursorclass=pymysql.cursors.DictCursor, + autocommit=True) + + +class Regelwerk: + """ + Das geladene Abbild der Datenbank: Automatiken mit ihren Bedingungen und + Aktionen, dazu die Messwerte und Kommandos, die sie benutzen. + """ + + def __init__(self, automatiken, states, kommandos, signatur): + self.automatiken = automatiken + self.states = states # state_id -> Beschreibung + self.kommandos = kommandos # command_id -> Beschreibung + self.signatur = signatur + + @staticmethod + def signatur_lesen(db): + """ + Woran der Runner merkt, dass er neu laden muss. `changed` allein + genuegt nicht: eine geloeschte Automatik veraendert den groessten + Zeitstempel nicht. Deshalb zaehlen die Zeilen mit - auch die der + Geraetetabellen, damit ein Discovery-Lauf ebenfalls durchschlaegt. + """ + with db.cursor() as c: + c.execute("""SELECT (SELECT COUNT(*) FROM automations) AS a, + (SELECT UNIX_TIMESTAMP(MAX(changed)) FROM automations) AS t, + (SELECT COUNT(*) FROM automation_conditions) AS b, + (SELECT COUNT(*) FROM automation_actions) AS c, + (SELECT COUNT(*) FROM actor_states) AS d, + (SELECT COUNT(*) FROM actor_commands) AS e""") + return tuple(sorted(c.fetchone().items())) + + @classmethod + def laden(cls, db): + signatur = cls.signatur_lesen(db) + + states = {} + with db.cursor() as c: + c.execute("""SELECT s.id, s.state_name, s.url AS state_url, s.value_path, + s.current_value, s.possible_values, + a.url AS actor_url, a.name AS actor_name, t.type + FROM actor_states s + JOIN actors a ON a.id = s.actor_id + LEFT JOIN state_types t ON s.state_type = t.id""") + for row in c.fetchall(): + row["type"] = row["type"] or "string" + # Werttabelle als flaches {gesendeter Wert: Bezeichnung}. In + # der Datenbank steht sie als Liste aus Ein-Schluessel- + # Objekten, weil der Editor sie so schon versteht. + row["wertetabelle"] = {} + try: + for eintrag in json.loads(row["possible_values"] or "[]"): + if isinstance(eintrag, dict): + for wert, name in eintrag.items(): + row["wertetabelle"][str(wert)] = name + except ValueError: + pass + states[row["id"]] = row + + kommandos = {} + with db.cursor() as c: + c.execute("""SELECT k.id, k.command_name, k.command_url, + a.url AS actor_url, a.name AS actor_name + FROM actor_commands k JOIN actors a ON a.id = k.actor_id""") + for row in c.fetchall(): + row["params"] = [] + kommandos[row["id"]] = row + with db.cursor() as c: + c.execute("""SELECT id, command_id, parameter_name, url FROM command_parameters + ORDER BY command_id, id""") + for row in c.fetchall(): + if row["command_id"] in kommandos: + kommandos[row["command_id"]]["params"].append(row) + + automatiken = {} + with db.cursor() as c: + c.execute("SELECT * FROM automations WHERE enabled = 1") + for row in c.fetchall(): + row["gruppen"] = {} + row["aktionen"] = [] + automatiken[row["id"]] = row + with db.cursor() as c: + c.execute("""SELECT * FROM automation_conditions + ORDER BY automation_id, group_no, position, id""") + for row in c.fetchall(): + auto = automatiken.get(row["automation_id"]) + if auto is None: + continue + if row["state_id"] not in states: + logger.warning("Automatik %s: Messwert %s gibt es nicht mehr", + auto["name"], row["state_id"]) + continue + auto["gruppen"].setdefault(row["group_no"], []).append(row) + with db.cursor() as c: + c.execute("""SELECT a.id, a.automation_id, a.command_id, p.parameter_id, p.value + FROM automation_actions a + LEFT JOIN automation_action_params p ON p.action_id = a.id + ORDER BY a.automation_id, a.position, a.id""") + gesammelt = {} + for row in c.fetchall(): + auto = automatiken.get(row["automation_id"]) + if auto is None: + continue + aktion = gesammelt.get(row["id"]) + if aktion is None: + aktion = {"command_id": row["command_id"], "werte": {}} + gesammelt[row["id"]] = aktion + auto["aktionen"].append(aktion) + if row["parameter_id"] is not None: + aktion["werte"][row["parameter_id"]] = row["value"] + + logger.info("Regelwerk geladen: %d Automatiken, %d Messwerte, %d Kommandos", + len(automatiken), len(states), len(kommandos)) + return cls(automatiken, states, kommandos, signatur) + + +# =========================================================================== +# Auswertung +# =========================================================================== + +def minuten(text): + """"16:30" oder "16:30:00" als Minuten seit Mitternacht.""" + teile = str(text).strip().split(":") + return int(teile[0]) * 60 + int(teile[1]) + + +def als_datum(text): + for form in ("%d.%m.%Y", "%Y-%m-%d", "%d.%m.%y"): + try: + return datetime.strptime(str(text).strip(), form).date() + except ValueError: + continue + return None + + +def als_zeitpunkt(text): + """Datum mit Uhrzeit. Das Web-Feld liefert "2026-08-30T16:30".""" + roh = str(text).strip().replace("T", " ") + for form in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", + "%d.%m.%Y %H:%M:%S", "%d.%m.%Y %H:%M"): + try: + return datetime.strptime(roh, form) + except ValueError: + continue + tag = als_datum(roh.split(" ")[0]) + return datetime.combine(tag, datetime.min.time()) if tag else None + + +WAHR = {"true", "1", "on", "ja", "yes", "an"} + +# Wie lange ein punktgenauer Ausloeser ("um 16:30", "Sonnenaufgang + 00:30") +# nachtraeglich noch gilt, in Minuten. Ohne dieses Fenster muesste die +# Auswertung genau in dieser einen Minute stattfinden; ein langsamer +# Durchlauf, ein Neustart oder ein haengendes Geraet haetten die Automatik +# fuer den Tag gekostet. Ausgeloest wird trotzdem nur einmal, weil nur die +# steigende Flanke zaehlt (automations.cond_met). +NACHHOLFENSTER = 5 + + +def im_nachholfenster(jetzt_m, ziel_m, fenster): + """ + Liegt die Zielminute hoechstens `fenster` Minuten zurueck? + + Gerechnet wird modulo 24 Stunden, damit ein Ziel kurz vor Mitternacht auch + nach Mitternacht noch zieht: 23:58 ist um 00:01 drei Minuten her. + """ + return (jetzt_m - ziel_m) % 1440 < max(1, fenster) + + +def uhrzeit_erfuellt(typ, op, soll, jetzt, fenster): + """ + Uhrzeit und Datum gegen die echte Uhr, nicht gegen einen Abtastwert. + + Frueher stand hier der zuletzt abgetastete Wert des Messwerts "Uhrzeit", + den der Runner im Geraete-Poll mitfuehrte. Der Poll brauchte aber laenger + als eine Minute, sodass jede dritte Minute nie vorkam - und "um 18:26" + schlicht nie wahr wurde. `jetzt` liegt hier ohnehin vor. + """ + if typ == "date": + soll_d = als_datum(soll) + if soll_d is None: + return False + ist_d = jetzt.date() + if op == "=": return ist_d == soll_d + if op == "<": return ist_d < soll_d + return ist_d >= soll_d + + jetzt_m, soll_m = jetzt.hour * 60 + jetzt.minute, minuten(soll) + if op == "=": return im_nachholfenster(jetzt_m, soll_m, fenster) + if op == "<": return jetzt_m < soll_m + return jetzt_m >= soll_m + + +def bedingung_erfuellt(bedingung, state, wert, jetzt, fenster=NACHHOLFENSTER): + """ + Ein einzelner Vergleich. `wert` ist der aktuelle Messwert als Text, so wie + er vom Geraet kam; `bedingung["value"]` die eingestellte Schwelle. + Unbekannter Wert heisst nicht erfuellt - lieber nicht schalten als auf + Verdacht schalten. + + Uhrzeit und Datum sind die Ausnahme: die werden nicht gemessen, sondern + abgelesen. Sie kommen deshalb direkt aus `jetzt` und nicht aus `wert` - + siehe uhrzeit_erfuellt(). Fuer die Anzeige im Editor fuehrt der Runner sie + zwar auch als Messwert mit, aber ein Vergleich darf nicht davon abhaengen, + wie frisch dieser Abtastwert gerade ist. + + `fenster` ist die Nachholzeit in Minuten fuer punktgenaue Ausloeser. + """ + typ = state["type"] + op = bedingung["operator"] + soll = bedingung["value"] + + if state.get("actor_url") == "Logic" and typ in ("time", "date"): + return uhrzeit_erfuellt(typ, op, soll, jetzt, fenster) + if wert is None or wert == "": + return False + + try: + if typ in ("integer", "float"): + ist_z, soll_z = float(str(wert).replace(",", ".")), float(str(soll).replace(",", ".")) + if op == "=": return ist_z == soll_z + if op == "!=": return ist_z != soll_z + if op == ">": return ist_z > soll_z + if op == "<": return ist_z < soll_z + if op == ">=": return ist_z >= soll_z + if op == "<=": return ist_z <= soll_z + return False + + if typ == "time": + # Zeit-Messwerte, die tatsaechlich von einem Geraet kommen. Die + # Uhr selbst laeuft ueber uhrzeit_erfuellt(). + ist_m, soll_m = minuten(wert), minuten(soll) + if op == "=": return ist_m == soll_m + if op == "<": return ist_m < soll_m + return ist_m >= soll_m + + if typ == "deltatime": + # Der Messwert ist der Sonnenauf- bzw. -untergang, die Schwelle + # ein Versatz. Das Vorzeichen steckt im Operator, der Vergleich + # davor: ">=-" heisst "ab einer halben Stunde davor". + versatz = minuten(soll) + ziel = minuten(wert) + (-versatz if op.endswith("-") else versatz) + # Ueber den Tagesrand wird gerechnet, nicht abgeschnitten: "sechs + # Stunden vor Sonnenaufgang" ist eine gewollte Angabe und landet + # dann eben am Vorabend. Abgeschnitten waeren solche Faelle gar + # nicht mehr formulierbar. + # + # Verglichen wird die Uhrzeit innerhalb des Tages. Ein Ziel + # jenseits von Mitternacht gilt also als diese Uhrzeit am selben + # Tag - bei "um" ist das genau der gemeinte Zeitpunkt, bei "ab" + # und "vor" verschiebt sich der wahre Bereich entsprechend. + ziel %= 1440 + jetzt_m = jetzt.hour * 60 + jetzt.minute + if op.startswith(">="): return jetzt_m >= ziel + if op.startswith("<"): return jetzt_m < ziel + return im_nachholfenster(jetzt_m, ziel, fenster) + + if typ in ("date", "datetime"): + wandeln = als_datum if typ == "date" else als_zeitpunkt + ist_d, soll_d = wandeln(wert), wandeln(soll) + if ist_d is None or soll_d is None: + return False + if op == "=": return ist_d == soll_d + if op == "<": return ist_d < soll_d + return ist_d >= soll_d + + if typ == "bool": + ist_b = str(wert).strip().lower() in WAHR + soll_b = str(soll).strip().lower() in WAHR + return ist_b == soll_b if op == "=" else ist_b != soll_b + + # string und alles Uebrige + if op == "=": + return str(wert).strip() == str(soll).strip() + return str(wert).strip() != str(soll).strip() + + except (ValueError, IndexError) as fehler: + logger.debug("Vergleich %s %s %s nicht moeglich: %s", wert, op, soll, fehler) + return False + + +def gruppen_erfuellt(automatik, regelwerk, werte, jetzt, fenster=NACHHOLFENSTER): + """ + Gleiche group_no = UND, verschiedene = ODER. Eine Automatik ohne + Bedingungen loest nie aus - sonst wuerde sie nach einem Discovery-Lauf, + der ihren Messwert entfernt hat, ploetzlich dauernd feuern. + """ + if not automatik["gruppen"]: + return False + for bedingungen in automatik["gruppen"].values(): + if all(bedingung_erfuellt(b, regelwerk.states[b["state_id"]], + werte.get(b["state_id"]), jetzt, fenster) + for b in bedingungen): + return True + return False + + +def im_zeitfenster(jetzt, von, bis): + """von > bis heisst: das Fenster reicht ueber Mitternacht.""" + m = jetzt.hour * 60 + jetzt.minute + a, b = minuten(von), minuten(bis) + return a <= m <= b if a <= b else (m >= a or m <= b) + + +def tag_passt(automatik, jetzt, kalender): + if not automatik["weekdays"] & (1 << jetzt.weekday()): + return False + if kalender["feiertag"] and not automatik["on_holiday"]: + return False + if kalender["ferien"] and not automatik["on_vacation"]: + return False + return True + + +# =========================================================================== +# Versand +# =========================================================================== + +class Versand: + """ + Schickt Kommandos im Hintergrund - je Geraet der Reihe nach, ueber + Geraete hinweg nebeneinander. + + Ohne das blockiert ein einziges Kommando die ganze Auswertung: eine + Jalousie zuzufahren dauert ueber eine Minute, weil zwischen den beiden + Schwenkbefehlen auf das Ende der Fahrt gewartet werden muss (siehe + TahomaTransport). So lange kaeme kein Zeit-Ausloeser mehr durch, kein + Messwert wuerde zurueckgeschrieben, und mehrere Rollladen in einer + Automatik wuerden sich aufaddieren. + + Je Geraet eine Warteschlange mit einem eigenen Faden: zwei Kommandos an + dieselbe Jalousie duerfen sich nicht ueberholen - "Neigung 0" und + "Neigung 100" sind sonst wirkungslos oder vertauscht -, zwei Kommandos an + verschiedene Jalousien duerfen ruhig gleichzeitig laufen. + + Die Datenbank bleibt aussen vor: die Faeden melden ihr Ergebnis nur + zurueck, geschrieben wird im Hauptfaden. Eine pymysql-Verbindung ist + nicht fuer mehrere Faeden gedacht. + """ + + def __init__(self): + self.warteschlangen = {} # actor_url -> Queue + self.ergebnisse = queue.Queue() + self.laeuft = True + + def einreihen(self, automation_id, beschreibung, transport, auftrag): + schlange = self.warteschlangen.get(auftrag["actor_url"]) + if schlange is None: + schlange = queue.Queue() + self.warteschlangen[auftrag["actor_url"]] = schlange + faden = threading.Thread(target=self._arbeiten, args=(schlange,), + name="versand", daemon=True) + faden.start() + schlange.put((automation_id, beschreibung, transport, auftrag)) + + def _arbeiten(self, schlange): + while self.laeuft: + posten = schlange.get() + if posten is None: + return + automation_id, beschreibung, transport, auftrag = posten + try: + transport.senden(auftrag) + self.ergebnisse.put((automation_id, beschreibung, None)) + except Exception as fehler: + self.ergebnisse.put((automation_id, beschreibung, str(fehler))) + finally: + schlange.task_done() + + def abholen(self): + """Alles, was seit dem letzten Mal fertig geworden ist.""" + fertig = [] + while True: + try: + fertig.append(self.ergebnisse.get_nowait()) + except queue.Empty: + return fertig + + def offen(self): + return sum(s.unfinished_tasks for s in self.warteschlangen.values()) + + def beenden(self): + self.laeuft = False + for schlange in self.warteschlangen.values(): + schlange.put(None) + + +class Sammler: + """ + Fragt die Geraete ab, die sich nicht von selbst melden - im Hintergrund. + + Frueher geschah das mitten in der Hauptschleife: neunzehn Tahoma-Geraete + nacheinander, jedes mit bis zu zehn Sekunden Zeitlimit, dazu die HTTP- und + WLED-Geraete. Eine Runde dauerte dadurch rund fuenfundvierzig statt + dreissig Sekunden. Gepollt wurde, sobald seit dem letzten Poll sechzig + Sekunden vergangen waren - also erst jede zweite Runde, in Wahrheit alle + neunzig Sekunden. Weil die Uhr an derselben Abfrage hing, uebersprang der + Runner jede dritte Minute, und ein Ausloeser "um 18:26" wurde nie wahr. + + Jetzt hat jeder Transport seinen eigenen Faden und seinen eigenen Abstand: + die Rollaeden duerfen gemuetlich alle fuenf Minuten, waehrend die + Hauptschleife im Sekundentakt weiterlaeuft. + + Die Faeden fassen die Datenbank nicht an - sie legen ihre Werte in eine + Queue, geschrieben wird im Hauptfaden. Dieselbe Regel wie beim Versand: + eine pymysql-Verbindung gehoert einem Faden. + """ + + def __init__(self): + self.ergebnisse = queue.Queue() + self.laeuft = True + + def aufnehmen(self, transport, abstand): + faden = threading.Thread(target=self._arbeiten, args=(transport, abstand), + name="sammler-" + transport.schema, daemon=True) + faden.start() + + def _arbeiten(self, transport, abstand): + while self.laeuft: + try: + werte = transport.zustaende_lesen() + if werte: + self.ergebnisse.put(werte) + except Exception as fehler: + logger.warning("%s nicht abfragbar: %s", transport.schema, fehler) + # In Sekundenschritten warten, damit das Beenden nicht bis zum + # naechsten Durchgang dauert - bei Tahoma waeren das fuenf Minuten. + for _ in range(max(1, int(abstand))): + if not self.laeuft: + return + time.sleep(1) + + def abholen(self): + """Alles, was die Faeden seit dem letzten Mal geliefert haben.""" + neu = {} + while True: + try: + neu.update(self.ergebnisse.get_nowait()) + except queue.Empty: + return neu + + def beenden(self): + self.laeuft = False + + +# =========================================================================== +# Der Runner +# =========================================================================== + +class Runner: + + def __init__(self, config, dry_run=False): + self.config = config + self.dry_run = dry_run or config.ja("runner", "dry_run") + self.db = verbinden(config) + self.solar = verbinden(config, "solar") + + self.werte = {} # state_id -> letzter bekannter Wert + self.geschrieben = {} # state_id -> zuletzt in die DB geschriebener Wert + self.geschrieben_um = {} # state_id -> wann das war + self.war_aktiv = {} # automation_id -> war im Zeitfenster + self.lief_im_fenster = {} # automation_id -> hat im Fenster ausgeloest + self._sonne = (None, "00:00", "00:00") # (datum, aufgang, untergang) + self._kalender = (None, {"feiertag": False, "ferien": False}) + self._letzte_saeuberung = None + + self.versand = Versand() + self.sammler = Sammler() + self.mqtt = self._mqtt_verbinden() + self.transporte = [ + MQTTTransport(self.mqtt, self.dry_run), + WLEDTransport(requests, dry_run=self.dry_run), + HTTPTransport(requests, dry_run=self.dry_run), + TahomaTransport(requests, config.text("tahoma", "pin"), + config.text("tahoma", "token"), + config.zahl("tahoma", "timeout", 10), self.dry_run), + LogicTransport(self.sonnenzeiten), + ] + self.regelwerk = None + + # --- Aufbau ---------------------------------------------------------- + + def _mqtt_verbinden(self): + client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, + client_id=self.config.text("mqtt", "client_id", "autoaction_runner")) + benutzer = self.config.text("mqtt", "username") + if benutzer: + client.username_pw_set(benutzer, self.config.text("mqtt", "password")) + client.on_message = self._mqtt_nachricht + client.connect(self.config.text("mqtt", "broker", "localhost"), + self.config.zahl("mqtt", "port", 1883), 60) + client.loop_start() + return client + + def _mqtt_nachricht(self, client, userdata, nachricht): + # Der Client laeuft schon, waehrend __init__ noch die Transporte baut. + for transport in getattr(self, "transporte", []): + if isinstance(transport, MQTTTransport): + transport.nachricht(nachricht.topic, nachricht.payload) + + def transport_fuer(self, actor_url): + for transport in self.transporte: + if transport.passt(actor_url): + return transport + return None + + def regelwerk_laden(self): + self.regelwerk = Regelwerk.laden(self.db) + + # Welche Geraete Position und Neigung zusammen koennen. Nur die haben + # die Kugelschreiber-Mechanik, und nur bei ihnen wird ein "Zu" zum + # zweifachen Schwenken - siehe TahomaTransport. + kombi = {k["actor_url"] for k in self.regelwerk.kommandos.values() + if k["command_url"] == "setClosureAndOrientation"} + for transport in self.transporte: + if isinstance(transport, TahomaTransport): + transport.kombigeraete_setzen(kombi) + + # Jeder Transport bekommt die Messwerte, fuer die er zustaendig ist. + for transport in self.transporte: + passende = [{"id": s["id"], "actor_url": s["actor_url"], + "state_url": s["state_url"], "value_path": s["value_path"], + "wertetabelle": s["wertetabelle"]} + for s in self.regelwerk.states.values() + if transport.passt(s["actor_url"])] + transport.zustaende_anmelden(passende) + # Der zuletzt bekannte Wert aus der Datenbank ist besser als gar + # keiner: nach einem Neustart steht sonst jede Bedingung auf "unklar", + # bis das Geraet zufaellig etwas schickt. + for s in self.regelwerk.states.values(): + if s["id"] not in self.werte and s["current_value"] is not None: + self.werte[s["id"]] = s["current_value"] + # Was schon in der Tabelle steht, muss nach einem Neustart nicht + # noch einmal hineingeschrieben werden. + self.geschrieben.setdefault(s["id"], s["current_value"]) + + # --- Umgebung -------------------------------------------------------- + + def sonnenzeiten(self): + """Aus solarLog.daylight, einmal je Tag geholt.""" + heute = date.today() + if self._sonne[0] == heute: + return self._sonne[1], self._sonne[2] + auf, unter = "00:00", "00:00" + try: + with self.solar.cursor() as c: + c.execute("SELECT sunrise, sunset FROM daylight WHERE date = %s", (heute,)) + zeile = c.fetchone() + if zeile: + auf = self._als_uhrzeit(zeile["sunrise"]) + unter = self._als_uhrzeit(zeile["sunset"]) + else: + logger.warning("Kein Eintrag in daylight fuer %s", heute) + except Exception as fehler: + logger.warning("Sonnenzeiten nicht lesbar: %s", fehler) + self._sonne = (heute, auf, unter) + return auf, unter + + @staticmethod + def _als_uhrzeit(wert): + """TIME-Spalten liefert pymysql als timedelta, nicht als Text.""" + if isinstance(wert, timedelta): + minute = int(wert.total_seconds()) // 60 + return "%02d:%02d" % (minute // 60 % 24, minute % 60) + return str(wert)[:5] + + def kalender(self): + """Ferien und Feiertage von heute, einmal je Tag geholt.""" + heute = date.today() + if self._kalender[0] == heute: + return self._kalender[1] + stand = {"feiertag": False, "ferien": False} + try: + with self.db.cursor() as c: + c.execute("SELECT holiday, vacation FROM calendar_days WHERE date = %s", (heute,)) + zeile = c.fetchone() + if zeile: + stand = {"feiertag": bool(zeile["holiday"]), "ferien": bool(zeile["vacation"])} + except Exception as fehler: + logger.warning("Kalender nicht lesbar: %s", fehler) + self._kalender = (heute, stand) + return stand + + # --- Werte ----------------------------------------------------------- + + def sammler_starten(self): + """ + Je gepolltem Transport ein Faden. MQTT meldet sich von selbst und die + Uhr rechnet der Runner - die beiden stehen hier nicht. + """ + vorgaben = [(HTTPTransport, "poll_http", 60), + (WLEDTransport, "poll_wled", 60), + (TahomaTransport, "poll_tahoma", 300)] + for transport in self.transporte: + for klasse, schluessel, vorgabe in vorgaben: + if isinstance(transport, klasse): + abstand = self.config.zahl("runner", schluessel, vorgabe) + self.sammler.aufnehmen(transport, abstand) + logger.info("%s wird alle %d s abgefragt", klasse.__name__, abstand) + break + + def werte_einsammeln(self, auch_geraete=False): + """ + MQTT und Uhr kosten nichts und werden jeden Takt gelesen; die Geraete + liefert der Sammler aus dem Hintergrund. Nur bei --once, wo es keine + Faeden gibt, fragt die Schleife die Geraete selbst. + """ + neu = {} + for transport in self.transporte: + billig = isinstance(transport, (MQTTTransport, LogicTransport)) + if billig or auch_geraete: + neu.update(transport.zustaende_lesen()) + neu.update(self.sammler.abholen()) + if neu: + self.werte.update(neu) + self.werte_zurueckschreiben(neu) + return neu + + def werte_zurueckschreiben(self, neu): + """ + current_value nachfuehren, damit der Editor im Browser aktuelle Zahlen + zeigt ("Temperatur (= 21,4 °C)"). + + Geschrieben wird nur, was sich geaendert hat. MQTT liefert ohnehin nur + neu Hereingekommenes, die gepollten Transporte dagegen bei jedem + Durchgang ihren kompletten Bestand - ohne diesen Vergleich gingen rund + 180 unveraenderte Werte je Runde in die Tabelle. Zusaetzlich + gedrosselt, sonst schreibt ein gespraechiger Sensor im Sekundentakt. + """ + jetzt = datetime.now() + faellig = [(w, i) for i, w in neu.items() + if self.geschrieben.get(i) != w + and self.geschrieben_um.get(i, datetime.min) + SCHREIB_ABSTAND <= jetzt] + if not faellig: + return + try: + with self.db.cursor() as c: + c.executemany("UPDATE actor_states SET current_value = %s WHERE id = %s", faellig) + for wert, state_id in faellig: + self.geschrieben[state_id] = wert + self.geschrieben_um[state_id] = jetzt + except Exception as fehler: + logger.warning("current_value nicht schreibbar: %s", fehler) + + # --- Ausfuehren ------------------------------------------------------ + + def ausloesen(self, automatik, anlass): + """ + Die Aktionen einer Automatik in den Versand geben. + + Geschickt wird im Hintergrund - eine Jalousie zuzufahren dauert ueber + eine Minute, und so lange darf die Auswertung nicht stehen. Was dabei + schiefgeht, kommt spaeter ueber ergebnisse_verbuchen() ins Protokoll. + """ + fehlerText = [] + eingereiht = 0 + for aktion in automatik["aktionen"]: + kommando = self.regelwerk.kommandos.get(aktion["command_id"]) + if kommando is None: + fehlerText.append("Kommando %s gibt es nicht mehr" % aktion["command_id"]) + logger.error("%s: Kommando %s gibt es nicht mehr", + automatik["name"], aktion["command_id"]) + continue + transport = self.transport_fuer(kommando["actor_url"]) + if transport is None: + fehlerText.append("Kein Transport fuer %s" % kommando["actor_url"]) + logger.error("%s: fuer %s (%s) gibt es keinen Transport", + automatik["name"], kommando["actor_name"], kommando["actor_url"]) + continue + auftrag = { + "actor_url": kommando["actor_url"], + "command_url": kommando["command_url"], + "params": [{"url": p["url"], "name": p["parameter_name"], + "wert": aktion["werte"].get(p["id"], "")} + for p in kommando["params"]], + } + beschreibung = "%s: %s" % (kommando["actor_name"], kommando["command_name"]) + logger.info("%s: %s (unterwegs)", automatik["name"], beschreibung) + self.versand.einreihen(automatik["id"], beschreibung, transport, auftrag) + eingereiht += 1 + + ergebnis = "error" if fehlerText else anlass + detail = "; ".join(fehlerText)[:255] if fehlerText \ + else ("%d Kommando(s) unterwegs" % eingereiht) + try: + with self.db.cursor() as c: + c.execute("""UPDATE automations SET last_run = NOW(), changed = changed + WHERE id = %s""", (automatik["id"],)) + c.execute("""INSERT INTO automation_log (automation_id, result, detail) + VALUES (%s, %s, %s)""", (automatik["id"], ergebnis, detail)) + except Exception as fehler: + logger.warning("Protokoll nicht schreibbar: %s", fehler) + automatik["last_run"] = datetime.now() + self.lief_im_fenster[automatik["id"]] = True + + def ergebnisse_verbuchen(self): + """ + Was der Versand inzwischen erledigt hat ins Protokoll schreiben. + + Nur Fehlschlaege bekommen eine eigene Zeile - der Lauf selbst steht + schon drin, und ein Protokoll, das jedes gelungene Kommando einzeln + auffuehrt, findet niemand mehr etwas darin. + """ + for automation_id, beschreibung, fehler in self.versand.abholen(): + if fehler is None: + logger.debug("erledigt: %s", beschreibung) + continue + logger.error("%s konnte nicht geschickt werden: %s", beschreibung, fehler) + try: + with self.db.cursor() as c: + c.execute("""INSERT INTO automation_log (automation_id, result, detail) + VALUES (%s, 'error', %s)""", + (automation_id, ("%s: %s" % (beschreibung, fehler))[:255])) + except Exception as schreibfehler: + logger.warning("Protokoll nicht schreibbar: %s", schreibfehler) + + def flanke_merken(self, automatik, erfuellt): + if bool(automatik["cond_met"]) == bool(erfuellt): + return + automatik["cond_met"] = 1 if erfuellt else 0 + try: + with self.db.cursor() as c: + c.execute("""UPDATE automations SET cond_met = %s, changed = changed + WHERE id = %s""", (automatik["cond_met"], automatik["id"])) + except Exception as fehler: + logger.warning("cond_met nicht schreibbar: %s", fehler) + + def durchlauf(self, fenster=NACHHOLFENSTER): + jetzt = datetime.now() + kalender = self.kalender() + + for automatik in self.regelwerk.automatiken.values(): + aktiv = (tag_passt(automatik, jetzt, kalender) + and im_zeitfenster(jetzt, automatik["window_from"], automatik["window_to"])) + vorher_aktiv = self.war_aktiv.get(automatik["id"], aktiv) + + if aktiv: + if not vorher_aktiv: + self.lief_im_fenster[automatik["id"]] = False + erfuellt = gruppen_erfuellt(automatik, self.regelwerk, self.werte, + jetzt, fenster) + if erfuellt and not automatik["cond_met"]: + if self.gesperrt(automatik, jetzt): + logger.debug("%s: Flanke faellt in die Sperrzeit, uebersprungen", + automatik["name"]) + else: + self.ausloesen(automatik, "fired") + self.flanke_merken(automatik, erfuellt) + else: + # Das Fenster ist gerade zugegangen. Wer "auf jeden Fall" + # angehakt hat, bekommt jetzt seinen Lauf - aber nur, wenn in + # diesem Fenster noch keiner stattgefunden hat. Nach einem + # Neustart mitten im Fenster weiss der Runner das nicht mehr + # aus dem Speicher, deshalb zaehlt zusaetzlich last_run. + if vorher_aktiv and automatik["force_once"] and not self.lief_im_fenster.get(automatik["id"]): + if not self.lief_heute(automatik, jetzt): + logger.info("%s: Zeitfenster vorbei, wird trotzdem ausgefuehrt", + automatik["name"]) + self.ausloesen(automatik, "forced") + # Ausserhalb des Fensters die Flanke zuruecksetzen, sonst + # koennte sie im naechsten Fenster nicht mehr steigen. + self.flanke_merken(automatik, False) + self.lief_im_fenster[automatik["id"]] = False + + self.war_aktiv[automatik["id"]] = aktiv + + @staticmethod + def gesperrt(automatik, jetzt): + """ + Liegt die letzte Ausloesung noch innerhalb der Sperrzeit? + + Gegen Messwerte, die um die Schwelle pendeln: "Temperatur > 22" bei + 22,1 / 21,9 / 22,1 Grad ist jedes Mal eine echte steigende Flanke, und + ueber MQTT koennen die Werte im Sekundentakt hereinkommen. + + Die Flanke wird dabei verworfen und nicht aufgehoben. Ein Rollladen, + der eine Viertelstunde spaeter doch noch losfaehrt, weil vor langer + Zeit einmal eine Schwelle gestreift wurde, waere unangenehmer als + einer, der gar nicht faehrt. Der naechste echte Anlass nach Ablauf + der Sperre kommt ohnehin durch. + + force_once ist davon nicht betroffen: es greift nur, wenn im Fenster + gar nichts gelaufen ist - dann ist auch keine Sperre aktiv. + """ + sperre = int(automatik.get("lockout_secs") or 0) + letzter = automatik.get("last_run") + if not sperre or not letzter: + return False + return (jetzt - letzter).total_seconds() < sperre + + @staticmethod + def lief_heute(automatik, jetzt): + letzter = automatik.get("last_run") + return bool(letzter) and letzter.date() == jetzt.date() + + def protokoll_saeubern(self): + heute = date.today() + if self._letzte_saeuberung == heute: + return + self._letzte_saeuberung = heute + try: + with self.db.cursor() as c: + c.execute("DELETE FROM automation_log WHERE ts < NOW() - INTERVAL %s DAY", + (LOG_AUFBEWAHRUNG_TAGE,)) + except Exception as fehler: + logger.warning("Protokoll nicht aufraeumbar: %s", fehler) + + # --- Hauptschleife --------------------------------------------------- + + def laufen(self, nur_einmal=False): + """ + Die Schleife macht nur noch Billiges: Werte abholen, auswerten, + protokollieren. Alles, was auf ein Netz warten muss, laeuft daneben - + die Geraeteabfrage im Sammler, das Schalten im Versand. Deshalb darf + der Takt kurz sein. + """ + self.regelwerk_laden() + takt = self.config.zahl("runner", "tick_seconds", 10) + neuladen = self.config.zahl("runner", "reload_seconds", 30) + fenster = self.config.zahl("runner", "catchup_minutes", NACHHOLFENSTER) + letztes_pruefen = 0.0 + if not nur_einmal: + self.sammler_starten() + + while True: + jetzt = time.monotonic() + self.werte_einsammeln(auch_geraete=nur_einmal) + self.durchlauf(fenster) + self.ergebnisse_verbuchen() + self.protokoll_saeubern() + + if jetzt - letztes_pruefen >= neuladen: + letztes_pruefen = jetzt + try: + if Regelwerk.signatur_lesen(self.db) != self.regelwerk.signatur: + logger.info("Regelwerk hat sich geaendert, wird neu geladen") + self.regelwerk_laden() + except Exception as fehler: + logger.warning("Regelwerk nicht pruefbar: %s", fehler) + + if nur_einmal: + return + time.sleep(takt) + + def beenden(self): + self.sammler.beenden() + offen = self.versand.offen() + if offen: + logger.info("%d Kommando(s) noch unterwegs - wird nicht abgewartet", offen) + self.versand.beenden() + try: + self.mqtt.loop_stop() + self.mqtt.disconnect() + except Exception: + pass + + +# =========================================================================== + +def main(): + parser = argparse.ArgumentParser(description="Fuehrt die Automatiken aus dem Web-UI aus.") + parser.add_argument("--dry-run", action="store_true", + help="nichts wirklich schalten, nur protokollieren") + parser.add_argument("--once", action="store_true", + help="einen einzigen Durchlauf, dann beenden") + parser.add_argument("--verbose", action="store_true", help="DEBUG-Ausgaben") + parser.add_argument("--config", default="config.ini") + args = parser.parse_args() + + config = Config(args.config) + logging.basicConfig( + level=logging.DEBUG if args.verbose else getattr( + logging, config.text("runner", "log_level", "INFO").upper(), logging.INFO), + format="%(asctime)s - %(levelname)s - %(message)s") + + runner = Runner(config, args.dry_run) + if runner.dry_run: + logger.info("Probelauf: es wird nichts geschaltet.") + + # startSolarServer.sh beendet eine laufende Instanz mit SIGTERM, bevor es + # die neue startet. Ohne diesen Handler faellt der Prozess sofort um und + # beenden() kaeme nie dran: der Broker hielte die Verbindung noch eine + # Weile fuer lebendig, und ein gerade laufendes Kommando bliebe auf halbem + # Weg stehen. Als KeyboardInterrupt geht es denselben Weg wie Strg-C. + def abbrechen(signum, rahmen): + raise KeyboardInterrupt + + signal.signal(signal.SIGTERM, abbrechen) + + try: + runner.laufen(args.once) + except KeyboardInterrupt: + logger.info("Abbruch - wird beendet") + finally: + runner.beenden() + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/autoActions/config.ini.example b/autoActions/config.ini.example new file mode 100644 index 0000000..7f8950e --- /dev/null +++ b/autoActions/config.ini.example @@ -0,0 +1,87 @@ +# ============================================================================ +# AutoAction-Runner - Konfiguration +# ============================================================================ +# Kopieren nach config.ini und ausfuellen. config.ini ist nicht versioniert +# (siehe .gitignore), weil hier Zugangsdaten stehen. + +# ============================================================================ +# DATENBANK - Geraete und Automatiken (homeMesh) +# ============================================================================ +[database] +host = nas.fritz.box +port = 3310 +database = homeMesh +user = homeMesh +password = + +# ============================================================================ +# DATENBANK - Messwerte (solarLog) +# ============================================================================ +# Nur fuer die Tabelle `daylight`, aus der Sonnenauf- und -untergang kommen. +# Leer lassen, wenn dieselben Zugangsdaten wie oben gelten. +[solar] +database = solarLog +user = +password = + +# ============================================================================ +# MQTT +# ============================================================================ +[mqtt] +broker = nas.fritz.box +port = 1883 +username = +password = +client_id = autoaction_runner + +# ============================================================================ +# TAHOMA +# ============================================================================ +# Fuer Aktoren, deren URL mit io:// beginnt. Ohne Token bleiben sie stumm - +# der Runner protokolliert das dann als Fehler, statt still nichts zu tun. +[tahoma] +pin = +token = +timeout = 10 + +# ============================================================================ +# KALENDER +# ============================================================================ +# Fuer fetch_calendar.py, das Feiertage und Schulferien in calendar_days +# eintraegt. Regionscodes siehe openholidaysapi.org, Bayern ist DE-BY. +[calendar] +country = DE +subdivision = DE-BY + +# ============================================================================ +# LAUFZEIT +# ============================================================================ +[runner] +# Abstand der Auswertung in Sekunden. Die Schleife wartet auf nichts mehr - +# Geraete fragt der Sammler in eigenen Faeden ab, geschaltet wird im Versand -, +# deshalb darf der Takt kurz sein. Er bestimmt nur noch, wie schnell eine +# hereingekommene MQTT-Nachricht ausgewertet wird. +tick_seconds = 10 + +# Wie oft Geraete abgefragt werden, die nichts von sich aus melden. Getrennt +# je Transport, weil sie unterschiedlich teuer sind: eine Shelly-Abfrage ist +# in Millisekunden zurueck, die Tahoma-Box braucht fuer ihre Geraete +# nacheinander gut zehn Sekunden. In Sekunden. +poll_http = 60 +poll_wled = 60 +poll_tahoma = 300 + +# Wie lange ein punktgenauer Ausloeser ("um 16:30", "Sonnenaufgang + 00:30") +# nachtraeglich noch gilt, in Minuten. Faellt die Auswertung in dieser Minute +# aus - Neustart, haengendes Geraet -, holt der naechste Takt sie nach. +# Ausgeloest wird trotzdem nur einmal, weil nur die steigende Flanke zaehlt. +catchup_minutes = 5 + +# Wie oft der Runner nachsieht, ob sich das Regelwerk geaendert hat. +reload_seconds = 30 + +# Nichts wirklich schalten, nur protokollieren. Zum Ausprobieren neuer Regeln. +dry_run = false + +# DEBUG, INFO, WARNING, ERROR +log_level = INFO diff --git a/autoActions/fetch_calendar.py b/autoActions/fetch_calendar.py new file mode 100644 index 0000000..c653c60 --- /dev/null +++ b/autoActions/fetch_calendar.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +""" +Fuellt die Tabelle `calendar_days` mit Feiertagen und Schulferien. + +Die Automatiken koennen mit "an Feiertagen ausfuehren" und "in den Ferien +ausfuehren" auf besondere Tage reagieren; woher das Wissen kommt, steht hier. +Nur besondere Tage werden eingetragen - ein fehlendes Datum ist ein +gewoehnlicher Tag. + +Quelle ist openholidaysapi.org: ein offenes Verzeichnis der EU-Kommission, das +gesetzliche Feiertage und Schulferien fuer alle deutschen Bundeslaender +liefert. Ein Aufruf je Jahr genuegt, deshalb ist das ein Cronjob und kein +Dauerlaeufer: + + 0 4 1 1 * /usr/bin/python3 /pfad/zu/fetch_calendar.py + +Ein zusaetzlicher Lauf im Herbst schadet nicht - die Ferientermine des +uebernaechsten Schuljahres stehen erst spaeter fest. + +Bereits eingetragene Tage werden ueberschrieben, nie doppelt angelegt. +""" + +import argparse +import logging +import sys +from datetime import date, timedelta + +import pymysql +import requests + +from autoaction_runner import Config, verbinden + +logger = logging.getLogger("kalender") + +BASIS = "https://openholidaysapi.org" + + +def zeitraum(api, land, region, von, bis): + """Eintraege einer Art (PublicHolidays oder SchoolHolidays) abholen.""" + antwort = requests.get( + BASIS + "/" + api, + params={"countryIsoCode": land, "subdivisionCode": region, + "languageIsoCode": "DE", "validFrom": von.isoformat(), + "validTo": bis.isoformat()}, + headers={"Accept": "application/json"}, timeout=20) + antwort.raise_for_status() + return antwort.json() + + +def name(eintrag): + for teil in eintrag.get("name", []): + if teil.get("text"): + return teil["text"][:80] + return "?" + + +def tage(eintrag): + """Ferien erstrecken sich ueber Wochen; hier wird daraus Tag fuer Tag.""" + start = date.fromisoformat(eintrag["startDate"]) + ende = date.fromisoformat(eintrag["endDate"]) + while start <= ende: + yield start + start += timedelta(days=1) + + +def main(): + parser = argparse.ArgumentParser(description="Feiertage und Ferien in calendar_days schreiben.") + parser.add_argument("--jahr", type=int, default=date.today().year) + parser.add_argument("--jahre", type=int, default=2, help="wie viele Jahre ab --jahr") + parser.add_argument("--config", default="config.ini") + args = parser.parse_args() + + logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") + config = Config(args.config) + land = config.text("calendar", "country", "DE") + region = config.text("calendar", "subdivision", "DE-BY") + + von = date(args.jahr, 1, 1) + bis = date(args.jahr + args.jahre - 1, 12, 31) + logger.info("Hole %s bis %s fuer %s", von, bis, region) + + gesammelt = {} + for eintrag in zeitraum("PublicHolidays", land, region, von, bis): + for tag in tage(eintrag): + gesammelt.setdefault(tag, {})["holiday"] = name(eintrag) + for eintrag in zeitraum("SchoolHolidays", land, region, von, bis): + for tag in tage(eintrag): + gesammelt.setdefault(tag, {})["vacation"] = name(eintrag) + + db = verbinden(config) + with db.cursor() as c: + c.executemany( + """INSERT INTO calendar_days (date, holiday, vacation) VALUES (%s, %s, %s) + ON DUPLICATE KEY UPDATE holiday = VALUES(holiday), vacation = VALUES(vacation)""", + [(tag, eintrag.get("holiday"), eintrag.get("vacation")) + for tag, eintrag in sorted(gesammelt.items())]) + logger.info("%d Tage eingetragen", len(gesammelt)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/autoActions/transports.py b/autoActions/transports.py new file mode 100644 index 0000000..e1e75f1 --- /dev/null +++ b/autoActions/transports.py @@ -0,0 +1,626 @@ +#!/usr/bin/env python3 +""" +Transporte fuer den AutoAction-Runner. + +Ein Transport weiss, wie man mit einer Sorte Geraet redet - er liest deren +Messwerte und schickt deren Kommandos. Welcher zustaendig ist, entscheidet die +URL des Aktors in der Tabelle `actors`: + + mqtt://... MQTT-Geraete (Home-Assistant-Discovery) + http://... Shelly und andere HTTP-Geraete + wled://... WLED-Lampen + io://, rts://, internal://, ogp:// Tahoma - das Schema haengt an der + Funkart des Geraets, deshalb wird dort nicht danach + entschieden, sondern an der Box-Kennung in der URL + Logic das gerechnete Geraet "Zeitpunkt" (Uhrzeit, Datum, Sonne) + +Alle liegen in einer Datei statt in einem Paket wie bei deviceDiscovery: es +sind fuenf kurze Klassen, und wer eine sechste Geraeteart anschliesst, sieht +hier auf einen Blick, was dafuer zu tun ist. + +Jeder Transport hat zwei Haelften: + + zustaende_anmelden(states) einmalig beim Start + zustaende_lesen() liefert {state_id: wert} - nur was neu ist + senden(aktion) fuehrt ein Kommando aus + +`states` ist eine Liste von Dicts mit actor_url, state_url, value_path und +id, `aktion` ein Dict mit actor_url, command_url und params (Liste aus +{url, name, wert}). +""" + +import json +import logging +import re +import time +import urllib3 +from datetime import datetime +from urllib.parse import quote + +logger = logging.getLogger("autoaction.transport") + +# Die Tahoma-Box hat ein selbst ausgestelltes Zertifikat auf einen Namen, den +# nur das Heimnetz kennt. Die Pruefung ist dort bewusst aus (wie in +# ajax/tahoma.php); ohne diese Zeile warnt urllib3 bei jeder einzelnen +# Abfrage und uebertoent das eigentliche Protokoll. +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + +def wert_aus_pfad(daten, pfad): + """ + Einen Teilwert aus einer Nutzlast holen: "[3]", "ssid", "seg[0].col[0]". + Ein leerer Pfad heisst: die Nutzlast selbst. + + Gebraucht wird das an zwei Stellen. MQTT-Geraete legen mehrere Messwerte + auf ein Topic - der go-eCharger schickt sechzehn Zahlen als JSON-Feld -, + und WLED liefert seinen gesamten Zustand als ein Dokument. + """ + if not pfad: + return daten + for teil in pfad.split("."): + treffer = re.match(r"^([^\[]*)((?:\[\d+\])*)$", teil) + if not treffer: + raise KeyError(pfad) + if treffer.group(1): + daten = daten[treffer.group(1)] + for index in re.findall(r"\[(\d+)\]", treffer.group(2)): + daten = daten[int(index)] + return daten + + +def uebersetze(wert, tabelle): + """ + Aus der gesendeten Zahl den Zustandsnamen machen: aus "2" wird "Charging". + + Manche Geraete schicken einen Zahlencode und meinen einen Zustand. Welche + Zahl welchen Namen hat, steht in possible_values - derselben Spalte, aus + der auch der Editor seine Auswahlliste baut. Eine Bedingung vergleicht + damit genau den Klartext, den man dort ausgewaehlt hat. + + Steht die Zahl nicht in der Tabelle, bleibt sie stehen: ein erfundener + Name waere schlimmer als ein roher Wert. + """ + if not tabelle: + return wert + if wert in tabelle: + return tabelle[wert] + try: # "2.0" und "2" meinen dieselbe Stufe + ganz = str(int(float(wert))) + except (TypeError, ValueError): + return wert + return tabelle.get(ganz, wert) + + +class Transport: + """Gemeinsame Form. Wer nichts zu lesen hat, erbt die leeren Methoden.""" + + schema = "" + + def passt(self, actor_url): + return actor_url.startswith(self.schema) + + def zustaende_anmelden(self, states): + pass + + def zustaende_lesen(self): + return {} + + def senden(self, aktion): + raise NotImplementedError + + +# --------------------------------------------------------------------------- +# MQTT +# --------------------------------------------------------------------------- + +class MQTTTransport(Transport): + """ + Die state_url ist hier das vollstaendige Topic, die parameter_url das + Kommando-Topic. Werte kommen von selbst herein und landen in einem + Zwischenspeicher, den der Runner im Takt abholt. + """ + + schema = "mqtt://" + + def __init__(self, client, dry_run=False): + self.client = client + self.dry_run = dry_run + self.topics = {} # topic -> [(state_id, value_path, wertetabelle), ...] + self.neu = {} # state_id -> wert + + def zustaende_anmelden(self, states): + self.topics = {} + for s in states: + if not s["state_url"]: + continue + self.topics.setdefault(s["state_url"], []).append( + (s["id"], s.get("value_path"), s.get("wertetabelle") or {})) + for topic in self.topics: + self.client.subscribe(topic) + mehrfach = sum(1 for e in self.topics.values() if len(e) > 1) + logger.info("MQTT: %d Topics abonniert, %d davon mit mehreren Messwerten", + len(self.topics), mehrfach) + + def nachricht(self, topic, payload): + """Wird vom Runner aus dem on_message-Rueckruf gerufen.""" + eintraege = self.topics.get(topic, []) + if not eintraege: + return + text = payload.decode("utf-8", "replace").strip() if isinstance(payload, bytes) else str(payload) + try: + daten = json.loads(text) + except ValueError: + daten = None # kein JSON - dann gilt der Rohtext + for state_id, pfad, tabelle in eintraege: + wert = self._wert(text, daten, pfad, tabelle) + if wert is not None: + self.neu[state_id] = wert + + @staticmethod + def _wert(text, daten, pfad, tabelle=None): + """ + Aus der Nutzlast den Wert eines einzelnen Messwerts machen. + + Mehrere Messwerte teilen sich oft ein Topic; welcher Teil gemeint ist, + steht in value_path - gelesen aus dem value_template der + Home-Assistant-Discovery. Ohne Pfad gilt die ganze Nutzlast, und + JSON-Skalare werden ausgepackt: manche Geraete schicken 21.4 mit + Anfuehrungszeichen, andere true statt ON. + + Steht eine Werttabelle dabei, wird aus der gesendeten Zahl der + Zustandsname: aus 2 wird "Charging". + """ + if daten is None: + return uebersetze(text, tabelle) + try: + wert = wert_aus_pfad(daten, pfad) + except (KeyError, IndexError, TypeError): + logger.debug("Pfad %s nicht in der Nutzlast: %s", pfad, text[:80]) + return None + if isinstance(wert, bool): + wert = "true" if wert else "false" + elif isinstance(wert, (int, float, str)): + wert = str(wert) + else: + return json.dumps(wert, ensure_ascii=False) + return uebersetze(wert, tabelle) + + def zustaende_lesen(self): + werte, self.neu = self.neu, {} + return werte + + def senden(self, aktion): + if not aktion["params"]: + # Kommando ohne Parameter: das Kommando selbst ist die Nutzlast. + self._publish(aktion["command_url"], "") + return + for p in aktion["params"]: + self._publish(p["url"] or aktion["command_url"], p["wert"]) + + def _publish(self, topic, nutzlast): + if not topic: + raise ValueError("Kommando ohne Topic") + if self.dry_run: + logger.info("[dry-run] MQTT %s <- %s", topic, nutzlast) + return + ergebnis = self.client.publish(topic, nutzlast, qos=1, retain=False) + ergebnis.wait_for_publish(timeout=5) + + +# --------------------------------------------------------------------------- +# HTTP (Shelly und Verwandte) +# --------------------------------------------------------------------------- + +class HTTPTransport(Transport): + """ + Die actor_url ist der Endpunkt, die state_url ein Feldname in dessen + JSON-Antwort ("tC", "a_voltage"). Solche Geraete melden sich nicht von + selbst, sie werden im Poll-Takt gefragt. + + Beim Senden wird die command_url als Abfrageargument an die actor_url + gehaengt ("turn=on") und die Parameter mit ihrem eigenen Namen dazu. + Frueher stand hier eine Uebersetzungstabelle, weil die Shelly-Kommandos + ohne URL in der Datenbank landeten - das ist im Discovery behoben, die + Zuordnung gehoert dorthin und nicht in den Runner. + """ + + schema = "http" + + def __init__(self, requests_modul, timeout=5, dry_run=False): + self.requests = requests_modul + self.timeout = timeout + self.dry_run = dry_run + self.states = [] + + def zustaende_anmelden(self, states): + self.states = [s for s in states if s["state_url"]] + logger.info("HTTP: %d Messwerte an %d Endpunkten", + len(self.states), len({s["actor_url"] for s in self.states})) + + def zustaende_lesen(self): + werte = {} + # Je Endpunkt eine Anfrage, auch wenn mehrere Messwerte daran haengen. + nach_url = {} + for s in self.states: + nach_url.setdefault(s["actor_url"], []).append(s) + for url, states in nach_url.items(): + try: + antwort = self.requests.get(url, timeout=self.timeout) + daten = antwort.json() + except Exception as fehler: + logger.debug("HTTP %s nicht erreichbar: %s", url, fehler) + continue + for s in states: + if isinstance(daten, dict) and s["state_url"] in daten: + werte[s["id"]] = str(daten[s["state_url"]]) + return werte + + def senden(self, aktion): + argumente = {} + for teil in (aktion["command_url"] or "").split("&"): + if "=" in teil: + schluessel, wert = teil.split("=", 1) + argumente[schluessel] = wert + for p in aktion["params"]: + if p["url"]: + argumente[p["url"]] = p["wert"] + if not argumente: + raise RuntimeError("Kommando ohne URL und ohne Parameter - im " + "Geraetemodell fehlt die Angabe, was zu schicken ist") + if self.dry_run: + logger.info("[dry-run] HTTP %s %s", aktion["actor_url"], argumente) + return + antwort = self.requests.get(aktion["actor_url"], params=argumente, timeout=self.timeout) + if antwort.status_code >= 400: + raise RuntimeError("HTTP %d von %s" % (antwort.status_code, aktion["actor_url"])) + + +# --------------------------------------------------------------------------- +# WLED +# --------------------------------------------------------------------------- + +class WLEDTransport(Transport): + """ + WLED-Lampen sprechen ueber eine einzige JSON-Schnittstelle: + GET http://IP/json/state liefert den Zustand, POST dorthin setzt ihn. + + Das Geraetemodell nutzt das elegant aus - die command_url ist eine + JSON-Vorlage mit Platzhaltern: + + {"bri":%brightness%} + {"seg":[{"col":[[%red%,%green%,%blue%]]}]} + + Gesendet wird also nicht Argument fuer Argument, sondern die ausgefuellte + Vorlage am Stueck. Deshalb haben die Parameter hier auch keine eigene URL: + ihr Name ist der Platzhalter. + + Die state_url ist ein Pfad in die Antwort ("on", "bri", + "seg[0].col[0]") - dieselbe Schreibweise, die auch in current_value steht. + """ + + schema = "wled://" + + def __init__(self, requests_modul, timeout=5, dry_run=False): + self.requests = requests_modul + self.timeout = timeout + self.dry_run = dry_run + self.states = [] + + @staticmethod + def _adresse(actor_url): + return "http://" + actor_url[len("wled://"):].rstrip("/") + + def zustaende_anmelden(self, states): + self.states = [s for s in states if s["state_url"]] + logger.info("WLED: %d Messwerte an %d Lampen", + len(self.states), len({s["actor_url"] for s in self.states})) + + def zustaende_lesen(self): + werte = {} + nach_lampe = {} + for s in self.states: + nach_lampe.setdefault(s["actor_url"], []).append(s) + for actor_url, states in nach_lampe.items(): + try: + antwort = self.requests.get(self._adresse(actor_url) + "/json/state", + timeout=self.timeout) + daten = antwort.json() + except Exception as fehler: + logger.debug("WLED %s nicht erreichbar: %s", actor_url, fehler) + continue + for s in states: + # Bei WLED ist die state_url selbst schon der Pfad. Ein + # gesetzter value_path hat trotzdem Vorrang, falls das + # Geraetemodell spaeter darauf umgestellt wird. + pfad = s.get("value_path") or s["state_url"] + try: + werte[s["id"]] = str(wert_aus_pfad(daten, pfad)) + except (KeyError, IndexError, TypeError): + logger.debug("WLED %s: Pfad %s nicht gefunden", actor_url, pfad) + return werte + + def senden(self, aktion): + vorlage = aktion["command_url"] + if not vorlage: + raise RuntimeError("WLED-Kommando ohne Vorlage") + for p in aktion["params"]: + vorlage = vorlage.replace("%" + p["name"] + "%", str(p["wert"])) + try: + rumpf = json.loads(vorlage) + except ValueError: + # Ein nicht ersetzter Platzhalter oder ein Textwert an einer + # Stelle, wo eine Zahl stehen muss. Lieber hier abbrechen als der + # Lampe etwas Unverstaendliches schicken. + raise RuntimeError("WLED-Vorlage ergibt kein gueltiges JSON: " + vorlage[:120]) + if self.dry_run: + logger.info("[dry-run] WLED %s <- %s", aktion["actor_url"], + json.dumps(rumpf, ensure_ascii=False)) + return + antwort = self.requests.post(self._adresse(aktion["actor_url"]) + "/json/state", + json=rumpf, timeout=self.timeout) + if antwort.status_code >= 400: + raise RuntimeError("WLED antwortete mit %d" % antwort.status_code) + + +# --------------------------------------------------------------------------- +# Tahoma +# --------------------------------------------------------------------------- + +class TahomaTransport(Transport): + """ + Die actor_url ist die deviceURL, die state_url ein Statusname + ("core:ClosureState"), die command_url ein Kommandoname ("setClosure"). + Geschickt wird ueber exec/apply - genauso wie in ajax/tahoma.php, nur ohne + die dortige Sonderbehandlung fuer "faehrt gerade". + + Zustaendig ist dieser Transport fuer alles, was die Kennung der eigenen + Box in der URL traegt. Am Schema laesst sich das nicht festmachen: es + beschreibt die Funkart, und dieselbe Box liefert io:// fuer die + Jalousien, rts:// fuer die Dachfenster und internal:// fuer die Alarm- + anlage. Ohne PIN in der config.ini ist niemand zustaendig - dann meldet + der Runner beim Ausloesen "kein Transport", statt still nichts zu tun. + """ + + schema = "io://" + + # Bis zu dieser Neigung fahren die Aussenjalousien direkt. + # + # Sie haben eine Kugelschreiber-Mechanik: beim Herunterfahren stehen die + # Lamellen bei etwa 30 %. In Richtung 0 % laesst sich von jeder Stellung + # aus direkt neigen; darueber hinaus muss die Mechanik erst einmal auf + # 0 % zurueck, sonst rastet sie nicht um - ein "Neigung 80 %" ohne + # diesen Umweg bleibt wirkungslos. + # + # Das gilt fuer jedes Kommando, das die Neigung setzt - "Neigung" ebenso + # wie "Position+Neigung". Erkannt wird es deshalb am Parameter und nicht + # am Kommandonamen: beide heissen ihren Neigungsparameter "Neigung". + # + # Dasselbe steht in restricted/commands.php: beide Versender brauchen es. + NEIGUNG_DIREKT_MAX = 30 + + # So heissen die beiden Parameter einer Jalousie im Geraetemodell. + NEIGUNG_PARAMETER = "Neigung" + POSITION_PARAMETER = "Position" + + # So lange wird hoechstens auf das Ende einer Fahrt gewartet. Gemessen: + # eine Neigung von 100 % auf 0 % dauert gut fuenfzehn Sekunden, eine + # volle Fahrt von oben nach unten rund sechzig. Die Grenze ist die + # Notbremse, nicht die uebliche Dauer. + JALOUSIE_WARTE_SEKUNDEN = 120 + + # So lange gilt ein "faehrt nicht" direkt nach dem Absenden als noch + # nicht aussagekraeftig: die Box meldet core:MovingState traege, kurz + # nach einem Kommando steht dort noch der alte Wert. + JALOUSIE_VORLAUF_SEKUNDEN = 8 + + def __init__(self, requests_modul, pin, token, timeout=10, dry_run=False): + self.requests = requests_modul + self.pin = pin + self.token = token + self.timeout = timeout + self.dry_run = dry_run + self.states = [] + self.kombigeraete = set() + + def passt(self, actor_url): + return bool(self.pin) and ("://" + self.pin + "/") in actor_url + + @property + def basis(self): + return "https://gateway-%s:8443/enduser-mobile-web/1/enduserAPI" % self.pin + + def _kopf(self): + return {"Content-Type": "application/json", + "Authorization": "Bearer " + self.token} + + def zustaende_anmelden(self, states): + self.states = [s for s in states if s["state_url"]] + logger.info("Tahoma: %d Messwerte an %d Geraeten", + len(self.states), len({s["actor_url"] for s in self.states})) + + def kombigeraete_setzen(self, urls): + """ + Welche Geraete Position und Neigung zusammen koennen - vom Runner aus + dem Geraetemodell gesetzt, damit der Transport dafuer nicht selbst in + die Datenbank greifen muss. + """ + self.kombigeraete = set(urls) + + def zustaende_lesen(self): + if not self.token: + return {} + werte = {} + nach_geraet = {} + for s in self.states: + nach_geraet.setdefault(s["actor_url"], []).append(s) + for geraet, states in nach_geraet.items(): + try: + antwort = self.requests.get( + self.basis + "/setup/devices/" + quote(geraet, safe="") + "/states", + headers=self._kopf(), timeout=self.timeout, verify=False) + zustaende = {z["name"]: z.get("value") for z in antwort.json()} + except Exception as fehler: + logger.debug("Tahoma %s nicht erreichbar: %s", geraet, fehler) + continue + for s in states: + if s["state_url"] in zustaende: + werte[s["id"]] = str(zustaende[s["state_url"]]) + return werte + + def senden(self, aktion): + if not self.token: + raise RuntimeError("Kein Tahoma-Token in der config.ini") + # Die Reihenfolge der Parameter ist die aus command_parameters - bei + # setClosureAndOrientation also erst Position, dann Winkel. + befehl = aktion["command_url"] + parameter = [self._zahl(p["wert"]) for p in aktion["params"]] + neigung_index = None + position_index = None + for i, p in enumerate(aktion["params"]): + if p["name"] == self.NEIGUNG_PARAMETER: + neigung_index = i + elif p["name"] == self.POSITION_PARAMETER: + position_index = i + + # "Zu" allein macht diese Jalousien nicht dicht: sie faehrt herunter, + # die Lamellen bleiben durch die Mechanik aber bei etwa 30 % offen. + # Gemeint ist "ganz unten, Lamellen geschlossen" - also dasselbe wie + # Position 100 mit Neigung 100, und damit ein Fall fuer die Regel. + if befehl == "down" and self._kannKombi(aktion["actor_url"]): + befehl = "setClosureAndOrientation" + parameter = [100, 100] + position_index, neigung_index = 0, 1 + + # Kugelschreiber-Mechanik, siehe NEIGUNG_DIREKT_MAX. Geschickt wird + # derselbe Befehl zweimal - erst mit Neigung 0, dann mit dem + # gewuenschten Wert. Die Position bleibt dabei stehen, die Jalousie + # faehrt also nur einmal. + umweg = (neigung_index is not None + and isinstance(parameter[neigung_index], (int, float)) + and parameter[neigung_index] > self.NEIGUNG_DIREKT_MAX) + vorstufe = list(parameter) + if umweg: + vorstufe[neigung_index] = 0 + + if self.dry_run: + logger.info("[dry-run] Tahoma %s %s%s", befehl, parameter, + " (zuerst %s, dann warten)" % vorstufe if umweg else "") + return + + if umweg: + self._apply(aktion["actor_url"], befehl, vorstufe) + self._warteAufJalousie(aktion["actor_url"], 0, + None if position_index is None else int(parameter[position_index])) + self._apply(aktion["actor_url"], befehl, parameter) + + def _kannKombi(self, actor_url): + """ + Hat das Geraet ein Kommando fuer Position und Neigung zusammen? + Nur solche Geraete sind Jalousien mit der Kugelschreiber-Mechanik. + """ + return actor_url in self.kombigeraete + + def _apply(self, actor_url, name, parameter): + """Ein Kommando an die Box schicken.""" + rumpf = {"label": "AutoAction", + "actions": [{"deviceURL": actor_url, + "commands": [{"name": name, "parameters": parameter}]}]} + antwort = self.requests.post(self.basis + "/exec/apply", headers=self._kopf(), + data=json.dumps(rumpf), timeout=self.timeout, verify=False) + if antwort.status_code >= 400: + raise RuntimeError("Tahoma antwortete mit %d: %s" + % (antwort.status_code, antwort.text[:120])) + + def _warteAufJalousie(self, actor_url, neigung_ziel, schliessung_ziel=None): + """ + Wartet, bis die Jalousie ihre Fahrt beendet hat und die Ziele zeigt. + + Zwei Auskuenfte zusammen, weil einzeln keine traegt: + core:MovingState taugt fuer die lange Fahrt hoch und runter, wird bei + kurzen Neigungsfahrten aber nie gesetzt; die Zustandswerte sind die + eigentliche Wahrheit, zeigen kurz nach dem Kommando aber noch den + alten Stand. Fertig ist die Fahrt, wenn nichts mehr faehrt, die Ziele + erreicht sind und entweder ein "faehrt" gesehen wurde oder der + Vorlauf um ist. + """ + start = time.time() + gestartet = False + while time.time() - start < self.JALOUSIE_WARTE_SEKUNDEN: + time.sleep(2) + try: + antwort = self.requests.get( + self.basis + "/setup/devices/" + quote(actor_url, safe="") + "/states", + headers=self._kopf(), timeout=self.timeout, verify=False) + z = {x.get("name"): x.get("value") for x in antwort.json()} + except Exception as fehler: + logger.debug("Zustand nicht lesbar: %s", fehler) + continue + if z.get("core:MovingState") is True: + gestartet = True + continue + neigung = z.get("core:SlateOrientationState") + schliessung = z.get("core:ClosureState") + # Ein fehlendes Feld darf nicht als 0 durchgehen - das waere + # ausgerechnet beim Ziel 0 ein falsches Erfolgssignal. + neigung_ok = neigung is not None and int(neigung) == neigung_ziel + schliessung_ok = (schliessung_ziel is None + or (schliessung is not None and int(schliessung) == schliessung_ziel)) + if neigung_ok and schliessung_ok and ( + gestartet or time.time() - start >= self.JALOUSIE_VORLAUF_SEKUNDEN): + return True + logger.warning("%s hat Neigung %s%% nicht innerhalb von %d s erreicht", + actor_url, neigung_ziel, self.JALOUSIE_WARTE_SEKUNDEN) + return False + + @staticmethod + def _zahl(wert): + """Tahoma erwartet Zahlen als Zahlen, Text als Text.""" + try: + return int(wert) + except (TypeError, ValueError): + pass + try: + return float(wert) + except (TypeError, ValueError): + return wert + + +# --------------------------------------------------------------------------- +# Logic - das gerechnete Geraet +# --------------------------------------------------------------------------- + +class LogicTransport(Transport): + """ + Uhrzeit, Datum, Sonnenauf- und -untergang. Es gibt nichts zu abonnieren und + nichts zu schalten, die Werte entstehen im Takt. Sonnenzeiten kommen aus + solarLog.daylight, dieselbe Tabelle, aus der auch ajax/getSunrise.php liest. + """ + + schema = "Logic" + + def __init__(self, sonnenzeiten): + """sonnenzeiten: Funktion() -> (sonnenaufgang, sonnenuntergang) als "HH:MM".""" + self.sonnenzeiten = sonnenzeiten + self.states = [] + + def passt(self, actor_url): + return actor_url == "Logic" + + def zustaende_anmelden(self, states): + self.states = states + logger.info("Logic: %d Messwerte", len(states)) + + def zustaende_lesen(self): + jetzt = datetime.now() + auf, unter = self.sonnenzeiten() + tabelle = { + "time": jetzt.strftime("%H:%M"), + "date": jetzt.strftime("%d.%m.%Y"), + "sunrise": auf, + "sunset": unter, + } + return {s["id"]: tabelle[s["state_url"]] + for s in self.states if s["state_url"] in tabelle} + + def senden(self, aktion): + raise RuntimeError("Das Geraet \"Zeitpunkt\" kann nichts schalten") diff --git a/charger_goE.py b/charger_goE.py new file mode 100644 index 0000000..a1156f6 --- /dev/null +++ b/charger_goE.py @@ -0,0 +1,132 @@ +import json +import asyncio +import aiohttp +import sys +import logging +import math +from dataclasses import dataclass +_LOGGER = logging.getLogger(__name__) + +@dataclass +class ChargerData: + p_l1ev:float = 0 + p_l2ev:float = 0 + p_l3ev:float = 0 + Iset_ev:float = 0 + Phase_set_ev:int = 0 + AllowCharging_ev:bool = False + mode:str = "" + connected:bool = False + eto:int = 0 # Gesamtzaehlerstand der Wallbox in Wh + + +async def gatherNeededStatus() -> ChargerData: + ret = ChargerData + #print("get charger status..."); + timeout = aiohttp.ClientTimeout(total=3) + try: + async with aiohttp.ClientSession() as session: + async with session.get('http://192.168.179.122/api/status?filter=psm,fup,amp,frc,nrg,lmo,car,pgrid,pakku,ppv,eto',timeout=timeout) as response: #?filter=psm,fup,amp,frc,nrg,lmo,car + if response.status == 200: + html = await response.text() + #logging.warning(html) + state = json.loads(html) + # Gesamtzaehler der Wallbox in Wh. Genauer als die Summe + # ueber die Fuenf-Minuten-Leistungswerte: die Differenz + # zweier Staende trifft den Ladevorgang auf die + # Wattstunde, waehrend eine Summe aus Mittelwerten an den + # Raendern immer danebenliegt. + ret.eto = int(state.get("eto",0) or 0) + ret.p_l1ev = state["nrg"][7]/1000.0 + ret.p_l2ev = state["nrg"][8]/1000.0 + ret.p_l3ev = state["nrg"][9]/1000.0 + ret.Iset_ev = state["amp"] + ret.Phase_set_ev = state["psm"] + ret.AllowCharging_ev = state["frc"] + if state["car"] == 2 or state["car"] == 4: + ret.connected = True + else: + ret.connected = False + if state["fup"] == True and state["lmo"] == 4: + ret.mode = "Eco" + elif state["fup"] == True and state["lmo"] == 5: + ret.mode = "Next Trip" + else: + ret.mode = "Default" + + except: + return ret + #print("Body:", html) + return ret + +async def setPVparameters(pGrid:int,pPv:int,pAkku:int): + timeout = aiohttp.ClientTimeout(total=3) + #logging.warning('{"pGrid":'+str(round(pGrid))+',"pAkku":'+str(round(pAkku))+',"pPv":'+str(round(pPv))+'}') + #_LOGGER.error('{"pGrid":'+str(round(pGrid))+',"pAkku":'+str(round(pAkku))+',"pPv":'+str(round(pPv))+'}') + try: + async with aiohttp.ClientSession() as session: + async with session.get('http://192.168.179.122/api/set?ids={"pGrid":'+str(round(pGrid))+',"pAkku":'+str(round(pAkku))+',"pPv":'+str(round(pPv))+'}',timeout=timeout) as response: + if response.status != 200: + #_LOGGER.error('goE Return: %s' % await response.text()) + return False + #else: + # _LOGGER.error('goE Return: %s' % await response.text()) + except Exception as error: + _LOGGER.error(f"Error during sending GoE Parameters: %s",error) + return False + + +async def setChargePower(power:int, currPhases:int, currAmp:int, currCharging:bool): + timeout = aiohttp.ClientTimeout(total=3) + + if(power < 3700): + phases = 1 + amp = math.floor(power/240) + elif(power > 4500): + phases = 2 + amp = math.floor((power/3)/240) + else: + amp = 16 + + if(amp < 6): + amp = 0 + elif(amp > 16): + amp = 16 + if(phases != currPhases): + logging.warning("Change phases: "+str(phases)) + #async with aiohttp.ClientSession() as session: + # async with session.get('http://192.168.178.64/api/?psm='+phases,timeout=timeout) as response: + # if response.status != 200: + # return False + if(amp != currAmp and amp): + logging.warning("Change amp: "+str(amp)) + if(currCharging == False): + logging.warning("start charging") + #async with aiohttp.ClientSession() as session: + # async with session.get('http://192.168.178.64/api/set?amp='+amp,timeout=timeout) as response: + # if response.status != 200: + # return False + elif(currCharging): + logging.warning("stop charging") + #async with aiohttp.ClientSession() as session: + # async with session.get('http://192.168.178.64/api/set?frc=1',timeout=timeout) as response: + # if response.status != 200: + # return False + + #status = charger.get_status(status_type=goecharger_api_lite.GoeCharger.STATUS_FULL) + #print(json.dumps(status, indent=4)) + + #adjust amps +#curl "http://1.2.3.4/api/set?amp=16" +#current energy: nrg energy array, U (L1, L2, L3, N), I (L1, L2, L3), P (L1, L2, L3, N, Total), pf (L1, L2, L3, N) +#set 1-phase +#curl "http://1.2.3.4/api/set?psm=1" + +#set 3-phase +#curl "http://1.2.3.4/api/set?psm=2" + +#start charging +#curl "http://1.2.3.4/api/set?frc=0" + +#stop charging +#curl "http://1.2.3.4/api/set?frc=1" diff --git a/config.ini.example b/config.ini.example new file mode 100644 index 0000000..ce09193 --- /dev/null +++ b/config.ini.example @@ -0,0 +1,40 @@ +; Zugangsdaten des SolarManagers. +; +; Diese Datei ist die Vorlage und steht im Repository. Zum Aufsetzen einmal +; +; cp config.ini.example config.ini +; +; und ausfuellen - config.ini selbst wird nicht eingecheckt (.gitignore). +; Gelesen wird sie von konfig.py. + +; =========================================================================== +; Messwerte (solarLog) +; =========================================================================== +; Benutzt von solarManager.py, gatherWaterData.py, gatherSkodaData.py, +; skoda_testdaten.py und zeit.py. +[database] +host = localhost +port = 3310 +user = solarLog +password = +database = solarLog + +; =========================================================================== +; Wecker (wecker.py) +; =========================================================================== +[alarm] +host = localhost +port = 3310 +user = alarm +password = +database = alarm + +; =========================================================================== +; Wallbox +; =========================================================================== +; Gelesen von startWattpilotMQTT.sh, das daraus die Umgebungsvariablen +; WATTPILOT_HOST und WATTPILOT_PASSWORD setzt. +[wattpilot] +host = +password = +mqtt_host = nas.fritz.box diff --git a/crcmod/__init__.py b/crcmod/__init__.py new file mode 100644 index 0000000..80f2ac3 --- /dev/null +++ b/crcmod/__init__.py @@ -0,0 +1,8 @@ +try: + from crcmod.crcmod import * + import crcmod.predefined +except ImportError: + # Make this backward compatible + from crcmod import * + import predefined +__doc__ = crcmod.__doc__ diff --git a/crcmod/_crcfunpy.py b/crcmod/_crcfunpy.py new file mode 100644 index 0000000..01476ca --- /dev/null +++ b/crcmod/_crcfunpy.py @@ -0,0 +1,107 @@ +#----------------------------------------------------------------------------- +# Low level CRC functions for use by crcmod. This version is implemented in +# Python for a couple of reasons. 1) Provide a reference implememtation. +# 2) Provide a version that can be used on systems where a C compiler is not +# available for building extension modules. +# +# Copyright (c) 2009 Raymond L. Buvel +# Copyright (c) 2010 Craig McQueen +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +#----------------------------------------------------------------------------- + +def _get_buffer_view(in_obj): + if isinstance(in_obj, str): + raise TypeError('Unicode-objects must be encoded before calculating a CRC') + mv = memoryview(in_obj) + if mv.ndim > 1: + raise BufferError('Buffer must be single dimension') + return mv + + +def _crc8(data, crc, table): + mv = _get_buffer_view(data) + crc = crc & 0xFF + for x in mv.tobytes(): + crc = table[x ^ crc] + return crc + +def _crc8r(data, crc, table): + mv = _get_buffer_view(data) + crc = crc & 0xFF + for x in mv.tobytes(): + crc = table[x ^ crc] + return crc + +def _crc16(data, crc, table): + mv = _get_buffer_view(data) + crc = crc & 0xFFFF + for x in mv.tobytes(): + crc = table[x ^ ((crc>>8) & 0xFF)] ^ ((crc << 8) & 0xFF00) + return crc + +def _crc16r(data, crc, table): + mv = _get_buffer_view(data) + crc = crc & 0xFFFF + for x in mv.tobytes(): + crc = table[x ^ (crc & 0xFF)] ^ (crc >> 8) + return crc + +def _crc24(data, crc, table): + mv = _get_buffer_view(data) + crc = crc & 0xFFFFFF + for x in mv.tobytes(): + crc = table[x ^ (crc>>16 & 0xFF)] ^ ((crc << 8) & 0xFFFF00) + return crc + +def _crc24r(data, crc, table): + mv = _get_buffer_view(data) + crc = crc & 0xFFFFFF + for x in mv.tobytes(): + crc = table[x ^ (crc & 0xFF)] ^ (crc >> 8) + return crc + +def _crc32(data, crc, table): + mv = _get_buffer_view(data) + crc = crc & 0xFFFFFFFF + for x in mv.tobytes(): + crc = table[x ^ ((crc>>24) & 0xFF)] ^ ((crc << 8) & 0xFFFFFF00) + return crc + +def _crc32r(data, crc, table): + mv = _get_buffer_view(data) + crc = crc & 0xFFFFFFFF + for x in mv.tobytes(): + crc = table[x ^ (crc & 0xFF)] ^ (crc >> 8) + return crc + +def _crc64(data, crc, table): + mv = _get_buffer_view(data) + crc = crc & 0xFFFFFFFFFFFFFFFF + for x in mv.tobytes(): + crc = table[x ^ ((crc>>56) & 0xFF)] ^ ((crc << 8) & 0xFFFFFFFFFFFFFF00) + return crc + +def _crc64r(data, crc, table): + mv = _get_buffer_view(data) + crc = crc & 0xFFFFFFFFFFFFFFFF + for x in mv.tobytes(): + crc = table[x ^ (crc & 0xFF)] ^ (crc >> 8) + return crc + diff --git a/crcmod/crcmod.py b/crcmod/crcmod.py new file mode 100644 index 0000000..ec5adaf --- /dev/null +++ b/crcmod/crcmod.py @@ -0,0 +1,457 @@ +#----------------------------------------------------------------------------- +# Copyright (c) 2010 Raymond L. Buvel +# Copyright (c) 2010 Craig McQueen +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +#----------------------------------------------------------------------------- +'''crcmod is a Python module for gererating objects that compute the Cyclic +Redundancy Check. Any 8, 16, 24, 32, or 64 bit polynomial can be used. + +The following are the public components of this module. + +Crc -- a class that creates instances providing the same interface as the +algorithms in the hashlib module in the Python standard library. These +instances also provide a method for generating a C/C++ function to compute +the CRC. + +mkCrcFun -- create a Python function to compute the CRC using the specified +polynomial and initial value. This provides a much simpler interface if +all you need is a function for CRC calculation. +''' + +__all__ = '''mkCrcFun Crc +'''.split() + +# Select the appropriate set of low-level CRC functions for this installation. +# If the extension module was not built, drop back to the Python implementation +# even though it is significantly slower. +try: + import crcmod._crcfunext as _crcfun + _usingExtension = True +except ImportError: + import crcmod._crcfunpy as _crcfun + _usingExtension = False + +import sys, struct + +#----------------------------------------------------------------------------- +class Crc: + '''Compute a Cyclic Redundancy Check (CRC) using the specified polynomial. + + Instances of this class have the same interface as the algorithms in the + hashlib module in the Python standard library. See the documentation of + this module for examples of how to use a Crc instance. + + The string representation of a Crc instance identifies the polynomial, + initial value, XOR out value, and the current CRC value. The print + statement can be used to output this information. + + If you need to generate a C/C++ function for use in another application, + use the generateCode method. If you need to generate code for another + language, subclass Crc and override the generateCode method. + + The following are the parameters supplied to the constructor. + + poly -- The generator polynomial to use in calculating the CRC. The value + is specified as a Python integer. The bits in this integer are the + coefficients of the polynomial. The only polynomials allowed are those + that generate 8, 16, 24, 32, or 64 bit CRCs. + + initCrc -- Initial value used to start the CRC calculation. This initial + value should be the initial shift register value XORed with the final XOR + value. That is equivalent to the CRC result the algorithm should return for + a zero-length string. Defaults to all bits set because that starting value + will take leading zero bytes into account. Starting with zero will ignore + all leading zero bytes. + + rev -- A flag that selects a bit reversed algorithm when True. Defaults to + True because the bit reversed algorithms are more efficient. + + xorOut -- Final value to XOR with the calculated CRC value. Used by some + CRC algorithms. Defaults to zero. + ''' + def __init__(self, poly, initCrc=~0, rev=True, xorOut=0, initialize=True): + if not initialize: + # Don't want to perform the initialization when using new or copy + # to create a new instance. + return + + (sizeBits, initCrc, xorOut) = _verifyParams(poly, initCrc, xorOut) + self.digest_size = sizeBits//8 + self.initCrc = initCrc + self.xorOut = xorOut + + self.poly = poly + self.reverse = rev + + (crcfun, table) = _mkCrcFun(poly, sizeBits, initCrc, rev, xorOut) + self._crc = crcfun + self.table = table + + self.crcValue = self.initCrc + + def __str__(self): + lst = [] + lst.append('poly = 0x%X' % self.poly) + lst.append('reverse = %s' % self.reverse) + fmt = '0x%%0%dX' % (self.digest_size*2) + lst.append('initCrc = %s' % (fmt % self.initCrc)) + lst.append('xorOut = %s' % (fmt % self.xorOut)) + lst.append('crcValue = %s' % (fmt % self.crcValue)) + return '\n'.join(lst) + + def new(self, arg=None): + '''Create a new instance of the Crc class initialized to the same + values as the original instance. The current CRC is set to the initial + value. If a string is provided in the optional arg parameter, it is + passed to the update method. + ''' + n = Crc(poly=None, initialize=False) + n._crc = self._crc + n.digest_size = self.digest_size + n.initCrc = self.initCrc + n.xorOut = self.xorOut + n.table = self.table + n.crcValue = self.initCrc + n.reverse = self.reverse + n.poly = self.poly + if arg is not None: + n.update(arg) + return n + + def copy(self): + '''Create a new instance of the Crc class initialized to the same + values as the original instance. The current CRC is set to the current + value. This allows multiple CRC calculations using a common initial + string. + ''' + c = self.new() + c.crcValue = self.crcValue + return c + + def update(self, data): + '''Update the current CRC value using the string specified as the data + parameter. + ''' + self.crcValue = self._crc(data, self.crcValue) + + def digest(self): + '''Return the current CRC value as a string of bytes. The length of + this string is specified in the digest_size attribute. + ''' + n = self.digest_size + crc = self.crcValue + lst = [] + while n > 0: + lst.append(crc & 0xFF) + crc = crc >> 8 + n -= 1 + lst.reverse() + return bytes(lst) + + def hexdigest(self): + '''Return the current CRC value as a string of hex digits. The length + of this string is twice the digest_size attribute. + ''' + n = self.digest_size + crc = self.crcValue + lst = [] + while n > 0: + lst.append('%02X' % (crc & 0xFF)) + crc = crc >> 8 + n -= 1 + lst.reverse() + return ''.join(lst) + + def generateCode(self, functionName, out, dataType=None, crcType=None): + '''Generate a C/C++ function. + + functionName -- String specifying the name of the function. + + out -- An open file-like object with a write method. This specifies + where the generated code is written. + + dataType -- An optional parameter specifying the data type of the input + data to the function. Defaults to UINT8. + + crcType -- An optional parameter specifying the data type of the CRC + value. Defaults to one of UINT8, UINT16, UINT32, or UINT64 depending + on the size of the CRC value. + ''' + if dataType is None: + dataType = 'UINT8' + + if crcType is None: + size = 8*self.digest_size + if size == 24: + size = 32 + crcType = 'UINT%d' % size + + if self.digest_size == 1: + # Both 8-bit CRC algorithms are the same + crcAlgor = 'table[*data ^ (%s)crc]' + elif self.reverse: + # The bit reverse algorithms are all the same except for the data + # type of the crc variable which is specified elsewhere. + crcAlgor = 'table[*data ^ (%s)crc] ^ (crc >> 8)' + else: + # The forward CRC algorithms larger than 8 bits have an extra shift + # operation to get the high byte. + shift = 8*(self.digest_size - 1) + crcAlgor = 'table[*data ^ (%%s)(crc >> %d)] ^ (crc << 8)' % shift + + fmt = '0x%%0%dX' % (2*self.digest_size) + if self.digest_size <= 4: + fmt = fmt + 'U,' + else: + # Need the long long type identifier to keep gcc from complaining. + fmt = fmt + 'ULL,' + + # Select the number of entries per row in the output code. + n = {1:8, 2:8, 3:4, 4:4, 8:2}[self.digest_size] + + lst = [] + for i, val in enumerate(self.table): + if (i % n) == 0: + lst.append('\n ') + lst.append(fmt % val) + + poly = 'polynomial: 0x%X' % self.poly + if self.reverse: + poly = poly + ', bit reverse algorithm' + + if self.xorOut: + # Need to remove the comma from the format. + preCondition = '\n crc = crc ^ %s;' % (fmt[:-1] % self.xorOut) + postCondition = preCondition + else: + preCondition = '' + postCondition = '' + + if self.digest_size == 3: + # The 24-bit CRC needs to be conditioned so that only 24-bits are + # used from the 32-bit variable. + if self.reverse: + preCondition += '\n crc = crc & 0xFFFFFFU;' + else: + postCondition += '\n crc = crc & 0xFFFFFFU;' + + + parms = { + 'dataType' : dataType, + 'crcType' : crcType, + 'name' : functionName, + 'crcAlgor' : crcAlgor % dataType, + 'crcTable' : ''.join(lst), + 'poly' : poly, + 'preCondition' : preCondition, + 'postCondition' : postCondition, + } + out.write(_codeTemplate % parms) + +#----------------------------------------------------------------------------- +def mkCrcFun(poly, initCrc=~0, rev=True, xorOut=0): + '''Return a function that computes the CRC using the specified polynomial. + + poly -- integer representation of the generator polynomial + initCrc -- default initial CRC value + rev -- when true, indicates that the data is processed bit reversed. + xorOut -- the final XOR value + + The returned function has the following user interface + def crcfun(data, crc=initCrc): + ''' + + # First we must verify the params + (sizeBits, initCrc, xorOut) = _verifyParams(poly, initCrc, xorOut) + # Make the function (and table), return the function + return _mkCrcFun(poly, sizeBits, initCrc, rev, xorOut)[0] + +#----------------------------------------------------------------------------- +# Naming convention: +# All function names ending with r are bit reverse variants of the ones +# without the r. + +#----------------------------------------------------------------------------- +# Check the polynomial to make sure that it is acceptable and return the number +# of bits in the CRC. + +def _verifyPoly(poly): + msg = 'The degree of the polynomial must be 8, 16, 24, 32 or 64' + for n in (8,16,24,32,64): + low = 1<> 1 + return y + +#----------------------------------------------------------------------------- +# The following functions compute the CRC for a single byte. These are used +# to build up the tables needed in the CRC algorithm. Assumes the high order +# bit of the polynomial has been stripped off. + +def _bytecrc(crc, poly, n): + mask = 1<<(n-1) + for i in range(8): + if crc & mask: + crc = (crc << 1) ^ poly + else: + crc = crc << 1 + mask = (1<> 1) ^ poly + else: + crc = crc >> 1 + mask = (1< 0) + { + crc = %(crcAlgor)s; + data++; + len--; + }%(postCondition)s + return crc; +} +''' + diff --git a/crcmod/predefined.py b/crcmod/predefined.py new file mode 100644 index 0000000..21e2b32 --- /dev/null +++ b/crcmod/predefined.py @@ -0,0 +1,162 @@ +#----------------------------------------------------------------------------- +# Copyright (c) 2010 Craig McQueen +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +#----------------------------------------------------------------------------- +''' +crcmod.predefined defines some well-known CRC algorithms. + +To use it, e.g.: + import crcmod.predefined + + crc32func = crcmod.predefined.mkPredefinedCrcFun("crc-32") + crc32class = crcmod.predefined.PredefinedCrc("crc-32") + +crcmod.predefined.Crc is an alias for crcmod.predefined.PredefinedCrc +But if doing 'from crc.predefined import *', only PredefinedCrc is imported. +''' + +# local imports +import crcmod + +__all__ = [ + 'PredefinedCrc', + 'mkPredefinedCrcFun', +] + +REVERSE = True +NON_REVERSE = False + +# The following table defines the parameters of well-known CRC algorithms. +# The "Check" value is the CRC for the ASCII byte sequence b"123456789". It +# can be used for unit tests. +_crc_definitions_table = [ +# Name Identifier-name, Poly Reverse Init-value XOR-out Check + [ 'crc-8', 'Crc8', 0x107, NON_REVERSE, 0x00, 0x00, 0xF4, ], + [ 'crc-8-darc', 'Crc8Darc', 0x139, REVERSE, 0x00, 0x00, 0x15, ], + [ 'crc-8-i-code', 'Crc8ICode', 0x11D, NON_REVERSE, 0xFD, 0x00, 0x7E, ], + [ 'crc-8-itu', 'Crc8Itu', 0x107, NON_REVERSE, 0x55, 0x55, 0xA1, ], + [ 'crc-8-maxim', 'Crc8Maxim', 0x131, REVERSE, 0x00, 0x00, 0xA1, ], + [ 'crc-8-rohc', 'Crc8Rohc', 0x107, REVERSE, 0xFF, 0x00, 0xD0, ], + [ 'crc-8-wcdma', 'Crc8Wcdma', 0x19B, REVERSE, 0x00, 0x00, 0x25, ], + + [ 'crc-16', 'Crc16', 0x18005, REVERSE, 0x0000, 0x0000, 0xBB3D, ], + [ 'crc-16-buypass', 'Crc16Buypass', 0x18005, NON_REVERSE, 0x0000, 0x0000, 0xFEE8, ], + [ 'crc-16-dds-110', 'Crc16Dds110', 0x18005, NON_REVERSE, 0x800D, 0x0000, 0x9ECF, ], + [ 'crc-16-dect', 'Crc16Dect', 0x10589, NON_REVERSE, 0x0001, 0x0001, 0x007E, ], + [ 'crc-16-dnp', 'Crc16Dnp', 0x13D65, REVERSE, 0xFFFF, 0xFFFF, 0xEA82, ], + [ 'crc-16-en-13757', 'Crc16En13757', 0x13D65, NON_REVERSE, 0xFFFF, 0xFFFF, 0xC2B7, ], + [ 'crc-16-genibus', 'Crc16Genibus', 0x11021, NON_REVERSE, 0x0000, 0xFFFF, 0xD64E, ], + [ 'crc-16-maxim', 'Crc16Maxim', 0x18005, REVERSE, 0xFFFF, 0xFFFF, 0x44C2, ], + [ 'crc-16-mcrf4xx', 'Crc16Mcrf4xx', 0x11021, REVERSE, 0xFFFF, 0x0000, 0x6F91, ], + [ 'crc-16-riello', 'Crc16Riello', 0x11021, REVERSE, 0x554D, 0x0000, 0x63D0, ], + [ 'crc-16-t10-dif', 'Crc16T10Dif', 0x18BB7, NON_REVERSE, 0x0000, 0x0000, 0xD0DB, ], + [ 'crc-16-teledisk', 'Crc16Teledisk', 0x1A097, NON_REVERSE, 0x0000, 0x0000, 0x0FB3, ], + [ 'crc-16-usb', 'Crc16Usb', 0x18005, REVERSE, 0x0000, 0xFFFF, 0xB4C8, ], + [ 'x-25', 'CrcX25', 0x11021, REVERSE, 0x0000, 0xFFFF, 0x906E, ], + [ 'xmodem', 'CrcXmodem', 0x11021, NON_REVERSE, 0x0000, 0x0000, 0x31C3, ], + [ 'modbus', 'CrcModbus', 0x18005, REVERSE, 0xFFFF, 0x0000, 0x4B37, ], + + # Note definitions of CCITT are disputable. See: + # http://homepages.tesco.net/~rainstorm/crc-catalogue.htm + # http://web.archive.org/web/20071229021252/http://www.joegeluso.com/software/articles/ccitt.htm + [ 'kermit', 'CrcKermit', 0x11021, REVERSE, 0x0000, 0x0000, 0x2189, ], + [ 'crc-ccitt-false', 'CrcCcittFalse', 0x11021, NON_REVERSE, 0xFFFF, 0x0000, 0x29B1, ], + [ 'crc-aug-ccitt', 'CrcAugCcitt', 0x11021, NON_REVERSE, 0x1D0F, 0x0000, 0xE5CC, ], + + [ 'crc-24', 'Crc24', 0x1864CFB, NON_REVERSE, 0xB704CE, 0x000000, 0x21CF02, ], + [ 'crc-24-flexray-a', 'Crc24FlexrayA', 0x15D6DCB, NON_REVERSE, 0xFEDCBA, 0x000000, 0x7979BD, ], + [ 'crc-24-flexray-b', 'Crc24FlexrayB', 0x15D6DCB, NON_REVERSE, 0xABCDEF, 0x000000, 0x1F23B8, ], + + [ 'crc-32', 'Crc32', 0x104C11DB7, REVERSE, 0x00000000, 0xFFFFFFFF, 0xCBF43926, ], + [ 'crc-32-bzip2', 'Crc32Bzip2', 0x104C11DB7, NON_REVERSE, 0x00000000, 0xFFFFFFFF, 0xFC891918, ], + [ 'crc-32c', 'Crc32C', 0x11EDC6F41, REVERSE, 0x00000000, 0xFFFFFFFF, 0xE3069283, ], + [ 'crc-32d', 'Crc32D', 0x1A833982B, REVERSE, 0x00000000, 0xFFFFFFFF, 0x87315576, ], + [ 'crc-32-mpeg', 'Crc32Mpeg', 0x104C11DB7, NON_REVERSE, 0xFFFFFFFF, 0x00000000, 0x0376E6E7, ], + [ 'posix', 'CrcPosix', 0x104C11DB7, NON_REVERSE, 0xFFFFFFFF, 0xFFFFFFFF, 0x765E7680, ], + [ 'crc-32q', 'Crc32Q', 0x1814141AB, NON_REVERSE, 0x00000000, 0x00000000, 0x3010BF7F, ], + [ 'jamcrc', 'CrcJamCrc', 0x104C11DB7, REVERSE, 0xFFFFFFFF, 0x00000000, 0x340BC6D9, ], + [ 'xfer', 'CrcXfer', 0x1000000AF, NON_REVERSE, 0x00000000, 0x00000000, 0xBD0BE338, ], + +# 64-bit +# Name Identifier-name, Poly Reverse Init-value XOR-out Check + [ 'crc-64', 'Crc64', 0x1000000000000001B, REVERSE, 0x0000000000000000, 0x0000000000000000, 0x46A5A9388A5BEFFE, ], + [ 'crc-64-we', 'Crc64We', 0x142F0E1EBA9EA3693, NON_REVERSE, 0x0000000000000000, 0xFFFFFFFFFFFFFFFF, 0x62EC59E3F1A4F00A, ], + [ 'crc-64-jones', 'Crc64Jones', 0x1AD93D23594C935A9, REVERSE, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000, 0xCAA717168609F281, ], +] + + +def _simplify_name(name): + """ + Reduce CRC definition name to a simplified form: + * lowercase + * dashes removed + * spaces removed + * any initial "CRC" string removed + """ + name = name.lower() + name = name.replace('-', '') + name = name.replace(' ', '') + if name.startswith('crc'): + name = name[len('crc'):] + return name + + +_crc_definitions_by_name = {} +_crc_definitions_by_identifier = {} +_crc_definitions = [] + +_crc_table_headings = [ 'name', 'identifier', 'poly', 'reverse', 'init', 'xor_out', 'check' ] + +for table_entry in _crc_definitions_table: + crc_definition = dict(zip(_crc_table_headings, table_entry)) + _crc_definitions.append(crc_definition) + name = _simplify_name(table_entry[0]) + if name in _crc_definitions_by_name: + raise Exception("Duplicate entry for '{0}' in CRC table".format(name)) + _crc_definitions_by_name[name] = crc_definition + _crc_definitions_by_identifier[table_entry[1]] = crc_definition + + +def _get_definition_by_name(crc_name): + definition = _crc_definitions_by_name.get(_simplify_name(crc_name), None) + if not definition: + definition = _crc_definitions_by_identifier.get(crc_name, None) + if not definition: + raise KeyError("Unkown CRC name '{0}'".format(crc_name)) + return definition + + +class PredefinedCrc(crcmod.Crc): + def __init__(self, crc_name): + definition = _get_definition_by_name(crc_name) + super().__init__(poly=definition['poly'], initCrc=definition['init'], rev=definition['reverse'], xorOut=definition['xor_out']) + + +# crcmod.predefined.Crc is an alias for crcmod.predefined.PredefinedCrc +Crc = PredefinedCrc + + +def mkPredefinedCrcFun(crc_name): + definition = _get_definition_by_name(crc_name) + return crcmod.mkCrcFun(poly=definition['poly'], initCrc=definition['init'], rev=definition['reverse'], xorOut=definition['xor_out']) + + +# crcmod.predefined.mkCrcFun is an alias for crcmod.predefined.mkPredefinedCrcFun +mkCrcFun = mkPredefinedCrcFun diff --git a/crcmod/test.py b/crcmod/test.py new file mode 100644 index 0000000..0190bb7 --- /dev/null +++ b/crcmod/test.py @@ -0,0 +1,540 @@ +#----------------------------------------------------------------------------- +# Copyright (c) 2010 Raymond L. Buvel +# Copyright (c) 2010 Craig McQueen +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +#----------------------------------------------------------------------------- +'''Unit tests for crcmod functionality''' + + +import unittest + +from array import array +import binascii + +from .crcmod import mkCrcFun, Crc +from .crcmod import _usingExtension +from .predefined import PredefinedCrc +from .predefined import mkPredefinedCrcFun +from .predefined import _crc_definitions as _predefined_crc_definitions + + +#----------------------------------------------------------------------------- +# This polynomial was chosen because it is the product of two irreducible +# polynomials. +# g8 = (x^7+x+1)*(x+1) +g8 = 0x185 + +#----------------------------------------------------------------------------- +# The following reproduces all of the entries in the Numerical Recipes table. +# This is the standard CCITT polynomial. +g16 = 0x11021 + +#----------------------------------------------------------------------------- +g24 = 0x15D6DCB + +#----------------------------------------------------------------------------- +# This is the standard AUTODIN-II polynomial which appears to be used in a +# wide variety of standards and applications. +g32 = 0x104C11DB7 + + +#----------------------------------------------------------------------------- +# I was able to locate a couple of 64-bit polynomials on the web. To make it +# easier to input the representation, define a function that builds a +# polynomial from a list of the bits that need to be turned on. + +def polyFromBits(bits): + p = 0 + for n in bits: + p = p | (1 << n) + return p + +# The following is from the paper "An Improved 64-bit Cyclic Redundancy Check +# for Protein Sequences" by David T. Jones + +g64a = polyFromBits([64, 63, 61, 59, 58, 56, 55, 52, 49, 48, 47, 46, 44, 41, + 37, 36, 34, 32, 31, 28, 26, 23, 22, 19, 16, 13, 12, 10, 9, 6, 4, + 3, 0]) + +# The following is from Standard ECMA-182 "Data Interchange on 12,7 mm 48-Track +# Magnetic Tape Cartridges -DLT1 Format-", December 1992. + +g64b = polyFromBits([64, 62, 57, 55, 54, 53, 52, 47, 46, 45, 40, 39, 38, 37, + 35, 33, 32, 31, 29, 27, 24, 23, 22, 21, 19, 17, 13, 12, 10, 9, 7, + 4, 1, 0]) + +#----------------------------------------------------------------------------- +# This class is used to check the CRC calculations against a direct +# implementation using polynomial division. + +class poly: + '''Class implementing polynomials over the field of integers mod 2''' + def __init__(self,p): + p = int(p) + if p < 0: raise ValueError('invalid polynomial') + self.p = p + + def __int__(self): + return self.p + + def __eq__(self,other): + return self.p == other.p + + def __ne__(self,other): + return self.p != other.p + + # To allow sorting of polynomials, use their long integer form for + # comparison + def __cmp__(self,other): + return cmp(self.p, other.p) + + def __bool__(self): + return self.p != 0 + + def __neg__(self): + return self # These polynomials are their own inverse under addition + + def __invert__(self): + n = max(self.deg() + 1, 1) + x = (1 << n) - 1 + return poly(self.p ^ x) + + def __add__(self,other): + return poly(self.p ^ other.p) + + def __sub__(self,other): + return poly(self.p ^ other.p) + + def __mul__(self,other): + a = self.p + b = other.p + if a == 0 or b == 0: return poly(0) + x = 0 + while b: + if b&1: + x = x ^ a + a = a<<1 + b = b>>1 + return poly(x) + + def __divmod__(self,other): + u = self.p + m = self.deg() + v = other.p + n = other.deg() + if v == 0: raise ZeroDivisionError('polynomial division by zero') + if n == 0: return (self,poly(0)) + if m < n: return (poly(0),self) + k = m-n + a = 1 << m + v = v << k + q = 0 + while k > 0: + if a & u: + u = u ^ v + q = q | 1 + q = q << 1 + a = a >> 1 + v = v >> 1 + k -= 1 + if a & u: + u = u ^ v + q = q | 1 + return (poly(q),poly(u)) + + def __div__(self,other): + return self.__divmod__(other)[0] + + def __mod__(self,other): + return self.__divmod__(other)[1] + + def __repr__(self): + return 'poly(0x%XL)' % self.p + + def __str__(self): + p = self.p + if p == 0: return '0' + lst = { 0:[], 1:['1'], 2:['x'], 3:['1','x'] }[p&3] + p = p>>2 + n = 2 + while p: + if p&1: lst.append('x^%d' % n) + p = p>>1 + n += 1 + lst.reverse() + return '+'.join(lst) + + def deg(self): + '''return the degree of the polynomial''' + a = self.p + if a == 0: return -1 + n = 0 + while a >= 0x10000: + n += 16 + a = a >> 16 + a = int(a) + while a > 1: + n += 1 + a = a >> 1 + return n + +#----------------------------------------------------------------------------- +# The following functions compute the CRC using direct polynomial division. +# These functions are checked against the result of the table driven +# algorithms. + +g8p = poly(g8) +x8p = poly(1<<8) +def crc8p(d): + p = 0 + for i in d: + p = p*256 + i + p = poly(p) + return int(p*x8p%g8p) + +g16p = poly(g16) +x16p = poly(1<<16) +def crc16p(d): + p = 0 + for i in d: + p = p*256 + i + p = poly(p) + return int(p*x16p%g16p) + +g24p = poly(g24) +x24p = poly(1<<24) +def crc24p(d): + p = 0 + for i in d: + p = p*256 + i + p = poly(p) + return int(p*x24p%g24p) + +g32p = poly(g32) +x32p = poly(1<<32) +def crc32p(d): + p = 0 + for i in d: + p = p*256 + i + p = poly(p) + return int(p*x32p%g32p) + +g64ap = poly(g64a) +x64p = poly(1<<64) +def crc64ap(d): + p = 0 + for i in d: + p = p*256 + i + p = poly(p) + return int(p*x64p%g64ap) + +g64bp = poly(g64b) +def crc64bp(d): + p = 0 + for i in d: + p = p*256 + i + p = poly(p) + return int(p*x64p%g64bp) + + +class KnownAnswerTests(unittest.TestCase): + test_messages = [ + b'T', + b'CatMouse987654321', + ] + + known_answers = [ + [ (g8,0,0), (0xFE, 0x9D) ], + [ (g8,-1,1), (0x4F, 0x9B) ], + [ (g8,0,1), (0xFE, 0x62) ], + [ (g16,0,0), (0x1A71, 0xE556) ], + [ (g16,-1,1), (0x1B26, 0xF56E) ], + [ (g16,0,1), (0x14A1, 0xC28D) ], + [ (g24,0,0), (0xBCC49D, 0xC4B507) ], + [ (g24,-1,1), (0x59BD0E, 0x0AAA37) ], + [ (g24,0,1), (0xD52B0F, 0x1523AB) ], + [ (g32,0,0), (0x6B93DDDB, 0x12DCA0F4) ], + [ (g32,0xFFFFFFFF,1), (0x41FB859F, 0xF7B400A7) ], + [ (g32,0,1), (0x6C0695ED, 0xC1A40EE5) ], + [ (g32,0,1,0xFFFFFFFF), (0xBE047A60, 0x084BFF58) ], + ] + + def test_known_answers(self): + for crcfun_params, v in self.known_answers: + crcfun = mkCrcFun(*crcfun_params) + self.assertEqual(crcfun(b'',0), 0, "Wrong answer for CRC parameters %s, input ''" % (crcfun_params,)) + for i, msg in enumerate(self.test_messages): + self.assertEqual(crcfun(msg), v[i], "Wrong answer for CRC parameters %s, input '%s'" % (crcfun_params,msg)) + self.assertEqual(crcfun(msg[4:], crcfun(msg[:4])), v[i], "Wrong answer for CRC parameters %s, input '%s'" % (crcfun_params,msg)) + self.assertEqual(crcfun(msg[-1:], crcfun(msg[:-1])), v[i], "Wrong answer for CRC parameters %s, input '%s'" % (crcfun_params,msg)) + + +class CompareReferenceCrcTest(unittest.TestCase): + test_messages = [ + b'', + b'T', + b'123456789', + b'CatMouse987654321', + ] + + test_poly_crcs = [ + [ (g8,0,0), crc8p ], + [ (g16,0,0), crc16p ], + [ (g24,0,0), crc24p ], + [ (g32,0,0), crc32p ], + [ (g64a,0,0), crc64ap ], + [ (g64b,0,0), crc64bp ], + ] + + @staticmethod + def reference_crc32(d, crc=0): + """This function modifies the return value of binascii.crc32 + to be an unsigned 32-bit value. I.e. in the range 0 to 2**32-1.""" + # Work around the future warning on constants. + if crc > 0x7FFFFFFF: + x = int(crc & 0x7FFFFFFF) + crc = x | -2147483648 + x = binascii.crc32(d,crc) + return int(x) & 0xFFFFFFFF + + def test_compare_crc32(self): + """The binascii module has a 32-bit CRC function that is used in a wide range + of applications including the checksum used in the ZIP file format. + This test compares the CRC-32 implementation of this crcmod module to + that of binascii.crc32.""" + # The following function should produce the same result as + # self.reference_crc32 which is derived from binascii.crc32. + crc32 = mkCrcFun(g32,0,1,0xFFFFFFFF) + + for msg in self.test_messages: + self.assertEqual(crc32(msg), self.reference_crc32(msg)) + + def test_compare_poly(self): + """Compare various CRCs of this crcmod module to a pure + polynomial-based implementation.""" + for crcfun_params, crc_poly_fun in self.test_poly_crcs: + # The following function should produce the same result as + # the associated polynomial CRC function. + crcfun = mkCrcFun(*crcfun_params) + + for msg in self.test_messages: + self.assertEqual(crcfun(msg), crc_poly_fun(msg)) + + +class CrcClassTest(unittest.TestCase): + """Verify the Crc class""" + + msg = b'CatMouse987654321' + + def test_simple_crc32_class(self): + """Verify the CRC class when not using xorOut""" + crc = Crc(g32) + + str_rep = \ +'''poly = 0x104C11DB7 +reverse = True +initCrc = 0xFFFFFFFF +xorOut = 0x00000000 +crcValue = 0xFFFFFFFF''' + self.assertEqual(str(crc), str_rep) + self.assertEqual(crc.digest(), b'\xff\xff\xff\xff') + self.assertEqual(crc.hexdigest(), 'FFFFFFFF') + + crc.update(self.msg) + self.assertEqual(crc.crcValue, 0xF7B400A7) + self.assertEqual(crc.digest(), b'\xf7\xb4\x00\xa7') + self.assertEqual(crc.hexdigest(), 'F7B400A7') + + # Verify the .copy() method + x = crc.copy() + self.assertTrue(x is not crc) + str_rep = \ +'''poly = 0x104C11DB7 +reverse = True +initCrc = 0xFFFFFFFF +xorOut = 0x00000000 +crcValue = 0xF7B400A7''' + self.assertEqual(str(crc), str_rep) + self.assertEqual(str(x), str_rep) + + def test_full_crc32_class(self): + """Verify the CRC class when using xorOut""" + + crc = Crc(g32, initCrc=0, xorOut= ~0) + + str_rep = \ +'''poly = 0x104C11DB7 +reverse = True +initCrc = 0x00000000 +xorOut = 0xFFFFFFFF +crcValue = 0x00000000''' + self.assertEqual(str(crc), str_rep) + self.assertEqual(crc.digest(), b'\x00\x00\x00\x00') + self.assertEqual(crc.hexdigest(), '00000000') + + crc.update(self.msg) + self.assertEqual(crc.crcValue, 0x84BFF58) + self.assertEqual(crc.digest(), b'\x08\x4b\xff\x58') + self.assertEqual(crc.hexdigest(), '084BFF58') + + # Verify the .copy() method + x = crc.copy() + self.assertTrue(x is not crc) + str_rep = \ +'''poly = 0x104C11DB7 +reverse = True +initCrc = 0x00000000 +xorOut = 0xFFFFFFFF +crcValue = 0x084BFF58''' + self.assertEqual(str(crc), str_rep) + self.assertEqual(str(x), str_rep) + + # Verify the .new() method + y = crc.new() + self.assertTrue(y is not crc) + self.assertTrue(y is not x) + str_rep = \ +'''poly = 0x104C11DB7 +reverse = True +initCrc = 0x00000000 +xorOut = 0xFFFFFFFF +crcValue = 0x00000000''' + self.assertEqual(str(y), str_rep) + + +class PredefinedCrcTest(unittest.TestCase): + """Verify the predefined CRCs""" + + test_messages_for_known_answers = [ + b'', # Test cases below depend on this first entry being the empty string. + b'T', + b'CatMouse987654321', + ] + + known_answers = [ + [ 'crc-aug-ccitt', (0x1D0F, 0xD6ED, 0x5637) ], + [ 'x-25', (0x0000, 0xE4D9, 0x0A91) ], + [ 'crc-32', (0x00000000, 0xBE047A60, 0x084BFF58) ], + ] + + def test_known_answers(self): + for crcfun_name, v in self.known_answers: + crcfun = mkPredefinedCrcFun(crcfun_name) + self.assertEqual(crcfun(b'',0), 0, "Wrong answer for CRC '%s', input ''" % crcfun_name) + for i, msg in enumerate(self.test_messages_for_known_answers): + self.assertEqual(crcfun(msg), v[i], "Wrong answer for CRC %s, input '%s'" % (crcfun_name,msg)) + self.assertEqual(crcfun(msg[4:], crcfun(msg[:4])), v[i], "Wrong answer for CRC %s, input '%s'" % (crcfun_name,msg)) + self.assertEqual(crcfun(msg[-1:], crcfun(msg[:-1])), v[i], "Wrong answer for CRC %s, input '%s'" % (crcfun_name,msg)) + + def test_class_with_known_answers(self): + for crcfun_name, v in self.known_answers: + for i, msg in enumerate(self.test_messages_for_known_answers): + crc1 = PredefinedCrc(crcfun_name) + crc1.update(msg) + self.assertEqual(crc1.crcValue, v[i], "Wrong answer for crc1 %s, input '%s'" % (crcfun_name,msg)) + + crc2 = crc1.new() + # Check that crc1 maintains its same value, after .new() call. + self.assertEqual(crc1.crcValue, v[i], "Wrong state for crc1 %s, input '%s'" % (crcfun_name,msg)) + # Check that the new class instance created by .new() contains the initialisation value. + # This depends on the first string in self.test_messages_for_known_answers being + # the empty string. + self.assertEqual(crc2.crcValue, v[0], "Wrong state for crc2 %s, input '%s'" % (crcfun_name,msg)) + + crc2.update(msg) + # Check that crc1 maintains its same value, after crc2 has called .update() + self.assertEqual(crc1.crcValue, v[i], "Wrong state for crc1 %s, input '%s'" % (crcfun_name,msg)) + # Check that crc2 contains the right value after calling .update() + self.assertEqual(crc2.crcValue, v[i], "Wrong state for crc2 %s, input '%s'" % (crcfun_name,msg)) + + def test_function_predefined_table(self): + for table_entry in _predefined_crc_definitions: + # Check predefined function + crc_func = mkPredefinedCrcFun(table_entry['name']) + calc_value = crc_func(b"123456789") + self.assertEqual(calc_value, table_entry['check'], "Wrong answer for CRC '%s'" % table_entry['name']) + + def test_class_predefined_table(self): + for table_entry in _predefined_crc_definitions: + # Check predefined class + crc1 = PredefinedCrc(table_entry['name']) + crc1.update(b"123456789") + self.assertEqual(crc1.crcValue, table_entry['check'], "Wrong answer for CRC '%s'" % table_entry['name']) + + +class InputTypesTest(unittest.TestCase): + """Check the various input types that CRC functions can accept.""" + + msg = b'CatMouse987654321' + + check_crc_names = [ + 'crc-aug-ccitt', + 'x-25', + 'crc-32', + ] + + array_check_types = [ + 'B', + 'H', + 'I', + 'L', + ] + + def test_bytearray_input(self): + """Test that bytearray inputs are accepted, as an example + of a type that implements the buffer protocol.""" + for crc_name in self.check_crc_names: + crcfun = mkPredefinedCrcFun(crc_name) + for i in range(len(self.msg) + 1): + test_msg = self.msg[:i] + bytes_answer = crcfun(test_msg) + bytearray_answer = crcfun(bytearray(test_msg)) + self.assertEqual(bytes_answer, bytearray_answer) + + def test_array_input(self): + """Test that array inputs are accepted, as an example + of a type that implements the buffer protocol.""" + for crc_name in self.check_crc_names: + crcfun = mkPredefinedCrcFun(crc_name) + for i in range(len(self.msg) + 1): + test_msg = self.msg[:i] + bytes_answer = crcfun(test_msg) + for array_type in self.array_check_types: + if i % array(array_type).itemsize == 0: + test_array = array(array_type, test_msg) + array_answer = crcfun(test_array) + self.assertEqual(bytes_answer, array_answer) + + def test_unicode_input(self): + """Test that Unicode input raises TypeError""" + for crc_name in self.check_crc_names: + crcfun = mkPredefinedCrcFun(crc_name) + with self.assertRaises(TypeError): + crcfun("123456789") + + +def runtests(): + print("Using extension:", _usingExtension) + print() + unittest.main() + + +if __name__ == '__main__': + runtests() diff --git a/dateutil/__init__.py b/dateutil/__init__.py new file mode 100644 index 0000000..a2c19c0 --- /dev/null +++ b/dateutil/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +import sys + +try: + from ._version import version as __version__ +except ImportError: + __version__ = 'unknown' + +__all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz', + 'utils', 'zoneinfo'] + +def __getattr__(name): + import importlib + + if name in __all__: + return importlib.import_module("." + name, __name__) + raise AttributeError( + "module {!r} has not attribute {!r}".format(__name__, name) + ) + + +def __dir__(): + # __dir__ should include all the lazy-importable modules as well. + return [x for x in globals() if x not in sys.modules] + __all__ diff --git a/dateutil/_common.py b/dateutil/_common.py new file mode 100644 index 0000000..4eb2659 --- /dev/null +++ b/dateutil/_common.py @@ -0,0 +1,43 @@ +""" +Common code used in multiple modules. +""" + + +class weekday(object): + __slots__ = ["weekday", "n"] + + def __init__(self, weekday, n=None): + self.weekday = weekday + self.n = n + + def __call__(self, n): + if n == self.n: + return self + else: + return self.__class__(self.weekday, n) + + def __eq__(self, other): + try: + if self.weekday != other.weekday or self.n != other.n: + return False + except AttributeError: + return False + return True + + def __hash__(self): + return hash(( + self.weekday, + self.n, + )) + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] + if not self.n: + return s + else: + return "%s(%+d)" % (s, self.n) + +# vim:ts=4:sw=4:et diff --git a/dateutil/_version.py b/dateutil/_version.py new file mode 100644 index 0000000..ddda980 --- /dev/null +++ b/dateutil/_version.py @@ -0,0 +1,4 @@ +# file generated by setuptools_scm +# don't change, don't track in version control +__version__ = version = '2.9.0.post0' +__version_tuple__ = version_tuple = (2, 9, 0) diff --git a/dateutil/easter.py b/dateutil/easter.py new file mode 100644 index 0000000..f74d1f7 --- /dev/null +++ b/dateutil/easter.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +""" +This module offers a generic Easter computing method for any given year, using +Western, Orthodox or Julian algorithms. +""" + +import datetime + +__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"] + +EASTER_JULIAN = 1 +EASTER_ORTHODOX = 2 +EASTER_WESTERN = 3 + + +def easter(year, method=EASTER_WESTERN): + """ + This method was ported from the work done by GM Arts, + on top of the algorithm by Claus Tondering, which was + based in part on the algorithm of Ouding (1940), as + quoted in "Explanatory Supplement to the Astronomical + Almanac", P. Kenneth Seidelmann, editor. + + This algorithm implements three different Easter + calculation methods: + + 1. Original calculation in Julian calendar, valid in + dates after 326 AD + 2. Original method, with date converted to Gregorian + calendar, valid in years 1583 to 4099 + 3. Revised method, in Gregorian calendar, valid in + years 1583 to 4099 as well + + These methods are represented by the constants: + + * ``EASTER_JULIAN = 1`` + * ``EASTER_ORTHODOX = 2`` + * ``EASTER_WESTERN = 3`` + + The default method is method 3. + + More about the algorithm may be found at: + + `GM Arts: Easter Algorithms `_ + + and + + `The Calendar FAQ: Easter `_ + + """ + + if not (1 <= method <= 3): + raise ValueError("invalid method") + + # g - Golden year - 1 + # c - Century + # h - (23 - Epact) mod 30 + # i - Number of days from March 21 to Paschal Full Moon + # j - Weekday for PFM (0=Sunday, etc) + # p - Number of days from March 21 to Sunday on or before PFM + # (-6 to 28 methods 1 & 3, to 56 for method 2) + # e - Extra days to add for method 2 (converting Julian + # date to Gregorian date) + + y = year + g = y % 19 + e = 0 + if method < 3: + # Old method + i = (19*g + 15) % 30 + j = (y + y//4 + i) % 7 + if method == 2: + # Extra dates to convert Julian to Gregorian date + e = 10 + if y > 1600: + e = e + y//100 - 16 - (y//100 - 16)//4 + else: + # New method + c = y//100 + h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30 + i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11)) + j = (y + y//4 + i + 2 - c + c//4) % 7 + + # p can be from -6 to 56 corresponding to dates 22 March to 23 May + # (later dates apply to method 2, although 23 May never actually occurs) + p = i - j + e + d = 1 + (p + 27 + (p + 6)//40) % 31 + m = 3 + (p + 26)//30 + return datetime.date(int(y), int(m), int(d)) diff --git a/dateutil/parser/__init__.py b/dateutil/parser/__init__.py new file mode 100644 index 0000000..d174b0e --- /dev/null +++ b/dateutil/parser/__init__.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +from ._parser import parse, parser, parserinfo, ParserError +from ._parser import DEFAULTPARSER, DEFAULTTZPARSER +from ._parser import UnknownTimezoneWarning + +from ._parser import __doc__ + +from .isoparser import isoparser, isoparse + +__all__ = ['parse', 'parser', 'parserinfo', + 'isoparse', 'isoparser', + 'ParserError', + 'UnknownTimezoneWarning'] + + +### +# Deprecate portions of the private interface so that downstream code that +# is improperly relying on it is given *some* notice. + + +def __deprecated_private_func(f): + from functools import wraps + import warnings + + msg = ('{name} is a private function and may break without warning, ' + 'it will be moved and or renamed in future versions.') + msg = msg.format(name=f.__name__) + + @wraps(f) + def deprecated_func(*args, **kwargs): + warnings.warn(msg, DeprecationWarning) + return f(*args, **kwargs) + + return deprecated_func + +def __deprecate_private_class(c): + import warnings + + msg = ('{name} is a private class and may break without warning, ' + 'it will be moved and or renamed in future versions.') + msg = msg.format(name=c.__name__) + + class private_class(c): + __doc__ = c.__doc__ + + def __init__(self, *args, **kwargs): + warnings.warn(msg, DeprecationWarning) + super(private_class, self).__init__(*args, **kwargs) + + private_class.__name__ = c.__name__ + + return private_class + + +from ._parser import _timelex, _resultbase +from ._parser import _tzparser, _parsetz + +_timelex = __deprecate_private_class(_timelex) +_tzparser = __deprecate_private_class(_tzparser) +_resultbase = __deprecate_private_class(_resultbase) +_parsetz = __deprecated_private_func(_parsetz) diff --git a/dateutil/parser/_parser.py b/dateutil/parser/_parser.py new file mode 100644 index 0000000..37d1663 --- /dev/null +++ b/dateutil/parser/_parser.py @@ -0,0 +1,1613 @@ +# -*- coding: utf-8 -*- +""" +This module offers a generic date/time string parser which is able to parse +most known formats to represent a date and/or time. + +This module attempts to be forgiving with regards to unlikely input formats, +returning a datetime object even for dates which are ambiguous. If an element +of a date/time stamp is omitted, the following rules are applied: + +- If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour + on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is + specified. +- If a time zone is omitted, a timezone-naive datetime is returned. + +If any other elements are missing, they are taken from the +:class:`datetime.datetime` object passed to the parameter ``default``. If this +results in a day number exceeding the valid number of days per month, the +value falls back to the end of the month. + +Additional resources about date/time string formats can be found below: + +- `A summary of the international standard date and time notation + `_ +- `W3C Date and Time Formats `_ +- `Time Formats (Planetary Rings Node) `_ +- `CPAN ParseDate module + `_ +- `Java SimpleDateFormat Class + `_ +""" +from __future__ import unicode_literals + +import datetime +import re +import string +import time +import warnings + +from calendar import monthrange +from io import StringIO + +import six +from six import integer_types, text_type + +from decimal import Decimal + +from warnings import warn + +from .. import relativedelta +from .. import tz + +__all__ = ["parse", "parserinfo", "ParserError"] + + +# TODO: pandas.core.tools.datetimes imports this explicitly. Might be worth +# making public and/or figuring out if there is something we can +# take off their plate. +class _timelex(object): + # Fractional seconds are sometimes split by a comma + _split_decimal = re.compile("([.,])") + + def __init__(self, instream): + if isinstance(instream, (bytes, bytearray)): + instream = instream.decode() + + if isinstance(instream, text_type): + instream = StringIO(instream) + elif getattr(instream, 'read', None) is None: + raise TypeError('Parser must be a string or character stream, not ' + '{itype}'.format(itype=instream.__class__.__name__)) + + self.instream = instream + self.charstack = [] + self.tokenstack = [] + self.eof = False + + def get_token(self): + """ + This function breaks the time string into lexical units (tokens), which + can be parsed by the parser. Lexical units are demarcated by changes in + the character set, so any continuous string of letters is considered + one unit, any continuous string of numbers is considered one unit. + + The main complication arises from the fact that dots ('.') can be used + both as separators (e.g. "Sep.20.2009") or decimal points (e.g. + "4:30:21.447"). As such, it is necessary to read the full context of + any dot-separated strings before breaking it into tokens; as such, this + function maintains a "token stack", for when the ambiguous context + demands that multiple tokens be parsed at once. + """ + if self.tokenstack: + return self.tokenstack.pop(0) + + seenletters = False + token = None + state = None + + while not self.eof: + # We only realize that we've reached the end of a token when we + # find a character that's not part of the current token - since + # that character may be part of the next token, it's stored in the + # charstack. + if self.charstack: + nextchar = self.charstack.pop(0) + else: + nextchar = self.instream.read(1) + while nextchar == '\x00': + nextchar = self.instream.read(1) + + if not nextchar: + self.eof = True + break + elif not state: + # First character of the token - determines if we're starting + # to parse a word, a number or something else. + token = nextchar + if self.isword(nextchar): + state = 'a' + elif self.isnum(nextchar): + state = '0' + elif self.isspace(nextchar): + token = ' ' + break # emit token + else: + break # emit token + elif state == 'a': + # If we've already started reading a word, we keep reading + # letters until we find something that's not part of a word. + seenletters = True + if self.isword(nextchar): + token += nextchar + elif nextchar == '.': + token += nextchar + state = 'a.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == '0': + # If we've already started reading a number, we keep reading + # numbers until we find something that doesn't fit. + if self.isnum(nextchar): + token += nextchar + elif nextchar == '.' or (nextchar == ',' and len(token) >= 2): + token += nextchar + state = '0.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == 'a.': + # If we've seen some letters and a dot separator, continue + # parsing, and the tokens will be broken up later. + seenletters = True + if nextchar == '.' or self.isword(nextchar): + token += nextchar + elif self.isnum(nextchar) and token[-1] == '.': + token += nextchar + state = '0.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == '0.': + # If we've seen at least one dot separator, keep going, we'll + # break up the tokens later. + if nextchar == '.' or self.isnum(nextchar): + token += nextchar + elif self.isword(nextchar) and token[-1] == '.': + token += nextchar + state = 'a.' + else: + self.charstack.append(nextchar) + break # emit token + + if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or + token[-1] in '.,')): + l = self._split_decimal.split(token) + token = l[0] + for tok in l[1:]: + if tok: + self.tokenstack.append(tok) + + if state == '0.' and token.count('.') == 0: + token = token.replace(',', '.') + + return token + + def __iter__(self): + return self + + def __next__(self): + token = self.get_token() + if token is None: + raise StopIteration + + return token + + def next(self): + return self.__next__() # Python 2.x support + + @classmethod + def split(cls, s): + return list(cls(s)) + + @classmethod + def isword(cls, nextchar): + """ Whether or not the next character is part of a word """ + return nextchar.isalpha() + + @classmethod + def isnum(cls, nextchar): + """ Whether the next character is part of a number """ + return nextchar.isdigit() + + @classmethod + def isspace(cls, nextchar): + """ Whether the next character is whitespace """ + return nextchar.isspace() + + +class _resultbase(object): + + def __init__(self): + for attr in self.__slots__: + setattr(self, attr, None) + + def _repr(self, classname): + l = [] + for attr in self.__slots__: + value = getattr(self, attr) + if value is not None: + l.append("%s=%s" % (attr, repr(value))) + return "%s(%s)" % (classname, ", ".join(l)) + + def __len__(self): + return (sum(getattr(self, attr) is not None + for attr in self.__slots__)) + + def __repr__(self): + return self._repr(self.__class__.__name__) + + +class parserinfo(object): + """ + Class which handles what inputs are accepted. Subclass this to customize + the language and acceptable values for each parameter. + + :param dayfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the day (``True``) or month (``False``). If + ``yearfirst`` is set to ``True``, this distinguishes between YDM + and YMD. Default is ``False``. + + :param yearfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the year. If ``True``, the first number is taken + to be the year, otherwise the last number is taken to be the year. + Default is ``False``. + """ + + # m from a.m/p.m, t from ISO T separator + JUMP = [" ", ".", ",", ";", "-", "/", "'", + "at", "on", "and", "ad", "m", "t", "of", + "st", "nd", "rd", "th"] + + WEEKDAYS = [("Mon", "Monday"), + ("Tue", "Tuesday"), # TODO: "Tues" + ("Wed", "Wednesday"), + ("Thu", "Thursday"), # TODO: "Thurs" + ("Fri", "Friday"), + ("Sat", "Saturday"), + ("Sun", "Sunday")] + MONTHS = [("Jan", "January"), + ("Feb", "February"), # TODO: "Febr" + ("Mar", "March"), + ("Apr", "April"), + ("May", "May"), + ("Jun", "June"), + ("Jul", "July"), + ("Aug", "August"), + ("Sep", "Sept", "September"), + ("Oct", "October"), + ("Nov", "November"), + ("Dec", "December")] + HMS = [("h", "hour", "hours"), + ("m", "minute", "minutes"), + ("s", "second", "seconds")] + AMPM = [("am", "a"), + ("pm", "p")] + UTCZONE = ["UTC", "GMT", "Z", "z"] + PERTAIN = ["of"] + TZOFFSET = {} + # TODO: ERA = ["AD", "BC", "CE", "BCE", "Stardate", + # "Anno Domini", "Year of Our Lord"] + + def __init__(self, dayfirst=False, yearfirst=False): + self._jump = self._convert(self.JUMP) + self._weekdays = self._convert(self.WEEKDAYS) + self._months = self._convert(self.MONTHS) + self._hms = self._convert(self.HMS) + self._ampm = self._convert(self.AMPM) + self._utczone = self._convert(self.UTCZONE) + self._pertain = self._convert(self.PERTAIN) + + self.dayfirst = dayfirst + self.yearfirst = yearfirst + + self._year = time.localtime().tm_year + self._century = self._year // 100 * 100 + + def _convert(self, lst): + dct = {} + for i, v in enumerate(lst): + if isinstance(v, tuple): + for v in v: + dct[v.lower()] = i + else: + dct[v.lower()] = i + return dct + + def jump(self, name): + return name.lower() in self._jump + + def weekday(self, name): + try: + return self._weekdays[name.lower()] + except KeyError: + pass + return None + + def month(self, name): + try: + return self._months[name.lower()] + 1 + except KeyError: + pass + return None + + def hms(self, name): + try: + return self._hms[name.lower()] + except KeyError: + return None + + def ampm(self, name): + try: + return self._ampm[name.lower()] + except KeyError: + return None + + def pertain(self, name): + return name.lower() in self._pertain + + def utczone(self, name): + return name.lower() in self._utczone + + def tzoffset(self, name): + if name in self._utczone: + return 0 + + return self.TZOFFSET.get(name) + + def convertyear(self, year, century_specified=False): + """ + Converts two-digit years to year within [-50, 49] + range of self._year (current local time) + """ + + # Function contract is that the year is always positive + assert year >= 0 + + if year < 100 and not century_specified: + # assume current century to start + year += self._century + + if year >= self._year + 50: # if too far in future + year -= 100 + elif year < self._year - 50: # if too far in past + year += 100 + + return year + + def validate(self, res): + # move to info + if res.year is not None: + res.year = self.convertyear(res.year, res.century_specified) + + if ((res.tzoffset == 0 and not res.tzname) or + (res.tzname == 'Z' or res.tzname == 'z')): + res.tzname = "UTC" + res.tzoffset = 0 + elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname): + res.tzoffset = 0 + return True + + +class _ymd(list): + def __init__(self, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + self.century_specified = False + self.dstridx = None + self.mstridx = None + self.ystridx = None + + @property + def has_year(self): + return self.ystridx is not None + + @property + def has_month(self): + return self.mstridx is not None + + @property + def has_day(self): + return self.dstridx is not None + + def could_be_day(self, value): + if self.has_day: + return False + elif not self.has_month: + return 1 <= value <= 31 + elif not self.has_year: + # Be permissive, assume leap year + month = self[self.mstridx] + return 1 <= value <= monthrange(2000, month)[1] + else: + month = self[self.mstridx] + year = self[self.ystridx] + return 1 <= value <= monthrange(year, month)[1] + + def append(self, val, label=None): + if hasattr(val, '__len__'): + if val.isdigit() and len(val) > 2: + self.century_specified = True + if label not in [None, 'Y']: # pragma: no cover + raise ValueError(label) + label = 'Y' + elif val > 100: + self.century_specified = True + if label not in [None, 'Y']: # pragma: no cover + raise ValueError(label) + label = 'Y' + + super(self.__class__, self).append(int(val)) + + if label == 'M': + if self.has_month: + raise ValueError('Month is already set') + self.mstridx = len(self) - 1 + elif label == 'D': + if self.has_day: + raise ValueError('Day is already set') + self.dstridx = len(self) - 1 + elif label == 'Y': + if self.has_year: + raise ValueError('Year is already set') + self.ystridx = len(self) - 1 + + def _resolve_from_stridxs(self, strids): + """ + Try to resolve the identities of year/month/day elements using + ystridx, mstridx, and dstridx, if enough of these are specified. + """ + if len(self) == 3 and len(strids) == 2: + # we can back out the remaining stridx value + missing = [x for x in range(3) if x not in strids.values()] + key = [x for x in ['y', 'm', 'd'] if x not in strids] + assert len(missing) == len(key) == 1 + key = key[0] + val = missing[0] + strids[key] = val + + assert len(self) == len(strids) # otherwise this should not be called + out = {key: self[strids[key]] for key in strids} + return (out.get('y'), out.get('m'), out.get('d')) + + def resolve_ymd(self, yearfirst, dayfirst): + len_ymd = len(self) + year, month, day = (None, None, None) + + strids = (('y', self.ystridx), + ('m', self.mstridx), + ('d', self.dstridx)) + + strids = {key: val for key, val in strids if val is not None} + if (len(self) == len(strids) > 0 or + (len(self) == 3 and len(strids) == 2)): + return self._resolve_from_stridxs(strids) + + mstridx = self.mstridx + + if len_ymd > 3: + raise ValueError("More than three YMD values") + elif len_ymd == 1 or (mstridx is not None and len_ymd == 2): + # One member, or two members with a month string + if mstridx is not None: + month = self[mstridx] + # since mstridx is 0 or 1, self[mstridx-1] always + # looks up the other element + other = self[mstridx - 1] + else: + other = self[0] + + if len_ymd > 1 or mstridx is None: + if other > 31: + year = other + else: + day = other + + elif len_ymd == 2: + # Two members with numbers + if self[0] > 31: + # 99-01 + year, month = self + elif self[1] > 31: + # 01-99 + month, year = self + elif dayfirst and self[1] <= 12: + # 13-01 + day, month = self + else: + # 01-13 + month, day = self + + elif len_ymd == 3: + # Three members + if mstridx == 0: + if self[1] > 31: + # Apr-2003-25 + month, year, day = self + else: + month, day, year = self + elif mstridx == 1: + if self[0] > 31 or (yearfirst and self[2] <= 31): + # 99-Jan-01 + year, month, day = self + else: + # 01-Jan-01 + # Give precedence to day-first, since + # two-digit years is usually hand-written. + day, month, year = self + + elif mstridx == 2: + # WTF!? + if self[1] > 31: + # 01-99-Jan + day, year, month = self + else: + # 99-01-Jan + year, day, month = self + + else: + if (self[0] > 31 or + self.ystridx == 0 or + (yearfirst and self[1] <= 12 and self[2] <= 31)): + # 99-01-01 + if dayfirst and self[2] <= 12: + year, day, month = self + else: + year, month, day = self + elif self[0] > 12 or (dayfirst and self[1] <= 12): + # 13-01-01 + day, month, year = self + else: + # 01-13-01 + month, day, year = self + + return year, month, day + + +class parser(object): + def __init__(self, info=None): + self.info = info or parserinfo() + + def parse(self, timestr, default=None, + ignoretz=False, tzinfos=None, **kwargs): + """ + Parse the date/time string into a :class:`datetime.datetime` object. + + :param timestr: + Any date/time string using the supported formats. + + :param default: + The default datetime object, if this is a datetime object and not + ``None``, elements specified in ``timestr`` replace elements in the + default object. + + :param ignoretz: + If set ``True``, time zones in parsed strings are ignored and a + naive :class:`datetime.datetime` object is returned. + + :param tzinfos: + Additional time zone names / aliases which may be present in the + string. This argument maps time zone names (and optionally offsets + from those time zones) to time zones. This parameter can be a + dictionary with timezone aliases mapping time zone names to time + zones or a function taking two parameters (``tzname`` and + ``tzoffset``) and returning a time zone. + + The timezones to which the names are mapped can be an integer + offset from UTC in seconds or a :class:`tzinfo` object. + + .. doctest:: + :options: +NORMALIZE_WHITESPACE + + >>> from dateutil.parser import parse + >>> from dateutil.tz import gettz + >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} + >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) + >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, + tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) + + This parameter is ignored if ``ignoretz`` is set. + + :param \\*\\*kwargs: + Keyword arguments as passed to ``_parse()``. + + :return: + Returns a :class:`datetime.datetime` object or, if the + ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the + first element being a :class:`datetime.datetime` object, the second + a tuple containing the fuzzy tokens. + + :raises ParserError: + Raised for invalid or unknown string format, if the provided + :class:`tzinfo` is not in a valid format, or if an invalid date + would be created. + + :raises TypeError: + Raised for non-string or character stream input. + + :raises OverflowError: + Raised if the parsed date exceeds the largest valid C integer on + your system. + """ + + if default is None: + default = datetime.datetime.now().replace(hour=0, minute=0, + second=0, microsecond=0) + + res, skipped_tokens = self._parse(timestr, **kwargs) + + if res is None: + raise ParserError("Unknown string format: %s", timestr) + + if len(res) == 0: + raise ParserError("String does not contain a date: %s", timestr) + + try: + ret = self._build_naive(res, default) + except ValueError as e: + six.raise_from(ParserError(str(e) + ": %s", timestr), e) + + if not ignoretz: + ret = self._build_tzaware(ret, res, tzinfos) + + if kwargs.get('fuzzy_with_tokens', False): + return ret, skipped_tokens + else: + return ret + + class _result(_resultbase): + __slots__ = ["year", "month", "day", "weekday", + "hour", "minute", "second", "microsecond", + "tzname", "tzoffset", "ampm","any_unused_tokens"] + + def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, + fuzzy_with_tokens=False): + """ + Private method which performs the heavy lifting of parsing, called from + ``parse()``, which passes on its ``kwargs`` to this function. + + :param timestr: + The string to parse. + + :param dayfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the day (``True``) or month (``False``). If + ``yearfirst`` is set to ``True``, this distinguishes between YDM + and YMD. If set to ``None``, this value is retrieved from the + current :class:`parserinfo` object (which itself defaults to + ``False``). + + :param yearfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the year. If ``True``, the first number is taken + to be the year, otherwise the last number is taken to be the year. + If this is set to ``None``, the value is retrieved from the current + :class:`parserinfo` object (which itself defaults to ``False``). + + :param fuzzy: + Whether to allow fuzzy parsing, allowing for string like "Today is + January 1, 2047 at 8:21:00AM". + + :param fuzzy_with_tokens: + If ``True``, ``fuzzy`` is automatically set to True, and the parser + will return a tuple where the first element is the parsed + :class:`datetime.datetime` datetimestamp and the second element is + a tuple containing the portions of the string which were ignored: + + .. doctest:: + + >>> from dateutil.parser import parse + >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) + (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) + + """ + if fuzzy_with_tokens: + fuzzy = True + + info = self.info + + if dayfirst is None: + dayfirst = info.dayfirst + + if yearfirst is None: + yearfirst = info.yearfirst + + res = self._result() + l = _timelex.split(timestr) # Splits the timestr into tokens + + skipped_idxs = [] + + # year/month/day list + ymd = _ymd() + + len_l = len(l) + i = 0 + try: + while i < len_l: + + # Check if it's a number + value_repr = l[i] + try: + value = float(value_repr) + except ValueError: + value = None + + if value is not None: + # Numeric token + i = self._parse_numeric_token(l, i, info, ymd, res, fuzzy) + + # Check weekday + elif info.weekday(l[i]) is not None: + value = info.weekday(l[i]) + res.weekday = value + + # Check month name + elif info.month(l[i]) is not None: + value = info.month(l[i]) + ymd.append(value, 'M') + + if i + 1 < len_l: + if l[i + 1] in ('-', '/'): + # Jan-01[-99] + sep = l[i + 1] + ymd.append(l[i + 2]) + + if i + 3 < len_l and l[i + 3] == sep: + # Jan-01-99 + ymd.append(l[i + 4]) + i += 2 + + i += 2 + + elif (i + 4 < len_l and l[i + 1] == l[i + 3] == ' ' and + info.pertain(l[i + 2])): + # Jan of 01 + # In this case, 01 is clearly year + if l[i + 4].isdigit(): + # Convert it here to become unambiguous + value = int(l[i + 4]) + year = str(info.convertyear(value)) + ymd.append(year, 'Y') + else: + # Wrong guess + pass + # TODO: not hit in tests + i += 4 + + # Check am/pm + elif info.ampm(l[i]) is not None: + value = info.ampm(l[i]) + val_is_ampm = self._ampm_valid(res.hour, res.ampm, fuzzy) + + if val_is_ampm: + res.hour = self._adjust_ampm(res.hour, value) + res.ampm = value + + elif fuzzy: + skipped_idxs.append(i) + + # Check for a timezone name + elif self._could_be_tzname(res.hour, res.tzname, res.tzoffset, l[i]): + res.tzname = l[i] + res.tzoffset = info.tzoffset(res.tzname) + + # Check for something like GMT+3, or BRST+3. Notice + # that it doesn't mean "I am 3 hours after GMT", but + # "my time +3 is GMT". If found, we reverse the + # logic so that timezone parsing code will get it + # right. + if i + 1 < len_l and l[i + 1] in ('+', '-'): + l[i + 1] = ('+', '-')[l[i + 1] == '+'] + res.tzoffset = None + if info.utczone(res.tzname): + # With something like GMT+3, the timezone + # is *not* GMT. + res.tzname = None + + # Check for a numbered timezone + elif res.hour is not None and l[i] in ('+', '-'): + signal = (-1, 1)[l[i] == '+'] + len_li = len(l[i + 1]) + + # TODO: check that l[i + 1] is integer? + if len_li == 4: + # -0300 + hour_offset = int(l[i + 1][:2]) + min_offset = int(l[i + 1][2:]) + elif i + 2 < len_l and l[i + 2] == ':': + # -03:00 + hour_offset = int(l[i + 1]) + min_offset = int(l[i + 3]) # TODO: Check that l[i+3] is minute-like? + i += 2 + elif len_li <= 2: + # -[0]3 + hour_offset = int(l[i + 1][:2]) + min_offset = 0 + else: + raise ValueError(timestr) + + res.tzoffset = signal * (hour_offset * 3600 + min_offset * 60) + + # Look for a timezone name between parenthesis + if (i + 5 < len_l and + info.jump(l[i + 2]) and l[i + 3] == '(' and + l[i + 5] == ')' and + 3 <= len(l[i + 4]) and + self._could_be_tzname(res.hour, res.tzname, + None, l[i + 4])): + # -0300 (BRST) + res.tzname = l[i + 4] + i += 4 + + i += 1 + + # Check jumps + elif not (info.jump(l[i]) or fuzzy): + raise ValueError(timestr) + + else: + skipped_idxs.append(i) + i += 1 + + # Process year/month/day + year, month, day = ymd.resolve_ymd(yearfirst, dayfirst) + + res.century_specified = ymd.century_specified + res.year = year + res.month = month + res.day = day + + except (IndexError, ValueError): + return None, None + + if not info.validate(res): + return None, None + + if fuzzy_with_tokens: + skipped_tokens = self._recombine_skipped(l, skipped_idxs) + return res, tuple(skipped_tokens) + else: + return res, None + + def _parse_numeric_token(self, tokens, idx, info, ymd, res, fuzzy): + # Token is a number + value_repr = tokens[idx] + try: + value = self._to_decimal(value_repr) + except Exception as e: + six.raise_from(ValueError('Unknown numeric token'), e) + + len_li = len(value_repr) + + len_l = len(tokens) + + if (len(ymd) == 3 and len_li in (2, 4) and + res.hour is None and + (idx + 1 >= len_l or + (tokens[idx + 1] != ':' and + info.hms(tokens[idx + 1]) is None))): + # 19990101T23[59] + s = tokens[idx] + res.hour = int(s[:2]) + + if len_li == 4: + res.minute = int(s[2:]) + + elif len_li == 6 or (len_li > 6 and tokens[idx].find('.') == 6): + # YYMMDD or HHMMSS[.ss] + s = tokens[idx] + + if not ymd and '.' not in tokens[idx]: + ymd.append(s[:2]) + ymd.append(s[2:4]) + ymd.append(s[4:]) + else: + # 19990101T235959[.59] + + # TODO: Check if res attributes already set. + res.hour = int(s[:2]) + res.minute = int(s[2:4]) + res.second, res.microsecond = self._parsems(s[4:]) + + elif len_li in (8, 12, 14): + # YYYYMMDD + s = tokens[idx] + ymd.append(s[:4], 'Y') + ymd.append(s[4:6]) + ymd.append(s[6:8]) + + if len_li > 8: + res.hour = int(s[8:10]) + res.minute = int(s[10:12]) + + if len_li > 12: + res.second = int(s[12:]) + + elif self._find_hms_idx(idx, tokens, info, allow_jump=True) is not None: + # HH[ ]h or MM[ ]m or SS[.ss][ ]s + hms_idx = self._find_hms_idx(idx, tokens, info, allow_jump=True) + (idx, hms) = self._parse_hms(idx, tokens, info, hms_idx) + if hms is not None: + # TODO: checking that hour/minute/second are not + # already set? + self._assign_hms(res, value_repr, hms) + + elif idx + 2 < len_l and tokens[idx + 1] == ':': + # HH:MM[:SS[.ss]] + res.hour = int(value) + value = self._to_decimal(tokens[idx + 2]) # TODO: try/except for this? + (res.minute, res.second) = self._parse_min_sec(value) + + if idx + 4 < len_l and tokens[idx + 3] == ':': + res.second, res.microsecond = self._parsems(tokens[idx + 4]) + + idx += 2 + + idx += 2 + + elif idx + 1 < len_l and tokens[idx + 1] in ('-', '/', '.'): + sep = tokens[idx + 1] + ymd.append(value_repr) + + if idx + 2 < len_l and not info.jump(tokens[idx + 2]): + if tokens[idx + 2].isdigit(): + # 01-01[-01] + ymd.append(tokens[idx + 2]) + else: + # 01-Jan[-01] + value = info.month(tokens[idx + 2]) + + if value is not None: + ymd.append(value, 'M') + else: + raise ValueError() + + if idx + 3 < len_l and tokens[idx + 3] == sep: + # We have three members + value = info.month(tokens[idx + 4]) + + if value is not None: + ymd.append(value, 'M') + else: + ymd.append(tokens[idx + 4]) + idx += 2 + + idx += 1 + idx += 1 + + elif idx + 1 >= len_l or info.jump(tokens[idx + 1]): + if idx + 2 < len_l and info.ampm(tokens[idx + 2]) is not None: + # 12 am + hour = int(value) + res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 2])) + idx += 1 + else: + # Year, month or day + ymd.append(value) + idx += 1 + + elif info.ampm(tokens[idx + 1]) is not None and (0 <= value < 24): + # 12am + hour = int(value) + res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 1])) + idx += 1 + + elif ymd.could_be_day(value): + ymd.append(value) + + elif not fuzzy: + raise ValueError() + + return idx + + def _find_hms_idx(self, idx, tokens, info, allow_jump): + len_l = len(tokens) + + if idx+1 < len_l and info.hms(tokens[idx+1]) is not None: + # There is an "h", "m", or "s" label following this token. We take + # assign the upcoming label to the current token. + # e.g. the "12" in 12h" + hms_idx = idx + 1 + + elif (allow_jump and idx+2 < len_l and tokens[idx+1] == ' ' and + info.hms(tokens[idx+2]) is not None): + # There is a space and then an "h", "m", or "s" label. + # e.g. the "12" in "12 h" + hms_idx = idx + 2 + + elif idx > 0 and info.hms(tokens[idx-1]) is not None: + # There is a "h", "m", or "s" preceding this token. Since neither + # of the previous cases was hit, there is no label following this + # token, so we use the previous label. + # e.g. the "04" in "12h04" + hms_idx = idx-1 + + elif (1 < idx == len_l-1 and tokens[idx-1] == ' ' and + info.hms(tokens[idx-2]) is not None): + # If we are looking at the final token, we allow for a + # backward-looking check to skip over a space. + # TODO: Are we sure this is the right condition here? + hms_idx = idx - 2 + + else: + hms_idx = None + + return hms_idx + + def _assign_hms(self, res, value_repr, hms): + # See GH issue #427, fixing float rounding + value = self._to_decimal(value_repr) + + if hms == 0: + # Hour + res.hour = int(value) + if value % 1: + res.minute = int(60*(value % 1)) + + elif hms == 1: + (res.minute, res.second) = self._parse_min_sec(value) + + elif hms == 2: + (res.second, res.microsecond) = self._parsems(value_repr) + + def _could_be_tzname(self, hour, tzname, tzoffset, token): + return (hour is not None and + tzname is None and + tzoffset is None and + len(token) <= 5 and + (all(x in string.ascii_uppercase for x in token) + or token in self.info.UTCZONE)) + + def _ampm_valid(self, hour, ampm, fuzzy): + """ + For fuzzy parsing, 'a' or 'am' (both valid English words) + may erroneously trigger the AM/PM flag. Deal with that + here. + """ + val_is_ampm = True + + # If there's already an AM/PM flag, this one isn't one. + if fuzzy and ampm is not None: + val_is_ampm = False + + # If AM/PM is found and hour is not, raise a ValueError + if hour is None: + if fuzzy: + val_is_ampm = False + else: + raise ValueError('No hour specified with AM or PM flag.') + elif not 0 <= hour <= 12: + # If AM/PM is found, it's a 12 hour clock, so raise + # an error for invalid range + if fuzzy: + val_is_ampm = False + else: + raise ValueError('Invalid hour specified for 12-hour clock.') + + return val_is_ampm + + def _adjust_ampm(self, hour, ampm): + if hour < 12 and ampm == 1: + hour += 12 + elif hour == 12 and ampm == 0: + hour = 0 + return hour + + def _parse_min_sec(self, value): + # TODO: Every usage of this function sets res.second to the return + # value. Are there any cases where second will be returned as None and + # we *don't* want to set res.second = None? + minute = int(value) + second = None + + sec_remainder = value % 1 + if sec_remainder: + second = int(60 * sec_remainder) + return (minute, second) + + def _parse_hms(self, idx, tokens, info, hms_idx): + # TODO: Is this going to admit a lot of false-positives for when we + # just happen to have digits and "h", "m" or "s" characters in non-date + # text? I guess hex hashes won't have that problem, but there's plenty + # of random junk out there. + if hms_idx is None: + hms = None + new_idx = idx + elif hms_idx > idx: + hms = info.hms(tokens[hms_idx]) + new_idx = hms_idx + else: + # Looking backwards, increment one. + hms = info.hms(tokens[hms_idx]) + 1 + new_idx = idx + + return (new_idx, hms) + + # ------------------------------------------------------------------ + # Handling for individual tokens. These are kept as methods instead + # of functions for the sake of customizability via subclassing. + + def _parsems(self, value): + """Parse a I[.F] seconds value into (seconds, microseconds).""" + if "." not in value: + return int(value), 0 + else: + i, f = value.split(".") + return int(i), int(f.ljust(6, "0")[:6]) + + def _to_decimal(self, val): + try: + decimal_value = Decimal(val) + # See GH 662, edge case, infinite value should not be converted + # via `_to_decimal` + if not decimal_value.is_finite(): + raise ValueError("Converted decimal value is infinite or NaN") + except Exception as e: + msg = "Could not convert %s to decimal" % val + six.raise_from(ValueError(msg), e) + else: + return decimal_value + + # ------------------------------------------------------------------ + # Post-Parsing construction of datetime output. These are kept as + # methods instead of functions for the sake of customizability via + # subclassing. + + def _build_tzinfo(self, tzinfos, tzname, tzoffset): + if callable(tzinfos): + tzdata = tzinfos(tzname, tzoffset) + else: + tzdata = tzinfos.get(tzname) + # handle case where tzinfo is paased an options that returns None + # eg tzinfos = {'BRST' : None} + if isinstance(tzdata, datetime.tzinfo) or tzdata is None: + tzinfo = tzdata + elif isinstance(tzdata, text_type): + tzinfo = tz.tzstr(tzdata) + elif isinstance(tzdata, integer_types): + tzinfo = tz.tzoffset(tzname, tzdata) + else: + raise TypeError("Offset must be tzinfo subclass, tz string, " + "or int offset.") + return tzinfo + + def _build_tzaware(self, naive, res, tzinfos): + if (callable(tzinfos) or (tzinfos and res.tzname in tzinfos)): + tzinfo = self._build_tzinfo(tzinfos, res.tzname, res.tzoffset) + aware = naive.replace(tzinfo=tzinfo) + aware = self._assign_tzname(aware, res.tzname) + + elif res.tzname and res.tzname in time.tzname: + aware = naive.replace(tzinfo=tz.tzlocal()) + + # Handle ambiguous local datetime + aware = self._assign_tzname(aware, res.tzname) + + # This is mostly relevant for winter GMT zones parsed in the UK + if (aware.tzname() != res.tzname and + res.tzname in self.info.UTCZONE): + aware = aware.replace(tzinfo=tz.UTC) + + elif res.tzoffset == 0: + aware = naive.replace(tzinfo=tz.UTC) + + elif res.tzoffset: + aware = naive.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset)) + + elif not res.tzname and not res.tzoffset: + # i.e. no timezone information was found. + aware = naive + + elif res.tzname: + # tz-like string was parsed but we don't know what to do + # with it + warnings.warn("tzname {tzname} identified but not understood. " + "Pass `tzinfos` argument in order to correctly " + "return a timezone-aware datetime. In a future " + "version, this will raise an " + "exception.".format(tzname=res.tzname), + category=UnknownTimezoneWarning) + aware = naive + + return aware + + def _build_naive(self, res, default): + repl = {} + for attr in ("year", "month", "day", "hour", + "minute", "second", "microsecond"): + value = getattr(res, attr) + if value is not None: + repl[attr] = value + + if 'day' not in repl: + # If the default day exceeds the last day of the month, fall back + # to the end of the month. + cyear = default.year if res.year is None else res.year + cmonth = default.month if res.month is None else res.month + cday = default.day if res.day is None else res.day + + if cday > monthrange(cyear, cmonth)[1]: + repl['day'] = monthrange(cyear, cmonth)[1] + + naive = default.replace(**repl) + + if res.weekday is not None and not res.day: + naive = naive + relativedelta.relativedelta(weekday=res.weekday) + + return naive + + def _assign_tzname(self, dt, tzname): + if dt.tzname() != tzname: + new_dt = tz.enfold(dt, fold=1) + if new_dt.tzname() == tzname: + return new_dt + + return dt + + def _recombine_skipped(self, tokens, skipped_idxs): + """ + >>> tokens = ["foo", " ", "bar", " ", "19June2000", "baz"] + >>> skipped_idxs = [0, 1, 2, 5] + >>> _recombine_skipped(tokens, skipped_idxs) + ["foo bar", "baz"] + """ + skipped_tokens = [] + for i, idx in enumerate(sorted(skipped_idxs)): + if i > 0 and idx - 1 == skipped_idxs[i - 1]: + skipped_tokens[-1] = skipped_tokens[-1] + tokens[idx] + else: + skipped_tokens.append(tokens[idx]) + + return skipped_tokens + + +DEFAULTPARSER = parser() + + +def parse(timestr, parserinfo=None, **kwargs): + """ + + Parse a string in one of the supported formats, using the + ``parserinfo`` parameters. + + :param timestr: + A string containing a date/time stamp. + + :param parserinfo: + A :class:`parserinfo` object containing parameters for the parser. + If ``None``, the default arguments to the :class:`parserinfo` + constructor are used. + + The ``**kwargs`` parameter takes the following keyword arguments: + + :param default: + The default datetime object, if this is a datetime object and not + ``None``, elements specified in ``timestr`` replace elements in the + default object. + + :param ignoretz: + If set ``True``, time zones in parsed strings are ignored and a naive + :class:`datetime` object is returned. + + :param tzinfos: + Additional time zone names / aliases which may be present in the + string. This argument maps time zone names (and optionally offsets + from those time zones) to time zones. This parameter can be a + dictionary with timezone aliases mapping time zone names to time + zones or a function taking two parameters (``tzname`` and + ``tzoffset``) and returning a time zone. + + The timezones to which the names are mapped can be an integer + offset from UTC in seconds or a :class:`tzinfo` object. + + .. doctest:: + :options: +NORMALIZE_WHITESPACE + + >>> from dateutil.parser import parse + >>> from dateutil.tz import gettz + >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} + >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) + >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, + tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) + + This parameter is ignored if ``ignoretz`` is set. + + :param dayfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the day (``True``) or month (``False``). If + ``yearfirst`` is set to ``True``, this distinguishes between YDM and + YMD. If set to ``None``, this value is retrieved from the current + :class:`parserinfo` object (which itself defaults to ``False``). + + :param yearfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the year. If ``True``, the first number is taken to + be the year, otherwise the last number is taken to be the year. If + this is set to ``None``, the value is retrieved from the current + :class:`parserinfo` object (which itself defaults to ``False``). + + :param fuzzy: + Whether to allow fuzzy parsing, allowing for string like "Today is + January 1, 2047 at 8:21:00AM". + + :param fuzzy_with_tokens: + If ``True``, ``fuzzy`` is automatically set to True, and the parser + will return a tuple where the first element is the parsed + :class:`datetime.datetime` datetimestamp and the second element is + a tuple containing the portions of the string which were ignored: + + .. doctest:: + + >>> from dateutil.parser import parse + >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) + (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) + + :return: + Returns a :class:`datetime.datetime` object or, if the + ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the + first element being a :class:`datetime.datetime` object, the second + a tuple containing the fuzzy tokens. + + :raises ParserError: + Raised for invalid or unknown string formats, if the provided + :class:`tzinfo` is not in a valid format, or if an invalid date would + be created. + + :raises OverflowError: + Raised if the parsed date exceeds the largest valid C integer on + your system. + """ + if parserinfo: + return parser(parserinfo).parse(timestr, **kwargs) + else: + return DEFAULTPARSER.parse(timestr, **kwargs) + + +class _tzparser(object): + + class _result(_resultbase): + + __slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset", + "start", "end"] + + class _attr(_resultbase): + __slots__ = ["month", "week", "weekday", + "yday", "jyday", "day", "time"] + + def __repr__(self): + return self._repr("") + + def __init__(self): + _resultbase.__init__(self) + self.start = self._attr() + self.end = self._attr() + + def parse(self, tzstr): + res = self._result() + l = [x for x in re.split(r'([,:.]|[a-zA-Z]+|[0-9]+)',tzstr) if x] + used_idxs = list() + try: + + len_l = len(l) + + i = 0 + while i < len_l: + # BRST+3[BRDT[+2]] + j = i + while j < len_l and not [x for x in l[j] + if x in "0123456789:,-+"]: + j += 1 + if j != i: + if not res.stdabbr: + offattr = "stdoffset" + res.stdabbr = "".join(l[i:j]) + else: + offattr = "dstoffset" + res.dstabbr = "".join(l[i:j]) + + for ii in range(j): + used_idxs.append(ii) + i = j + if (i < len_l and (l[i] in ('+', '-') or l[i][0] in + "0123456789")): + if l[i] in ('+', '-'): + # Yes, that's right. See the TZ variable + # documentation. + signal = (1, -1)[l[i] == '+'] + used_idxs.append(i) + i += 1 + else: + signal = -1 + len_li = len(l[i]) + if len_li == 4: + # -0300 + setattr(res, offattr, (int(l[i][:2]) * 3600 + + int(l[i][2:]) * 60) * signal) + elif i + 1 < len_l and l[i + 1] == ':': + # -03:00 + setattr(res, offattr, + (int(l[i]) * 3600 + + int(l[i + 2]) * 60) * signal) + used_idxs.append(i) + i += 2 + elif len_li <= 2: + # -[0]3 + setattr(res, offattr, + int(l[i][:2]) * 3600 * signal) + else: + return None + used_idxs.append(i) + i += 1 + if res.dstabbr: + break + else: + break + + + if i < len_l: + for j in range(i, len_l): + if l[j] == ';': + l[j] = ',' + + assert l[i] == ',' + + i += 1 + + if i >= len_l: + pass + elif (8 <= l.count(',') <= 9 and + not [y for x in l[i:] if x != ',' + for y in x if y not in "0123456789+-"]): + # GMT0BST,3,0,30,3600,10,0,26,7200[,3600] + for x in (res.start, res.end): + x.month = int(l[i]) + used_idxs.append(i) + i += 2 + if l[i] == '-': + value = int(l[i + 1]) * -1 + used_idxs.append(i) + i += 1 + else: + value = int(l[i]) + used_idxs.append(i) + i += 2 + if value: + x.week = value + x.weekday = (int(l[i]) - 1) % 7 + else: + x.day = int(l[i]) + used_idxs.append(i) + i += 2 + x.time = int(l[i]) + used_idxs.append(i) + i += 2 + if i < len_l: + if l[i] in ('-', '+'): + signal = (-1, 1)[l[i] == "+"] + used_idxs.append(i) + i += 1 + else: + signal = 1 + used_idxs.append(i) + res.dstoffset = (res.stdoffset + int(l[i]) * signal) + + # This was a made-up format that is not in normal use + warn(('Parsed time zone "%s"' % tzstr) + + 'is in a non-standard dateutil-specific format, which ' + + 'is now deprecated; support for parsing this format ' + + 'will be removed in future versions. It is recommended ' + + 'that you switch to a standard format like the GNU ' + + 'TZ variable format.', tz.DeprecatedTzFormatWarning) + elif (l.count(',') == 2 and l[i:].count('/') <= 2 and + not [y for x in l[i:] if x not in (',', '/', 'J', 'M', + '.', '-', ':') + for y in x if y not in "0123456789"]): + for x in (res.start, res.end): + if l[i] == 'J': + # non-leap year day (1 based) + used_idxs.append(i) + i += 1 + x.jyday = int(l[i]) + elif l[i] == 'M': + # month[-.]week[-.]weekday + used_idxs.append(i) + i += 1 + x.month = int(l[i]) + used_idxs.append(i) + i += 1 + assert l[i] in ('-', '.') + used_idxs.append(i) + i += 1 + x.week = int(l[i]) + if x.week == 5: + x.week = -1 + used_idxs.append(i) + i += 1 + assert l[i] in ('-', '.') + used_idxs.append(i) + i += 1 + x.weekday = (int(l[i]) - 1) % 7 + else: + # year day (zero based) + x.yday = int(l[i]) + 1 + + used_idxs.append(i) + i += 1 + + if i < len_l and l[i] == '/': + used_idxs.append(i) + i += 1 + # start time + len_li = len(l[i]) + if len_li == 4: + # -0300 + x.time = (int(l[i][:2]) * 3600 + + int(l[i][2:]) * 60) + elif i + 1 < len_l and l[i + 1] == ':': + # -03:00 + x.time = int(l[i]) * 3600 + int(l[i + 2]) * 60 + used_idxs.append(i) + i += 2 + if i + 1 < len_l and l[i + 1] == ':': + used_idxs.append(i) + i += 2 + x.time += int(l[i]) + elif len_li <= 2: + # -[0]3 + x.time = (int(l[i][:2]) * 3600) + else: + return None + used_idxs.append(i) + i += 1 + + assert i == len_l or l[i] == ',' + + i += 1 + + assert i >= len_l + + except (IndexError, ValueError, AssertionError): + return None + + unused_idxs = set(range(len_l)).difference(used_idxs) + res.any_unused_tokens = not {l[n] for n in unused_idxs}.issubset({",",":"}) + return res + + +DEFAULTTZPARSER = _tzparser() + + +def _parsetz(tzstr): + return DEFAULTTZPARSER.parse(tzstr) + + +class ParserError(ValueError): + """Exception subclass used for any failure to parse a datetime string. + + This is a subclass of :py:exc:`ValueError`, and should be raised any time + earlier versions of ``dateutil`` would have raised ``ValueError``. + + .. versionadded:: 2.8.1 + """ + def __str__(self): + try: + return self.args[0] % self.args[1:] + except (TypeError, IndexError): + return super(ParserError, self).__str__() + + def __repr__(self): + args = ", ".join("'%s'" % arg for arg in self.args) + return "%s(%s)" % (self.__class__.__name__, args) + + +class UnknownTimezoneWarning(RuntimeWarning): + """Raised when the parser finds a timezone it cannot parse into a tzinfo. + + .. versionadded:: 2.7.0 + """ +# vim:ts=4:sw=4:et diff --git a/dateutil/parser/isoparser.py b/dateutil/parser/isoparser.py new file mode 100644 index 0000000..7060087 --- /dev/null +++ b/dateutil/parser/isoparser.py @@ -0,0 +1,416 @@ +# -*- coding: utf-8 -*- +""" +This module offers a parser for ISO-8601 strings + +It is intended to support all valid date, time and datetime formats per the +ISO-8601 specification. + +..versionadded:: 2.7.0 +""" +from datetime import datetime, timedelta, time, date +import calendar +from dateutil import tz + +from functools import wraps + +import re +import six + +__all__ = ["isoparse", "isoparser"] + + +def _takes_ascii(f): + @wraps(f) + def func(self, str_in, *args, **kwargs): + # If it's a stream, read the whole thing + str_in = getattr(str_in, 'read', lambda: str_in)() + + # If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII + if isinstance(str_in, six.text_type): + # ASCII is the same in UTF-8 + try: + str_in = str_in.encode('ascii') + except UnicodeEncodeError as e: + msg = 'ISO-8601 strings should contain only ASCII characters' + six.raise_from(ValueError(msg), e) + + return f(self, str_in, *args, **kwargs) + + return func + + +class isoparser(object): + def __init__(self, sep=None): + """ + :param sep: + A single character that separates date and time portions. If + ``None``, the parser will accept any single character. + For strict ISO-8601 adherence, pass ``'T'``. + """ + if sep is not None: + if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'): + raise ValueError('Separator must be a single, non-numeric ' + + 'ASCII character') + + sep = sep.encode('ascii') + + self._sep = sep + + @_takes_ascii + def isoparse(self, dt_str): + """ + Parse an ISO-8601 datetime string into a :class:`datetime.datetime`. + + An ISO-8601 datetime string consists of a date portion, followed + optionally by a time portion - the date and time portions are separated + by a single character separator, which is ``T`` in the official + standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be + combined with a time portion. + + Supported date formats are: + + Common: + + - ``YYYY`` + - ``YYYY-MM`` + - ``YYYY-MM-DD`` or ``YYYYMMDD`` + + Uncommon: + + - ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0) + - ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day + + The ISO week and day numbering follows the same logic as + :func:`datetime.date.isocalendar`. + + Supported time formats are: + + - ``hh`` + - ``hh:mm`` or ``hhmm`` + - ``hh:mm:ss`` or ``hhmmss`` + - ``hh:mm:ss.ssssss`` (Up to 6 sub-second digits) + + Midnight is a special case for `hh`, as the standard supports both + 00:00 and 24:00 as a representation. The decimal separator can be + either a dot or a comma. + + + .. caution:: + + Support for fractional components other than seconds is part of the + ISO-8601 standard, but is not currently implemented in this parser. + + Supported time zone offset formats are: + + - `Z` (UTC) + - `±HH:MM` + - `±HHMM` + - `±HH` + + Offsets will be represented as :class:`dateutil.tz.tzoffset` objects, + with the exception of UTC, which will be represented as + :class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such + as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`. + + :param dt_str: + A string or stream containing only an ISO-8601 datetime string + + :return: + Returns a :class:`datetime.datetime` representing the string. + Unspecified components default to their lowest value. + + .. warning:: + + As of version 2.7.0, the strictness of the parser should not be + considered a stable part of the contract. Any valid ISO-8601 string + that parses correctly with the default settings will continue to + parse correctly in future versions, but invalid strings that + currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not + guaranteed to continue failing in future versions if they encode + a valid date. + + .. versionadded:: 2.7.0 + """ + components, pos = self._parse_isodate(dt_str) + + if len(dt_str) > pos: + if self._sep is None or dt_str[pos:pos + 1] == self._sep: + components += self._parse_isotime(dt_str[pos + 1:]) + else: + raise ValueError('String contains unknown ISO components') + + if len(components) > 3 and components[3] == 24: + components[3] = 0 + return datetime(*components) + timedelta(days=1) + + return datetime(*components) + + @_takes_ascii + def parse_isodate(self, datestr): + """ + Parse the date portion of an ISO string. + + :param datestr: + The string portion of an ISO string, without a separator + + :return: + Returns a :class:`datetime.date` object + """ + components, pos = self._parse_isodate(datestr) + if pos < len(datestr): + raise ValueError('String contains unknown ISO ' + + 'components: {!r}'.format(datestr.decode('ascii'))) + return date(*components) + + @_takes_ascii + def parse_isotime(self, timestr): + """ + Parse the time portion of an ISO string. + + :param timestr: + The time portion of an ISO string, without a separator + + :return: + Returns a :class:`datetime.time` object + """ + components = self._parse_isotime(timestr) + if components[0] == 24: + components[0] = 0 + return time(*components) + + @_takes_ascii + def parse_tzstr(self, tzstr, zero_as_utc=True): + """ + Parse a valid ISO time zone string. + + See :func:`isoparser.isoparse` for details on supported formats. + + :param tzstr: + A string representing an ISO time zone offset + + :param zero_as_utc: + Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones + + :return: + Returns :class:`dateutil.tz.tzoffset` for offsets and + :class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is + specified) offsets equivalent to UTC. + """ + return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc) + + # Constants + _DATE_SEP = b'-' + _TIME_SEP = b':' + _FRACTION_REGEX = re.compile(b'[\\.,]([0-9]+)') + + def _parse_isodate(self, dt_str): + try: + return self._parse_isodate_common(dt_str) + except ValueError: + return self._parse_isodate_uncommon(dt_str) + + def _parse_isodate_common(self, dt_str): + len_str = len(dt_str) + components = [1, 1, 1] + + if len_str < 4: + raise ValueError('ISO string too short') + + # Year + components[0] = int(dt_str[0:4]) + pos = 4 + if pos >= len_str: + return components, pos + + has_sep = dt_str[pos:pos + 1] == self._DATE_SEP + if has_sep: + pos += 1 + + # Month + if len_str - pos < 2: + raise ValueError('Invalid common month') + + components[1] = int(dt_str[pos:pos + 2]) + pos += 2 + + if pos >= len_str: + if has_sep: + return components, pos + else: + raise ValueError('Invalid ISO format') + + if has_sep: + if dt_str[pos:pos + 1] != self._DATE_SEP: + raise ValueError('Invalid separator in ISO string') + pos += 1 + + # Day + if len_str - pos < 2: + raise ValueError('Invalid common day') + components[2] = int(dt_str[pos:pos + 2]) + return components, pos + 2 + + def _parse_isodate_uncommon(self, dt_str): + if len(dt_str) < 4: + raise ValueError('ISO string too short') + + # All ISO formats start with the year + year = int(dt_str[0:4]) + + has_sep = dt_str[4:5] == self._DATE_SEP + + pos = 4 + has_sep # Skip '-' if it's there + if dt_str[pos:pos + 1] == b'W': + # YYYY-?Www-?D? + pos += 1 + weekno = int(dt_str[pos:pos + 2]) + pos += 2 + + dayno = 1 + if len(dt_str) > pos: + if (dt_str[pos:pos + 1] == self._DATE_SEP) != has_sep: + raise ValueError('Inconsistent use of dash separator') + + pos += has_sep + + dayno = int(dt_str[pos:pos + 1]) + pos += 1 + + base_date = self._calculate_weekdate(year, weekno, dayno) + else: + # YYYYDDD or YYYY-DDD + if len(dt_str) - pos < 3: + raise ValueError('Invalid ordinal day') + + ordinal_day = int(dt_str[pos:pos + 3]) + pos += 3 + + if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)): + raise ValueError('Invalid ordinal day' + + ' {} for year {}'.format(ordinal_day, year)) + + base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1) + + components = [base_date.year, base_date.month, base_date.day] + return components, pos + + def _calculate_weekdate(self, year, week, day): + """ + Calculate the day of corresponding to the ISO year-week-day calendar. + + This function is effectively the inverse of + :func:`datetime.date.isocalendar`. + + :param year: + The year in the ISO calendar + + :param week: + The week in the ISO calendar - range is [1, 53] + + :param day: + The day in the ISO calendar - range is [1 (MON), 7 (SUN)] + + :return: + Returns a :class:`datetime.date` + """ + if not 0 < week < 54: + raise ValueError('Invalid week: {}'.format(week)) + + if not 0 < day < 8: # Range is 1-7 + raise ValueError('Invalid weekday: {}'.format(day)) + + # Get week 1 for the specific year: + jan_4 = date(year, 1, 4) # Week 1 always has January 4th in it + week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1) + + # Now add the specific number of weeks and days to get what we want + week_offset = (week - 1) * 7 + (day - 1) + return week_1 + timedelta(days=week_offset) + + def _parse_isotime(self, timestr): + len_str = len(timestr) + components = [0, 0, 0, 0, None] + pos = 0 + comp = -1 + + if len_str < 2: + raise ValueError('ISO time too short') + + has_sep = False + + while pos < len_str and comp < 5: + comp += 1 + + if timestr[pos:pos + 1] in b'-+Zz': + # Detect time zone boundary + components[-1] = self._parse_tzstr(timestr[pos:]) + pos = len_str + break + + if comp == 1 and timestr[pos:pos+1] == self._TIME_SEP: + has_sep = True + pos += 1 + elif comp == 2 and has_sep: + if timestr[pos:pos+1] != self._TIME_SEP: + raise ValueError('Inconsistent use of colon separator') + pos += 1 + + if comp < 3: + # Hour, minute, second + components[comp] = int(timestr[pos:pos + 2]) + pos += 2 + + if comp == 3: + # Fraction of a second + frac = self._FRACTION_REGEX.match(timestr[pos:]) + if not frac: + continue + + us_str = frac.group(1)[:6] # Truncate to microseconds + components[comp] = int(us_str) * 10**(6 - len(us_str)) + pos += len(frac.group()) + + if pos < len_str: + raise ValueError('Unused components in ISO string') + + if components[0] == 24: + # Standard supports 00:00 and 24:00 as representations of midnight + if any(component != 0 for component in components[1:4]): + raise ValueError('Hour may only be 24 at 24:00:00.000') + + return components + + def _parse_tzstr(self, tzstr, zero_as_utc=True): + if tzstr == b'Z' or tzstr == b'z': + return tz.UTC + + if len(tzstr) not in {3, 5, 6}: + raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters') + + if tzstr[0:1] == b'-': + mult = -1 + elif tzstr[0:1] == b'+': + mult = 1 + else: + raise ValueError('Time zone offset requires sign') + + hours = int(tzstr[1:3]) + if len(tzstr) == 3: + minutes = 0 + else: + minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):]) + + if zero_as_utc and hours == 0 and minutes == 0: + return tz.UTC + else: + if minutes > 59: + raise ValueError('Invalid minutes in time zone offset') + + if hours > 23: + raise ValueError('Invalid hours in time zone offset') + + return tz.tzoffset(None, mult * (hours * 60 + minutes) * 60) + + +DEFAULT_ISOPARSER = isoparser() +isoparse = DEFAULT_ISOPARSER.isoparse diff --git a/dateutil/relativedelta.py b/dateutil/relativedelta.py new file mode 100644 index 0000000..cd323a5 --- /dev/null +++ b/dateutil/relativedelta.py @@ -0,0 +1,599 @@ +# -*- coding: utf-8 -*- +import datetime +import calendar + +import operator +from math import copysign + +from six import integer_types +from warnings import warn + +from ._common import weekday + +MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7)) + +__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] + + +class relativedelta(object): + """ + The relativedelta type is designed to be applied to an existing datetime and + can replace specific components of that datetime, or represents an interval + of time. + + It is based on the specification of the excellent work done by M.-A. Lemburg + in his + `mx.DateTime `_ extension. + However, notice that this type does *NOT* implement the same algorithm as + his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. + + There are two different ways to build a relativedelta instance. The + first one is passing it two date/datetime classes:: + + relativedelta(datetime1, datetime2) + + The second one is passing it any number of the following keyword arguments:: + + relativedelta(arg1=x,arg2=y,arg3=z...) + + year, month, day, hour, minute, second, microsecond: + Absolute information (argument is singular); adding or subtracting a + relativedelta with absolute information does not perform an arithmetic + operation, but rather REPLACES the corresponding value in the + original datetime with the value(s) in relativedelta. + + years, months, weeks, days, hours, minutes, seconds, microseconds: + Relative information, may be negative (argument is plural); adding + or subtracting a relativedelta with relative information performs + the corresponding arithmetic operation on the original datetime value + with the information in the relativedelta. + + weekday: + One of the weekday instances (MO, TU, etc) available in the + relativedelta module. These instances may receive a parameter N, + specifying the Nth weekday, which could be positive or negative + (like MO(+1) or MO(-2)). Not specifying it is the same as specifying + +1. You can also use an integer, where 0=MO. This argument is always + relative e.g. if the calculated date is already Monday, using MO(1) + or MO(-1) won't change the day. To effectively make it absolute, use + it in combination with the day argument (e.g. day=1, MO(1) for first + Monday of the month). + + leapdays: + Will add given days to the date found, if year is a leap + year, and the date found is post 28 of february. + + yearday, nlyearday: + Set the yearday or the non-leap year day (jump leap days). + These are converted to day/month/leapdays information. + + There are relative and absolute forms of the keyword + arguments. The plural is relative, and the singular is + absolute. For each argument in the order below, the absolute form + is applied first (by setting each attribute to that value) and + then the relative form (by adding the value to the attribute). + + The order of attributes considered when this relativedelta is + added to a datetime is: + + 1. Year + 2. Month + 3. Day + 4. Hours + 5. Minutes + 6. Seconds + 7. Microseconds + + Finally, weekday is applied, using the rule described above. + + For example + + >>> from datetime import datetime + >>> from dateutil.relativedelta import relativedelta, MO + >>> dt = datetime(2018, 4, 9, 13, 37, 0) + >>> delta = relativedelta(hours=25, day=1, weekday=MO(1)) + >>> dt + delta + datetime.datetime(2018, 4, 2, 14, 37) + + First, the day is set to 1 (the first of the month), then 25 hours + are added, to get to the 2nd day and 14th hour, finally the + weekday is applied, but since the 2nd is already a Monday there is + no effect. + + """ + + def __init__(self, dt1=None, dt2=None, + years=0, months=0, days=0, leapdays=0, weeks=0, + hours=0, minutes=0, seconds=0, microseconds=0, + year=None, month=None, day=None, weekday=None, + yearday=None, nlyearday=None, + hour=None, minute=None, second=None, microsecond=None): + + if dt1 and dt2: + # datetime is a subclass of date. So both must be date + if not (isinstance(dt1, datetime.date) and + isinstance(dt2, datetime.date)): + raise TypeError("relativedelta only diffs datetime/date") + + # We allow two dates, or two datetimes, so we coerce them to be + # of the same type + if (isinstance(dt1, datetime.datetime) != + isinstance(dt2, datetime.datetime)): + if not isinstance(dt1, datetime.datetime): + dt1 = datetime.datetime.fromordinal(dt1.toordinal()) + elif not isinstance(dt2, datetime.datetime): + dt2 = datetime.datetime.fromordinal(dt2.toordinal()) + + self.years = 0 + self.months = 0 + self.days = 0 + self.leapdays = 0 + self.hours = 0 + self.minutes = 0 + self.seconds = 0 + self.microseconds = 0 + self.year = None + self.month = None + self.day = None + self.weekday = None + self.hour = None + self.minute = None + self.second = None + self.microsecond = None + self._has_time = 0 + + # Get year / month delta between the two + months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month) + self._set_months(months) + + # Remove the year/month delta so the timedelta is just well-defined + # time units (seconds, days and microseconds) + dtm = self.__radd__(dt2) + + # If we've overshot our target, make an adjustment + if dt1 < dt2: + compare = operator.gt + increment = 1 + else: + compare = operator.lt + increment = -1 + + while compare(dt1, dtm): + months += increment + self._set_months(months) + dtm = self.__radd__(dt2) + + # Get the timedelta between the "months-adjusted" date and dt1 + delta = dt1 - dtm + self.seconds = delta.seconds + delta.days * 86400 + self.microseconds = delta.microseconds + else: + # Check for non-integer values in integer-only quantities + if any(x is not None and x != int(x) for x in (years, months)): + raise ValueError("Non-integer years and months are " + "ambiguous and not currently supported.") + + # Relative information + self.years = int(years) + self.months = int(months) + self.days = days + weeks * 7 + self.leapdays = leapdays + self.hours = hours + self.minutes = minutes + self.seconds = seconds + self.microseconds = microseconds + + # Absolute information + self.year = year + self.month = month + self.day = day + self.hour = hour + self.minute = minute + self.second = second + self.microsecond = microsecond + + if any(x is not None and int(x) != x + for x in (year, month, day, hour, + minute, second, microsecond)): + # For now we'll deprecate floats - later it'll be an error. + warn("Non-integer value passed as absolute information. " + + "This is not a well-defined condition and will raise " + + "errors in future versions.", DeprecationWarning) + + if isinstance(weekday, integer_types): + self.weekday = weekdays[weekday] + else: + self.weekday = weekday + + yday = 0 + if nlyearday: + yday = nlyearday + elif yearday: + yday = yearday + if yearday > 59: + self.leapdays = -1 + if yday: + ydayidx = [31, 59, 90, 120, 151, 181, 212, + 243, 273, 304, 334, 366] + for idx, ydays in enumerate(ydayidx): + if yday <= ydays: + self.month = idx+1 + if idx == 0: + self.day = yday + else: + self.day = yday-ydayidx[idx-1] + break + else: + raise ValueError("invalid year day (%d)" % yday) + + self._fix() + + def _fix(self): + if abs(self.microseconds) > 999999: + s = _sign(self.microseconds) + div, mod = divmod(self.microseconds * s, 1000000) + self.microseconds = mod * s + self.seconds += div * s + if abs(self.seconds) > 59: + s = _sign(self.seconds) + div, mod = divmod(self.seconds * s, 60) + self.seconds = mod * s + self.minutes += div * s + if abs(self.minutes) > 59: + s = _sign(self.minutes) + div, mod = divmod(self.minutes * s, 60) + self.minutes = mod * s + self.hours += div * s + if abs(self.hours) > 23: + s = _sign(self.hours) + div, mod = divmod(self.hours * s, 24) + self.hours = mod * s + self.days += div * s + if abs(self.months) > 11: + s = _sign(self.months) + div, mod = divmod(self.months * s, 12) + self.months = mod * s + self.years += div * s + if (self.hours or self.minutes or self.seconds or self.microseconds + or self.hour is not None or self.minute is not None or + self.second is not None or self.microsecond is not None): + self._has_time = 1 + else: + self._has_time = 0 + + @property + def weeks(self): + return int(self.days / 7.0) + + @weeks.setter + def weeks(self, value): + self.days = self.days - (self.weeks * 7) + value * 7 + + def _set_months(self, months): + self.months = months + if abs(self.months) > 11: + s = _sign(self.months) + div, mod = divmod(self.months * s, 12) + self.months = mod * s + self.years = div * s + else: + self.years = 0 + + def normalized(self): + """ + Return a version of this object represented entirely using integer + values for the relative attributes. + + >>> relativedelta(days=1.5, hours=2).normalized() + relativedelta(days=+1, hours=+14) + + :return: + Returns a :class:`dateutil.relativedelta.relativedelta` object. + """ + # Cascade remainders down (rounding each to roughly nearest microsecond) + days = int(self.days) + + hours_f = round(self.hours + 24 * (self.days - days), 11) + hours = int(hours_f) + + minutes_f = round(self.minutes + 60 * (hours_f - hours), 10) + minutes = int(minutes_f) + + seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8) + seconds = int(seconds_f) + + microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds)) + + # Constructor carries overflow back up with call to _fix() + return self.__class__(years=self.years, months=self.months, + days=days, hours=hours, minutes=minutes, + seconds=seconds, microseconds=microseconds, + leapdays=self.leapdays, year=self.year, + month=self.month, day=self.day, + weekday=self.weekday, hour=self.hour, + minute=self.minute, second=self.second, + microsecond=self.microsecond) + + def __add__(self, other): + if isinstance(other, relativedelta): + return self.__class__(years=other.years + self.years, + months=other.months + self.months, + days=other.days + self.days, + hours=other.hours + self.hours, + minutes=other.minutes + self.minutes, + seconds=other.seconds + self.seconds, + microseconds=(other.microseconds + + self.microseconds), + leapdays=other.leapdays or self.leapdays, + year=(other.year if other.year is not None + else self.year), + month=(other.month if other.month is not None + else self.month), + day=(other.day if other.day is not None + else self.day), + weekday=(other.weekday if other.weekday is not None + else self.weekday), + hour=(other.hour if other.hour is not None + else self.hour), + minute=(other.minute if other.minute is not None + else self.minute), + second=(other.second if other.second is not None + else self.second), + microsecond=(other.microsecond if other.microsecond + is not None else + self.microsecond)) + if isinstance(other, datetime.timedelta): + return self.__class__(years=self.years, + months=self.months, + days=self.days + other.days, + hours=self.hours, + minutes=self.minutes, + seconds=self.seconds + other.seconds, + microseconds=self.microseconds + other.microseconds, + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + if not isinstance(other, datetime.date): + return NotImplemented + elif self._has_time and not isinstance(other, datetime.datetime): + other = datetime.datetime.fromordinal(other.toordinal()) + year = (self.year or other.year)+self.years + month = self.month or other.month + if self.months: + assert 1 <= abs(self.months) <= 12 + month += self.months + if month > 12: + year += 1 + month -= 12 + elif month < 1: + year -= 1 + month += 12 + day = min(calendar.monthrange(year, month)[1], + self.day or other.day) + repl = {"year": year, "month": month, "day": day} + for attr in ["hour", "minute", "second", "microsecond"]: + value = getattr(self, attr) + if value is not None: + repl[attr] = value + days = self.days + if self.leapdays and month > 2 and calendar.isleap(year): + days += self.leapdays + ret = (other.replace(**repl) + + datetime.timedelta(days=days, + hours=self.hours, + minutes=self.minutes, + seconds=self.seconds, + microseconds=self.microseconds)) + if self.weekday: + weekday, nth = self.weekday.weekday, self.weekday.n or 1 + jumpdays = (abs(nth) - 1) * 7 + if nth > 0: + jumpdays += (7 - ret.weekday() + weekday) % 7 + else: + jumpdays += (ret.weekday() - weekday) % 7 + jumpdays *= -1 + ret += datetime.timedelta(days=jumpdays) + return ret + + def __radd__(self, other): + return self.__add__(other) + + def __rsub__(self, other): + return self.__neg__().__radd__(other) + + def __sub__(self, other): + if not isinstance(other, relativedelta): + return NotImplemented # In case the other object defines __rsub__ + return self.__class__(years=self.years - other.years, + months=self.months - other.months, + days=self.days - other.days, + hours=self.hours - other.hours, + minutes=self.minutes - other.minutes, + seconds=self.seconds - other.seconds, + microseconds=self.microseconds - other.microseconds, + leapdays=self.leapdays or other.leapdays, + year=(self.year if self.year is not None + else other.year), + month=(self.month if self.month is not None else + other.month), + day=(self.day if self.day is not None else + other.day), + weekday=(self.weekday if self.weekday is not None else + other.weekday), + hour=(self.hour if self.hour is not None else + other.hour), + minute=(self.minute if self.minute is not None else + other.minute), + second=(self.second if self.second is not None else + other.second), + microsecond=(self.microsecond if self.microsecond + is not None else + other.microsecond)) + + def __abs__(self): + return self.__class__(years=abs(self.years), + months=abs(self.months), + days=abs(self.days), + hours=abs(self.hours), + minutes=abs(self.minutes), + seconds=abs(self.seconds), + microseconds=abs(self.microseconds), + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + def __neg__(self): + return self.__class__(years=-self.years, + months=-self.months, + days=-self.days, + hours=-self.hours, + minutes=-self.minutes, + seconds=-self.seconds, + microseconds=-self.microseconds, + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + def __bool__(self): + return not (not self.years and + not self.months and + not self.days and + not self.hours and + not self.minutes and + not self.seconds and + not self.microseconds and + not self.leapdays and + self.year is None and + self.month is None and + self.day is None and + self.weekday is None and + self.hour is None and + self.minute is None and + self.second is None and + self.microsecond is None) + # Compatibility with Python 2.x + __nonzero__ = __bool__ + + def __mul__(self, other): + try: + f = float(other) + except TypeError: + return NotImplemented + + return self.__class__(years=int(self.years * f), + months=int(self.months * f), + days=int(self.days * f), + hours=int(self.hours * f), + minutes=int(self.minutes * f), + seconds=int(self.seconds * f), + microseconds=int(self.microseconds * f), + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + __rmul__ = __mul__ + + def __eq__(self, other): + if not isinstance(other, relativedelta): + return NotImplemented + if self.weekday or other.weekday: + if not self.weekday or not other.weekday: + return False + if self.weekday.weekday != other.weekday.weekday: + return False + n1, n2 = self.weekday.n, other.weekday.n + if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)): + return False + return (self.years == other.years and + self.months == other.months and + self.days == other.days and + self.hours == other.hours and + self.minutes == other.minutes and + self.seconds == other.seconds and + self.microseconds == other.microseconds and + self.leapdays == other.leapdays and + self.year == other.year and + self.month == other.month and + self.day == other.day and + self.hour == other.hour and + self.minute == other.minute and + self.second == other.second and + self.microsecond == other.microsecond) + + def __hash__(self): + return hash(( + self.weekday, + self.years, + self.months, + self.days, + self.hours, + self.minutes, + self.seconds, + self.microseconds, + self.leapdays, + self.year, + self.month, + self.day, + self.hour, + self.minute, + self.second, + self.microsecond, + )) + + def __ne__(self, other): + return not self.__eq__(other) + + def __div__(self, other): + try: + reciprocal = 1 / float(other) + except TypeError: + return NotImplemented + + return self.__mul__(reciprocal) + + __truediv__ = __div__ + + def __repr__(self): + l = [] + for attr in ["years", "months", "days", "leapdays", + "hours", "minutes", "seconds", "microseconds"]: + value = getattr(self, attr) + if value: + l.append("{attr}={value:+g}".format(attr=attr, value=value)) + for attr in ["year", "month", "day", "weekday", + "hour", "minute", "second", "microsecond"]: + value = getattr(self, attr) + if value is not None: + l.append("{attr}={value}".format(attr=attr, value=repr(value))) + return "{classname}({attrs})".format(classname=self.__class__.__name__, + attrs=", ".join(l)) + + +def _sign(x): + return int(copysign(1, x)) + +# vim:ts=4:sw=4:et diff --git a/dateutil/rrule.py b/dateutil/rrule.py new file mode 100644 index 0000000..571a0d2 --- /dev/null +++ b/dateutil/rrule.py @@ -0,0 +1,1737 @@ +# -*- coding: utf-8 -*- +""" +The rrule module offers a small, complete, and very fast, implementation of +the recurrence rules documented in the +`iCalendar RFC `_, +including support for caching of results. +""" +import calendar +import datetime +import heapq +import itertools +import re +import sys +from functools import wraps +# For warning about deprecation of until and count +from warnings import warn + +from six import advance_iterator, integer_types + +from six.moves import _thread, range + +from ._common import weekday as weekdaybase + +try: + from math import gcd +except ImportError: + from fractions import gcd + +__all__ = ["rrule", "rruleset", "rrulestr", + "YEARLY", "MONTHLY", "WEEKLY", "DAILY", + "HOURLY", "MINUTELY", "SECONDLY", + "MO", "TU", "WE", "TH", "FR", "SA", "SU"] + +# Every mask is 7 days longer to handle cross-year weekly periods. +M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30 + + [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7) +M365MASK = list(M366MASK) +M29, M30, M31 = list(range(1, 30)), list(range(1, 31)), list(range(1, 32)) +MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) +MDAY365MASK = list(MDAY366MASK) +M29, M30, M31 = list(range(-29, 0)), list(range(-30, 0)), list(range(-31, 0)) +NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) +NMDAY365MASK = list(NMDAY366MASK) +M366RANGE = (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366) +M365RANGE = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365) +WDAYMASK = [0, 1, 2, 3, 4, 5, 6]*55 +del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31] +MDAY365MASK = tuple(MDAY365MASK) +M365MASK = tuple(M365MASK) + +FREQNAMES = ['YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', 'HOURLY', 'MINUTELY', 'SECONDLY'] + +(YEARLY, + MONTHLY, + WEEKLY, + DAILY, + HOURLY, + MINUTELY, + SECONDLY) = list(range(7)) + +# Imported on demand. +easter = None +parser = None + + +class weekday(weekdaybase): + """ + This version of weekday does not allow n = 0. + """ + def __init__(self, wkday, n=None): + if n == 0: + raise ValueError("Can't create weekday with n==0") + + super(weekday, self).__init__(wkday, n) + + +MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7)) + + +def _invalidates_cache(f): + """ + Decorator for rruleset methods which may invalidate the + cached length. + """ + @wraps(f) + def inner_func(self, *args, **kwargs): + rv = f(self, *args, **kwargs) + self._invalidate_cache() + return rv + + return inner_func + + +class rrulebase(object): + def __init__(self, cache=False): + if cache: + self._cache = [] + self._cache_lock = _thread.allocate_lock() + self._invalidate_cache() + else: + self._cache = None + self._cache_complete = False + self._len = None + + def __iter__(self): + if self._cache_complete: + return iter(self._cache) + elif self._cache is None: + return self._iter() + else: + return self._iter_cached() + + def _invalidate_cache(self): + if self._cache is not None: + self._cache = [] + self._cache_complete = False + self._cache_gen = self._iter() + + if self._cache_lock.locked(): + self._cache_lock.release() + + self._len = None + + def _iter_cached(self): + i = 0 + gen = self._cache_gen + cache = self._cache + acquire = self._cache_lock.acquire + release = self._cache_lock.release + while gen: + if i == len(cache): + acquire() + if self._cache_complete: + break + try: + for j in range(10): + cache.append(advance_iterator(gen)) + except StopIteration: + self._cache_gen = gen = None + self._cache_complete = True + break + release() + yield cache[i] + i += 1 + while i < self._len: + yield cache[i] + i += 1 + + def __getitem__(self, item): + if self._cache_complete: + return self._cache[item] + elif isinstance(item, slice): + if item.step and item.step < 0: + return list(iter(self))[item] + else: + return list(itertools.islice(self, + item.start or 0, + item.stop or sys.maxsize, + item.step or 1)) + elif item >= 0: + gen = iter(self) + try: + for i in range(item+1): + res = advance_iterator(gen) + except StopIteration: + raise IndexError + return res + else: + return list(iter(self))[item] + + def __contains__(self, item): + if self._cache_complete: + return item in self._cache + else: + for i in self: + if i == item: + return True + elif i > item: + return False + return False + + # __len__() introduces a large performance penalty. + def count(self): + """ Returns the number of recurrences in this set. It will have go + through the whole recurrence, if this hasn't been done before. """ + if self._len is None: + for x in self: + pass + return self._len + + def before(self, dt, inc=False): + """ Returns the last recurrence before the given datetime instance. The + inc keyword defines what happens if dt is an occurrence. With + inc=True, if dt itself is an occurrence, it will be returned. """ + if self._cache_complete: + gen = self._cache + else: + gen = self + last = None + if inc: + for i in gen: + if i > dt: + break + last = i + else: + for i in gen: + if i >= dt: + break + last = i + return last + + def after(self, dt, inc=False): + """ Returns the first recurrence after the given datetime instance. The + inc keyword defines what happens if dt is an occurrence. With + inc=True, if dt itself is an occurrence, it will be returned. """ + if self._cache_complete: + gen = self._cache + else: + gen = self + if inc: + for i in gen: + if i >= dt: + return i + else: + for i in gen: + if i > dt: + return i + return None + + def xafter(self, dt, count=None, inc=False): + """ + Generator which yields up to `count` recurrences after the given + datetime instance, equivalent to `after`. + + :param dt: + The datetime at which to start generating recurrences. + + :param count: + The maximum number of recurrences to generate. If `None` (default), + dates are generated until the recurrence rule is exhausted. + + :param inc: + If `dt` is an instance of the rule and `inc` is `True`, it is + included in the output. + + :yields: Yields a sequence of `datetime` objects. + """ + + if self._cache_complete: + gen = self._cache + else: + gen = self + + # Select the comparison function + if inc: + comp = lambda dc, dtc: dc >= dtc + else: + comp = lambda dc, dtc: dc > dtc + + # Generate dates + n = 0 + for d in gen: + if comp(d, dt): + if count is not None: + n += 1 + if n > count: + break + + yield d + + def between(self, after, before, inc=False, count=1): + """ Returns all the occurrences of the rrule between after and before. + The inc keyword defines what happens if after and/or before are + themselves occurrences. With inc=True, they will be included in the + list, if they are found in the recurrence set. """ + if self._cache_complete: + gen = self._cache + else: + gen = self + started = False + l = [] + if inc: + for i in gen: + if i > before: + break + elif not started: + if i >= after: + started = True + l.append(i) + else: + l.append(i) + else: + for i in gen: + if i >= before: + break + elif not started: + if i > after: + started = True + l.append(i) + else: + l.append(i) + return l + + +class rrule(rrulebase): + """ + That's the base of the rrule operation. It accepts all the keywords + defined in the RFC as its constructor parameters (except byday, + which was renamed to byweekday) and more. The constructor prototype is:: + + rrule(freq) + + Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, + or SECONDLY. + + .. note:: + Per RFC section 3.3.10, recurrence instances falling on invalid dates + and times are ignored rather than coerced: + + Recurrence rules may generate recurrence instances with an invalid + date (e.g., February 30) or nonexistent local time (e.g., 1:30 AM + on a day where the local time is moved forward by an hour at 1:00 + AM). Such recurrence instances MUST be ignored and MUST NOT be + counted as part of the recurrence set. + + This can lead to possibly surprising behavior when, for example, the + start date occurs at the end of the month: + + >>> from dateutil.rrule import rrule, MONTHLY + >>> from datetime import datetime + >>> start_date = datetime(2014, 12, 31) + >>> list(rrule(freq=MONTHLY, count=4, dtstart=start_date)) + ... # doctest: +NORMALIZE_WHITESPACE + [datetime.datetime(2014, 12, 31, 0, 0), + datetime.datetime(2015, 1, 31, 0, 0), + datetime.datetime(2015, 3, 31, 0, 0), + datetime.datetime(2015, 5, 31, 0, 0)] + + Additionally, it supports the following keyword arguments: + + :param dtstart: + The recurrence start. Besides being the base for the recurrence, + missing parameters in the final recurrence instances will also be + extracted from this date. If not given, datetime.now() will be used + instead. + :param interval: + The interval between each freq iteration. For example, when using + YEARLY, an interval of 2 means once every two years, but with HOURLY, + it means once every two hours. The default interval is 1. + :param wkst: + The week start day. Must be one of the MO, TU, WE constants, or an + integer, specifying the first day of the week. This will affect + recurrences based on weekly periods. The default week start is got + from calendar.firstweekday(), and may be modified by + calendar.setfirstweekday(). + :param count: + If given, this determines how many occurrences will be generated. + + .. note:: + As of version 2.5.0, the use of the keyword ``until`` in conjunction + with ``count`` is deprecated, to make sure ``dateutil`` is fully + compliant with `RFC-5545 Sec. 3.3.10 `_. Therefore, ``until`` and ``count`` + **must not** occur in the same call to ``rrule``. + :param until: + If given, this must be a datetime instance specifying the upper-bound + limit of the recurrence. The last recurrence in the rule is the greatest + datetime that is less than or equal to the value specified in the + ``until`` parameter. + + .. note:: + As of version 2.5.0, the use of the keyword ``until`` in conjunction + with ``count`` is deprecated, to make sure ``dateutil`` is fully + compliant with `RFC-5545 Sec. 3.3.10 `_. Therefore, ``until`` and ``count`` + **must not** occur in the same call to ``rrule``. + :param bysetpos: + If given, it must be either an integer, or a sequence of integers, + positive or negative. Each given integer will specify an occurrence + number, corresponding to the nth occurrence of the rule inside the + frequency period. For example, a bysetpos of -1 if combined with a + MONTHLY frequency, and a byweekday of (MO, TU, WE, TH, FR), will + result in the last work day of every month. + :param bymonth: + If given, it must be either an integer, or a sequence of integers, + meaning the months to apply the recurrence to. + :param bymonthday: + If given, it must be either an integer, or a sequence of integers, + meaning the month days to apply the recurrence to. + :param byyearday: + If given, it must be either an integer, or a sequence of integers, + meaning the year days to apply the recurrence to. + :param byeaster: + If given, it must be either an integer, or a sequence of integers, + positive or negative. Each integer will define an offset from the + Easter Sunday. Passing the offset 0 to byeaster will yield the Easter + Sunday itself. This is an extension to the RFC specification. + :param byweekno: + If given, it must be either an integer, or a sequence of integers, + meaning the week numbers to apply the recurrence to. Week numbers + have the meaning described in ISO8601, that is, the first week of + the year is that containing at least four days of the new year. + :param byweekday: + If given, it must be either an integer (0 == MO), a sequence of + integers, one of the weekday constants (MO, TU, etc), or a sequence + of these constants. When given, these variables will define the + weekdays where the recurrence will be applied. It's also possible to + use an argument n for the weekday instances, which will mean the nth + occurrence of this weekday in the period. For example, with MONTHLY, + or with YEARLY and BYMONTH, using FR(+1) in byweekday will specify the + first friday of the month where the recurrence happens. Notice that in + the RFC documentation, this is specified as BYDAY, but was renamed to + avoid the ambiguity of that keyword. + :param byhour: + If given, it must be either an integer, or a sequence of integers, + meaning the hours to apply the recurrence to. + :param byminute: + If given, it must be either an integer, or a sequence of integers, + meaning the minutes to apply the recurrence to. + :param bysecond: + If given, it must be either an integer, or a sequence of integers, + meaning the seconds to apply the recurrence to. + :param cache: + If given, it must be a boolean value specifying to enable or disable + caching of results. If you will use the same rrule instance multiple + times, enabling caching will improve the performance considerably. + """ + def __init__(self, freq, dtstart=None, + interval=1, wkst=None, count=None, until=None, bysetpos=None, + bymonth=None, bymonthday=None, byyearday=None, byeaster=None, + byweekno=None, byweekday=None, + byhour=None, byminute=None, bysecond=None, + cache=False): + super(rrule, self).__init__(cache) + global easter + if not dtstart: + if until and until.tzinfo: + dtstart = datetime.datetime.now(tz=until.tzinfo).replace(microsecond=0) + else: + dtstart = datetime.datetime.now().replace(microsecond=0) + elif not isinstance(dtstart, datetime.datetime): + dtstart = datetime.datetime.fromordinal(dtstart.toordinal()) + else: + dtstart = dtstart.replace(microsecond=0) + self._dtstart = dtstart + self._tzinfo = dtstart.tzinfo + self._freq = freq + self._interval = interval + self._count = count + + # Cache the original byxxx rules, if they are provided, as the _byxxx + # attributes do not necessarily map to the inputs, and this can be + # a problem in generating the strings. Only store things if they've + # been supplied (the string retrieval will just use .get()) + self._original_rule = {} + + if until and not isinstance(until, datetime.datetime): + until = datetime.datetime.fromordinal(until.toordinal()) + self._until = until + + if self._dtstart and self._until: + if (self._dtstart.tzinfo is not None) != (self._until.tzinfo is not None): + # According to RFC5545 Section 3.3.10: + # https://tools.ietf.org/html/rfc5545#section-3.3.10 + # + # > If the "DTSTART" property is specified as a date with UTC + # > time or a date with local time and time zone reference, + # > then the UNTIL rule part MUST be specified as a date with + # > UTC time. + raise ValueError( + 'RRULE UNTIL values must be specified in UTC when DTSTART ' + 'is timezone-aware' + ) + + if count is not None and until: + warn("Using both 'count' and 'until' is inconsistent with RFC 5545" + " and has been deprecated in dateutil. Future versions will " + "raise an error.", DeprecationWarning) + + if wkst is None: + self._wkst = calendar.firstweekday() + elif isinstance(wkst, integer_types): + self._wkst = wkst + else: + self._wkst = wkst.weekday + + if bysetpos is None: + self._bysetpos = None + elif isinstance(bysetpos, integer_types): + if bysetpos == 0 or not (-366 <= bysetpos <= 366): + raise ValueError("bysetpos must be between 1 and 366, " + "or between -366 and -1") + self._bysetpos = (bysetpos,) + else: + self._bysetpos = tuple(bysetpos) + for pos in self._bysetpos: + if pos == 0 or not (-366 <= pos <= 366): + raise ValueError("bysetpos must be between 1 and 366, " + "or between -366 and -1") + + if self._bysetpos: + self._original_rule['bysetpos'] = self._bysetpos + + if (byweekno is None and byyearday is None and bymonthday is None and + byweekday is None and byeaster is None): + if freq == YEARLY: + if bymonth is None: + bymonth = dtstart.month + self._original_rule['bymonth'] = None + bymonthday = dtstart.day + self._original_rule['bymonthday'] = None + elif freq == MONTHLY: + bymonthday = dtstart.day + self._original_rule['bymonthday'] = None + elif freq == WEEKLY: + byweekday = dtstart.weekday() + self._original_rule['byweekday'] = None + + # bymonth + if bymonth is None: + self._bymonth = None + else: + if isinstance(bymonth, integer_types): + bymonth = (bymonth,) + + self._bymonth = tuple(sorted(set(bymonth))) + + if 'bymonth' not in self._original_rule: + self._original_rule['bymonth'] = self._bymonth + + # byyearday + if byyearday is None: + self._byyearday = None + else: + if isinstance(byyearday, integer_types): + byyearday = (byyearday,) + + self._byyearday = tuple(sorted(set(byyearday))) + self._original_rule['byyearday'] = self._byyearday + + # byeaster + if byeaster is not None: + if not easter: + from dateutil import easter + if isinstance(byeaster, integer_types): + self._byeaster = (byeaster,) + else: + self._byeaster = tuple(sorted(byeaster)) + + self._original_rule['byeaster'] = self._byeaster + else: + self._byeaster = None + + # bymonthday + if bymonthday is None: + self._bymonthday = () + self._bynmonthday = () + else: + if isinstance(bymonthday, integer_types): + bymonthday = (bymonthday,) + + bymonthday = set(bymonthday) # Ensure it's unique + + self._bymonthday = tuple(sorted(x for x in bymonthday if x > 0)) + self._bynmonthday = tuple(sorted(x for x in bymonthday if x < 0)) + + # Storing positive numbers first, then negative numbers + if 'bymonthday' not in self._original_rule: + self._original_rule['bymonthday'] = tuple( + itertools.chain(self._bymonthday, self._bynmonthday)) + + # byweekno + if byweekno is None: + self._byweekno = None + else: + if isinstance(byweekno, integer_types): + byweekno = (byweekno,) + + self._byweekno = tuple(sorted(set(byweekno))) + + self._original_rule['byweekno'] = self._byweekno + + # byweekday / bynweekday + if byweekday is None: + self._byweekday = None + self._bynweekday = None + else: + # If it's one of the valid non-sequence types, convert to a + # single-element sequence before the iterator that builds the + # byweekday set. + if isinstance(byweekday, integer_types) or hasattr(byweekday, "n"): + byweekday = (byweekday,) + + self._byweekday = set() + self._bynweekday = set() + for wday in byweekday: + if isinstance(wday, integer_types): + self._byweekday.add(wday) + elif not wday.n or freq > MONTHLY: + self._byweekday.add(wday.weekday) + else: + self._bynweekday.add((wday.weekday, wday.n)) + + if not self._byweekday: + self._byweekday = None + elif not self._bynweekday: + self._bynweekday = None + + if self._byweekday is not None: + self._byweekday = tuple(sorted(self._byweekday)) + orig_byweekday = [weekday(x) for x in self._byweekday] + else: + orig_byweekday = () + + if self._bynweekday is not None: + self._bynweekday = tuple(sorted(self._bynweekday)) + orig_bynweekday = [weekday(*x) for x in self._bynweekday] + else: + orig_bynweekday = () + + if 'byweekday' not in self._original_rule: + self._original_rule['byweekday'] = tuple(itertools.chain( + orig_byweekday, orig_bynweekday)) + + # byhour + if byhour is None: + if freq < HOURLY: + self._byhour = {dtstart.hour} + else: + self._byhour = None + else: + if isinstance(byhour, integer_types): + byhour = (byhour,) + + if freq == HOURLY: + self._byhour = self.__construct_byset(start=dtstart.hour, + byxxx=byhour, + base=24) + else: + self._byhour = set(byhour) + + self._byhour = tuple(sorted(self._byhour)) + self._original_rule['byhour'] = self._byhour + + # byminute + if byminute is None: + if freq < MINUTELY: + self._byminute = {dtstart.minute} + else: + self._byminute = None + else: + if isinstance(byminute, integer_types): + byminute = (byminute,) + + if freq == MINUTELY: + self._byminute = self.__construct_byset(start=dtstart.minute, + byxxx=byminute, + base=60) + else: + self._byminute = set(byminute) + + self._byminute = tuple(sorted(self._byminute)) + self._original_rule['byminute'] = self._byminute + + # bysecond + if bysecond is None: + if freq < SECONDLY: + self._bysecond = ((dtstart.second,)) + else: + self._bysecond = None + else: + if isinstance(bysecond, integer_types): + bysecond = (bysecond,) + + self._bysecond = set(bysecond) + + if freq == SECONDLY: + self._bysecond = self.__construct_byset(start=dtstart.second, + byxxx=bysecond, + base=60) + else: + self._bysecond = set(bysecond) + + self._bysecond = tuple(sorted(self._bysecond)) + self._original_rule['bysecond'] = self._bysecond + + if self._freq >= HOURLY: + self._timeset = None + else: + self._timeset = [] + for hour in self._byhour: + for minute in self._byminute: + for second in self._bysecond: + self._timeset.append( + datetime.time(hour, minute, second, + tzinfo=self._tzinfo)) + self._timeset.sort() + self._timeset = tuple(self._timeset) + + def __str__(self): + """ + Output a string that would generate this RRULE if passed to rrulestr. + This is mostly compatible with RFC5545, except for the + dateutil-specific extension BYEASTER. + """ + + output = [] + h, m, s = [None] * 3 + if self._dtstart: + output.append(self._dtstart.strftime('DTSTART:%Y%m%dT%H%M%S')) + h, m, s = self._dtstart.timetuple()[3:6] + + parts = ['FREQ=' + FREQNAMES[self._freq]] + if self._interval != 1: + parts.append('INTERVAL=' + str(self._interval)) + + if self._wkst: + parts.append('WKST=' + repr(weekday(self._wkst))[0:2]) + + if self._count is not None: + parts.append('COUNT=' + str(self._count)) + + if self._until: + parts.append(self._until.strftime('UNTIL=%Y%m%dT%H%M%S')) + + if self._original_rule.get('byweekday') is not None: + # The str() method on weekday objects doesn't generate + # RFC5545-compliant strings, so we should modify that. + original_rule = dict(self._original_rule) + wday_strings = [] + for wday in original_rule['byweekday']: + if wday.n: + wday_strings.append('{n:+d}{wday}'.format( + n=wday.n, + wday=repr(wday)[0:2])) + else: + wday_strings.append(repr(wday)) + + original_rule['byweekday'] = wday_strings + else: + original_rule = self._original_rule + + partfmt = '{name}={vals}' + for name, key in [('BYSETPOS', 'bysetpos'), + ('BYMONTH', 'bymonth'), + ('BYMONTHDAY', 'bymonthday'), + ('BYYEARDAY', 'byyearday'), + ('BYWEEKNO', 'byweekno'), + ('BYDAY', 'byweekday'), + ('BYHOUR', 'byhour'), + ('BYMINUTE', 'byminute'), + ('BYSECOND', 'bysecond'), + ('BYEASTER', 'byeaster')]: + value = original_rule.get(key) + if value: + parts.append(partfmt.format(name=name, vals=(','.join(str(v) + for v in value)))) + + output.append('RRULE:' + ';'.join(parts)) + return '\n'.join(output) + + def replace(self, **kwargs): + """Return new rrule with same attributes except for those attributes given new + values by whichever keyword arguments are specified.""" + new_kwargs = {"interval": self._interval, + "count": self._count, + "dtstart": self._dtstart, + "freq": self._freq, + "until": self._until, + "wkst": self._wkst, + "cache": False if self._cache is None else True } + new_kwargs.update(self._original_rule) + new_kwargs.update(kwargs) + return rrule(**new_kwargs) + + def _iter(self): + year, month, day, hour, minute, second, weekday, yearday, _ = \ + self._dtstart.timetuple() + + # Some local variables to speed things up a bit + freq = self._freq + interval = self._interval + wkst = self._wkst + until = self._until + bymonth = self._bymonth + byweekno = self._byweekno + byyearday = self._byyearday + byweekday = self._byweekday + byeaster = self._byeaster + bymonthday = self._bymonthday + bynmonthday = self._bynmonthday + bysetpos = self._bysetpos + byhour = self._byhour + byminute = self._byminute + bysecond = self._bysecond + + ii = _iterinfo(self) + ii.rebuild(year, month) + + getdayset = {YEARLY: ii.ydayset, + MONTHLY: ii.mdayset, + WEEKLY: ii.wdayset, + DAILY: ii.ddayset, + HOURLY: ii.ddayset, + MINUTELY: ii.ddayset, + SECONDLY: ii.ddayset}[freq] + + if freq < HOURLY: + timeset = self._timeset + else: + gettimeset = {HOURLY: ii.htimeset, + MINUTELY: ii.mtimeset, + SECONDLY: ii.stimeset}[freq] + if ((freq >= HOURLY and + self._byhour and hour not in self._byhour) or + (freq >= MINUTELY and + self._byminute and minute not in self._byminute) or + (freq >= SECONDLY and + self._bysecond and second not in self._bysecond)): + timeset = () + else: + timeset = gettimeset(hour, minute, second) + + total = 0 + count = self._count + while True: + # Get dayset with the right frequency + dayset, start, end = getdayset(year, month, day) + + # Do the "hard" work ;-) + filtered = False + for i in dayset[start:end]: + if ((bymonth and ii.mmask[i] not in bymonth) or + (byweekno and not ii.wnomask[i]) or + (byweekday and ii.wdaymask[i] not in byweekday) or + (ii.nwdaymask and not ii.nwdaymask[i]) or + (byeaster and not ii.eastermask[i]) or + ((bymonthday or bynmonthday) and + ii.mdaymask[i] not in bymonthday and + ii.nmdaymask[i] not in bynmonthday) or + (byyearday and + ((i < ii.yearlen and i+1 not in byyearday and + -ii.yearlen+i not in byyearday) or + (i >= ii.yearlen and i+1-ii.yearlen not in byyearday and + -ii.nextyearlen+i-ii.yearlen not in byyearday)))): + dayset[i] = None + filtered = True + + # Output results + if bysetpos and timeset: + poslist = [] + for pos in bysetpos: + if pos < 0: + daypos, timepos = divmod(pos, len(timeset)) + else: + daypos, timepos = divmod(pos-1, len(timeset)) + try: + i = [x for x in dayset[start:end] + if x is not None][daypos] + time = timeset[timepos] + except IndexError: + pass + else: + date = datetime.date.fromordinal(ii.yearordinal+i) + res = datetime.datetime.combine(date, time) + if res not in poslist: + poslist.append(res) + poslist.sort() + for res in poslist: + if until and res > until: + self._len = total + return + elif res >= self._dtstart: + if count is not None: + count -= 1 + if count < 0: + self._len = total + return + total += 1 + yield res + else: + for i in dayset[start:end]: + if i is not None: + date = datetime.date.fromordinal(ii.yearordinal + i) + for time in timeset: + res = datetime.datetime.combine(date, time) + if until and res > until: + self._len = total + return + elif res >= self._dtstart: + if count is not None: + count -= 1 + if count < 0: + self._len = total + return + + total += 1 + yield res + + # Handle frequency and interval + fixday = False + if freq == YEARLY: + year += interval + if year > datetime.MAXYEAR: + self._len = total + return + ii.rebuild(year, month) + elif freq == MONTHLY: + month += interval + if month > 12: + div, mod = divmod(month, 12) + month = mod + year += div + if month == 0: + month = 12 + year -= 1 + if year > datetime.MAXYEAR: + self._len = total + return + ii.rebuild(year, month) + elif freq == WEEKLY: + if wkst > weekday: + day += -(weekday+1+(6-wkst))+self._interval*7 + else: + day += -(weekday-wkst)+self._interval*7 + weekday = wkst + fixday = True + elif freq == DAILY: + day += interval + fixday = True + elif freq == HOURLY: + if filtered: + # Jump to one iteration before next day + hour += ((23-hour)//interval)*interval + + if byhour: + ndays, hour = self.__mod_distance(value=hour, + byxxx=self._byhour, + base=24) + else: + ndays, hour = divmod(hour+interval, 24) + + if ndays: + day += ndays + fixday = True + + timeset = gettimeset(hour, minute, second) + elif freq == MINUTELY: + if filtered: + # Jump to one iteration before next day + minute += ((1439-(hour*60+minute))//interval)*interval + + valid = False + rep_rate = (24*60) + for j in range(rep_rate // gcd(interval, rep_rate)): + if byminute: + nhours, minute = \ + self.__mod_distance(value=minute, + byxxx=self._byminute, + base=60) + else: + nhours, minute = divmod(minute+interval, 60) + + div, hour = divmod(hour+nhours, 24) + if div: + day += div + fixday = True + filtered = False + + if not byhour or hour in byhour: + valid = True + break + + if not valid: + raise ValueError('Invalid combination of interval and ' + + 'byhour resulting in empty rule.') + + timeset = gettimeset(hour, minute, second) + elif freq == SECONDLY: + if filtered: + # Jump to one iteration before next day + second += (((86399 - (hour * 3600 + minute * 60 + second)) + // interval) * interval) + + rep_rate = (24 * 3600) + valid = False + for j in range(0, rep_rate // gcd(interval, rep_rate)): + if bysecond: + nminutes, second = \ + self.__mod_distance(value=second, + byxxx=self._bysecond, + base=60) + else: + nminutes, second = divmod(second+interval, 60) + + div, minute = divmod(minute+nminutes, 60) + if div: + hour += div + div, hour = divmod(hour, 24) + if div: + day += div + fixday = True + + if ((not byhour or hour in byhour) and + (not byminute or minute in byminute) and + (not bysecond or second in bysecond)): + valid = True + break + + if not valid: + raise ValueError('Invalid combination of interval, ' + + 'byhour and byminute resulting in empty' + + ' rule.') + + timeset = gettimeset(hour, minute, second) + + if fixday and day > 28: + daysinmonth = calendar.monthrange(year, month)[1] + if day > daysinmonth: + while day > daysinmonth: + day -= daysinmonth + month += 1 + if month == 13: + month = 1 + year += 1 + if year > datetime.MAXYEAR: + self._len = total + return + daysinmonth = calendar.monthrange(year, month)[1] + ii.rebuild(year, month) + + def __construct_byset(self, start, byxxx, base): + """ + If a `BYXXX` sequence is passed to the constructor at the same level as + `FREQ` (e.g. `FREQ=HOURLY,BYHOUR={2,4,7},INTERVAL=3`), there are some + specifications which cannot be reached given some starting conditions. + + This occurs whenever the interval is not coprime with the base of a + given unit and the difference between the starting position and the + ending position is not coprime with the greatest common denominator + between the interval and the base. For example, with a FREQ of hourly + starting at 17:00 and an interval of 4, the only valid values for + BYHOUR would be {21, 1, 5, 9, 13, 17}, because 4 and 24 are not + coprime. + + :param start: + Specifies the starting position. + :param byxxx: + An iterable containing the list of allowed values. + :param base: + The largest allowable value for the specified frequency (e.g. + 24 hours, 60 minutes). + + This does not preserve the type of the iterable, returning a set, since + the values should be unique and the order is irrelevant, this will + speed up later lookups. + + In the event of an empty set, raises a :exception:`ValueError`, as this + results in an empty rrule. + """ + + cset = set() + + # Support a single byxxx value. + if isinstance(byxxx, integer_types): + byxxx = (byxxx, ) + + for num in byxxx: + i_gcd = gcd(self._interval, base) + # Use divmod rather than % because we need to wrap negative nums. + if i_gcd == 1 or divmod(num - start, i_gcd)[1] == 0: + cset.add(num) + + if len(cset) == 0: + raise ValueError("Invalid rrule byxxx generates an empty set.") + + return cset + + def __mod_distance(self, value, byxxx, base): + """ + Calculates the next value in a sequence where the `FREQ` parameter is + specified along with a `BYXXX` parameter at the same "level" + (e.g. `HOURLY` specified with `BYHOUR`). + + :param value: + The old value of the component. + :param byxxx: + The `BYXXX` set, which should have been generated by + `rrule._construct_byset`, or something else which checks that a + valid rule is present. + :param base: + The largest allowable value for the specified frequency (e.g. + 24 hours, 60 minutes). + + If a valid value is not found after `base` iterations (the maximum + number before the sequence would start to repeat), this raises a + :exception:`ValueError`, as no valid values were found. + + This returns a tuple of `divmod(n*interval, base)`, where `n` is the + smallest number of `interval` repetitions until the next specified + value in `byxxx` is found. + """ + accumulator = 0 + for ii in range(1, base + 1): + # Using divmod() over % to account for negative intervals + div, value = divmod(value + self._interval, base) + accumulator += div + if value in byxxx: + return (accumulator, value) + + +class _iterinfo(object): + __slots__ = ["rrule", "lastyear", "lastmonth", + "yearlen", "nextyearlen", "yearordinal", "yearweekday", + "mmask", "mrange", "mdaymask", "nmdaymask", + "wdaymask", "wnomask", "nwdaymask", "eastermask"] + + def __init__(self, rrule): + for attr in self.__slots__: + setattr(self, attr, None) + self.rrule = rrule + + def rebuild(self, year, month): + # Every mask is 7 days longer to handle cross-year weekly periods. + rr = self.rrule + if year != self.lastyear: + self.yearlen = 365 + calendar.isleap(year) + self.nextyearlen = 365 + calendar.isleap(year + 1) + firstyday = datetime.date(year, 1, 1) + self.yearordinal = firstyday.toordinal() + self.yearweekday = firstyday.weekday() + + wday = datetime.date(year, 1, 1).weekday() + if self.yearlen == 365: + self.mmask = M365MASK + self.mdaymask = MDAY365MASK + self.nmdaymask = NMDAY365MASK + self.wdaymask = WDAYMASK[wday:] + self.mrange = M365RANGE + else: + self.mmask = M366MASK + self.mdaymask = MDAY366MASK + self.nmdaymask = NMDAY366MASK + self.wdaymask = WDAYMASK[wday:] + self.mrange = M366RANGE + + if not rr._byweekno: + self.wnomask = None + else: + self.wnomask = [0]*(self.yearlen+7) + # no1wkst = firstwkst = self.wdaymask.index(rr._wkst) + no1wkst = firstwkst = (7-self.yearweekday+rr._wkst) % 7 + if no1wkst >= 4: + no1wkst = 0 + # Number of days in the year, plus the days we got + # from last year. + wyearlen = self.yearlen+(self.yearweekday-rr._wkst) % 7 + else: + # Number of days in the year, minus the days we + # left in last year. + wyearlen = self.yearlen-no1wkst + div, mod = divmod(wyearlen, 7) + numweeks = div+mod//4 + for n in rr._byweekno: + if n < 0: + n += numweeks+1 + if not (0 < n <= numweeks): + continue + if n > 1: + i = no1wkst+(n-1)*7 + if no1wkst != firstwkst: + i -= 7-firstwkst + else: + i = no1wkst + for j in range(7): + self.wnomask[i] = 1 + i += 1 + if self.wdaymask[i] == rr._wkst: + break + if 1 in rr._byweekno: + # Check week number 1 of next year as well + # TODO: Check -numweeks for next year. + i = no1wkst+numweeks*7 + if no1wkst != firstwkst: + i -= 7-firstwkst + if i < self.yearlen: + # If week starts in next year, we + # don't care about it. + for j in range(7): + self.wnomask[i] = 1 + i += 1 + if self.wdaymask[i] == rr._wkst: + break + if no1wkst: + # Check last week number of last year as + # well. If no1wkst is 0, either the year + # started on week start, or week number 1 + # got days from last year, so there are no + # days from last year's last week number in + # this year. + if -1 not in rr._byweekno: + lyearweekday = datetime.date(year-1, 1, 1).weekday() + lno1wkst = (7-lyearweekday+rr._wkst) % 7 + lyearlen = 365+calendar.isleap(year-1) + if lno1wkst >= 4: + lno1wkst = 0 + lnumweeks = 52+(lyearlen + + (lyearweekday-rr._wkst) % 7) % 7//4 + else: + lnumweeks = 52+(self.yearlen-no1wkst) % 7//4 + else: + lnumweeks = -1 + if lnumweeks in rr._byweekno: + for i in range(no1wkst): + self.wnomask[i] = 1 + + if (rr._bynweekday and (month != self.lastmonth or + year != self.lastyear)): + ranges = [] + if rr._freq == YEARLY: + if rr._bymonth: + for month in rr._bymonth: + ranges.append(self.mrange[month-1:month+1]) + else: + ranges = [(0, self.yearlen)] + elif rr._freq == MONTHLY: + ranges = [self.mrange[month-1:month+1]] + if ranges: + # Weekly frequency won't get here, so we may not + # care about cross-year weekly periods. + self.nwdaymask = [0]*self.yearlen + for first, last in ranges: + last -= 1 + for wday, n in rr._bynweekday: + if n < 0: + i = last+(n+1)*7 + i -= (self.wdaymask[i]-wday) % 7 + else: + i = first+(n-1)*7 + i += (7-self.wdaymask[i]+wday) % 7 + if first <= i <= last: + self.nwdaymask[i] = 1 + + if rr._byeaster: + self.eastermask = [0]*(self.yearlen+7) + eyday = easter.easter(year).toordinal()-self.yearordinal + for offset in rr._byeaster: + self.eastermask[eyday+offset] = 1 + + self.lastyear = year + self.lastmonth = month + + def ydayset(self, year, month, day): + return list(range(self.yearlen)), 0, self.yearlen + + def mdayset(self, year, month, day): + dset = [None]*self.yearlen + start, end = self.mrange[month-1:month+1] + for i in range(start, end): + dset[i] = i + return dset, start, end + + def wdayset(self, year, month, day): + # We need to handle cross-year weeks here. + dset = [None]*(self.yearlen+7) + i = datetime.date(year, month, day).toordinal()-self.yearordinal + start = i + for j in range(7): + dset[i] = i + i += 1 + # if (not (0 <= i < self.yearlen) or + # self.wdaymask[i] == self.rrule._wkst): + # This will cross the year boundary, if necessary. + if self.wdaymask[i] == self.rrule._wkst: + break + return dset, start, i + + def ddayset(self, year, month, day): + dset = [None] * self.yearlen + i = datetime.date(year, month, day).toordinal() - self.yearordinal + dset[i] = i + return dset, i, i + 1 + + def htimeset(self, hour, minute, second): + tset = [] + rr = self.rrule + for minute in rr._byminute: + for second in rr._bysecond: + tset.append(datetime.time(hour, minute, second, + tzinfo=rr._tzinfo)) + tset.sort() + return tset + + def mtimeset(self, hour, minute, second): + tset = [] + rr = self.rrule + for second in rr._bysecond: + tset.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo)) + tset.sort() + return tset + + def stimeset(self, hour, minute, second): + return (datetime.time(hour, minute, second, + tzinfo=self.rrule._tzinfo),) + + +class rruleset(rrulebase): + """ The rruleset type allows more complex recurrence setups, mixing + multiple rules, dates, exclusion rules, and exclusion dates. The type + constructor takes the following keyword arguments: + + :param cache: If True, caching of results will be enabled, improving + performance of multiple queries considerably. """ + + class _genitem(object): + def __init__(self, genlist, gen): + try: + self.dt = advance_iterator(gen) + genlist.append(self) + except StopIteration: + pass + self.genlist = genlist + self.gen = gen + + def __next__(self): + try: + self.dt = advance_iterator(self.gen) + except StopIteration: + if self.genlist[0] is self: + heapq.heappop(self.genlist) + else: + self.genlist.remove(self) + heapq.heapify(self.genlist) + + next = __next__ + + def __lt__(self, other): + return self.dt < other.dt + + def __gt__(self, other): + return self.dt > other.dt + + def __eq__(self, other): + return self.dt == other.dt + + def __ne__(self, other): + return self.dt != other.dt + + def __init__(self, cache=False): + super(rruleset, self).__init__(cache) + self._rrule = [] + self._rdate = [] + self._exrule = [] + self._exdate = [] + + @_invalidates_cache + def rrule(self, rrule): + """ Include the given :py:class:`rrule` instance in the recurrence set + generation. """ + self._rrule.append(rrule) + + @_invalidates_cache + def rdate(self, rdate): + """ Include the given :py:class:`datetime` instance in the recurrence + set generation. """ + self._rdate.append(rdate) + + @_invalidates_cache + def exrule(self, exrule): + """ Include the given rrule instance in the recurrence set exclusion + list. Dates which are part of the given recurrence rules will not + be generated, even if some inclusive rrule or rdate matches them. + """ + self._exrule.append(exrule) + + @_invalidates_cache + def exdate(self, exdate): + """ Include the given datetime instance in the recurrence set + exclusion list. Dates included that way will not be generated, + even if some inclusive rrule or rdate matches them. """ + self._exdate.append(exdate) + + def _iter(self): + rlist = [] + self._rdate.sort() + self._genitem(rlist, iter(self._rdate)) + for gen in [iter(x) for x in self._rrule]: + self._genitem(rlist, gen) + exlist = [] + self._exdate.sort() + self._genitem(exlist, iter(self._exdate)) + for gen in [iter(x) for x in self._exrule]: + self._genitem(exlist, gen) + lastdt = None + total = 0 + heapq.heapify(rlist) + heapq.heapify(exlist) + while rlist: + ritem = rlist[0] + if not lastdt or lastdt != ritem.dt: + while exlist and exlist[0] < ritem: + exitem = exlist[0] + advance_iterator(exitem) + if exlist and exlist[0] is exitem: + heapq.heapreplace(exlist, exitem) + if not exlist or ritem != exlist[0]: + total += 1 + yield ritem.dt + lastdt = ritem.dt + advance_iterator(ritem) + if rlist and rlist[0] is ritem: + heapq.heapreplace(rlist, ritem) + self._len = total + + + + +class _rrulestr(object): + """ Parses a string representation of a recurrence rule or set of + recurrence rules. + + :param s: + Required, a string defining one or more recurrence rules. + + :param dtstart: + If given, used as the default recurrence start if not specified in the + rule string. + + :param cache: + If set ``True`` caching of results will be enabled, improving + performance of multiple queries considerably. + + :param unfold: + If set ``True`` indicates that a rule string is split over more + than one line and should be joined before processing. + + :param forceset: + If set ``True`` forces a :class:`dateutil.rrule.rruleset` to + be returned. + + :param compatible: + If set ``True`` forces ``unfold`` and ``forceset`` to be ``True``. + + :param ignoretz: + If set ``True``, time zones in parsed strings are ignored and a naive + :class:`datetime.datetime` object is returned. + + :param tzids: + If given, a callable or mapping used to retrieve a + :class:`datetime.tzinfo` from a string representation. + Defaults to :func:`dateutil.tz.gettz`. + + :param tzinfos: + Additional time zone names / aliases which may be present in a string + representation. See :func:`dateutil.parser.parse` for more + information. + + :return: + Returns a :class:`dateutil.rrule.rruleset` or + :class:`dateutil.rrule.rrule` + """ + + _freq_map = {"YEARLY": YEARLY, + "MONTHLY": MONTHLY, + "WEEKLY": WEEKLY, + "DAILY": DAILY, + "HOURLY": HOURLY, + "MINUTELY": MINUTELY, + "SECONDLY": SECONDLY} + + _weekday_map = {"MO": 0, "TU": 1, "WE": 2, "TH": 3, + "FR": 4, "SA": 5, "SU": 6} + + def _handle_int(self, rrkwargs, name, value, **kwargs): + rrkwargs[name.lower()] = int(value) + + def _handle_int_list(self, rrkwargs, name, value, **kwargs): + rrkwargs[name.lower()] = [int(x) for x in value.split(',')] + + _handle_INTERVAL = _handle_int + _handle_COUNT = _handle_int + _handle_BYSETPOS = _handle_int_list + _handle_BYMONTH = _handle_int_list + _handle_BYMONTHDAY = _handle_int_list + _handle_BYYEARDAY = _handle_int_list + _handle_BYEASTER = _handle_int_list + _handle_BYWEEKNO = _handle_int_list + _handle_BYHOUR = _handle_int_list + _handle_BYMINUTE = _handle_int_list + _handle_BYSECOND = _handle_int_list + + def _handle_FREQ(self, rrkwargs, name, value, **kwargs): + rrkwargs["freq"] = self._freq_map[value] + + def _handle_UNTIL(self, rrkwargs, name, value, **kwargs): + global parser + if not parser: + from dateutil import parser + try: + rrkwargs["until"] = parser.parse(value, + ignoretz=kwargs.get("ignoretz"), + tzinfos=kwargs.get("tzinfos")) + except ValueError: + raise ValueError("invalid until date") + + def _handle_WKST(self, rrkwargs, name, value, **kwargs): + rrkwargs["wkst"] = self._weekday_map[value] + + def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwargs): + """ + Two ways to specify this: +1MO or MO(+1) + """ + l = [] + for wday in value.split(','): + if '(' in wday: + # If it's of the form TH(+1), etc. + splt = wday.split('(') + w = splt[0] + n = int(splt[1][:-1]) + elif len(wday): + # If it's of the form +1MO + for i in range(len(wday)): + if wday[i] not in '+-0123456789': + break + n = wday[:i] or None + w = wday[i:] + if n: + n = int(n) + else: + raise ValueError("Invalid (empty) BYDAY specification.") + + l.append(weekdays[self._weekday_map[w]](n)) + rrkwargs["byweekday"] = l + + _handle_BYDAY = _handle_BYWEEKDAY + + def _parse_rfc_rrule(self, line, + dtstart=None, + cache=False, + ignoretz=False, + tzinfos=None): + if line.find(':') != -1: + name, value = line.split(':') + if name != "RRULE": + raise ValueError("unknown parameter name") + else: + value = line + rrkwargs = {} + for pair in value.split(';'): + name, value = pair.split('=') + name = name.upper() + value = value.upper() + try: + getattr(self, "_handle_"+name)(rrkwargs, name, value, + ignoretz=ignoretz, + tzinfos=tzinfos) + except AttributeError: + raise ValueError("unknown parameter '%s'" % name) + except (KeyError, ValueError): + raise ValueError("invalid '%s': %s" % (name, value)) + return rrule(dtstart=dtstart, cache=cache, **rrkwargs) + + def _parse_date_value(self, date_value, parms, rule_tzids, + ignoretz, tzids, tzinfos): + global parser + if not parser: + from dateutil import parser + + datevals = [] + value_found = False + TZID = None + + for parm in parms: + if parm.startswith("TZID="): + try: + tzkey = rule_tzids[parm.split('TZID=')[-1]] + except KeyError: + continue + if tzids is None: + from . import tz + tzlookup = tz.gettz + elif callable(tzids): + tzlookup = tzids + else: + tzlookup = getattr(tzids, 'get', None) + if tzlookup is None: + msg = ('tzids must be a callable, mapping, or None, ' + 'not %s' % tzids) + raise ValueError(msg) + + TZID = tzlookup(tzkey) + continue + + # RFC 5445 3.8.2.4: The VALUE parameter is optional, but may be found + # only once. + if parm not in {"VALUE=DATE-TIME", "VALUE=DATE"}: + raise ValueError("unsupported parm: " + parm) + else: + if value_found: + msg = ("Duplicate value parameter found in: " + parm) + raise ValueError(msg) + value_found = True + + for datestr in date_value.split(','): + date = parser.parse(datestr, ignoretz=ignoretz, tzinfos=tzinfos) + if TZID is not None: + if date.tzinfo is None: + date = date.replace(tzinfo=TZID) + else: + raise ValueError('DTSTART/EXDATE specifies multiple timezone') + datevals.append(date) + + return datevals + + def _parse_rfc(self, s, + dtstart=None, + cache=False, + unfold=False, + forceset=False, + compatible=False, + ignoretz=False, + tzids=None, + tzinfos=None): + global parser + if compatible: + forceset = True + unfold = True + + TZID_NAMES = dict(map( + lambda x: (x.upper(), x), + re.findall('TZID=(?P[^:]+):', s) + )) + s = s.upper() + if not s.strip(): + raise ValueError("empty string") + if unfold: + lines = s.splitlines() + i = 0 + while i < len(lines): + line = lines[i].rstrip() + if not line: + del lines[i] + elif i > 0 and line[0] == " ": + lines[i-1] += line[1:] + del lines[i] + else: + i += 1 + else: + lines = s.split() + if (not forceset and len(lines) == 1 and (s.find(':') == -1 or + s.startswith('RRULE:'))): + return self._parse_rfc_rrule(lines[0], cache=cache, + dtstart=dtstart, ignoretz=ignoretz, + tzinfos=tzinfos) + else: + rrulevals = [] + rdatevals = [] + exrulevals = [] + exdatevals = [] + for line in lines: + if not line: + continue + if line.find(':') == -1: + name = "RRULE" + value = line + else: + name, value = line.split(':', 1) + parms = name.split(';') + if not parms: + raise ValueError("empty property name") + name = parms[0] + parms = parms[1:] + if name == "RRULE": + for parm in parms: + raise ValueError("unsupported RRULE parm: "+parm) + rrulevals.append(value) + elif name == "RDATE": + for parm in parms: + if parm != "VALUE=DATE-TIME": + raise ValueError("unsupported RDATE parm: "+parm) + rdatevals.append(value) + elif name == "EXRULE": + for parm in parms: + raise ValueError("unsupported EXRULE parm: "+parm) + exrulevals.append(value) + elif name == "EXDATE": + exdatevals.extend( + self._parse_date_value(value, parms, + TZID_NAMES, ignoretz, + tzids, tzinfos) + ) + elif name == "DTSTART": + dtvals = self._parse_date_value(value, parms, TZID_NAMES, + ignoretz, tzids, tzinfos) + if len(dtvals) != 1: + raise ValueError("Multiple DTSTART values specified:" + + value) + dtstart = dtvals[0] + else: + raise ValueError("unsupported property: "+name) + if (forceset or len(rrulevals) > 1 or rdatevals + or exrulevals or exdatevals): + if not parser and (rdatevals or exdatevals): + from dateutil import parser + rset = rruleset(cache=cache) + for value in rrulevals: + rset.rrule(self._parse_rfc_rrule(value, dtstart=dtstart, + ignoretz=ignoretz, + tzinfos=tzinfos)) + for value in rdatevals: + for datestr in value.split(','): + rset.rdate(parser.parse(datestr, + ignoretz=ignoretz, + tzinfos=tzinfos)) + for value in exrulevals: + rset.exrule(self._parse_rfc_rrule(value, dtstart=dtstart, + ignoretz=ignoretz, + tzinfos=tzinfos)) + for value in exdatevals: + rset.exdate(value) + if compatible and dtstart: + rset.rdate(dtstart) + return rset + else: + return self._parse_rfc_rrule(rrulevals[0], + dtstart=dtstart, + cache=cache, + ignoretz=ignoretz, + tzinfos=tzinfos) + + def __call__(self, s, **kwargs): + return self._parse_rfc(s, **kwargs) + + +rrulestr = _rrulestr() + +# vim:ts=4:sw=4:et diff --git a/dateutil/tz/__init__.py b/dateutil/tz/__init__.py new file mode 100644 index 0000000..af1352c --- /dev/null +++ b/dateutil/tz/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +from .tz import * +from .tz import __doc__ + +__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange", + "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz", + "enfold", "datetime_ambiguous", "datetime_exists", + "resolve_imaginary", "UTC", "DeprecatedTzFormatWarning"] + + +class DeprecatedTzFormatWarning(Warning): + """Warning raised when time zones are parsed from deprecated formats.""" diff --git a/dateutil/tz/_common.py b/dateutil/tz/_common.py new file mode 100644 index 0000000..e6ac118 --- /dev/null +++ b/dateutil/tz/_common.py @@ -0,0 +1,419 @@ +from six import PY2 + +from functools import wraps + +from datetime import datetime, timedelta, tzinfo + + +ZERO = timedelta(0) + +__all__ = ['tzname_in_python2', 'enfold'] + + +def tzname_in_python2(namefunc): + """Change unicode output into bytestrings in Python 2 + + tzname() API changed in Python 3. It used to return bytes, but was changed + to unicode strings + """ + if PY2: + @wraps(namefunc) + def adjust_encoding(*args, **kwargs): + name = namefunc(*args, **kwargs) + if name is not None: + name = name.encode() + + return name + + return adjust_encoding + else: + return namefunc + + +# The following is adapted from Alexander Belopolsky's tz library +# https://github.com/abalkin/tz +if hasattr(datetime, 'fold'): + # This is the pre-python 3.6 fold situation + def enfold(dt, fold=1): + """ + Provides a unified interface for assigning the ``fold`` attribute to + datetimes both before and after the implementation of PEP-495. + + :param fold: + The value for the ``fold`` attribute in the returned datetime. This + should be either 0 or 1. + + :return: + Returns an object for which ``getattr(dt, 'fold', 0)`` returns + ``fold`` for all versions of Python. In versions prior to + Python 3.6, this is a ``_DatetimeWithFold`` object, which is a + subclass of :py:class:`datetime.datetime` with the ``fold`` + attribute added, if ``fold`` is 1. + + .. versionadded:: 2.6.0 + """ + return dt.replace(fold=fold) + +else: + class _DatetimeWithFold(datetime): + """ + This is a class designed to provide a PEP 495-compliant interface for + Python versions before 3.6. It is used only for dates in a fold, so + the ``fold`` attribute is fixed at ``1``. + + .. versionadded:: 2.6.0 + """ + __slots__ = () + + def replace(self, *args, **kwargs): + """ + Return a datetime with the same attributes, except for those + attributes given new values by whichever keyword arguments are + specified. Note that tzinfo=None can be specified to create a naive + datetime from an aware datetime with no conversion of date and time + data. + + This is reimplemented in ``_DatetimeWithFold`` because pypy3 will + return a ``datetime.datetime`` even if ``fold`` is unchanged. + """ + argnames = ( + 'year', 'month', 'day', 'hour', 'minute', 'second', + 'microsecond', 'tzinfo' + ) + + for arg, argname in zip(args, argnames): + if argname in kwargs: + raise TypeError('Duplicate argument: {}'.format(argname)) + + kwargs[argname] = arg + + for argname in argnames: + if argname not in kwargs: + kwargs[argname] = getattr(self, argname) + + dt_class = self.__class__ if kwargs.get('fold', 1) else datetime + + return dt_class(**kwargs) + + @property + def fold(self): + return 1 + + def enfold(dt, fold=1): + """ + Provides a unified interface for assigning the ``fold`` attribute to + datetimes both before and after the implementation of PEP-495. + + :param fold: + The value for the ``fold`` attribute in the returned datetime. This + should be either 0 or 1. + + :return: + Returns an object for which ``getattr(dt, 'fold', 0)`` returns + ``fold`` for all versions of Python. In versions prior to + Python 3.6, this is a ``_DatetimeWithFold`` object, which is a + subclass of :py:class:`datetime.datetime` with the ``fold`` + attribute added, if ``fold`` is 1. + + .. versionadded:: 2.6.0 + """ + if getattr(dt, 'fold', 0) == fold: + return dt + + args = dt.timetuple()[:6] + args += (dt.microsecond, dt.tzinfo) + + if fold: + return _DatetimeWithFold(*args) + else: + return datetime(*args) + + +def _validate_fromutc_inputs(f): + """ + The CPython version of ``fromutc`` checks that the input is a ``datetime`` + object and that ``self`` is attached as its ``tzinfo``. + """ + @wraps(f) + def fromutc(self, dt): + if not isinstance(dt, datetime): + raise TypeError("fromutc() requires a datetime argument") + if dt.tzinfo is not self: + raise ValueError("dt.tzinfo is not self") + + return f(self, dt) + + return fromutc + + +class _tzinfo(tzinfo): + """ + Base class for all ``dateutil`` ``tzinfo`` objects. + """ + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + + dt = dt.replace(tzinfo=self) + + wall_0 = enfold(dt, fold=0) + wall_1 = enfold(dt, fold=1) + + same_offset = wall_0.utcoffset() == wall_1.utcoffset() + same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None) + + return same_dt and not same_offset + + def _fold_status(self, dt_utc, dt_wall): + """ + Determine the fold status of a "wall" datetime, given a representation + of the same datetime as a (naive) UTC datetime. This is calculated based + on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all + datetimes, and that this offset is the actual number of hours separating + ``dt_utc`` and ``dt_wall``. + + :param dt_utc: + Representation of the datetime as UTC + + :param dt_wall: + Representation of the datetime as "wall time". This parameter must + either have a `fold` attribute or have a fold-naive + :class:`datetime.tzinfo` attached, otherwise the calculation may + fail. + """ + if self.is_ambiguous(dt_wall): + delta_wall = dt_wall - dt_utc + _fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst())) + else: + _fold = 0 + + return _fold + + def _fold(self, dt): + return getattr(dt, 'fold', 0) + + def _fromutc(self, dt): + """ + Given a timezone-aware datetime in a given timezone, calculates a + timezone-aware datetime in a new timezone. + + Since this is the one time that we *know* we have an unambiguous + datetime object, we take this opportunity to determine whether the + datetime is ambiguous and in a "fold" state (e.g. if it's the first + occurrence, chronologically, of the ambiguous datetime). + + :param dt: + A timezone-aware :class:`datetime.datetime` object. + """ + + # Re-implement the algorithm from Python's datetime.py + dtoff = dt.utcoffset() + if dtoff is None: + raise ValueError("fromutc() requires a non-None utcoffset() " + "result") + + # The original datetime.py code assumes that `dst()` defaults to + # zero during ambiguous times. PEP 495 inverts this presumption, so + # for pre-PEP 495 versions of python, we need to tweak the algorithm. + dtdst = dt.dst() + if dtdst is None: + raise ValueError("fromutc() requires a non-None dst() result") + delta = dtoff - dtdst + + dt += delta + # Set fold=1 so we can default to being in the fold for + # ambiguous dates. + dtdst = enfold(dt, fold=1).dst() + if dtdst is None: + raise ValueError("fromutc(): dt.dst gave inconsistent " + "results; cannot convert") + return dt + dtdst + + @_validate_fromutc_inputs + def fromutc(self, dt): + """ + Given a timezone-aware datetime in a given timezone, calculates a + timezone-aware datetime in a new timezone. + + Since this is the one time that we *know* we have an unambiguous + datetime object, we take this opportunity to determine whether the + datetime is ambiguous and in a "fold" state (e.g. if it's the first + occurrence, chronologically, of the ambiguous datetime). + + :param dt: + A timezone-aware :class:`datetime.datetime` object. + """ + dt_wall = self._fromutc(dt) + + # Calculate the fold status given the two datetimes. + _fold = self._fold_status(dt, dt_wall) + + # Set the default fold value for ambiguous dates + return enfold(dt_wall, fold=_fold) + + +class tzrangebase(_tzinfo): + """ + This is an abstract base class for time zones represented by an annual + transition into and out of DST. Child classes should implement the following + methods: + + * ``__init__(self, *args, **kwargs)`` + * ``transitions(self, year)`` - this is expected to return a tuple of + datetimes representing the DST on and off transitions in standard + time. + + A fully initialized ``tzrangebase`` subclass should also provide the + following attributes: + * ``hasdst``: Boolean whether or not the zone uses DST. + * ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects + representing the respective UTC offsets. + * ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short + abbreviations in DST and STD, respectively. + * ``_hasdst``: Whether or not the zone has DST. + + .. versionadded:: 2.6.0 + """ + def __init__(self): + raise NotImplementedError('tzrangebase is an abstract base class') + + def utcoffset(self, dt): + isdst = self._isdst(dt) + + if isdst is None: + return None + elif isdst: + return self._dst_offset + else: + return self._std_offset + + def dst(self, dt): + isdst = self._isdst(dt) + + if isdst is None: + return None + elif isdst: + return self._dst_base_offset + else: + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + if self._isdst(dt): + return self._dst_abbr + else: + return self._std_abbr + + def fromutc(self, dt): + """ Given a datetime in UTC, return local time """ + if not isinstance(dt, datetime): + raise TypeError("fromutc() requires a datetime argument") + + if dt.tzinfo is not self: + raise ValueError("dt.tzinfo is not self") + + # Get transitions - if there are none, fixed offset + transitions = self.transitions(dt.year) + if transitions is None: + return dt + self.utcoffset(dt) + + # Get the transition times in UTC + dston, dstoff = transitions + + dston -= self._std_offset + dstoff -= self._std_offset + + utc_transitions = (dston, dstoff) + dt_utc = dt.replace(tzinfo=None) + + isdst = self._naive_isdst(dt_utc, utc_transitions) + + if isdst: + dt_wall = dt + self._dst_offset + else: + dt_wall = dt + self._std_offset + + _fold = int(not isdst and self.is_ambiguous(dt_wall)) + + return enfold(dt_wall, fold=_fold) + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + if not self.hasdst: + return False + + start, end = self.transitions(dt.year) + + dt = dt.replace(tzinfo=None) + return (end <= dt < end + self._dst_base_offset) + + def _isdst(self, dt): + if not self.hasdst: + return False + elif dt is None: + return None + + transitions = self.transitions(dt.year) + + if transitions is None: + return False + + dt = dt.replace(tzinfo=None) + + isdst = self._naive_isdst(dt, transitions) + + # Handle ambiguous dates + if not isdst and self.is_ambiguous(dt): + return not self._fold(dt) + else: + return isdst + + def _naive_isdst(self, dt, transitions): + dston, dstoff = transitions + + dt = dt.replace(tzinfo=None) + + if dston < dstoff: + isdst = dston <= dt < dstoff + else: + isdst = not dstoff <= dt < dston + + return isdst + + @property + def _dst_base_offset(self): + return self._dst_offset - self._std_offset + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s(...)" % self.__class__.__name__ + + __reduce__ = object.__reduce__ diff --git a/dateutil/tz/_factories.py b/dateutil/tz/_factories.py new file mode 100644 index 0000000..f8a6589 --- /dev/null +++ b/dateutil/tz/_factories.py @@ -0,0 +1,80 @@ +from datetime import timedelta +import weakref +from collections import OrderedDict + +from six.moves import _thread + + +class _TzSingleton(type): + def __init__(cls, *args, **kwargs): + cls.__instance = None + super(_TzSingleton, cls).__init__(*args, **kwargs) + + def __call__(cls): + if cls.__instance is None: + cls.__instance = super(_TzSingleton, cls).__call__() + return cls.__instance + + +class _TzFactory(type): + def instance(cls, *args, **kwargs): + """Alternate constructor that returns a fresh instance""" + return type.__call__(cls, *args, **kwargs) + + +class _TzOffsetFactory(_TzFactory): + def __init__(cls, *args, **kwargs): + cls.__instances = weakref.WeakValueDictionary() + cls.__strong_cache = OrderedDict() + cls.__strong_cache_size = 8 + + cls._cache_lock = _thread.allocate_lock() + + def __call__(cls, name, offset): + if isinstance(offset, timedelta): + key = (name, offset.total_seconds()) + else: + key = (name, offset) + + instance = cls.__instances.get(key, None) + if instance is None: + instance = cls.__instances.setdefault(key, + cls.instance(name, offset)) + + # This lock may not be necessary in Python 3. See GH issue #901 + with cls._cache_lock: + cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance) + + # Remove an item if the strong cache is overpopulated + if len(cls.__strong_cache) > cls.__strong_cache_size: + cls.__strong_cache.popitem(last=False) + + return instance + + +class _TzStrFactory(_TzFactory): + def __init__(cls, *args, **kwargs): + cls.__instances = weakref.WeakValueDictionary() + cls.__strong_cache = OrderedDict() + cls.__strong_cache_size = 8 + + cls.__cache_lock = _thread.allocate_lock() + + def __call__(cls, s, posix_offset=False): + key = (s, posix_offset) + instance = cls.__instances.get(key, None) + + if instance is None: + instance = cls.__instances.setdefault(key, + cls.instance(s, posix_offset)) + + # This lock may not be necessary in Python 3. See GH issue #901 + with cls.__cache_lock: + cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance) + + # Remove an item if the strong cache is overpopulated + if len(cls.__strong_cache) > cls.__strong_cache_size: + cls.__strong_cache.popitem(last=False) + + return instance + diff --git a/dateutil/tz/tz.py b/dateutil/tz/tz.py new file mode 100644 index 0000000..6175914 --- /dev/null +++ b/dateutil/tz/tz.py @@ -0,0 +1,1849 @@ +# -*- coding: utf-8 -*- +""" +This module offers timezone implementations subclassing the abstract +:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format +files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, +etc), TZ environment string (in all known formats), given ranges (with help +from relative deltas), local machine timezone, fixed offset timezone, and UTC +timezone. +""" +import datetime +import struct +import time +import sys +import os +import bisect +import weakref +from collections import OrderedDict + +import six +from six import string_types +from six.moves import _thread +from ._common import tzname_in_python2, _tzinfo +from ._common import tzrangebase, enfold +from ._common import _validate_fromutc_inputs + +from ._factories import _TzSingleton, _TzOffsetFactory +from ._factories import _TzStrFactory +try: + from .win import tzwin, tzwinlocal +except ImportError: + tzwin = tzwinlocal = None + +# For warning about rounding tzinfo +from warnings import warn + +ZERO = datetime.timedelta(0) +EPOCH = datetime.datetime(1970, 1, 1, 0, 0) +EPOCHORDINAL = EPOCH.toordinal() + + +@six.add_metaclass(_TzSingleton) +class tzutc(datetime.tzinfo): + """ + This is a tzinfo object that represents the UTC time zone. + + **Examples:** + + .. doctest:: + + >>> from datetime import * + >>> from dateutil.tz import * + + >>> datetime.now() + datetime.datetime(2003, 9, 27, 9, 40, 1, 521290) + + >>> datetime.now(tzutc()) + datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc()) + + >>> datetime.now(tzutc()).tzname() + 'UTC' + + .. versionchanged:: 2.7.0 + ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will + always return the same object. + + .. doctest:: + + >>> from dateutil.tz import tzutc, UTC + >>> tzutc() is tzutc() + True + >>> tzutc() is UTC + True + """ + def utcoffset(self, dt): + return ZERO + + def dst(self, dt): + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return "UTC" + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + return False + + @_validate_fromutc_inputs + def fromutc(self, dt): + """ + Fast track version of fromutc() returns the original ``dt`` object for + any valid :py:class:`datetime.datetime` object. + """ + return dt + + def __eq__(self, other): + if not isinstance(other, (tzutc, tzoffset)): + return NotImplemented + + return (isinstance(other, tzutc) or + (isinstance(other, tzoffset) and other._offset == ZERO)) + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s()" % self.__class__.__name__ + + __reduce__ = object.__reduce__ + + +#: Convenience constant providing a :class:`tzutc()` instance +#: +#: .. versionadded:: 2.7.0 +UTC = tzutc() + + +@six.add_metaclass(_TzOffsetFactory) +class tzoffset(datetime.tzinfo): + """ + A simple class for representing a fixed offset from UTC. + + :param name: + The timezone name, to be returned when ``tzname()`` is called. + :param offset: + The time zone offset in seconds, or (since version 2.6.0, represented + as a :py:class:`datetime.timedelta` object). + """ + def __init__(self, name, offset): + self._name = name + + try: + # Allow a timedelta + offset = offset.total_seconds() + except (TypeError, AttributeError): + pass + + self._offset = datetime.timedelta(seconds=_get_supported_offset(offset)) + + def utcoffset(self, dt): + return self._offset + + def dst(self, dt): + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return self._name + + @_validate_fromutc_inputs + def fromutc(self, dt): + return dt + self._offset + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + return False + + def __eq__(self, other): + if not isinstance(other, tzoffset): + return NotImplemented + + return self._offset == other._offset + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s(%s, %s)" % (self.__class__.__name__, + repr(self._name), + int(self._offset.total_seconds())) + + __reduce__ = object.__reduce__ + + +class tzlocal(_tzinfo): + """ + A :class:`tzinfo` subclass built around the ``time`` timezone functions. + """ + def __init__(self): + super(tzlocal, self).__init__() + + self._std_offset = datetime.timedelta(seconds=-time.timezone) + if time.daylight: + self._dst_offset = datetime.timedelta(seconds=-time.altzone) + else: + self._dst_offset = self._std_offset + + self._dst_saved = self._dst_offset - self._std_offset + self._hasdst = bool(self._dst_saved) + self._tznames = tuple(time.tzname) + + def utcoffset(self, dt): + if dt is None and self._hasdst: + return None + + if self._isdst(dt): + return self._dst_offset + else: + return self._std_offset + + def dst(self, dt): + if dt is None and self._hasdst: + return None + + if self._isdst(dt): + return self._dst_offset - self._std_offset + else: + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return self._tznames[self._isdst(dt)] + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + naive_dst = self._naive_is_dst(dt) + return (not naive_dst and + (naive_dst != self._naive_is_dst(dt - self._dst_saved))) + + def _naive_is_dst(self, dt): + timestamp = _datetime_to_timestamp(dt) + return time.localtime(timestamp + time.timezone).tm_isdst + + def _isdst(self, dt, fold_naive=True): + # We can't use mktime here. It is unstable when deciding if + # the hour near to a change is DST or not. + # + # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour, + # dt.minute, dt.second, dt.weekday(), 0, -1)) + # return time.localtime(timestamp).tm_isdst + # + # The code above yields the following result: + # + # >>> import tz, datetime + # >>> t = tz.tzlocal() + # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() + # 'BRDT' + # >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname() + # 'BRST' + # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() + # 'BRST' + # >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname() + # 'BRDT' + # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() + # 'BRDT' + # + # Here is a more stable implementation: + # + if not self._hasdst: + return False + + # Check for ambiguous times: + dstval = self._naive_is_dst(dt) + fold = getattr(dt, 'fold', None) + + if self.is_ambiguous(dt): + if fold is not None: + return not self._fold(dt) + else: + return True + + return dstval + + def __eq__(self, other): + if isinstance(other, tzlocal): + return (self._std_offset == other._std_offset and + self._dst_offset == other._dst_offset) + elif isinstance(other, tzutc): + return (not self._hasdst and + self._tznames[0] in {'UTC', 'GMT'} and + self._std_offset == ZERO) + elif isinstance(other, tzoffset): + return (not self._hasdst and + self._tznames[0] == other._name and + self._std_offset == other._offset) + else: + return NotImplemented + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s()" % self.__class__.__name__ + + __reduce__ = object.__reduce__ + + +class _ttinfo(object): + __slots__ = ["offset", "delta", "isdst", "abbr", + "isstd", "isgmt", "dstoffset"] + + def __init__(self): + for attr in self.__slots__: + setattr(self, attr, None) + + def __repr__(self): + l = [] + for attr in self.__slots__: + value = getattr(self, attr) + if value is not None: + l.append("%s=%s" % (attr, repr(value))) + return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) + + def __eq__(self, other): + if not isinstance(other, _ttinfo): + return NotImplemented + + return (self.offset == other.offset and + self.delta == other.delta and + self.isdst == other.isdst and + self.abbr == other.abbr and + self.isstd == other.isstd and + self.isgmt == other.isgmt and + self.dstoffset == other.dstoffset) + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __getstate__(self): + state = {} + for name in self.__slots__: + state[name] = getattr(self, name, None) + return state + + def __setstate__(self, state): + for name in self.__slots__: + if name in state: + setattr(self, name, state[name]) + + +class _tzfile(object): + """ + Lightweight class for holding the relevant transition and time zone + information read from binary tzfiles. + """ + attrs = ['trans_list', 'trans_list_utc', 'trans_idx', 'ttinfo_list', + 'ttinfo_std', 'ttinfo_dst', 'ttinfo_before', 'ttinfo_first'] + + def __init__(self, **kwargs): + for attr in self.attrs: + setattr(self, attr, kwargs.get(attr, None)) + + +class tzfile(_tzinfo): + """ + This is a ``tzinfo`` subclass that allows one to use the ``tzfile(5)`` + format timezone files to extract current and historical zone information. + + :param fileobj: + This can be an opened file stream or a file name that the time zone + information can be read from. + + :param filename: + This is an optional parameter specifying the source of the time zone + information in the event that ``fileobj`` is a file object. If omitted + and ``fileobj`` is a file stream, this parameter will be set either to + ``fileobj``'s ``name`` attribute or to ``repr(fileobj)``. + + See `Sources for Time Zone and Daylight Saving Time Data + `_ for more information. + Time zone files can be compiled from the `IANA Time Zone database files + `_ with the `zic time zone compiler + `_ + + .. note:: + + Only construct a ``tzfile`` directly if you have a specific timezone + file on disk that you want to read into a Python ``tzinfo`` object. + If you want to get a ``tzfile`` representing a specific IANA zone, + (e.g. ``'America/New_York'``), you should call + :func:`dateutil.tz.gettz` with the zone identifier. + + + **Examples:** + + Using the US Eastern time zone as an example, we can see that a ``tzfile`` + provides time zone information for the standard Daylight Saving offsets: + + .. testsetup:: tzfile + + from dateutil.tz import gettz + from datetime import datetime + + .. doctest:: tzfile + + >>> NYC = gettz('America/New_York') + >>> NYC + tzfile('/usr/share/zoneinfo/America/New_York') + + >>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST + 2016-01-03 00:00:00-05:00 + + >>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT + 2016-07-07 00:00:00-04:00 + + + The ``tzfile`` structure contains a fully history of the time zone, + so historical dates will also have the right offsets. For example, before + the adoption of the UTC standards, New York used local solar mean time: + + .. doctest:: tzfile + + >>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT + 1901-04-12 00:00:00-04:56 + + And during World War II, New York was on "Eastern War Time", which was a + state of permanent daylight saving time: + + .. doctest:: tzfile + + >>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT + 1944-02-07 00:00:00-04:00 + + """ + + def __init__(self, fileobj, filename=None): + super(tzfile, self).__init__() + + file_opened_here = False + if isinstance(fileobj, string_types): + self._filename = fileobj + fileobj = open(fileobj, 'rb') + file_opened_here = True + elif filename is not None: + self._filename = filename + elif hasattr(fileobj, "name"): + self._filename = fileobj.name + else: + self._filename = repr(fileobj) + + if fileobj is not None: + if not file_opened_here: + fileobj = _nullcontext(fileobj) + + with fileobj as file_stream: + tzobj = self._read_tzfile(file_stream) + + self._set_tzdata(tzobj) + + def _set_tzdata(self, tzobj): + """ Set the time zone data of this object from a _tzfile object """ + # Copy the relevant attributes over as private attributes + for attr in _tzfile.attrs: + setattr(self, '_' + attr, getattr(tzobj, attr)) + + def _read_tzfile(self, fileobj): + out = _tzfile() + + # From tzfile(5): + # + # The time zone information files used by tzset(3) + # begin with the magic characters "TZif" to identify + # them as time zone information files, followed by + # sixteen bytes reserved for future use, followed by + # six four-byte values of type long, written in a + # ``standard'' byte order (the high-order byte + # of the value is written first). + if fileobj.read(4).decode() != "TZif": + raise ValueError("magic not found") + + fileobj.read(16) + + ( + # The number of UTC/local indicators stored in the file. + ttisgmtcnt, + + # The number of standard/wall indicators stored in the file. + ttisstdcnt, + + # The number of leap seconds for which data is + # stored in the file. + leapcnt, + + # The number of "transition times" for which data + # is stored in the file. + timecnt, + + # The number of "local time types" for which data + # is stored in the file (must not be zero). + typecnt, + + # The number of characters of "time zone + # abbreviation strings" stored in the file. + charcnt, + + ) = struct.unpack(">6l", fileobj.read(24)) + + # The above header is followed by tzh_timecnt four-byte + # values of type long, sorted in ascending order. + # These values are written in ``standard'' byte order. + # Each is used as a transition time (as returned by + # time(2)) at which the rules for computing local time + # change. + + if timecnt: + out.trans_list_utc = list(struct.unpack(">%dl" % timecnt, + fileobj.read(timecnt*4))) + else: + out.trans_list_utc = [] + + # Next come tzh_timecnt one-byte values of type unsigned + # char; each one tells which of the different types of + # ``local time'' types described in the file is associated + # with the same-indexed transition time. These values + # serve as indices into an array of ttinfo structures that + # appears next in the file. + + if timecnt: + out.trans_idx = struct.unpack(">%dB" % timecnt, + fileobj.read(timecnt)) + else: + out.trans_idx = [] + + # Each ttinfo structure is written as a four-byte value + # for tt_gmtoff of type long, in a standard byte + # order, followed by a one-byte value for tt_isdst + # and a one-byte value for tt_abbrind. In each + # structure, tt_gmtoff gives the number of + # seconds to be added to UTC, tt_isdst tells whether + # tm_isdst should be set by localtime(3), and + # tt_abbrind serves as an index into the array of + # time zone abbreviation characters that follow the + # ttinfo structure(s) in the file. + + ttinfo = [] + + for i in range(typecnt): + ttinfo.append(struct.unpack(">lbb", fileobj.read(6))) + + abbr = fileobj.read(charcnt).decode() + + # Then there are tzh_leapcnt pairs of four-byte + # values, written in standard byte order; the + # first value of each pair gives the time (as + # returned by time(2)) at which a leap second + # occurs; the second gives the total number of + # leap seconds to be applied after the given time. + # The pairs of values are sorted in ascending order + # by time. + + # Not used, for now (but seek for correct file position) + if leapcnt: + fileobj.seek(leapcnt * 8, os.SEEK_CUR) + + # Then there are tzh_ttisstdcnt standard/wall + # indicators, each stored as a one-byte value; + # they tell whether the transition times associated + # with local time types were specified as standard + # time or wall clock time, and are used when + # a time zone file is used in handling POSIX-style + # time zone environment variables. + + if ttisstdcnt: + isstd = struct.unpack(">%db" % ttisstdcnt, + fileobj.read(ttisstdcnt)) + + # Finally, there are tzh_ttisgmtcnt UTC/local + # indicators, each stored as a one-byte value; + # they tell whether the transition times associated + # with local time types were specified as UTC or + # local time, and are used when a time zone file + # is used in handling POSIX-style time zone envi- + # ronment variables. + + if ttisgmtcnt: + isgmt = struct.unpack(">%db" % ttisgmtcnt, + fileobj.read(ttisgmtcnt)) + + # Build ttinfo list + out.ttinfo_list = [] + for i in range(typecnt): + gmtoff, isdst, abbrind = ttinfo[i] + gmtoff = _get_supported_offset(gmtoff) + tti = _ttinfo() + tti.offset = gmtoff + tti.dstoffset = datetime.timedelta(0) + tti.delta = datetime.timedelta(seconds=gmtoff) + tti.isdst = isdst + tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)] + tti.isstd = (ttisstdcnt > i and isstd[i] != 0) + tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0) + out.ttinfo_list.append(tti) + + # Replace ttinfo indexes for ttinfo objects. + out.trans_idx = [out.ttinfo_list[idx] for idx in out.trans_idx] + + # Set standard, dst, and before ttinfos. before will be + # used when a given time is before any transitions, + # and will be set to the first non-dst ttinfo, or to + # the first dst, if all of them are dst. + out.ttinfo_std = None + out.ttinfo_dst = None + out.ttinfo_before = None + if out.ttinfo_list: + if not out.trans_list_utc: + out.ttinfo_std = out.ttinfo_first = out.ttinfo_list[0] + else: + for i in range(timecnt-1, -1, -1): + tti = out.trans_idx[i] + if not out.ttinfo_std and not tti.isdst: + out.ttinfo_std = tti + elif not out.ttinfo_dst and tti.isdst: + out.ttinfo_dst = tti + + if out.ttinfo_std and out.ttinfo_dst: + break + else: + if out.ttinfo_dst and not out.ttinfo_std: + out.ttinfo_std = out.ttinfo_dst + + for tti in out.ttinfo_list: + if not tti.isdst: + out.ttinfo_before = tti + break + else: + out.ttinfo_before = out.ttinfo_list[0] + + # Now fix transition times to become relative to wall time. + # + # I'm not sure about this. In my tests, the tz source file + # is setup to wall time, and in the binary file isstd and + # isgmt are off, so it should be in wall time. OTOH, it's + # always in gmt time. Let me know if you have comments + # about this. + lastdst = None + lastoffset = None + lastdstoffset = None + lastbaseoffset = None + out.trans_list = [] + + for i, tti in enumerate(out.trans_idx): + offset = tti.offset + dstoffset = 0 + + if lastdst is not None: + if tti.isdst: + if not lastdst: + dstoffset = offset - lastoffset + + if not dstoffset and lastdstoffset: + dstoffset = lastdstoffset + + tti.dstoffset = datetime.timedelta(seconds=dstoffset) + lastdstoffset = dstoffset + + # If a time zone changes its base offset during a DST transition, + # then you need to adjust by the previous base offset to get the + # transition time in local time. Otherwise you use the current + # base offset. Ideally, I would have some mathematical proof of + # why this is true, but I haven't really thought about it enough. + baseoffset = offset - dstoffset + adjustment = baseoffset + if (lastbaseoffset is not None and baseoffset != lastbaseoffset + and tti.isdst != lastdst): + # The base DST has changed + adjustment = lastbaseoffset + + lastdst = tti.isdst + lastoffset = offset + lastbaseoffset = baseoffset + + out.trans_list.append(out.trans_list_utc[i] + adjustment) + + out.trans_idx = tuple(out.trans_idx) + out.trans_list = tuple(out.trans_list) + out.trans_list_utc = tuple(out.trans_list_utc) + + return out + + def _find_last_transition(self, dt, in_utc=False): + # If there's no list, there are no transitions to find + if not self._trans_list: + return None + + timestamp = _datetime_to_timestamp(dt) + + # Find where the timestamp fits in the transition list - if the + # timestamp is a transition time, it's part of the "after" period. + trans_list = self._trans_list_utc if in_utc else self._trans_list + idx = bisect.bisect_right(trans_list, timestamp) + + # We want to know when the previous transition was, so subtract off 1 + return idx - 1 + + def _get_ttinfo(self, idx): + # For no list or after the last transition, default to _ttinfo_std + if idx is None or (idx + 1) >= len(self._trans_list): + return self._ttinfo_std + + # If there is a list and the time is before it, return _ttinfo_before + if idx < 0: + return self._ttinfo_before + + return self._trans_idx[idx] + + def _find_ttinfo(self, dt): + idx = self._resolve_ambiguous_time(dt) + + return self._get_ttinfo(idx) + + def fromutc(self, dt): + """ + The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`. + + :param dt: + A :py:class:`datetime.datetime` object. + + :raises TypeError: + Raised if ``dt`` is not a :py:class:`datetime.datetime` object. + + :raises ValueError: + Raised if this is called with a ``dt`` which does not have this + ``tzinfo`` attached. + + :return: + Returns a :py:class:`datetime.datetime` object representing the + wall time in ``self``'s time zone. + """ + # These isinstance checks are in datetime.tzinfo, so we'll preserve + # them, even if we don't care about duck typing. + if not isinstance(dt, datetime.datetime): + raise TypeError("fromutc() requires a datetime argument") + + if dt.tzinfo is not self: + raise ValueError("dt.tzinfo is not self") + + # First treat UTC as wall time and get the transition we're in. + idx = self._find_last_transition(dt, in_utc=True) + tti = self._get_ttinfo(idx) + + dt_out = dt + datetime.timedelta(seconds=tti.offset) + + fold = self.is_ambiguous(dt_out, idx=idx) + + return enfold(dt_out, fold=int(fold)) + + def is_ambiguous(self, dt, idx=None): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + if idx is None: + idx = self._find_last_transition(dt) + + # Calculate the difference in offsets from current to previous + timestamp = _datetime_to_timestamp(dt) + tti = self._get_ttinfo(idx) + + if idx is None or idx <= 0: + return False + + od = self._get_ttinfo(idx - 1).offset - tti.offset + tt = self._trans_list[idx] # Transition time + + return timestamp < tt + od + + def _resolve_ambiguous_time(self, dt): + idx = self._find_last_transition(dt) + + # If we have no transitions, return the index + _fold = self._fold(dt) + if idx is None or idx == 0: + return idx + + # If it's ambiguous and we're in a fold, shift to a different index. + idx_offset = int(not _fold and self.is_ambiguous(dt, idx)) + + return idx - idx_offset + + def utcoffset(self, dt): + if dt is None: + return None + + if not self._ttinfo_std: + return ZERO + + return self._find_ttinfo(dt).delta + + def dst(self, dt): + if dt is None: + return None + + if not self._ttinfo_dst: + return ZERO + + tti = self._find_ttinfo(dt) + + if not tti.isdst: + return ZERO + + # The documentation says that utcoffset()-dst() must + # be constant for every dt. + return tti.dstoffset + + @tzname_in_python2 + def tzname(self, dt): + if not self._ttinfo_std or dt is None: + return None + return self._find_ttinfo(dt).abbr + + def __eq__(self, other): + if not isinstance(other, tzfile): + return NotImplemented + return (self._trans_list == other._trans_list and + self._trans_idx == other._trans_idx and + self._ttinfo_list == other._ttinfo_list) + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self._filename)) + + def __reduce__(self): + return self.__reduce_ex__(None) + + def __reduce_ex__(self, protocol): + return (self.__class__, (None, self._filename), self.__dict__) + + +class tzrange(tzrangebase): + """ + The ``tzrange`` object is a time zone specified by a set of offsets and + abbreviations, equivalent to the way the ``TZ`` variable can be specified + in POSIX-like systems, but using Python delta objects to specify DST + start, end and offsets. + + :param stdabbr: + The abbreviation for standard time (e.g. ``'EST'``). + + :param stdoffset: + An integer or :class:`datetime.timedelta` object or equivalent + specifying the base offset from UTC. + + If unspecified, +00:00 is used. + + :param dstabbr: + The abbreviation for DST / "Summer" time (e.g. ``'EDT'``). + + If specified, with no other DST information, DST is assumed to occur + and the default behavior or ``dstoffset``, ``start`` and ``end`` is + used. If unspecified and no other DST information is specified, it + is assumed that this zone has no DST. + + If this is unspecified and other DST information is *is* specified, + DST occurs in the zone but the time zone abbreviation is left + unchanged. + + :param dstoffset: + A an integer or :class:`datetime.timedelta` object or equivalent + specifying the UTC offset during DST. If unspecified and any other DST + information is specified, it is assumed to be the STD offset +1 hour. + + :param start: + A :class:`relativedelta.relativedelta` object or equivalent specifying + the time and time of year that daylight savings time starts. To + specify, for example, that DST starts at 2AM on the 2nd Sunday in + March, pass: + + ``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))`` + + If unspecified and any other DST information is specified, the default + value is 2 AM on the first Sunday in April. + + :param end: + A :class:`relativedelta.relativedelta` object or equivalent + representing the time and time of year that daylight savings time + ends, with the same specification method as in ``start``. One note is + that this should point to the first time in the *standard* zone, so if + a transition occurs at 2AM in the DST zone and the clocks are set back + 1 hour to 1AM, set the ``hours`` parameter to +1. + + + **Examples:** + + .. testsetup:: tzrange + + from dateutil.tz import tzrange, tzstr + + .. doctest:: tzrange + + >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT") + True + + >>> from dateutil.relativedelta import * + >>> range1 = tzrange("EST", -18000, "EDT") + >>> range2 = tzrange("EST", -18000, "EDT", -14400, + ... relativedelta(hours=+2, month=4, day=1, + ... weekday=SU(+1)), + ... relativedelta(hours=+1, month=10, day=31, + ... weekday=SU(-1))) + >>> tzstr('EST5EDT') == range1 == range2 + True + + """ + def __init__(self, stdabbr, stdoffset=None, + dstabbr=None, dstoffset=None, + start=None, end=None): + + global relativedelta + from dateutil import relativedelta + + self._std_abbr = stdabbr + self._dst_abbr = dstabbr + + try: + stdoffset = stdoffset.total_seconds() + except (TypeError, AttributeError): + pass + + try: + dstoffset = dstoffset.total_seconds() + except (TypeError, AttributeError): + pass + + if stdoffset is not None: + self._std_offset = datetime.timedelta(seconds=stdoffset) + else: + self._std_offset = ZERO + + if dstoffset is not None: + self._dst_offset = datetime.timedelta(seconds=dstoffset) + elif dstabbr and stdoffset is not None: + self._dst_offset = self._std_offset + datetime.timedelta(hours=+1) + else: + self._dst_offset = ZERO + + if dstabbr and start is None: + self._start_delta = relativedelta.relativedelta( + hours=+2, month=4, day=1, weekday=relativedelta.SU(+1)) + else: + self._start_delta = start + + if dstabbr and end is None: + self._end_delta = relativedelta.relativedelta( + hours=+1, month=10, day=31, weekday=relativedelta.SU(-1)) + else: + self._end_delta = end + + self._dst_base_offset_ = self._dst_offset - self._std_offset + self.hasdst = bool(self._start_delta) + + def transitions(self, year): + """ + For a given year, get the DST on and off transition times, expressed + always on the standard time side. For zones with no transitions, this + function returns ``None``. + + :param year: + The year whose transitions you would like to query. + + :return: + Returns a :class:`tuple` of :class:`datetime.datetime` objects, + ``(dston, dstoff)`` for zones with an annual DST transition, or + ``None`` for fixed offset zones. + """ + if not self.hasdst: + return None + + base_year = datetime.datetime(year, 1, 1) + + start = base_year + self._start_delta + end = base_year + self._end_delta + + return (start, end) + + def __eq__(self, other): + if not isinstance(other, tzrange): + return NotImplemented + + return (self._std_abbr == other._std_abbr and + self._dst_abbr == other._dst_abbr and + self._std_offset == other._std_offset and + self._dst_offset == other._dst_offset and + self._start_delta == other._start_delta and + self._end_delta == other._end_delta) + + @property + def _dst_base_offset(self): + return self._dst_base_offset_ + + +@six.add_metaclass(_TzStrFactory) +class tzstr(tzrange): + """ + ``tzstr`` objects are time zone objects specified by a time-zone string as + it would be passed to a ``TZ`` variable on POSIX-style systems (see + the `GNU C Library: TZ Variable`_ for more details). + + There is one notable exception, which is that POSIX-style time zones use an + inverted offset format, so normally ``GMT+3`` would be parsed as an offset + 3 hours *behind* GMT. The ``tzstr`` time zone object will parse this as an + offset 3 hours *ahead* of GMT. If you would like to maintain the POSIX + behavior, pass a ``True`` value to ``posix_offset``. + + The :class:`tzrange` object provides the same functionality, but is + specified using :class:`relativedelta.relativedelta` objects. rather than + strings. + + :param s: + A time zone string in ``TZ`` variable format. This can be a + :class:`bytes` (2.x: :class:`str`), :class:`str` (2.x: + :class:`unicode`) or a stream emitting unicode characters + (e.g. :class:`StringIO`). + + :param posix_offset: + Optional. If set to ``True``, interpret strings such as ``GMT+3`` or + ``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the + POSIX standard. + + .. caution:: + + Prior to version 2.7.0, this function also supported time zones + in the format: + + * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600`` + * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600`` + + This format is non-standard and has been deprecated; this function + will raise a :class:`DeprecatedTZFormatWarning` until + support is removed in a future version. + + .. _`GNU C Library: TZ Variable`: + https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html + """ + def __init__(self, s, posix_offset=False): + global parser + from dateutil.parser import _parser as parser + + self._s = s + + res = parser._parsetz(s) + if res is None or res.any_unused_tokens: + raise ValueError("unknown string format") + + # Here we break the compatibility with the TZ variable handling. + # GMT-3 actually *means* the timezone -3. + if res.stdabbr in ("GMT", "UTC") and not posix_offset: + res.stdoffset *= -1 + + # We must initialize it first, since _delta() needs + # _std_offset and _dst_offset set. Use False in start/end + # to avoid building it two times. + tzrange.__init__(self, res.stdabbr, res.stdoffset, + res.dstabbr, res.dstoffset, + start=False, end=False) + + if not res.dstabbr: + self._start_delta = None + self._end_delta = None + else: + self._start_delta = self._delta(res.start) + if self._start_delta: + self._end_delta = self._delta(res.end, isend=1) + + self.hasdst = bool(self._start_delta) + + def _delta(self, x, isend=0): + from dateutil import relativedelta + kwargs = {} + if x.month is not None: + kwargs["month"] = x.month + if x.weekday is not None: + kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week) + if x.week > 0: + kwargs["day"] = 1 + else: + kwargs["day"] = 31 + elif x.day: + kwargs["day"] = x.day + elif x.yday is not None: + kwargs["yearday"] = x.yday + elif x.jyday is not None: + kwargs["nlyearday"] = x.jyday + if not kwargs: + # Default is to start on first sunday of april, and end + # on last sunday of october. + if not isend: + kwargs["month"] = 4 + kwargs["day"] = 1 + kwargs["weekday"] = relativedelta.SU(+1) + else: + kwargs["month"] = 10 + kwargs["day"] = 31 + kwargs["weekday"] = relativedelta.SU(-1) + if x.time is not None: + kwargs["seconds"] = x.time + else: + # Default is 2AM. + kwargs["seconds"] = 7200 + if isend: + # Convert to standard time, to follow the documented way + # of working with the extra hour. See the documentation + # of the tzinfo class. + delta = self._dst_offset - self._std_offset + kwargs["seconds"] -= delta.seconds + delta.days * 86400 + return relativedelta.relativedelta(**kwargs) + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self._s)) + + +class _tzicalvtzcomp(object): + def __init__(self, tzoffsetfrom, tzoffsetto, isdst, + tzname=None, rrule=None): + self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom) + self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto) + self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom + self.isdst = isdst + self.tzname = tzname + self.rrule = rrule + + +class _tzicalvtz(_tzinfo): + def __init__(self, tzid, comps=[]): + super(_tzicalvtz, self).__init__() + + self._tzid = tzid + self._comps = comps + self._cachedate = [] + self._cachecomp = [] + self._cache_lock = _thread.allocate_lock() + + def _find_comp(self, dt): + if len(self._comps) == 1: + return self._comps[0] + + dt = dt.replace(tzinfo=None) + + try: + with self._cache_lock: + return self._cachecomp[self._cachedate.index( + (dt, self._fold(dt)))] + except ValueError: + pass + + lastcompdt = None + lastcomp = None + + for comp in self._comps: + compdt = self._find_compdt(comp, dt) + + if compdt and (not lastcompdt or lastcompdt < compdt): + lastcompdt = compdt + lastcomp = comp + + if not lastcomp: + # RFC says nothing about what to do when a given + # time is before the first onset date. We'll look for the + # first standard component, or the first component, if + # none is found. + for comp in self._comps: + if not comp.isdst: + lastcomp = comp + break + else: + lastcomp = comp[0] + + with self._cache_lock: + self._cachedate.insert(0, (dt, self._fold(dt))) + self._cachecomp.insert(0, lastcomp) + + if len(self._cachedate) > 10: + self._cachedate.pop() + self._cachecomp.pop() + + return lastcomp + + def _find_compdt(self, comp, dt): + if comp.tzoffsetdiff < ZERO and self._fold(dt): + dt -= comp.tzoffsetdiff + + compdt = comp.rrule.before(dt, inc=True) + + return compdt + + def utcoffset(self, dt): + if dt is None: + return None + + return self._find_comp(dt).tzoffsetto + + def dst(self, dt): + comp = self._find_comp(dt) + if comp.isdst: + return comp.tzoffsetdiff + else: + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return self._find_comp(dt).tzname + + def __repr__(self): + return "" % repr(self._tzid) + + __reduce__ = object.__reduce__ + + +class tzical(object): + """ + This object is designed to parse an iCalendar-style ``VTIMEZONE`` structure + as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects. + + :param `fileobj`: + A file or stream in iCalendar format, which should be UTF-8 encoded + with CRLF endings. + + .. _`RFC 5545`: https://tools.ietf.org/html/rfc5545 + """ + def __init__(self, fileobj): + global rrule + from dateutil import rrule + + if isinstance(fileobj, string_types): + self._s = fileobj + # ical should be encoded in UTF-8 with CRLF + fileobj = open(fileobj, 'r') + else: + self._s = getattr(fileobj, 'name', repr(fileobj)) + fileobj = _nullcontext(fileobj) + + self._vtz = {} + + with fileobj as fobj: + self._parse_rfc(fobj.read()) + + def keys(self): + """ + Retrieves the available time zones as a list. + """ + return list(self._vtz.keys()) + + def get(self, tzid=None): + """ + Retrieve a :py:class:`datetime.tzinfo` object by its ``tzid``. + + :param tzid: + If there is exactly one time zone available, omitting ``tzid`` + or passing :py:const:`None` value returns it. Otherwise a valid + key (which can be retrieved from :func:`keys`) is required. + + :raises ValueError: + Raised if ``tzid`` is not specified but there are either more + or fewer than 1 zone defined. + + :returns: + Returns either a :py:class:`datetime.tzinfo` object representing + the relevant time zone or :py:const:`None` if the ``tzid`` was + not found. + """ + if tzid is None: + if len(self._vtz) == 0: + raise ValueError("no timezones defined") + elif len(self._vtz) > 1: + raise ValueError("more than one timezone available") + tzid = next(iter(self._vtz)) + + return self._vtz.get(tzid) + + def _parse_offset(self, s): + s = s.strip() + if not s: + raise ValueError("empty offset") + if s[0] in ('+', '-'): + signal = (-1, +1)[s[0] == '+'] + s = s[1:] + else: + signal = +1 + if len(s) == 4: + return (int(s[:2]) * 3600 + int(s[2:]) * 60) * signal + elif len(s) == 6: + return (int(s[:2]) * 3600 + int(s[2:4]) * 60 + int(s[4:])) * signal + else: + raise ValueError("invalid offset: " + s) + + def _parse_rfc(self, s): + lines = s.splitlines() + if not lines: + raise ValueError("empty string") + + # Unfold + i = 0 + while i < len(lines): + line = lines[i].rstrip() + if not line: + del lines[i] + elif i > 0 and line[0] == " ": + lines[i-1] += line[1:] + del lines[i] + else: + i += 1 + + tzid = None + comps = [] + invtz = False + comptype = None + for line in lines: + if not line: + continue + name, value = line.split(':', 1) + parms = name.split(';') + if not parms: + raise ValueError("empty property name") + name = parms[0].upper() + parms = parms[1:] + if invtz: + if name == "BEGIN": + if value in ("STANDARD", "DAYLIGHT"): + # Process component + pass + else: + raise ValueError("unknown component: "+value) + comptype = value + founddtstart = False + tzoffsetfrom = None + tzoffsetto = None + rrulelines = [] + tzname = None + elif name == "END": + if value == "VTIMEZONE": + if comptype: + raise ValueError("component not closed: "+comptype) + if not tzid: + raise ValueError("mandatory TZID not found") + if not comps: + raise ValueError( + "at least one component is needed") + # Process vtimezone + self._vtz[tzid] = _tzicalvtz(tzid, comps) + invtz = False + elif value == comptype: + if not founddtstart: + raise ValueError("mandatory DTSTART not found") + if tzoffsetfrom is None: + raise ValueError( + "mandatory TZOFFSETFROM not found") + if tzoffsetto is None: + raise ValueError( + "mandatory TZOFFSETFROM not found") + # Process component + rr = None + if rrulelines: + rr = rrule.rrulestr("\n".join(rrulelines), + compatible=True, + ignoretz=True, + cache=True) + comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto, + (comptype == "DAYLIGHT"), + tzname, rr) + comps.append(comp) + comptype = None + else: + raise ValueError("invalid component end: "+value) + elif comptype: + if name == "DTSTART": + # DTSTART in VTIMEZONE takes a subset of valid RRULE + # values under RFC 5545. + for parm in parms: + if parm != 'VALUE=DATE-TIME': + msg = ('Unsupported DTSTART param in ' + + 'VTIMEZONE: ' + parm) + raise ValueError(msg) + rrulelines.append(line) + founddtstart = True + elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"): + rrulelines.append(line) + elif name == "TZOFFSETFROM": + if parms: + raise ValueError( + "unsupported %s parm: %s " % (name, parms[0])) + tzoffsetfrom = self._parse_offset(value) + elif name == "TZOFFSETTO": + if parms: + raise ValueError( + "unsupported TZOFFSETTO parm: "+parms[0]) + tzoffsetto = self._parse_offset(value) + elif name == "TZNAME": + if parms: + raise ValueError( + "unsupported TZNAME parm: "+parms[0]) + tzname = value + elif name == "COMMENT": + pass + else: + raise ValueError("unsupported property: "+name) + else: + if name == "TZID": + if parms: + raise ValueError( + "unsupported TZID parm: "+parms[0]) + tzid = value + elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"): + pass + else: + raise ValueError("unsupported property: "+name) + elif name == "BEGIN" and value == "VTIMEZONE": + tzid = None + comps = [] + invtz = True + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self._s)) + + +if sys.platform != "win32": + TZFILES = ["/etc/localtime", "localtime"] + TZPATHS = ["/usr/share/zoneinfo", + "/usr/lib/zoneinfo", + "/usr/share/lib/zoneinfo", + "/etc/zoneinfo"] +else: + TZFILES = [] + TZPATHS = [] + + +def __get_gettz(): + tzlocal_classes = (tzlocal,) + if tzwinlocal is not None: + tzlocal_classes += (tzwinlocal,) + + class GettzFunc(object): + """ + Retrieve a time zone object from a string representation + + This function is intended to retrieve the :py:class:`tzinfo` subclass + that best represents the time zone that would be used if a POSIX + `TZ variable`_ were set to the same value. + + If no argument or an empty string is passed to ``gettz``, local time + is returned: + + .. code-block:: python3 + + >>> gettz() + tzfile('/etc/localtime') + + This function is also the preferred way to map IANA tz database keys + to :class:`tzfile` objects: + + .. code-block:: python3 + + >>> gettz('Pacific/Kiritimati') + tzfile('/usr/share/zoneinfo/Pacific/Kiritimati') + + On Windows, the standard is extended to include the Windows-specific + zone names provided by the operating system: + + .. code-block:: python3 + + >>> gettz('Egypt Standard Time') + tzwin('Egypt Standard Time') + + Passing a GNU ``TZ`` style string time zone specification returns a + :class:`tzstr` object: + + .. code-block:: python3 + + >>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') + tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') + + :param name: + A time zone name (IANA, or, on Windows, Windows keys), location of + a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone + specifier. An empty string, no argument or ``None`` is interpreted + as local time. + + :return: + Returns an instance of one of ``dateutil``'s :py:class:`tzinfo` + subclasses. + + .. versionchanged:: 2.7.0 + + After version 2.7.0, any two calls to ``gettz`` using the same + input strings will return the same object: + + .. code-block:: python3 + + >>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago') + True + + In addition to improving performance, this ensures that + `"same zone" semantics`_ are used for datetimes in the same zone. + + + .. _`TZ variable`: + https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html + + .. _`"same zone" semantics`: + https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html + """ + def __init__(self): + + self.__instances = weakref.WeakValueDictionary() + self.__strong_cache_size = 8 + self.__strong_cache = OrderedDict() + self._cache_lock = _thread.allocate_lock() + + def __call__(self, name=None): + with self._cache_lock: + rv = self.__instances.get(name, None) + + if rv is None: + rv = self.nocache(name=name) + if not (name is None + or isinstance(rv, tzlocal_classes) + or rv is None): + # tzlocal is slightly more complicated than the other + # time zone providers because it depends on environment + # at construction time, so don't cache that. + # + # We also cannot store weak references to None, so we + # will also not store that. + self.__instances[name] = rv + else: + # No need for strong caching, return immediately + return rv + + self.__strong_cache[name] = self.__strong_cache.pop(name, rv) + + if len(self.__strong_cache) > self.__strong_cache_size: + self.__strong_cache.popitem(last=False) + + return rv + + def set_cache_size(self, size): + with self._cache_lock: + self.__strong_cache_size = size + while len(self.__strong_cache) > size: + self.__strong_cache.popitem(last=False) + + def cache_clear(self): + with self._cache_lock: + self.__instances = weakref.WeakValueDictionary() + self.__strong_cache.clear() + + @staticmethod + def nocache(name=None): + """A non-cached version of gettz""" + tz = None + if not name: + try: + name = os.environ["TZ"] + except KeyError: + pass + if name is None or name in ("", ":"): + for filepath in TZFILES: + if not os.path.isabs(filepath): + filename = filepath + for path in TZPATHS: + filepath = os.path.join(path, filename) + if os.path.isfile(filepath): + break + else: + continue + if os.path.isfile(filepath): + try: + tz = tzfile(filepath) + break + except (IOError, OSError, ValueError): + pass + else: + tz = tzlocal() + else: + try: + if name.startswith(":"): + name = name[1:] + except TypeError as e: + if isinstance(name, bytes): + new_msg = "gettz argument should be str, not bytes" + six.raise_from(TypeError(new_msg), e) + else: + raise + if os.path.isabs(name): + if os.path.isfile(name): + tz = tzfile(name) + else: + tz = None + else: + for path in TZPATHS: + filepath = os.path.join(path, name) + if not os.path.isfile(filepath): + filepath = filepath.replace(' ', '_') + if not os.path.isfile(filepath): + continue + try: + tz = tzfile(filepath) + break + except (IOError, OSError, ValueError): + pass + else: + tz = None + if tzwin is not None: + try: + tz = tzwin(name) + except (WindowsError, UnicodeEncodeError): + # UnicodeEncodeError is for Python 2.7 compat + tz = None + + if not tz: + from dateutil.zoneinfo import get_zonefile_instance + tz = get_zonefile_instance().get(name) + + if not tz: + for c in name: + # name is not a tzstr unless it has at least + # one offset. For short values of "name", an + # explicit for loop seems to be the fastest way + # To determine if a string contains a digit + if c in "0123456789": + try: + tz = tzstr(name) + except ValueError: + pass + break + else: + if name in ("GMT", "UTC"): + tz = UTC + elif name in time.tzname: + tz = tzlocal() + return tz + + return GettzFunc() + + +gettz = __get_gettz() +del __get_gettz + + +def datetime_exists(dt, tz=None): + """ + Given a datetime and a time zone, determine whether or not a given datetime + would fall in a gap. + + :param dt: + A :class:`datetime.datetime` (whose time zone will be ignored if ``tz`` + is provided.) + + :param tz: + A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If + ``None`` or not provided, the datetime's own time zone will be used. + + :return: + Returns a boolean value whether or not the "wall time" exists in + ``tz``. + + .. versionadded:: 2.7.0 + """ + if tz is None: + if dt.tzinfo is None: + raise ValueError('Datetime is naive and no time zone provided.') + tz = dt.tzinfo + + dt = dt.replace(tzinfo=None) + + # This is essentially a test of whether or not the datetime can survive + # a round trip to UTC. + dt_rt = dt.replace(tzinfo=tz).astimezone(UTC).astimezone(tz) + dt_rt = dt_rt.replace(tzinfo=None) + + return dt == dt_rt + + +def datetime_ambiguous(dt, tz=None): + """ + Given a datetime and a time zone, determine whether or not a given datetime + is ambiguous (i.e if there are two times differentiated only by their DST + status). + + :param dt: + A :class:`datetime.datetime` (whose time zone will be ignored if ``tz`` + is provided.) + + :param tz: + A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If + ``None`` or not provided, the datetime's own time zone will be used. + + :return: + Returns a boolean value whether or not the "wall time" is ambiguous in + ``tz``. + + .. versionadded:: 2.6.0 + """ + if tz is None: + if dt.tzinfo is None: + raise ValueError('Datetime is naive and no time zone provided.') + + tz = dt.tzinfo + + # If a time zone defines its own "is_ambiguous" function, we'll use that. + is_ambiguous_fn = getattr(tz, 'is_ambiguous', None) + if is_ambiguous_fn is not None: + try: + return tz.is_ambiguous(dt) + except Exception: + pass + + # If it doesn't come out and tell us it's ambiguous, we'll just check if + # the fold attribute has any effect on this particular date and time. + dt = dt.replace(tzinfo=tz) + wall_0 = enfold(dt, fold=0) + wall_1 = enfold(dt, fold=1) + + same_offset = wall_0.utcoffset() == wall_1.utcoffset() + same_dst = wall_0.dst() == wall_1.dst() + + return not (same_offset and same_dst) + + +def resolve_imaginary(dt): + """ + Given a datetime that may be imaginary, return an existing datetime. + + This function assumes that an imaginary datetime represents what the + wall time would be in a zone had the offset transition not occurred, so + it will always fall forward by the transition's change in offset. + + .. doctest:: + + >>> from dateutil import tz + >>> from datetime import datetime + >>> NYC = tz.gettz('America/New_York') + >>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC))) + 2017-03-12 03:30:00-04:00 + + >>> KIR = tz.gettz('Pacific/Kiritimati') + >>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR))) + 1995-01-02 12:30:00+14:00 + + As a note, :func:`datetime.astimezone` is guaranteed to produce a valid, + existing datetime, so a round-trip to and from UTC is sufficient to get + an extant datetime, however, this generally "falls back" to an earlier time + rather than falling forward to the STD side (though no guarantees are made + about this behavior). + + :param dt: + A :class:`datetime.datetime` which may or may not exist. + + :return: + Returns an existing :class:`datetime.datetime`. If ``dt`` was not + imaginary, the datetime returned is guaranteed to be the same object + passed to the function. + + .. versionadded:: 2.7.0 + """ + if dt.tzinfo is not None and not datetime_exists(dt): + + curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset() + old_offset = (dt - datetime.timedelta(hours=24)).utcoffset() + + dt += curr_offset - old_offset + + return dt + + +def _datetime_to_timestamp(dt): + """ + Convert a :class:`datetime.datetime` object to an epoch timestamp in + seconds since January 1, 1970, ignoring the time zone. + """ + return (dt.replace(tzinfo=None) - EPOCH).total_seconds() + + +if sys.version_info >= (3, 6): + def _get_supported_offset(second_offset): + return second_offset +else: + def _get_supported_offset(second_offset): + # For python pre-3.6, round to full-minutes if that's not the case. + # Python's datetime doesn't accept sub-minute timezones. Check + # http://python.org/sf/1447945 or https://bugs.python.org/issue5288 + # for some information. + old_offset = second_offset + calculated_offset = 60 * ((second_offset + 30) // 60) + return calculated_offset + + +try: + # Python 3.7 feature + from contextlib import nullcontext as _nullcontext +except ImportError: + class _nullcontext(object): + """ + Class for wrapping contexts so that they are passed through in a + with statement. + """ + def __init__(self, context): + self.context = context + + def __enter__(self): + return self.context + + def __exit__(*args, **kwargs): + pass + +# vim:ts=4:sw=4:et diff --git a/dateutil/tz/win.py b/dateutil/tz/win.py new file mode 100644 index 0000000..cde07ba --- /dev/null +++ b/dateutil/tz/win.py @@ -0,0 +1,370 @@ +# -*- coding: utf-8 -*- +""" +This module provides an interface to the native time zone data on Windows, +including :py:class:`datetime.tzinfo` implementations. + +Attempting to import this module on a non-Windows platform will raise an +:py:obj:`ImportError`. +""" +# This code was originally contributed by Jeffrey Harris. +import datetime +import struct + +from six.moves import winreg +from six import text_type + +try: + import ctypes + from ctypes import wintypes +except ValueError: + # ValueError is raised on non-Windows systems for some horrible reason. + raise ImportError("Running tzwin on non-Windows system") + +from ._common import tzrangebase + +__all__ = ["tzwin", "tzwinlocal", "tzres"] + +ONEWEEK = datetime.timedelta(7) + +TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" +TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones" +TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" + + +def _settzkeyname(): + handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) + try: + winreg.OpenKey(handle, TZKEYNAMENT).Close() + TZKEYNAME = TZKEYNAMENT + except WindowsError: + TZKEYNAME = TZKEYNAME9X + handle.Close() + return TZKEYNAME + + +TZKEYNAME = _settzkeyname() + + +class tzres(object): + """ + Class for accessing ``tzres.dll``, which contains timezone name related + resources. + + .. versionadded:: 2.5.0 + """ + p_wchar = ctypes.POINTER(wintypes.WCHAR) # Pointer to a wide char + + def __init__(self, tzres_loc='tzres.dll'): + # Load the user32 DLL so we can load strings from tzres + user32 = ctypes.WinDLL('user32') + + # Specify the LoadStringW function + user32.LoadStringW.argtypes = (wintypes.HINSTANCE, + wintypes.UINT, + wintypes.LPWSTR, + ctypes.c_int) + + self.LoadStringW = user32.LoadStringW + self._tzres = ctypes.WinDLL(tzres_loc) + self.tzres_loc = tzres_loc + + def load_name(self, offset): + """ + Load a timezone name from a DLL offset (integer). + + >>> from dateutil.tzwin import tzres + >>> tzr = tzres() + >>> print(tzr.load_name(112)) + 'Eastern Standard Time' + + :param offset: + A positive integer value referring to a string from the tzres dll. + + .. note:: + + Offsets found in the registry are generally of the form + ``@tzres.dll,-114``. The offset in this case is 114, not -114. + + """ + resource = self.p_wchar() + lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR) + nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0) + return resource[:nchar] + + def name_from_string(self, tzname_str): + """ + Parse strings as returned from the Windows registry into the time zone + name as defined in the registry. + + >>> from dateutil.tzwin import tzres + >>> tzr = tzres() + >>> print(tzr.name_from_string('@tzres.dll,-251')) + 'Dateline Daylight Time' + >>> print(tzr.name_from_string('Eastern Standard Time')) + 'Eastern Standard Time' + + :param tzname_str: + A timezone name string as returned from a Windows registry key. + + :return: + Returns the localized timezone string from tzres.dll if the string + is of the form `@tzres.dll,-offset`, else returns the input string. + """ + if not tzname_str.startswith('@'): + return tzname_str + + name_splt = tzname_str.split(',-') + try: + offset = int(name_splt[1]) + except: + raise ValueError("Malformed timezone string.") + + return self.load_name(offset) + + +class tzwinbase(tzrangebase): + """tzinfo class based on win32's timezones available in the registry.""" + def __init__(self): + raise NotImplementedError('tzwinbase is an abstract base class') + + def __eq__(self, other): + # Compare on all relevant dimensions, including name. + if not isinstance(other, tzwinbase): + return NotImplemented + + return (self._std_offset == other._std_offset and + self._dst_offset == other._dst_offset and + self._stddayofweek == other._stddayofweek and + self._dstdayofweek == other._dstdayofweek and + self._stdweeknumber == other._stdweeknumber and + self._dstweeknumber == other._dstweeknumber and + self._stdhour == other._stdhour and + self._dsthour == other._dsthour and + self._stdminute == other._stdminute and + self._dstminute == other._dstminute and + self._std_abbr == other._std_abbr and + self._dst_abbr == other._dst_abbr) + + @staticmethod + def list(): + """Return a list of all time zones known to the system.""" + with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: + with winreg.OpenKey(handle, TZKEYNAME) as tzkey: + result = [winreg.EnumKey(tzkey, i) + for i in range(winreg.QueryInfoKey(tzkey)[0])] + return result + + def display(self): + """ + Return the display name of the time zone. + """ + return self._display + + def transitions(self, year): + """ + For a given year, get the DST on and off transition times, expressed + always on the standard time side. For zones with no transitions, this + function returns ``None``. + + :param year: + The year whose transitions you would like to query. + + :return: + Returns a :class:`tuple` of :class:`datetime.datetime` objects, + ``(dston, dstoff)`` for zones with an annual DST transition, or + ``None`` for fixed offset zones. + """ + + if not self.hasdst: + return None + + dston = picknthweekday(year, self._dstmonth, self._dstdayofweek, + self._dsthour, self._dstminute, + self._dstweeknumber) + + dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek, + self._stdhour, self._stdminute, + self._stdweeknumber) + + # Ambiguous dates default to the STD side + dstoff -= self._dst_base_offset + + return dston, dstoff + + def _get_hasdst(self): + return self._dstmonth != 0 + + @property + def _dst_base_offset(self): + return self._dst_base_offset_ + + +class tzwin(tzwinbase): + """ + Time zone object created from the zone info in the Windows registry + + These are similar to :py:class:`dateutil.tz.tzrange` objects in that + the time zone data is provided in the format of a single offset rule + for either 0 or 2 time zone transitions per year. + + :param: name + The name of a Windows time zone key, e.g. "Eastern Standard Time". + The full list of keys can be retrieved with :func:`tzwin.list`. + """ + + def __init__(self, name): + self._name = name + + with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: + tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name) + with winreg.OpenKey(handle, tzkeyname) as tzkey: + keydict = valuestodict(tzkey) + + self._std_abbr = keydict["Std"] + self._dst_abbr = keydict["Dlt"] + + self._display = keydict["Display"] + + # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm + tup = struct.unpack("=3l16h", keydict["TZI"]) + stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1 + dstoffset = stdoffset-tup[2] # + DaylightBias * -1 + self._std_offset = datetime.timedelta(minutes=stdoffset) + self._dst_offset = datetime.timedelta(minutes=dstoffset) + + # for the meaning see the win32 TIME_ZONE_INFORMATION structure docs + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx + (self._stdmonth, + self._stddayofweek, # Sunday = 0 + self._stdweeknumber, # Last = 5 + self._stdhour, + self._stdminute) = tup[4:9] + + (self._dstmonth, + self._dstdayofweek, # Sunday = 0 + self._dstweeknumber, # Last = 5 + self._dsthour, + self._dstminute) = tup[12:17] + + self._dst_base_offset_ = self._dst_offset - self._std_offset + self.hasdst = self._get_hasdst() + + def __repr__(self): + return "tzwin(%s)" % repr(self._name) + + def __reduce__(self): + return (self.__class__, (self._name,)) + + +class tzwinlocal(tzwinbase): + """ + Class representing the local time zone information in the Windows registry + + While :class:`dateutil.tz.tzlocal` makes system calls (via the :mod:`time` + module) to retrieve time zone information, ``tzwinlocal`` retrieves the + rules directly from the Windows registry and creates an object like + :class:`dateutil.tz.tzwin`. + + Because Windows does not have an equivalent of :func:`time.tzset`, on + Windows, :class:`dateutil.tz.tzlocal` instances will always reflect the + time zone settings *at the time that the process was started*, meaning + changes to the machine's time zone settings during the run of a program + on Windows will **not** be reflected by :class:`dateutil.tz.tzlocal`. + Because ``tzwinlocal`` reads the registry directly, it is unaffected by + this issue. + """ + def __init__(self): + with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: + with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey: + keydict = valuestodict(tzlocalkey) + + self._std_abbr = keydict["StandardName"] + self._dst_abbr = keydict["DaylightName"] + + try: + tzkeyname = text_type('{kn}\\{sn}').format(kn=TZKEYNAME, + sn=self._std_abbr) + with winreg.OpenKey(handle, tzkeyname) as tzkey: + _keydict = valuestodict(tzkey) + self._display = _keydict["Display"] + except OSError: + self._display = None + + stdoffset = -keydict["Bias"]-keydict["StandardBias"] + dstoffset = stdoffset-keydict["DaylightBias"] + + self._std_offset = datetime.timedelta(minutes=stdoffset) + self._dst_offset = datetime.timedelta(minutes=dstoffset) + + # For reasons unclear, in this particular key, the day of week has been + # moved to the END of the SYSTEMTIME structure. + tup = struct.unpack("=8h", keydict["StandardStart"]) + + (self._stdmonth, + self._stdweeknumber, # Last = 5 + self._stdhour, + self._stdminute) = tup[1:5] + + self._stddayofweek = tup[7] + + tup = struct.unpack("=8h", keydict["DaylightStart"]) + + (self._dstmonth, + self._dstweeknumber, # Last = 5 + self._dsthour, + self._dstminute) = tup[1:5] + + self._dstdayofweek = tup[7] + + self._dst_base_offset_ = self._dst_offset - self._std_offset + self.hasdst = self._get_hasdst() + + def __repr__(self): + return "tzwinlocal()" + + def __str__(self): + # str will return the standard name, not the daylight name. + return "tzwinlocal(%s)" % repr(self._std_abbr) + + def __reduce__(self): + return (self.__class__, ()) + + +def picknthweekday(year, month, dayofweek, hour, minute, whichweek): + """ dayofweek == 0 means Sunday, whichweek 5 means last instance """ + first = datetime.datetime(year, month, 1, hour, minute) + + # This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6), + # Because 7 % 7 = 0 + weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1) + wd = weekdayone + ((whichweek - 1) * ONEWEEK) + if (wd.month != month): + wd -= ONEWEEK + + return wd + + +def valuestodict(key): + """Convert a registry key's values to a dictionary.""" + dout = {} + size = winreg.QueryInfoKey(key)[1] + tz_res = None + + for i in range(size): + key_name, value, dtype = winreg.EnumValue(key, i) + if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN: + # If it's a DWORD (32-bit integer), it's stored as unsigned - convert + # that to a proper signed integer + if value & (1 << 31): + value = value - (1 << 32) + elif dtype == winreg.REG_SZ: + # If it's a reference to the tzres DLL, load the actual string + if value.startswith('@tzres'): + tz_res = tz_res or tzres() + value = tz_res.name_from_string(value) + + value = value.rstrip('\x00') # Remove trailing nulls + + dout[key_name] = value + + return dout diff --git a/dateutil/tzwin.py b/dateutil/tzwin.py new file mode 100644 index 0000000..cebc673 --- /dev/null +++ b/dateutil/tzwin.py @@ -0,0 +1,2 @@ +# tzwin has moved to dateutil.tz.win +from .tz.win import * diff --git a/dateutil/utils.py b/dateutil/utils.py new file mode 100644 index 0000000..dd2d245 --- /dev/null +++ b/dateutil/utils.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +""" +This module offers general convenience and utility functions for dealing with +datetimes. + +.. versionadded:: 2.7.0 +""" +from __future__ import unicode_literals + +from datetime import datetime, time + + +def today(tzinfo=None): + """ + Returns a :py:class:`datetime` representing the current day at midnight + + :param tzinfo: + The time zone to attach (also used to determine the current day). + + :return: + A :py:class:`datetime.datetime` object representing the current day + at midnight. + """ + + dt = datetime.now(tzinfo) + return datetime.combine(dt.date(), time(0, tzinfo=tzinfo)) + + +def default_tzinfo(dt, tzinfo): + """ + Sets the ``tzinfo`` parameter on naive datetimes only + + This is useful for example when you are provided a datetime that may have + either an implicit or explicit time zone, such as when parsing a time zone + string. + + .. doctest:: + + >>> from dateutil.tz import tzoffset + >>> from dateutil.parser import parse + >>> from dateutil.utils import default_tzinfo + >>> dflt_tz = tzoffset("EST", -18000) + >>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz)) + 2014-01-01 12:30:00+00:00 + >>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz)) + 2014-01-01 12:30:00-05:00 + + :param dt: + The datetime on which to replace the time zone + + :param tzinfo: + The :py:class:`datetime.tzinfo` subclass instance to assign to + ``dt`` if (and only if) it is naive. + + :return: + Returns an aware :py:class:`datetime.datetime`. + """ + if dt.tzinfo is not None: + return dt + else: + return dt.replace(tzinfo=tzinfo) + + +def within_delta(dt1, dt2, delta): + """ + Useful for comparing two datetimes that may have a negligible difference + to be considered equal. + """ + delta = abs(delta) + difference = dt1 - dt2 + return -delta <= difference <= delta diff --git a/dateutil/zoneinfo/__init__.py b/dateutil/zoneinfo/__init__.py new file mode 100644 index 0000000..34f11ad --- /dev/null +++ b/dateutil/zoneinfo/__init__.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +import warnings +import json + +from tarfile import TarFile +from pkgutil import get_data +from io import BytesIO + +from dateutil.tz import tzfile as _tzfile + +__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"] + +ZONEFILENAME = "dateutil-zoneinfo.tar.gz" +METADATA_FN = 'METADATA' + + +class tzfile(_tzfile): + def __reduce__(self): + return (gettz, (self._filename,)) + + +def getzoneinfofile_stream(): + try: + return BytesIO(get_data(__name__, ZONEFILENAME)) + except IOError as e: # TODO switch to FileNotFoundError? + warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror)) + return None + + +class ZoneInfoFile(object): + def __init__(self, zonefile_stream=None): + if zonefile_stream is not None: + with TarFile.open(fileobj=zonefile_stream) as tf: + self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name) + for zf in tf.getmembers() + if zf.isfile() and zf.name != METADATA_FN} + # deal with links: They'll point to their parent object. Less + # waste of memory + links = {zl.name: self.zones[zl.linkname] + for zl in tf.getmembers() if + zl.islnk() or zl.issym()} + self.zones.update(links) + try: + metadata_json = tf.extractfile(tf.getmember(METADATA_FN)) + metadata_str = metadata_json.read().decode('UTF-8') + self.metadata = json.loads(metadata_str) + except KeyError: + # no metadata in tar file + self.metadata = None + else: + self.zones = {} + self.metadata = None + + def get(self, name, default=None): + """ + Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method + for retrieving zones from the zone dictionary. + + :param name: + The name of the zone to retrieve. (Generally IANA zone names) + + :param default: + The value to return in the event of a missing key. + + .. versionadded:: 2.6.0 + + """ + return self.zones.get(name, default) + + +# The current API has gettz as a module function, although in fact it taps into +# a stateful class. So as a workaround for now, without changing the API, we +# will create a new "global" class instance the first time a user requests a +# timezone. Ugly, but adheres to the api. +# +# TODO: Remove after deprecation period. +_CLASS_ZONE_INSTANCE = [] + + +def get_zonefile_instance(new_instance=False): + """ + This is a convenience function which provides a :class:`ZoneInfoFile` + instance using the data provided by the ``dateutil`` package. By default, it + caches a single instance of the ZoneInfoFile object and returns that. + + :param new_instance: + If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and + used as the cached instance for the next call. Otherwise, new instances + are created only as necessary. + + :return: + Returns a :class:`ZoneInfoFile` object. + + .. versionadded:: 2.6 + """ + if new_instance: + zif = None + else: + zif = getattr(get_zonefile_instance, '_cached_instance', None) + + if zif is None: + zif = ZoneInfoFile(getzoneinfofile_stream()) + + get_zonefile_instance._cached_instance = zif + + return zif + + +def gettz(name): + """ + This retrieves a time zone from the local zoneinfo tarball that is packaged + with dateutil. + + :param name: + An IANA-style time zone name, as found in the zoneinfo file. + + :return: + Returns a :class:`dateutil.tz.tzfile` time zone object. + + .. warning:: + It is generally inadvisable to use this function, and it is only + provided for API compatibility with earlier versions. This is *not* + equivalent to ``dateutil.tz.gettz()``, which selects an appropriate + time zone based on the inputs, favoring system zoneinfo. This is ONLY + for accessing the dateutil-specific zoneinfo (which may be out of + date compared to the system zoneinfo). + + .. deprecated:: 2.6 + If you need to use a specific zoneinfofile over the system zoneinfo, + instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call + :func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead. + + Use :func:`get_zonefile_instance` to retrieve an instance of the + dateutil-provided zoneinfo. + """ + warnings.warn("zoneinfo.gettz() will be removed in future versions, " + "to use the dateutil-provided zoneinfo files, instantiate a " + "ZoneInfoFile object and use ZoneInfoFile.zones.get() " + "instead. See the documentation for details.", + DeprecationWarning) + + if len(_CLASS_ZONE_INSTANCE) == 0: + _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) + return _CLASS_ZONE_INSTANCE[0].zones.get(name) + + +def gettz_db_metadata(): + """ Get the zonefile metadata + + See `zonefile_metadata`_ + + :returns: + A dictionary with the database metadata + + .. deprecated:: 2.6 + See deprecation warning in :func:`zoneinfo.gettz`. To get metadata, + query the attribute ``zoneinfo.ZoneInfoFile.metadata``. + """ + warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future " + "versions, to use the dateutil-provided zoneinfo files, " + "ZoneInfoFile object and query the 'metadata' attribute " + "instead. See the documentation for details.", + DeprecationWarning) + + if len(_CLASS_ZONE_INSTANCE) == 0: + _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) + return _CLASS_ZONE_INSTANCE[0].metadata diff --git a/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz b/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz new file mode 100644 index 0000000..1461f8c Binary files /dev/null and b/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz differ diff --git a/dateutil/zoneinfo/rebuild.py b/dateutil/zoneinfo/rebuild.py new file mode 100644 index 0000000..684c658 --- /dev/null +++ b/dateutil/zoneinfo/rebuild.py @@ -0,0 +1,75 @@ +import logging +import os +import tempfile +import shutil +import json +from subprocess import check_call, check_output +from tarfile import TarFile + +from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME + + +def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None): + """Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar* + + filename is the timezone tarball from ``ftp.iana.org/tz``. + + """ + tmpdir = tempfile.mkdtemp() + zonedir = os.path.join(tmpdir, "zoneinfo") + moduledir = os.path.dirname(__file__) + try: + with TarFile.open(filename) as tf: + for name in zonegroups: + tf.extract(name, tmpdir) + filepaths = [os.path.join(tmpdir, n) for n in zonegroups] + + _run_zic(zonedir, filepaths) + + # write metadata file + with open(os.path.join(zonedir, METADATA_FN), 'w') as f: + json.dump(metadata, f, indent=4, sort_keys=True) + target = os.path.join(moduledir, ZONEFILENAME) + with TarFile.open(target, "w:%s" % format) as tf: + for entry in os.listdir(zonedir): + entrypath = os.path.join(zonedir, entry) + tf.add(entrypath, entry) + finally: + shutil.rmtree(tmpdir) + + +def _run_zic(zonedir, filepaths): + """Calls the ``zic`` compiler in a compatible way to get a "fat" binary. + + Recent versions of ``zic`` default to ``-b slim``, while older versions + don't even have the ``-b`` option (but default to "fat" binaries). The + current version of dateutil does not support Version 2+ TZif files, which + causes problems when used in conjunction with "slim" binaries, so this + function is used to ensure that we always get a "fat" binary. + """ + + try: + help_text = check_output(["zic", "--help"]) + except OSError as e: + _print_on_nosuchfile(e) + raise + + if b"-b " in help_text: + bloat_args = ["-b", "fat"] + else: + bloat_args = [] + + check_call(["zic"] + bloat_args + ["-d", zonedir] + filepaths) + + +def _print_on_nosuchfile(e): + """Print helpful troubleshooting message + + e is an exception raised by subprocess.check_call() + + """ + if e.errno == 2: + logging.error( + "Could not find zic. Perhaps you need to install " + "libc-bin or some other package that provides it, " + "or it's not in your PATH?") diff --git a/frozenlist/__init__.py b/frozenlist/__init__.py new file mode 100644 index 0000000..1523565 --- /dev/null +++ b/frozenlist/__init__.py @@ -0,0 +1,95 @@ +import os +import sys +import types +from collections.abc import MutableSequence +from functools import total_ordering +from typing import Type + +__version__ = "1.4.0" + +__all__ = ("FrozenList", "PyFrozenList") # type: Tuple[str, ...] + + +NO_EXTENSIONS = bool(os.environ.get("FROZENLIST_NO_EXTENSIONS")) # type: bool + + +@total_ordering +class FrozenList(MutableSequence): + __slots__ = ("_frozen", "_items") + + if sys.version_info >= (3, 9): + __class_getitem__ = classmethod(types.GenericAlias) + else: + + @classmethod + def __class_getitem__(cls: Type["FrozenList"]) -> Type["FrozenList"]: + return cls + + def __init__(self, items=None): + self._frozen = False + if items is not None: + items = list(items) + else: + items = [] + self._items = items + + @property + def frozen(self): + return self._frozen + + def freeze(self): + self._frozen = True + + def __getitem__(self, index): + return self._items[index] + + def __setitem__(self, index, value): + if self._frozen: + raise RuntimeError("Cannot modify frozen list.") + self._items[index] = value + + def __delitem__(self, index): + if self._frozen: + raise RuntimeError("Cannot modify frozen list.") + del self._items[index] + + def __len__(self): + return self._items.__len__() + + def __iter__(self): + return self._items.__iter__() + + def __reversed__(self): + return self._items.__reversed__() + + def __eq__(self, other): + return list(self) == other + + def __le__(self, other): + return list(self) <= other + + def insert(self, pos, item): + if self._frozen: + raise RuntimeError("Cannot modify frozen list.") + self._items.insert(pos, item) + + def __repr__(self): + return f"" + + def __hash__(self): + if self._frozen: + return hash(tuple(self)) + else: + raise RuntimeError("Cannot hash unfrozen list.") + + +PyFrozenList = FrozenList + + +try: + from ._frozenlist import FrozenList as CFrozenList # type: ignore + + if not NO_EXTENSIONS: # pragma: no cover + FrozenList = CFrozenList # type: ignore +except ImportError: # pragma: no cover + pass diff --git a/frozenlist/__init__.pyi b/frozenlist/__init__.pyi new file mode 100644 index 0000000..ae803ef --- /dev/null +++ b/frozenlist/__init__.pyi @@ -0,0 +1,47 @@ +from typing import ( + Generic, + Iterable, + Iterator, + List, + MutableSequence, + Optional, + TypeVar, + Union, + overload, +) + +_T = TypeVar("_T") +_Arg = Union[List[_T], Iterable[_T]] + +class FrozenList(MutableSequence[_T], Generic[_T]): + def __init__(self, items: Optional[_Arg[_T]] = None) -> None: ... + @property + def frozen(self) -> bool: ... + def freeze(self) -> None: ... + @overload + def __getitem__(self, i: int) -> _T: ... + @overload + def __getitem__(self, s: slice) -> FrozenList[_T]: ... + @overload + def __setitem__(self, i: int, o: _T) -> None: ... + @overload + def __setitem__(self, s: slice, o: Iterable[_T]) -> None: ... + @overload + def __delitem__(self, i: int) -> None: ... + @overload + def __delitem__(self, i: slice) -> None: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[_T]: ... + def __reversed__(self) -> Iterator[_T]: ... + def __eq__(self, other: object) -> bool: ... + def __le__(self, other: FrozenList[_T]) -> bool: ... + def __ne__(self, other: object) -> bool: ... + def __lt__(self, other: FrozenList[_T]) -> bool: ... + def __ge__(self, other: FrozenList[_T]) -> bool: ... + def __gt__(self, other: FrozenList[_T]) -> bool: ... + def insert(self, pos: int, item: _T) -> None: ... + def __repr__(self) -> str: ... + def __hash__(self) -> int: ... + +# types for C accelerators are the same +CFrozenList = PyFrozenList = FrozenList diff --git a/frozenlist/_frozenlist.pyx b/frozenlist/_frozenlist.pyx new file mode 100644 index 0000000..9ee846c --- /dev/null +++ b/frozenlist/_frozenlist.pyx @@ -0,0 +1,123 @@ +import sys +import types +from collections.abc import MutableSequence + + +cdef class FrozenList: + + if sys.version_info >= (3, 9): + __class_getitem__ = classmethod(types.GenericAlias) + else: + @classmethod + def __class_getitem__(cls): + return cls + + cdef readonly bint frozen + cdef list _items + + def __init__(self, items=None): + self.frozen = False + if items is not None: + items = list(items) + else: + items = [] + self._items = items + + cdef object _check_frozen(self): + if self.frozen: + raise RuntimeError("Cannot modify frozen list.") + + cdef inline object _fast_len(self): + return len(self._items) + + def freeze(self): + self.frozen = True + + def __getitem__(self, index): + return self._items[index] + + def __setitem__(self, index, value): + self._check_frozen() + self._items[index] = value + + def __delitem__(self, index): + self._check_frozen() + del self._items[index] + + def __len__(self): + return self._fast_len() + + def __iter__(self): + return self._items.__iter__() + + def __reversed__(self): + return self._items.__reversed__() + + def __richcmp__(self, other, op): + if op == 0: # < + return list(self) < other + if op == 1: # <= + return list(self) <= other + if op == 2: # == + return list(self) == other + if op == 3: # != + return list(self) != other + if op == 4: # > + return list(self) > other + if op == 5: # => + return list(self) >= other + + def insert(self, pos, item): + self._check_frozen() + self._items.insert(pos, item) + + def __contains__(self, item): + return item in self._items + + def __iadd__(self, items): + self._check_frozen() + self._items += list(items) + return self + + def index(self, item): + return self._items.index(item) + + def remove(self, item): + self._check_frozen() + self._items.remove(item) + + def clear(self): + self._check_frozen() + self._items.clear() + + def extend(self, items): + self._check_frozen() + self._items += list(items) + + def reverse(self): + self._check_frozen() + self._items.reverse() + + def pop(self, index=-1): + self._check_frozen() + return self._items.pop(index) + + def append(self, item): + self._check_frozen() + return self._items.append(item) + + def count(self, item): + return self._items.count(item) + + def __repr__(self): + return ''.format(self.frozen, + self._items) + + def __hash__(self): + if self.frozen: + return hash(tuple(self._items)) + else: + raise RuntimeError("Cannot hash unfrozen list.") + + +MutableSequence.register(FrozenList) diff --git a/frozenlist/py.typed b/frozenlist/py.typed new file mode 100644 index 0000000..f5642f7 --- /dev/null +++ b/frozenlist/py.typed @@ -0,0 +1 @@ +Marker diff --git a/gatherDTUBIData.py b/gatherDTUBIData.py new file mode 100644 index 0000000..d498d2c --- /dev/null +++ b/gatherDTUBIData.py @@ -0,0 +1,59 @@ +import json +from hoymiles_wifi.dtu import DTU +import asyncio +import aiohttp +import logging +import math +import time +from typing import List +from dataclasses import dataclass +from dataclasses import field +UPDATE_INTERVAL = 31 +_LOGGER = logging.getLogger(__name__) + +dtu = DTU("192.168.179.184") + +@dataclass +class DTUBIInvData: + p_PV:List[float] = field(default_factory=lambda: [0.0,0.0]) + p_AC:float = 0.0 + temp:float = 0.0 + error:int = 0 + name:str = "" + +@dataclass +class DTUBIData: + inverter:List[DTUBIInvData] = field(default_factory=lambda: [DTUBIInvData()])#default_factory=lambda: [DTUInvData(), DTUInvData(),DTUInvData(),DTUInvData(),DTUInvData(),DTUInvData()]) + p_AC_sum:float = 0 + error = 0 + + +ret = DTUBIData() + +ret.lastUpdate = time.clock_gettime(0)-UPDATE_INTERVAL +ret.inverter[0].name = "Veranda UG2" +async def gatherData() -> DTUBIData: + if(time.clock_gettime(0) >= ret.lastUpdate + UPDATE_INTERVAL): + ret.lastUpdate = time.clock_gettime(0) + #print("get charger status..."); + try: + state = await dtu.async_get_real_data_new() + #logging.warning(html) + #state = json.load(html) + ret.inverter[0].error = 0 + ret.inverter[0].p_PV[0] = state.pv_data[0].power/10 + ret.inverter[0].p_PV[1] = state.pv_data[1].power/10 + ret.inverter[0].p_AC = state.sgs_data[0].active_power/10 + ret.p_AC_sum = ret.inverter[0].p_AC + ret.inverter[0].temp = state.sgs_data[0].temperature/10 + + except: + if(ret.inverter[0].error > 1): + ret.p_AC_sum = 0 + ret.inverter[0].p_PV[0] = 0 + ret.inverter[0].p_PV[1] = 0 + ret.inverter[0].temp = 0 + ret.inverter[0].p_AC = 0 + else: + ret.inverter[0].error += 1 + return ret \ No newline at end of file diff --git a/gatherHeaterData.py b/gatherHeaterData.py new file mode 100644 index 0000000..bf4170e --- /dev/null +++ b/gatherHeaterData.py @@ -0,0 +1,294 @@ +import json +import asyncio +import aiohttp +import logging +import math +from typing import List +from dataclasses import dataclass +from dataclasses import field + +_LOGGER = logging.getLogger(__name__) + +@dataclass +class HeaterData: + t_buffT:float = 0 + t_buffM:float = 0 + t_buffB:float = 0 + t_heatVL:float = 0 + t_heatRL:float = 0 + t_gasVLu:float = 0 + t_gasVLo:float = 0 + t_gasRL:float = 0 + t_fbVL:float = 0 + t_fbRL:float = 0 + t_triac:float = 0 + p_heat:float = 0 + error:int = 0 + mode:str = "" + +ret = HeaterData() + +async def gatherData() -> HeaterData: + + #print("get charger status..."); + timeout = aiohttp.ClientTimeout(total=3) + try: + async with aiohttp.ClientSession() as session: + async with session.get('http://192.168.179.169/data',timeout=timeout) as response: #?filter=psm,fup,amp,frc,nrg,lmo,car + if response.status == 200: + html = await response.text() + #logging.warning(html) + state = json.loads(html) + ret.error = 0 + ret.t_buffT = state["PufferO"] + ret.t_buffM = state["PufferM"] + ret.t_buffB = state["PufferU"] + ret.t_heatVL = state["HeaterVL"] + ret.t_heatRL = state["HeaterRL"] + ret.t_gasVLu = state["ThermeVLu"] + ret.t_gasVLo = state["ThermeVLo"] + ret.t_gasRL = state["ThermeRL"] + ret.t_triac = state["Triac"] + ret.t_fbVL = state["fbVL"] + ret.t_fbRL = state["fbRL"] + ret.p_heat = state["heatPower"] + ret.mode = state["mode"] + except: + if(ret.error > 5): + ret.p_heat = 0 + _LOGGER.warning("heater data could not be fetched") + else: + ret.error += 1 + return ret + +#async def main(): +# await gatherNeededStatus() + +#loop = asyncio.get_event_loop() +#loop.run_until_complete(main()) + + +""" +{ + "inverters":[ + { + "serial":"116491626890", + "name":"Terrassendach", + "order":0, + "data_age":1, + "poll_enabled":true, + "reachable":true, + "producing":true, + "limit_relative":100, + "limit_absolute":1600, + "AC":{ + "0":{ + "Power":{ + "v":74.09999847, + "u":"W", + "d":1 + }, + "Voltage":{ + "v":235.3000031, + "u":"V", + "d":1 + }, + "Current":{ + "v":0.310000002, + "u":"A", + "d":2 + }, + "Power DC":{ + "v":77.80000305, + "u":"W", + "d":1 + }, + "YieldDay":{ + "v":179, + "u":"Wh", + "d":0 + }, + "YieldTotal":{ + "v":8.732000351, + "u":"kWh", + "d":3 + }, + "Frequency":{ + "v":50.00999832, + "u":"Hz", + "d":2 + }, + "PowerFactor":{ + "v":1, + "u":"", + "d":3 + }, + "ReactivePower":{ + "v":0, + "u":"var", + "d":1 + }, + "Efficiency":{ + "v":95.24420929, + "u":"%", + "d":3 + } + } + }, + "DC":{ + "0":{ + "name":{ + "u":"" + }, + "Power":{ + "v":12.80000019, + "u":"W", + "d":1 + }, + "Voltage":{ + "v":21.20000076, + "u":"V", + "d":1 + }, + "Current":{ + "v":0.610000014, + "u":"A", + "d":2 + }, + "YieldDay":{ + "v":31, + "u":"Wh", + "d":0 + }, + "YieldTotal":{ + "v":1.710000038, + "u":"kWh", + "d":3 + } + }, + "1":{ + "name":{ + "u":"" + }, + "Power":{ + "v":26.60000038, + "u":"W", + "d":1 + }, + "Voltage":{ + "v":43.20000076, + "u":"V", + "d":1 + }, + "Current":{ + "v":0.610000014, + "u":"A", + "d":2 + }, + "YieldDay":{ + "v":62, + "u":"Wh", + "d":0 + }, + "YieldTotal":{ + "v":2.496000051, + "u":"kWh", + "d":3 + } + }, + "2":{ + "name":{ + "u":"" + }, + "Power":{ + "v":12.5, + "u":"W", + "d":1 + }, + "Voltage":{ + "v":21.20000076, + "u":"V", + "d":1 + }, + "Current":{ + "v":0.589999974, + "u":"A", + "d":2 + }, + "YieldDay":{ + "v":26, + "u":"Wh", + "d":0 + }, + "YieldTotal":{ + "v":1.179999948, + "u":"kWh", + "d":3 + } + }, + "3":{ + "name":{ + "u":"" + }, + "Power":{ + "v":25.89999962, + "u":"W", + "d":1 + }, + "Voltage":{ + "v":43, + "u":"V", + "d":1 + }, + "Current":{ + "v":0.600000024, + "u":"A", + "d":2 + }, + "YieldDay":{ + "v":60, + "u":"Wh", + "d":0 + }, + "YieldTotal":{ + "v":3.345999956, + "u":"kWh", + "d":3 + } + } + }, + "INV":{ + "0":{ + "Temperature":{ + "v":12.5, + "u":"°C", + "d":1 + } + } + }, + "events":1 + } + ], + "total":{ + "Power":{ + "v":74.09999847, + "u":"W", + "d":1 + }, + "YieldDay":{ + "v":179, + "u":"Wh", + "d":0 + }, + "YieldTotal":{ + "v":8.732000351, + "u":"kWh", + "d":3 + } + }, + "hints":{ + "time_sync":false, + "radio_problem":false, + "default_password":true + } +}""" \ No newline at end of file diff --git a/gatherModbusData.py b/gatherModbusData.py new file mode 100644 index 0000000..f29ac94 --- /dev/null +++ b/gatherModbusData.py @@ -0,0 +1,561 @@ +import asyncio +import json +import aiohttp +import sys +import logging +import sunspec2.modbus.client as client +import datetime +from typing import List +from dataclasses import dataclass +from dataclasses import field +from operator import attrgetter +from time import sleep +import traceback + +_LOGGER = logging.getLogger(__name__) + + +batStat = { + 1: "OFF", + 2: "EMPTY", + 3: "DISCHARGING", + 4: "CHARGING", + 5: "FULL", + 6: "HOLDING", + 7: "TESTING", +} +wrStat = { + 1: "OFF", + 2: "SLEEPING", + 3: "STARTING", + 4: "MPPT", + 5: "THROTTLED", + 6: "SHUTTING_DOWN", + 7: "FAULT", +} + +@dataclass +class ModbusRegisters: + ppv0_reg = "" + ppv1_reg = "" + pbat_crg_reg = "" + pbat_discrg_reg = "" + ubat_reg = "" + ibat_crg_reg = "" + ibat_discrg_reg = "" + pACinv_reg = "" + soc_reg = "" + battStat_reg = "" + wrStat_reg = "" + + pACevu_reg = "" + pL1ACevu_reg = "" + pL2ACevu_reg = "" + pL3ACevu_reg = "" + iL1evu_reg = "" + iL2evu_reg = "" + iL3evu_reg = "" + + pACog_reg = "" + pL1ACog_reg = "" + pL2ACog_reg = "" + pL3ACog_reg = "" + iL1og_reg = "" + iL2og_reg = "" + iL3og_reg = "" + + limitChrgRate = "" + setChargeLimits = "" + + setPowerLimitPercent = "" + setPowerLimitEN = "" + + + +@dataclass +class InverterData: + p_PV:List[float] = field(default_factory=lambda: [0.0,0.0]) + error:int = 0 + load: float = 0.0 + soc: float = 0.0 + pbat: float = 0.0 + ppv: float = 0.0 + ppv2: float = 0.0 + aut: float = 0.0 + pgrid: float = 0.0 + ibatt: float = 0.0 + ubatt: float = 0.0 + temp: float = 0.0 + p_l1evu: float = 0.0 + p_l2evu: float = 0.0 + p_l3evu: float = 0.0 + p_l1og: float = 0.0 + p_l2og: float = 0.0 + p_l3og: float = 0.0 + i_l1evu: float = 0.0 + i_l2evu: float = 0.0 + i_l3evu: float = 0.0 + i_l1og: float = 0.0 + i_l2og: float = 0.0 + i_l3og : float = 0.0 + p_wr : float = 0.0 + crgMaxPct : float = 0.0 + wr_status : str = "" + batt_status : str = "" + pwrMaxPct : float = 0.0 + p_AC : float = 0.0 + +@dataclass +class estimation: + estProd = 0.1 + increaseTmr = 0 + +try: + inv = client.SunSpecModbusClientDeviceTCP(1, "192.168.179.155", 1502) + meter0 = client.SunSpecModbusClientDeviceTCP(200, "192.168.179.155", 1502) + meter1 = client.SunSpecModbusClientDeviceTCP(201, "192.168.179.155", 1502) + regs = ModbusRegisters() + estimations = estimation() +except client.SunSpecModbusClientError as e: + _LOGGER.error('Modbus TCP Error: %s' % e) + _LOGGER.erorr('trace: %s' % traceback.print_exc()) + sys.exit(1) + + + +async def connect(): + if inv is not None and meter0 is not None and meter1 is not None: + print('Scanning: ') + # print( '\nTimestamp: %s' % (time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()))) + + # read all models in the device + inv.scan() + meter0.scan() + meter1.scan() + #_LOGGER.debug(inv.get_text()) + if(inv.common[0].Md.value == "Symo GEN24 10.0"): + regs.ppv0_reg = "inv.mppt[0].module[0].DCW" + regs.ppv1_reg = "inv.mppt[0].module[1].DCW" + regs.pbat_crg_reg = "inv.mppt[0].module[2].DCW" + regs.pbat_discrg_reg = "inv.mppt[0].module[3].DCW" + regs.ubat_reg = "inv.mppt[0].module[3].DCV" + regs.ibat_crg_reg = "inv.mppt[0].module[2].DCA" + regs.ibat_discrg_reg = "inv.mppt[0].module[3].DCA" + regs.pACinv_reg = "inv.inverter_three_phase[0].W" + regs.tmpCab_reg = "inv.inverter_three_phase[0].TmpCab" + regs.soc_reg = "inv.storage_basic[0].ChaState" + regs.battStat_reg = "inv.storage_basic[0].ChaSt" + regs.wrStat_reg = "inv.inverter_three_phase[0].St" + + regs.limitChrgRate = "inv.storage_basic[0].InWRte" + regs.setChargeLimits = "inv.storage_basic[0].StorCtl_Mod" + + regs.setPowerLimitPercent = "inv.controls[0].WMaxLimPct" + regs.setPowerLimitEN = "inv.controls[0].WMaxLim_Ena" + + regs.pACevu_reg = "meter0.ac_meter_abcn[0].W" + regs.pL1ACevu_reg = "meter0.ac_meter_abcn[0].WphA" + regs.pL2ACevu_reg = "meter0.ac_meter_abcn[0].WphB" + regs.pL3ACevu_reg = "meter0.ac_meter_abcn[0].WphC" + regs.iL1evu_reg = "meter0.ac_meter_abcn[0].AphA" + regs.iL2evu_reg = "meter0.ac_meter_abcn[0].AphB" + regs.iL3evu_reg = "meter0.ac_meter_abcn[0].AphC" + + regs.pACog_reg = "meter1.ac_meter_abcn[0].W" + regs.pL1ACog_reg = "meter1.ac_meter_abcn[0].WphA" + regs.pL2ACog_reg = "meter1.ac_meter_abcn[0].WphB" + regs.pL3ACog_reg = "meter1.ac_meter_abcn[0].WphC" + regs.iL1og_reg = "meter1.ac_meter_abcn[0].AphA" + regs.iL2og_reg = "meter1.ac_meter_abcn[0].AphB" + regs.iL3og_reg = "meter1.ac_meter_abcn[0].AphC" + else: + _LOGGER.error('undefined Inverter, please define inverter registers!') + sys.exit(1) + else: + _LOGGER.error('One of the expected modbus devices was unreachable') + sys.exit(1) + #while True: + +async def get_runtime_data(estProduction, sunset) -> InverterData: + ret = InverterData() + try: + inv.mppt[0].read() + inv.inverter_three_phase[0].read() + inv.storage_basic[0].read() + meter0.ac_meter_abcn[0].read() + meter1.ac_meter_abcn[0].read() + #print(inv.controls[0].__dir__()) + ret.p_PV[0] = eval(regs.ppv0_reg).cvalue + ret.p_PV[1] = eval(regs.ppv1_reg).cvalue + ret.error = 0 + ret.load = eval(regs.pACevu_reg).cvalue + eval(regs.pbat_discrg_reg).cvalue + eval(regs.ppv0_reg).cvalue + eval(regs.ppv1_reg).cvalue + ret.soc = eval(regs.soc_reg).cvalue + ret.pbat = eval(regs.pbat_discrg_reg).cvalue + if(ret.pbat == 0): + ret.pbat = -eval(regs.pbat_crg_reg).cvalue + ret.crgMaxPct = (eval(regs.limitChrgRate).cvalue)*2.04 #references max charge of batt. (20,48kw) instead of INV (10kw) + ret.pwrMaxPct = eval(regs.setPowerLimitPercent).cvalue #references max charge of batt. (20,48kw) instead of INV (10kw) + ret.ppv = ret.p_PV[0] + ret.ppv2 = ret.p_PV[1] + ret.pgrid = eval(regs.pACevu_reg).cvalue + if(ret.pgrid < 0): + ret.aut = 100 + else: + ret.aut = 100/ret.load * ret.pgrid + if(ret.aut < 0): + ret.aut=0 + elif(ret.aut > 100): + ret.aut = 100 + ret.ibatt = eval(regs.ibat_discrg_reg).cvalue + if(ret.ibatt == 0): + ret.ibatt = -eval(regs.ibat_crg_reg).cvalue + ret.ubatt = eval(regs.ubat_reg).cvalue + ret.p_l1evu = eval(regs.pL1ACevu_reg).cvalue + ret.p_l2evu = eval(regs.pL2ACevu_reg).cvalue + ret.p_l3evu = eval(regs.pL3ACevu_reg).cvalue + ret.p_l1og = eval(regs.pL1ACog_reg).cvalue + ret.p_l2og = eval(regs.pL2ACog_reg).cvalue + ret.p_l3og = eval(regs.pL3ACog_reg).cvalue + ret.i_l1evu = eval(regs.iL1evu_reg).cvalue + ret.i_l2evu = eval(regs.iL2evu_reg).cvalue + ret.i_l3evu = eval(regs.iL3evu_reg).cvalue + ret.i_l1og = eval(regs.iL1og_reg).cvalue + ret.i_l2og = eval(regs.iL2og_reg).cvalue + ret.i_l3og = eval(regs.iL3og_reg).cvalue + ret.p_AC = eval(regs.pACinv_reg).cvalue + ret.p_wr = ret.ppv + ret.ppv2 + ret.pbat - ret.p_AC + ret.temp = 0#no need to read reg, reads always null ... eval(regs.tmpCab_reg).cvalue + ret.batt_status = batStat[eval(regs.battStat_reg).cvalue] + ret.wr_status = wrStat[eval(regs.wrStat_reg).cvalue] + except Exception as error: + _LOGGER.warning(f"No modbus data received from Inverter: %s",error) + #_LOGGER.erorr('trace: %s' % traceback.print_exc()) + try: + inv.connect() #reconnect the modbus client + meter0.connect() #reconnect the modbus client + meter1.connect() #reconnect the modbus client + _LOGGER.warning(f"Tried to reconnect") + except: + _LOGGER.warning(f"reconnect failed...") + +# if(ret.soc > 90 and (ret.i_l1evu > 22 or ret.i_l2evu > 22 or ret.i_l3evu > 22)): +# eval(regs.setPowerLimitPercent).value = ret.pwrMaxPct*100 - 500; #divide by two because this references 20.5kw max charge of battery instead of 10kw from INV +# eval(regs.setPowerLimitEN).value = 1 #set charge rate: bit1 set discharge rate: bit 2 +# eval(regs.limitChrgRate).write() +# eval(regs.setChargeLimits).write() +# elif (ret.i_l1evu < 18 and ret.i_l2evu < 18 and ret.i_l3evu < 18 and ret.pwrMaxPct < 100): +# eval(regs.setPowerLimitPercent).value = ret.pwrMaxPct*100 + 500; #divide by two because this references 20.5kw max charge of battery instead of 10kw from INV +# eval(regs.setPowerLimitEN).value = 1 #set charge rate: bit1 set discharge rate: bit 2 +# eval(regs.limitChrgRate).write() +# eval(regs.setChargeLimits).write() +# if datetime.datetime.now().hour == 23 and ret.pwrMaxPct < 99: #reset power limit every night +# eval(regs.setPowerLimitPercent).value = 10000; #divide by two because this references 20.5kw max charge of battery instead of 10kw from INV +# eval(regs.setPowerLimitEN).value = 1 #set charge rate: bit1 set discharge rate: bit 2 +# eval(regs.limitChrgRate).write() +# eval(regs.setChargeLimits).write() + try: + if(estProduction != estimations.estProd and datetime.datetime.now().hour < 9): + estimations.estProd = estProduction + estimations.increaseTmr = 0 + if(estimations.estProd > 35): + #_LOGGER.debug("High yeld predicted, reducing battery charge current") + if ret.soc > 45: #if SOC in the morning is above 45% wait with charging until midday + eval(regs.limitChrgRate).value = int(500/2.04) #divide by two because this references 20.5kw max charge of battery instead of 10kw from INV + else: #if less than 45% SOC in the morning charge slow (SOC dependant) until midday + eval(regs.limitChrgRate).value = int((5000 - ret.soc*100)/2.04) #divide by two because this references 20.5kw max charge of battery instead of 10kw from INV + eval(regs.setChargeLimits).value = 1 #set charge rate: bit1 set discharge rate: bit 2 + eval(regs.limitChrgRate).write() + eval(regs.setChargeLimits).write() + elif ret.crgMaxPct < 70: + eval(regs.limitChrgRate).value = int(8000/2.04) #set charge rate to 80% if no high yeld is predicted + eval(regs.setChargeLimits).value = 1 #set charge rate: bit1 set discharge rate: bit 2 + eval(regs.limitChrgRate).write() + eval(regs.setChargeLimits).write() + elif datetime.datetime.now().hour == 11 or datetime.datetime.now().hour == 12: #inclease charge rate at midday + estimations.increaseTmr = estimations.increaseTmr + 1 + if estimations.increaseTmr > 300: #slowdown rate change to 300 * 3sec (15 min.) + estimations.increaseTmr = 0 + maxMiddDayChrg = 30 + if(int(sunset.strftime('%H')) < 18): #inclrease max charge rate during wintertime, where time to full SOC is less. + maxMiddDayChrg = 70 + if ret.crgMaxPct < maxMiddDayChrg: + estimations.increaseTmr = 0 + eval(regs.limitChrgRate).value = int((ret.crgMaxPct*100+500)/2.04) #increase by 5% every 15 min --> reach 30% after 5*15min max + eval(regs.setChargeLimits).value = 1 #set charge rate: bit1 set discharge rate: bit 2 + eval(regs.limitChrgRate).write() + eval(regs.setChargeLimits).write() + _LOGGER.debug("Increasing charge rate to buffer feed peaks to " + str(int((ret.crgMaxPct*100+200)/2.04)) + "%") + if datetime.datetime.now().hour >= int(sunset.strftime('%H'))-4 and ret.soc < 70 and ret.crgMaxPct < 90: + eval(regs.limitChrgRate).value = int((10000)/2.04) + eval(regs.setChargeLimits).value = 1 #set charge rate: bit1 set discharge rate: bit 2 + eval(regs.limitChrgRate).write() + eval(regs.setChargeLimits).write() + _LOGGER.debug("Increasing charge rate because of too low SOC " + str(int((10000)/2.04)) + "%") + return ret + #print(ret.p_wr) + #print("-----") + #sleep(1) + except Exception as error: + _LOGGER.warning(f"No modbus data received from Inverter: %s",error) + +""" +Model: common (1) + + ID 1 + L 65 + Mn Fronius + Md Symo GEN24 10.0 + Opt None + Vr 1.34.2-1 + SN 33095286 + DA 1 + +Model: inverter_three_phase (103) + + ID 103 + L 50 + A 22300 A + AphA 8115 A + AphB 7527 A + AphC 6658 A + A_SF -5 + PPVphAB 4000 V + PPVphBC 4015 V + PPVphCA 4033 V + PhVphA 2310 V + PhVphB 2317 V + PhVphC 2328 V + V_SF -1 + W -5145 W + W_SF -2 + Hz 4998 Hz + Hz_SF -2 + VA 5145 VA + VA_SF -2 + VAr 31275 var + VAr_SF -5 + PF 1000 Pct + PF_SF -1 + WH 3939319098 Wh + WH_SF -2 + DCA None A + DCA_SF None + DCV None V + DCV_SF None + DCW 25076 W + DCW_SF -5 + TmpCab 526 C + TmpSnk None C + TmpTrns None C + TmpOt None C + Tmp_SF -1 + St 4 + StVnd 4 + Evt1 0 + Evt2 0 + EvtVnd1 0 + EvtVnd2 0 + EvtVnd3 None + EvtVnd4 None + +Model: nameplate (120) + + ID 120 + L 26 + DERTyp 82 + WRtg 1000 W + WRtg_SF 1 + VARtg 1000 VA + VARtg_SF 1 + VArRtgQ1 714 var + VArRtgQ2 714 var + VArRtgQ3 -714 var + VArRtgQ4 -714 var + VArRtg_SF 1 + ARtg 1443 A + ARtg_SF -2 + PFRtgQ1 -700 cos() + PFRtgQ2 700 cos() + PFRtgQ3 -700 cos() + PFRtgQ4 700 cos() + PFRtg_SF -3 + WHRtg 22118 Wh + WHRtg_SF 0 + AhrRtg None AH + AhrRtg_SF None + MaxChaRte 20480 W + MaxChaRte_SF 0 + MaxDisChaRte 20480 W + MaxDisChaRte_SF 0 + Pad 32768 + +Model: settings (121) + + ID 121 + L 30 + WMax 1000 W + VRef 231 V + VRefOfs 0 V + VMax None V + VMin None V + VAMax 1000 VA + VArMaxQ1 714 var + VArMaxQ2 714 var + VArMaxQ3 -714 var + VArMaxQ4 -714 var + WGra None % WMax/sec + PFMinQ1 -700 cos() + PFMinQ2 700 cos() + PFMinQ3 -700 cos() + PFMinQ4 700 cos() + VArAct None + ClcTotVA None + MaxRmpRte None % WGra + ECPNomHz None Hz + ConnPh None + WMax_SF 1 + VRef_SF 0 + VRefOfs_SF 0 + VMinMax_SF None + VAMax_SF 1 + VArMax_SF 1 + WGra_SF None + PFMin_SF -3 + MaxRmpRte_SF None + ECPNomHz_SF None + +Model: status (122) + + ID 122 + L 44 + PVConn 7 + StorConn 7 + ECPConn 1 + ActWh 39393190 Wh + ActVAh None VAh + ActVArhQ1 None varh + ActVArhQ2 None varh + ActVArhQ3 None varh + ActVArhQ4 None varh + VArAval None var + VArAval_SF None + WAval None var + WAval_SF None + StSetLimMsk None + StActCtl 0 + TmSrc RTC + Tms 791196551 Secs + RtSt None + Ris None ohms + Ris_SF None + +Model: controls (123) + + ID 123 + L 24 + Conn_WinTms 0 Secs + Conn_RvrtTms 0 Secs + Conn 1 + WMaxLimPct 10000 % WMax + WMaxLimPct_WinTms 0 Secs + WMaxLimPct_RvrtTms 0 Secs + WMaxLimPct_RmpTms 0 Secs + WMaxLim_Ena 0 + OutPFSet 1000 cos() + OutPFSet_WinTms 0 Secs + OutPFSet_RvrtTms 0 Secs + OutPFSet_RmpTms 0 Secs + OutPFSet_Ena 0 + VArWMaxPct None % WMax + VArMaxPct 100 % VArMax + VArAvalPct None % VArAval + VArPct_WinTms 0 Secs + VArPct_RvrtTms 0 Secs + VArPct_RmpTms 0 Secs + VArPct_Mod 2 + VArPct_Ena 0 + WMaxLimPct_SF -2 + OutPFSet_SF -3 + VArPct_SF 0 + +Model: mppt (160) + + ID 160 + L 88 + DCA_SF -4 + DCV_SF -2 + DCW_SF -1 + DCWH_SF -2 + Evt None + N 4 + TmsPer None + 01:ID 1 + 01:IDStr MPPT 1 + 01:DCA 22696 A + 01:DCV 55400 V + 01:DCW 12574 W + 01:DCWH 3156686802 Wh + 01:Tms 791196551 Secs + 01:Tmp None C + 01:DCSt None + 01:DCEvt None + 02:ID 2 + 02:IDStr MPPT 2 + 02:DCA 15510 A + 02:DCV 32689 V + 02:DCW 5070 W + 02:DCWH 989224180 Wh + 02:Tms 791196551 Secs + 02:Tmp None C + 02:DCSt None + 02:DCEvt None + 03:ID 3 + 03:IDStr StCha 3 + 03:DCA 42263 A + 03:DCV 41721 V + 03:DCW 17641 W + 03:DCWH 877835025 Wh + 03:Tms 791196551 Secs + 03:Tmp None C + 03:DCSt None + 03:DCEvt None + 04:ID 4 + 04:IDStr StDisCha 4 + 04:DCA 0 A + 04:DCV 41721 V + 04:DCW 0 W + 04:DCWH 824803735 Wh + 04:Tms 791196551 Secs + 04:Tmp None C + 04:DCSt None + 04:DCEvt None + +Model: storage_basic (124) + + ID 124 + L 24 + WChaMax 20480 W + WChaGra 100 % WChaMax/sec + WDisChaGra 100 % WChaMax/sec + StorCtl_Mod 0 + VAChaMax None VA + MinRsvPct 0 % WChaMax + ChaState 1240 % AhrRtg + StorAval None AH + InBatV None V + ChaSt 4 + OutWRte 10000 % WDisChaMax + InWRte 10000 % WChaMax + InOutWRte_WinTms None Secs + InOutWRte_RvrtTms 0 Secs + InOutWRte_RmpTms None Secs + ChaGriSet 1 + WChaMax_SF 0 + WChaDisChaGra_SF 0 + VAChaMax_SF None + MinRsvPct_SF -2 + ChaState_SF -2 + StorAval_SF None + InBatV_SF None + InOutWRte_SF -2 +""" \ No newline at end of file diff --git a/gatherOpenDTUData.py b/gatherOpenDTUData.py new file mode 100644 index 0000000..1c462b9 --- /dev/null +++ b/gatherOpenDTUData.py @@ -0,0 +1,371 @@ +import json +import asyncio +import aiohttp +import sys +import logging +import math +from typing import List +from dataclasses import dataclass +from dataclasses import field +import copy + + +_LOGGER = logging.getLogger(__name__) + +@dataclass +class DTUInvData: + p_PV:List[float] = field(default_factory=list) + p_AC:float = 0.0 + temp:float = 0.0 + error:int = 0 + name:str = "" + estimated:bool = False # Werte hochgerechnet statt gemessen + p_PV_est:float = 0.0 # davon geschaetzte DC-Leistung in W + limit:float = 0.0 # limit_absolute der DTU, W - Deckel der Schaetzung + +@dataclass +class DTUData: + inverter:List[DTUInvData] = field(default_factory=list)#default_factory=lambda: [DTUInvData(), DTUInvData(),DTUInvData(),DTUInvData(),DTUInvData(),DTUInvData()]) + p_AC_sum:float = 0 + + +ret = DTUData() + +# Stand aus dem letzten Moment, in dem ALLE Wechselrichter frische Daten +# geliefert haben. Faellt spaeter einer aus, laesst sich sein Beitrag daraus +# hochrechnen: die uebrigen stehen daneben und sehen dieselbe Sonne, ihr +# Verhaeltnis zu diesem Stand traegt Sonnenstand und Bewoelkung mit. +_ref = {"p_AC": {}, "p_PV": {}} + +def schaetzeAusfaelle(stale): + """Beitrag ausgefallener Wechselrichter aus den gesunden hochrechnen. + + Tritt an die Stelle der frueheren _CP-Kopien: die galten nur fuer das Paar + 1/2 und uebernahmen den Nachbarwert unskaliert. Grundlage ist _ref, der + letzte Moment mit lueckenlosen Daten. Der Faktor ist das Verhaeltnis, in dem + die weiterhin gesunden Geraete seither gestiegen oder gefallen sind - damit + traegt die Schaetzung Sonnenstand und Bewoelkung mit. Ohne Referenz oder + ohne gesunden Nachbarn wird wie frueher genullt. Gedeckelt wird auf + limit_absolute, die von der DTU gemeldete Wirkleistungsgrenze des Geraets - + die steht auch dann noch in der Uebersichtsantwort, wenn die Detailabfrage + schon ins Leere laeuft. + """ + for i in range(len(ret.inverter)): + ret.inverter[i].estimated = False + ret.inverter[i].p_PV_est = 0.0 + gesund = [i for i in range(len(ret.inverter)) if i not in stale] + if not stale: + for i in gesund: # Referenz nachfuehren + _ref["p_AC"][i] = ret.inverter[i].p_AC + _ref["p_PV"][i] = list(ret.inverter[i].p_PV) + return + refSum = sum(_ref["p_AC"].get(i,0.0) for i in gesund) + nowSum = sum(ret.inverter[i].p_AC for i in gesund) + faktor = nowSum/refSum if refSum > 0 else 0.0 + for i in stale: + if _ref["p_AC"].get(i,0.0) > 0 and faktor > 0: + pAC = _ref["p_AC"][i]*faktor + if ret.inverter[i].limit > 0: + pAC = min(pAC, ret.inverter[i].limit) + skala = pAC/_ref["p_AC"][i] + ret.inverter[i].p_AC = round(pAC,2) + ret.inverter[i].p_PV = [round(v*skala,2) for v in _ref["p_PV"].get(i,[])] + ret.inverter[i].p_PV_est = round(sum(ret.inverter[i].p_PV),2) + ret.inverter[i].estimated = True + else: # keine Grundlage - wie bisher nullen + ret.inverter[i].p_AC = 0 + ret.inverter[i].p_PV = [0]*len(ret.inverter[i].p_PV) + + +async def gatherData() -> DTUData: + + #print("get charger status..."); + stale = set() + timeout = aiohttp.ClientTimeout(total=3) + try: + async with aiohttp.ClientSession() as session: + async with session.get('http://192.168.179.36/api/livedata/status',timeout=timeout) as response: #?filter=psm,fup,amp,frc,nrg,lmo,car + if response.status == 200: + html = await response.text() + #logging.warning(html) + state = json.loads(html) + ret.p_AC_sum = state["total"]["Power"]["v"] + invNum = 0 + for inverter in state["inverters"]: + if len(ret.inverter) <= invNum: + ret.inverter.append(DTUInvData()) + ret.inverter[invNum].name = inverter["name"] + ret.inverter[invNum].limit = inverter.get("limit_absolute",0) or 0 + try: + async with session.get('http://192.168.179.36/api/livedata/status?inv='+inverter["serial"],timeout=timeout) as response: #?filter=psm,fup,amp,frc,nrg,lmo,car + html = await response.text() + invState = json.loads(html) + if(invState["inverters"][0]["data_age"] < 60): + ret.inverter[invNum].p_AC = invState["inverters"][0]["AC"]["0"]["Power"]["v"] + ret.inverter[invNum].temp = invState["inverters"][0]["INV"]["0"]["Temperature"]["v"] + ret.inverter[invNum].error = 0 + pvNum = 0 + for key, pvs in invState["inverters"][0]["DC"].items(): + if len(ret.inverter[invNum].p_PV) <= pvNum: + ret.inverter[invNum].p_PV.append(pvs["Power"]["v"]) + else: + ret.inverter[invNum].p_PV[pvNum] = pvs["Power"]["v"] + pvNum = pvNum + 1 + else: + ret.inverter[invNum].error += 1 + stale.add(invNum) + except: + ret.inverter[invNum].error += 1 + stale.add(invNum) + _LOGGER.warning("Inverter data error: "+inverter["serial"]) + invNum = invNum + 1 + schaetzeAusfaelle(stale) + + except: + # Die DTU selbst antwortet nicht - es gibt keinen gesunden Nachbarn, aus + # dem sich etwas hochrechnen liesse. Beide Seiten werden genullt, damit + # p_AC und p_PV zueinander passen; frueher blieb p_AC auf dem alten Wert + # stehen, waehrend p_PV auf 0 fiel. Der error-Zaehler traegt es nach aussen. + for inv in ret.inverter: + inv.error += 1 + inv.estimated = False + inv.p_PV_est = 0.0 + inv.p_AC = 0 + try: + inv.p_PV = [0]*len(inv.p_PV) + except: + pass + _LOGGER.debug("OpenDTU not working") + return ret + +#async def main(): +# await gatherNeededStatus() + +#loop = asyncio.get_event_loop() +#loop.run_until_complete(main()) + + +""" +{ + "inverters":[ + { + "serial":"116491626890", + "name":"Terrassendach", + "order":0, + "data_age":1, + "poll_enabled":true, + "reachable":true, + "producing":true, + "limit_relative":100, + "limit_absolute":1600, + "AC":{ + "0":{ + "Power":{ + "v":74.09999847, + "u":"W", + "d":1 + }, + "Voltage":{ + "v":235.3000031, + "u":"V", + "d":1 + }, + "Current":{ + "v":0.310000002, + "u":"A", + "d":2 + }, + "Power DC":{ + "v":77.80000305, + "u":"W", + "d":1 + }, + "YieldDay":{ + "v":179, + "u":"Wh", + "d":0 + }, + "YieldTotal":{ + "v":8.732000351, + "u":"kWh", + "d":3 + }, + "Frequency":{ + "v":50.00999832, + "u":"Hz", + "d":2 + }, + "PowerFactor":{ + "v":1, + "u":"", + "d":3 + }, + "ReactivePower":{ + "v":0, + "u":"var", + "d":1 + }, + "Efficiency":{ + "v":95.24420929, + "u":"%", + "d":3 + } + } + }, + "DC":{ + "0":{ + "name":{ + "u":"" + }, + "Power":{ + "v":12.80000019, + "u":"W", + "d":1 + }, + "Voltage":{ + "v":21.20000076, + "u":"V", + "d":1 + }, + "Current":{ + "v":0.610000014, + "u":"A", + "d":2 + }, + "YieldDay":{ + "v":31, + "u":"Wh", + "d":0 + }, + "YieldTotal":{ + "v":1.710000038, + "u":"kWh", + "d":3 + } + }, + "1":{ + "name":{ + "u":"" + }, + "Power":{ + "v":26.60000038, + "u":"W", + "d":1 + }, + "Voltage":{ + "v":43.20000076, + "u":"V", + "d":1 + }, + "Current":{ + "v":0.610000014, + "u":"A", + "d":2 + }, + "YieldDay":{ + "v":62, + "u":"Wh", + "d":0 + }, + "YieldTotal":{ + "v":2.496000051, + "u":"kWh", + "d":3 + } + }, + "2":{ + "name":{ + "u":"" + }, + "Power":{ + "v":12.5, + "u":"W", + "d":1 + }, + "Voltage":{ + "v":21.20000076, + "u":"V", + "d":1 + }, + "Current":{ + "v":0.589999974, + "u":"A", + "d":2 + }, + "YieldDay":{ + "v":26, + "u":"Wh", + "d":0 + }, + "YieldTotal":{ + "v":1.179999948, + "u":"kWh", + "d":3 + } + }, + "3":{ + "name":{ + "u":"" + }, + "Power":{ + "v":25.89999962, + "u":"W", + "d":1 + }, + "Voltage":{ + "v":43, + "u":"V", + "d":1 + }, + "Current":{ + "v":0.600000024, + "u":"A", + "d":2 + }, + "YieldDay":{ + "v":60, + "u":"Wh", + "d":0 + }, + "YieldTotal":{ + "v":3.345999956, + "u":"kWh", + "d":3 + } + } + }, + "INV":{ + "0":{ + "Temperature":{ + "v":12.5, + "u":"°C", + "d":1 + } + } + }, + "events":1 + } + ], + "total":{ + "Power":{ + "v":74.09999847, + "u":"W", + "d":1 + }, + "YieldDay":{ + "v":179, + "u":"Wh", + "d":0 + }, + "YieldTotal":{ + "v":8.732000351, + "u":"kWh", + "d":3 + } + }, + "hints":{ + "time_sync":false, + "radio_problem":false, + "default_password":true + } +}""" \ No newline at end of file diff --git a/gatherShellyEM3DataEG.py b/gatherShellyEM3DataEG.py new file mode 100644 index 0000000..d1dd55a --- /dev/null +++ b/gatherShellyEM3DataEG.py @@ -0,0 +1,44 @@ +import json +import asyncio +import aiohttp +import logging +import math +from typing import List +from dataclasses import dataclass +from dataclasses import field + +_LOGGER = logging.getLogger(__name__) + +@dataclass +class PowerEGData: + P_L1:float = 0 + P_L2:float = 0 + P_L3:float = 0 + error:int = 0 +ret = PowerEGData() + +async def gatherData() -> PowerEGData: + + #print("get power EG status..."); + timeout = aiohttp.ClientTimeout(total=3) + try: + async with aiohttp.ClientSession() as session: + #_LOGGER.warning("fetching Power EG") + async with session.get('http://192.168.179.113/rpc/EM.GetStatus?id=0',timeout=timeout) as response: #?filter=psm,fup,amp,frc,nrg,lmo,car + if response.status == 200: + html = await response.text() + #print(html) + state = json.loads(html) + ret.error = 0 + ret.P_L1 = state["a_act_power"] + ret.P_L2 = state["b_act_power"] + ret.P_L3 = state["c_act_power"] + except: + if(ret.error > 5): + ret.P_L1 = 0 + ret.P_L2 = 0 + ret.P_L3 = 0 + _LOGGER.warning("Power EG could not be fetched") + else: + ret.error += 1 + return ret diff --git a/gatherShellyEM3DataUG.py b/gatherShellyEM3DataUG.py new file mode 100644 index 0000000..4642a7b --- /dev/null +++ b/gatherShellyEM3DataUG.py @@ -0,0 +1,43 @@ +import json +import asyncio +import aiohttp +import logging +import math +from typing import List +from dataclasses import dataclass +from dataclasses import field + +_LOGGER = logging.getLogger(__name__) + +@dataclass +class PowerUGData: + P_L1:float = 0 + P_L2:float = 0 + P_L3:float = 0 + error:int = 0 +ret = PowerUGData() + +async def gatherData() -> PowerUGData: + + #print("get charger status..."); + timeout = aiohttp.ClientTimeout(total=3) + try: + async with aiohttp.ClientSession() as session: + async with session.get('http://192.168.179.161/rpc/EM.GetStatus?id=0',timeout=timeout) as response: #?filter=psm,fup,amp,frc,nrg,lmo,car + if response.status == 200: + html = await response.text() + #logging.warning(html) + state = json.loads(html) + ret.error = 0 + ret.P_L1 = state["a_act_power"] + ret.P_L2 = state["b_act_power"] + ret.P_L3 = state["c_act_power"] + except: + if(ret.error > 5): + ret.P_L1 = 0 + ret.P_L2 = 0 + ret.P_L3 = 0 + _LOGGER.warning("Power UG could not be fetched") + else: + ret.error += 1 + return ret diff --git a/gatherSkodaData.py b/gatherSkodaData.py new file mode 100644 index 0000000..7d9e453 --- /dev/null +++ b/gatherSkodaData.py @@ -0,0 +1,839 @@ +"""Anbindung der oeffentlichen MySkoda-API. + +Loest den Weg ueber den Kia ab, dessen Dateien mit dem Fahrzeug weg sind. Der +lief ueber einen Cron-Job, der die Tabelle car fuellte, waehrend der Manager +daraus nur die jeweils letzte Zeile wieder herauslas - Daten also durch die +Datenbank hindurch von einem Prozess zum anderen. Hier laeuft alles im Manager +selbst: der Abruf fuellt rtData direkt und schreibt seine eigene, +ausfuehrliche Historie. + +Die API ist gegenueber dem frueheren Weg deutlich schlichter. Ein Header +X-API-Key genuegt, kein Login, kein Token-Refresh. Der Schluessel wird in der +MySkoda-App unter https://go.skoda.eu/api-keys erzeugt, ist an die dort +ausgewaehlten Fahrzeuge gebunden und laeuft ab - jede erfolgreiche Antwort +traegt X-API-Key-Expires-At mit, das Modul warnt rechtzeitig vorher. + +Ein einziger GET liefert Ladezustand, Verriegelung, Kilometerstand, +Parkposition und Klima. Spec: https://public.api.connect.skoda-auto.cz/docs + +Zwei Dinge unterscheiden das Modul von den uebrigen Sammlern: + + Es ist eine Cloud-API mit knappem Kontingent, kein Geraet im Haus: laut + Doku 20 Anfragen je Stunde und Schluessel, ausdruecklich vorlaeufig. Der + Manager ruft gatherData() im 3-Sekunden-Takt auf, angefragt wird aber nur, + wenn das eigene Intervall abgelaufen ist - alle vier Minuten waehrend des + Ladens, stuendlich beim Parken. Darueber liegen eine eigene Stundenbilanz + und die RateLimit-Header der Antwort, die als massgebliche Quelle gelten. + Zieht Skoda das Kontingent enger, folgt das Modul von selbst. + + Ladebeginn und Ladeende stossen einen Abruf ausser der Reihe an. Die + Wallbox merkt beides sofort, das Fahrzeug erst beim naechsten Abruf - und + bei so wenigen Anfragen sind genau diese beiden Augenblicke die + wertvollsten: der Ladestand davor und danach traegt die + Kapazitaetsrechnung. + + Die Antwort kann lange dauern. Deshalb blockiert gatherData() nie: der + Abruf laeuft als Hintergrund-Task, zurueckgegeben wird immer sofort der + zuletzt bekannte Stand. Eine haengende Cloud-Verbindung kann den + 3-Sekunden-Takt des Managers damit nicht ausbremsen. +""" + +import json +import asyncio +import aiohttp +import logging +import os +import re +import collections +import time +import datetime +from typing import List, Optional +from dataclasses import dataclass, field + +import mysql.connector as mc + + +_LOGGER = logging.getLogger(__name__) + +_URL = "https://public.api.connect.skoda-auto.cz/api/v1/vehicles/" + +# Der include-Parameter bleibt bewusst weg. Ohne ihn liefert die API alles, +# was das Fahrzeug unterstuetzt, und meldet UNSUPPORTED nur fuer Teile, die +# ausdruecklich angefordert wurden - eine Liste haette also bei jedem Abruf +# Fehler fuer alles erzeugt, was dieses Modell nicht kann. So passt sich der +# Abruf von selbst an, ob ein reiner Stromer oder ein Plug-in-Hybrid ankommt, +# und die Ladeprofile kommen ohne Zutun in skoda_raw mit. + +# Schluessel und VIN stehen bewusst nicht im Quelltext: der Schluessel laeuft +# ab und muss dann getauscht werden. Die Datei wird bei jeder Aenderung neu +# gelesen, ein Neustart des Managers ist dafuer nicht noetig. +_KONFIG = os.path.join(os.path.dirname(os.path.abspath(__file__)), "skoda.conf") + +# Das Kontingent ist der enge Punkt dieser Schnittstelle: laut Doku +# 20 Anfragen je Stunde und Schluessel, ausdruecklich nicht endgueltig. Ein +# Abruf alle drei Minuten ist damit schon das Aeusserste. Alle Intervalle +# unten sind daran bemessen; _BUDGET zieht zusaetzlich eine eigene Grenze +# knapp darunter, damit fuer einen Fehlversuch noch Luft bleibt. +# +# Fehlerantworten ab 500 zaehlen laut Doku mit, 401 und 403 nicht. +_LIMIT = 20 # bekanntes Kontingent je Stunde + +# Die API begrenzt je Schluessel. Steht in skoda.conf ein eigener CMD_API_KEY, +# schickt die Weboberflaeche ihre Befehle darueber und nimmt diesem Modul +# nichts weg - dann darf es fast das ganze Kontingent nutzen. Ohne zweiten +# Schluessel teilen sich beide eines, und weil sie in getrennten Prozessen +# laufen und keinen gemeinsamen Zaehler haben, ist es fest aufgeteilt: +# +# eigener CMD_API_KEY nur API_KEY +# dieses Modul 18 / Stunde 14 / Stunde +# Befehle 15 / Stunde 4 / Stunde (ajax/skodaCmd.php) +# Reserve 2 / Stunde 2 / Stunde +# +# Umgestellt wird von selbst, sobald der zweite Schluessel auftaucht - von +# Hand ist hier nichts nachzuziehen. +_BUDGET_ALLEIN = 18 +_BUDGET_GETEILT = 14 +_FENSTER = 3600 # Bezugszeitraum des Kontingents + +# Abrufintervalle in Sekunden. +_I_MIN = 200 # harte Untergrenze, egal was sonst gilt +_I_LADEN = 240 # laedt gerade - 15 Abrufe je Stunde +_I_FAHRT = 300 # unterwegs +_I_GESTECKT = 900 # Kabel steckt, laedt aber nicht +_I_AKTIV = 1800 # steht, hat sich zuletzt aber noch geruehrt +_I_RUHE = 3600 # seit Stunden unveraendert + +_HEARTBEAT = 3600 # auch ohne Aenderung so oft eine Zeile schreiben +_RUHE_AB = 7200 # ab so langer Unveraendertheit gilt _I_RUHE +_WARN_KEY = 14 # Tage vor Ablauf des Schluessels warnen + +_TIMEOUT = aiohttp.ClientTimeout(total=20) + +# Ladezustaende, in denen das Kabel steckt. +_GESTECKT = ("CHARGING", "CONSERVING", "READY_FOR_CHARGING", + "CHARGING_INTERRUPTED", "DISCHARGING") + + +@dataclass +class SkodaData: + # --- Identitaet ------------------------------------------------------- + vin:str = "" + name:str = "" + plate:str = "" + + # --- Batterie und Laden ---------------------------------------------- + soc:int = 0 # Prozent + range_m:int = 0 # Restreichweite, Einheit wie geliefert + range_km:float = 0.0 # daraus abgeleitet, immer Kilometer + chgState:str = "" # CHARGING, CONSERVING, CONNECT_CABLE, ... + chgType:str = "" # AC, DC, OFF + chgKw:float = 0.0 # Ladeleistung laut Fahrzeug + chgKmh:float = 0.0 # Ladegeschwindigkeit in km/h + chgRemMin:int = 0 # Restladezeit in Minuten + chgFullAt:Optional[datetime.datetime] = None + savedLoc:bool = False # steht an einem gespeicherten Ladeort + + # --- Ladeeinstellungen ------------------------------------------------ + targetSoc:int = 0 + careTargetSoc:int = 0 + careMode:bool = False # Batterieschonung aktiv + chgMode:str = "" # MANUAL, TIMER, ... + maxAc:str = "" # REDUCED, MAXIMUM + maxAcA:int = 0 # Ampere-Grenze + autoUnlock:bool = False + + # --- Zustand ---------------------------------------------------------- + locked:Optional[bool] = None # None, wenn das Fahrzeug UNKNOWN meldet + doorsLocked:str = "" + doors:str = "" + windows:str = "" + lights:str = "" + sunroof:str = "" + trunk:str = "" + bonnet:str = "" + + odoKm:int = 0 + + # --- Verbrenner, nur bei Hybrid oder Verbrenner besetzt --------------- + carType:str = "" # HYBRID, GASOLINE, DIESEL, CNG, LPG + totalRangeKm:float = 0.0 # Gesamtreichweite ueber alle Antriebe + adBlueKm:float = 0.0 + eng1Type:str = "" # ELECTRIC, GASOLINE, DIESEL, ... + eng1Soc:int = 0 + eng1FuelPct:int = 0 + eng1RangeKm:float = 0.0 + eng2Type:str = "" + eng2Soc:int = 0 + eng2FuelPct:int = 0 + eng2RangeKm:float = 0.0 + fuelPct:int = 0 # Tankfuellung, unabhaengig davon, an + # welcher der beiden Motorstellen der + # Verbrenner gemeldet wird + + # --- Position --------------------------------------------------------- + parkState:str = "" # PARKED, IN_MOTION + lat:Optional[float] = None + lon:Optional[float] = None + address:str = "" + + # --- Klima ------------------------------------------------------------ + acState:str = "" + acTargetC:Optional[float] = None + acWinFront:Optional[bool] = None + acWinRear:Optional[bool] = None + auxState:str = "" + ventState:str = "" + + # --- Zeitstempel des Fahrzeugs --------------------------------------- + # Die Antwort setzt sich aus mehreren Quellen zusammen, jede mit eigenem + # Stand. Alle vier mitzufuehren zeigt spaeter, wie alt ein Wert war. + capChg:Optional[datetime.datetime] = None + capStatus:Optional[datetime.datetime] = None + capOdo:Optional[datetime.datetime] = None + capFuel:Optional[datetime.datetime] = None + capAc:Optional[datetime.datetime] = None + + # --- Betrieb des Moduls ---------------------------------------------- + error:int = 0 # aufeinanderfolgende Fehlversuche + httpStatus:int = 0 + apiErrors:str = "" # Fehlerliste der Antwort, kommagetrennt + rlRemaining:int = -1 # Restkontingent laut RateLimit-Header + keyExpires:Optional[datetime.datetime] = None + lastOk:float = 0.0 # Zeitpunkt der letzten guten Antwort + alter:float = 0.0 # Sekunden seit der letzten guten Antwort + + +ret = SkodaData() # Modul-Singleton, ueberlebt zwischen Aufrufen + +# Alles, was nur den Ablauf steuert und nicht nach aussen gehoert. +_st = { + "naechster": 0.0, # fruehester naechster Abruf + "laeuft": False, # ein Abruf ist unterwegs + "letzteAend": 0.0, # wann sich zuletzt etwas am Fahrzeug ruehrte + "letzteZeile": 0.0, # wann zuletzt eine Zeile geschrieben wurde + "signatur": None, # Fingerabdruck der zuletzt geschriebenen Zeile + "keyGewarnt": False, + "konfMtime": 0.0, + "apiKey": "", + "vin": "", + "ladenVorher": False, # Hausseite lieferte beim letzten Aufruf Strom + "eigenerCmdKey": False, # Steuerung hat einen eigenen Schluessel +} + +# Zeitpunkte der Anfragen der letzten Stunde. Eigene Buchfuehrung neben den +# RateLimit-Headern: die kommen erst mit der Antwort, und ihr Reset-Wert +# schrumpft ueber das Fenster, sodass sich gegen Ende ein Schwall erlauben +# liesse, der zu Beginn des naechsten Fensters sofort auflaeuft. Die eigene +# Liste haelt den Abstand ueber jede Fenstergrenze hinweg. +_verbrauch = collections.deque() + +# Hausseitige Werte im Moment des Abrufs. Der Manager reicht sie bei jedem +# Aufruf herein; der Hintergrund-Task greift auf den letzten Stand zu. +_haus = {"wbKw":0.0, "wbPlug":False, "wbogKw":0.0, "wbogPlug":False, + "pvKw":0.0, "gridKw":0.0, "wbWh":0, "wbogWh":0} + +_db = {"host":"localhost", "port":3310, "user":"solarLog", + "passwd":"", "database":"solarLog"} + + +# --------------------------------------------------------------------------- +# Kleinkram +# --------------------------------------------------------------------------- + +_ISO = re.compile(r"(\d{4})-(\d\d)-(\d\d)[T ](\d\d):(\d\d):(\d\d)" + r"(?:\.\d+)?(Z|[+-]\d\d:?\d\d)?") + +def _zeit(s) -> Optional[datetime.datetime]: + """ISO-8601 der API in lokale, naive Zeit fuer MySQL DATETIME. + + Die API liefert UTC mit Z. Der Rest der Datenbank steht in Ortszeit, also + wird hier umgerechnet - sonst laegen Ladevorgaenge im Sommer zwei Stunden + neben den Zaehlerwerten in EnergyFlow, mit denen sie verglichen werden + sollen. datetime.fromisoformat kann in Python 3.8 weder Z noch beliebige + Bruchteile, daher der eigene Ausdruck. + """ + if not s: + return None + m = _ISO.match(str(s).strip()) + if not m: + return None + y, mo, d, h, mi, se, off = m.groups() + dt = datetime.datetime(int(y), int(mo), int(d), int(h), int(mi), int(se)) + if not off: + return dt # ohne Zone: schon lokal + if off != "Z": + vz = 1 if off[0] == "+" else -1 + off = off[1:].replace(":", "") + dt -= vz*datetime.timedelta(hours=int(off[:2]), minutes=int(off[2:])) + return dt.replace(tzinfo=datetime.timezone.utc).astimezone().replace(tzinfo=None) + + +def _janein(v) -> Optional[bool]: + """YES/NO/ON/OFF/ACTIVATED der API in bool, UNKNOWN in None. + + Ein fehlender Wert und ein ausdrueckliches UNKNOWN sind nicht dasselbe wie + ein Nein. Beim Kia wurde beides zu 0 und war hinterher nicht mehr zu + unterscheiden; deshalb hier None. + """ + if v is None: + return None + v = str(v).upper() + if v in ("YES", "ON", "TRUE", "ACTIVATED", "LOCKED", "PERMANENT"): + return True + if v in ("NO", "OFF", "FALSE", "DEACTIVATED", "UNLOCKED"): + return False + return None + + +def _konfig() -> bool: + """Schluessel und VIN aus skoda.conf lesen, wenn die Datei sich geaendert hat. + + Format, eine Zuweisung je Zeile: API_KEY=... und VIN=... + Der Schluessel laeuft ab. Weil die Datei bei jeder Aenderung neu gelesen + wird, genuegt zum Tausch das Ueberschreiben - der Manager laeuft weiter. + """ + try: + mtime = os.path.getmtime(_KONFIG) + except OSError: + if _st["apiKey"]: + _LOGGER.warning("skoda.conf nicht mehr lesbar, benutze den letzten Stand.") + return True + return False + if mtime == _st["konfMtime"]: + return bool(_st["apiKey"] and _st["vin"]) + werte = {} + try: + with open(_KONFIG, "r") as f: + for zeile in f: + zeile = zeile.strip() + if not zeile or zeile.startswith("#") or "=" not in zeile: + continue + k, v = zeile.split("=", 1) + werte[k.strip().upper()] = v.strip().strip('"').strip("'") + except OSError as e: + _LOGGER.error("skoda.conf nicht lesbar: "+str(e)) + return bool(_st["apiKey"] and _st["vin"]) + _st["konfMtime"] = mtime + _st["apiKey"] = werte.get("API_KEY", "") + _st["vin"] = werte.get("VIN", "") + _st["eigenerCmdKey"] = bool(werte.get("CMD_API_KEY")) + _db["passwd"] = werte.get("DB_PASSWORD", _db["passwd"]) + _st["keyGewarnt"] = False + if not _st["apiKey"] or not _st["vin"]: + _LOGGER.error("skoda.conf braucht API_KEY und VIN.") + return False + ret.vin = _st["vin"] + _LOGGER.info("skoda.conf gelesen, VIN endet auf "+_st["vin"][-4:] + +(", eigener Schluessel fuer die Steuerung" + if _st["eigenerCmdKey"] else ", ein Schluessel fuer alles")) + return True + + +def setDbPasswort(pw:str): + """Datenbank-Passwort vom Manager uebernehmen. + + Alternativ steht DB_PASSWORD in skoda.conf. So oder so taucht es hier + nicht im Quelltext auf. + """ + _db["passwd"] = pw + + +# --------------------------------------------------------------------------- +# Antwort auswerten +# --------------------------------------------------------------------------- + +def uebernehmen(antwort:dict): + """Eine Antwort des Fahrzeugs in ret uebertragen. + + Bewusst eine eigene Funktion ohne Netz und ohne Datenbank: so laesst sich + die Zuordnung mit einer gespeicherten Antwort pruefen, bevor das Auto da + ist (siehe __main__ am Dateiende). + + Fehlende Teile werden uebersprungen statt genullt. Die API laesst einen + Teil weg, wenn sie ihn gerade nicht bekommt, und legt dann einen Eintrag + in errors ab - der alte Wert ist dann die bessere Auskunft als eine Null. + """ + v = antwort.get("vehicle") or {} + + fehler = [] + for e in antwort.get("errors") or []: + if e.get("type"): + fehler.append(e["type"]) + ret.apiErrors = ",".join(fehler)[:255] + + if v.get("vin"): + ret.vin = v["vin"] + if v.get("name"): + ret.name = v["name"] + if v.get("licensePlate"): + ret.plate = v["licensePlate"] + + # --- Laden ------------------------------------------------------------ + chg = v.get("charging") + if chg: + ret.savedLoc = bool(chg.get("isVehicleInSavedLocation", False)) + ret.capChg = _zeit(chg.get("carCapturedTimestamp")) or ret.capChg + s = chg.get("status") or {} + ret.chgState = s.get("state", ret.chgState) or "" + ret.chgType = s.get("chargeType", ret.chgType) or "" + if s.get("chargePowerInKw") is not None: + ret.chgKw = round(float(s["chargePowerInKw"]), 3) + elif ret.chgState not in _GESTECKT: + ret.chgKw = 0.0 + if s.get("chargingRateInKilometersPerHour") is not None: + ret.chgKmh = round(float(s["chargingRateInKilometersPerHour"]), 2) + if s.get("remainingTimeToFullyChargedInMinutes") is not None: + ret.chgRemMin = int(s["remainingTimeToFullyChargedInMinutes"]) + ret.chgFullAt = _zeit(s.get("fullyChargedAt")) or ret.chgFullAt + b = s.get("battery") or {} + if b.get("stateOfChargeInPercent") is not None: + ret.soc = int(b["stateOfChargeInPercent"]) + if b.get("remainingCruisingRangeInMeters") is not None: + ret.range_m = int(b["remainingCruisingRangeInMeters"]) + # Das Feld heisst Meter, das Beispiel der Spec (249) sieht nach + # Kilometern aus. Der Rohwert wird unveraendert mitgeschrieben, + # abgeleitet wird nur diese Anzeige: ueber 1500 kann nur Meter + # gemeint sein, ein Akku traegt keine 1500 Kilometer. + ret.range_km = round(ret.range_m/1000.0, 1) if ret.range_m > 1500 else float(ret.range_m) + cs = chg.get("settings") or {} + if cs.get("targetStateOfChargeInPercent") is not None: + ret.targetSoc = int(cs["targetStateOfChargeInPercent"]) + if cs.get("batteryCareModeTargetValueInPercent") is not None: + ret.careTargetSoc = int(cs["batteryCareModeTargetValueInPercent"]) + cm = _janein(cs.get("chargingCareMode")) + if cm is not None: + ret.careMode = cm + ret.chgMode = cs.get("preferredChargeMode", ret.chgMode) or "" + ret.maxAc = cs.get("maxChargeCurrentAc", ret.maxAc) or "" + if cs.get("maxChargeCurrentAcAmpere") is not None: + ret.maxAcA = int(cs["maxChargeCurrentAcAmpere"]) + au = _janein(cs.get("autoUnlockPlugWhenCharged")) + if au is not None: + ret.autoUnlock = au + + # --- Tueren, Fenster, Licht ------------------------------------------- + st = v.get("status") + if st: + ret.capStatus = _zeit(st.get("carCapturedTimestamp")) or ret.capStatus + o = st.get("overall") or {} + ret.locked = _janein(o.get("locked")) + ret.doorsLocked = o.get("doorsLocked", "") or "" + ret.doors = o.get("doors", "") or "" + ret.windows = o.get("windows", "") or "" + ret.lights = o.get("lights", "") or "" + d = st.get("detail") or {} + ret.sunroof = d.get("sunroof", "") or "" + ret.trunk = d.get("trunk", "") or "" + ret.bonnet = d.get("bonnet", "") or "" + + # --- Kilometerstand --------------------------------------------------- + od = v.get("odometer") + if od and od.get("mileageInKm") is not None: + ret.odoKm = int(od["mileageInKm"]) + ret.capOdo = _zeit(od.get("carCapturedTimestamp")) or ret.capOdo + + # --- Verbrenner ------------------------------------------------------- + # Ein reiner Stromer laesst diesen Block weg, dann bleiben die Spalten + # NULL. Ein Hybrid liesse sich sonst nachtraeglich nicht auswerten - was + # hier nicht mitgeschrieben wird, ist fuer immer fort. + fs = v.get("fuelStatus") + if fs: + ret.carType = fs.get("carType", ret.carType) or "" + ret.capFuel = _zeit(fs.get("carCapturedTimestamp")) or ret.capFuel + if fs.get("totalRangeInKm") is not None: + ret.totalRangeKm = round(float(fs["totalRangeInKm"]), 1) + if fs.get("adBlueRange") is not None: + ret.adBlueKm = round(float(fs["adBlueRange"]), 1) + for nr, schluessel in ((1, "primaryEngineRange"), (2, "secondaryEngineRange")): + er = fs.get(schluessel) or {} + if not er: + continue + setattr(ret, "eng"+str(nr)+"Type", er.get("engineType", "") or "") + if er.get("currentSoCInPercent") is not None: + setattr(ret, "eng"+str(nr)+"Soc", int(er["currentSoCInPercent"])) + if er.get("currentFuelLevelInPercent") is not None: + setattr(ret, "eng"+str(nr)+"FuelPct", int(er["currentFuelLevelInPercent"])) + if er.get("remainingRangeInKm") is not None: + setattr(ret, "eng"+str(nr)+"RangeKm", round(float(er["remainingRangeInKm"]), 1)) + if (er.get("engineType") not in (None, "ELECTRIC") + and er.get("currentFuelLevelInPercent") is not None): + ret.fuelPct = int(er["currentFuelLevelInPercent"]) + + # --- Position --------------------------------------------------------- + pp = v.get("parkingPosition") + if pp: + ret.parkState = pp.get("state", ret.parkState) or "" + g = pp.get("gpsCoordinates") or {} + if g.get("latitude") is not None: + ret.lat = round(float(g["latitude"]), 6) + ret.lon = round(float(g["longitude"]), 6) + ret.address = (pp.get("formattedAddress") or ret.address)[:160] + + # --- Klima ------------------------------------------------------------ + ac = v.get("airConditioning") + if ac: + ret.acState = ac.get("state", ret.acState) or "" + ret.capAc = _zeit(ac.get("carCapturedTimestamp")) or ret.capAc + tt = ac.get("targetTemperature") or {} + if tt.get("value") is not None: + t = float(tt["value"]) + if str(tt.get("unit", "CELSIUS")).upper() == "FAHRENHEIT": + t = (t-32.0)*5.0/9.0 + ret.acTargetC = round(t, 1) + wh = ac.get("windowHeating") or {} + ret.acWinFront = _janein(wh.get("front")) + ret.acWinRear = _janein(wh.get("rear")) + aux = v.get("auxiliaryHeating") + if aux: + ret.auxState = aux.get("state", ret.auxState) or "" + vt = v.get("activeVentilation") + if vt: + ret.ventState = vt.get("state", ret.ventState) or "" + + +def _signatur() -> tuple: + """Fingerabdruck der Werte, deren Aenderung eine neue Zeile rechtfertigt. + + Das Fahrzeug meldet sich nur, wenn es etwas zu melden hat; zwischendurch + liefert die Cloud denselben Stand erneut. Ohne diesen Vergleich stuenden + in der Tabelle vor allem Wiederholungen. Die hausseitigen Werte gehen + absichtlich nicht ein - die stehen ohnehin alle 300 s in EnergyFlow. + + chgKw gehoert dagegen hinein, obwohl es waehrend des Ladens fast jeden + Abruf veraendert: genau diese Punkte sind die Ladekurve. Ohne den Wert + entstuende eine Zeile erst, wenn der Ladestand um einen Prozentpunkt + weiterspringt - der Knick, an dem das Fahrzeug abregelt, faende sich + hinterher nicht wieder. + """ + return (ret.capChg, ret.capStatus, ret.capOdo, ret.capFuel, ret.capAc, + ret.soc, ret.range_m, ret.chgState, ret.chgType, ret.chgRemMin, + ret.chgKw, + ret.locked, ret.doorsLocked, ret.doors, ret.windows, ret.lights, + ret.sunroof, ret.trunk, ret.bonnet, ret.odoKm, + ret.carType, ret.eng1FuelPct, ret.eng1RangeKm, + ret.eng2FuelPct, ret.eng2RangeKm, ret.totalRangeKm, + ret.parkState, ret.lat, ret.lon, + ret.acState, ret.auxState, ret.ventState, + ret.targetSoc, ret.chgMode, ret.maxAcA, ret.careMode) + + +def _intervall() -> float: + """Wie lange bis zum naechsten Abruf. + + Waehrend des Ladens dicht, damit die Ladekurve genug Stuetzstellen hat - + eine DC-Ladung ist nach einer halben Stunde vorbei. Beim Parken weit, weil + sich dann ohnehin nichts aendert und das Kontingent begrenzt ist. + """ + if ret.error: + # Nach einem Fehler zurueckhaltend erneut versuchen. Ist die Cloud weg, + # bringt haeufiges Klopfen nichts und kostet nur Kontingent. + return min(_I_RUHE, 60.0*(2**min(ret.error-1, 5))) + if ret.chgState == "CHARGING" or (_haus["wbPlug"] and _haus["wbKw"] > 0.5): + return _I_LADEN + if ret.parkState == "IN_MOTION": + return _I_FAHRT + if _haus["wbPlug"] or ret.chgState in _GESTECKT: + return _I_GESTECKT + if time.time() - _st["letzteAend"] > _RUHE_AB: + return _I_RUHE + return _I_AKTIV + + +def _gezaehlt(jetzt:float): + """Eine Anfrage in die Stundenbilanz aufnehmen.""" + _verbrauch.append(jetzt) + _aufraeumen(jetzt) + + +def _aufraeumen(jetzt:float): + while _verbrauch and jetzt - _verbrauch[0] >= _FENSTER: + _verbrauch.popleft() + + +def _budgetSperre(jetzt:float) -> float: + """Fruehester Zeitpunkt, zu dem wieder eine Anfrage frei ist. + + Sind in der zurueckliegenden Stunde bereits _BUDGET Anfragen gelaufen, + wird gewartet, bis die aelteste aus dem Fenster faellt. Das ist die + eigentliche Sicherung: die Intervalle unten sind zwar so bemessen, dass + sie passen, aber Sonderfaelle wie der Anstoss beim Ladebeginn kommen + zusaetzlich - und ein 429 kostet zwar kein Kontingent, verraet aber, dass + die Rechnung nicht aufging. + """ + _aufraeumen(jetzt) + budget = _BUDGET_ALLEIN if _st["eigenerCmdKey"] else _BUDGET_GETEILT + if len(_verbrauch) < budget: + return 0.0 + return _verbrauch[0] + _FENSTER + 5.0 - jetzt + + +def _kontingent(headers): + """Aus den RateLimit-Headern eine Untergrenze fuer den Abstand ableiten. + + Die Doku nennt derzeit 20 Anfragen je Stunde, ausdruecklich nicht + endgueltig, und erklaert die Header zur massgeblichen Quelle. Bleiben im + laufenden Fenster noch n Anfragen und laeuft es in t Sekunden ab, dann + sind t/n Sekunden Abstand gerade noch tragbar; der Zuschlag haelt Abstand + zur Grenze. Zieht Skoda das Kontingent enger, folgt das Modul von selbst, + ohne dass hier eine Zahl nachgetragen werden muesste. + """ + try: + rest = int(headers.get("RateLimit-Remaining", -1)) + reset = int(headers.get("RateLimit-Reset", -1)) + except (TypeError, ValueError): + return 0.0 + ret.rlRemaining = rest + if rest < 0 or reset < 0: + return 0.0 + if rest == 0: + return float(reset) + 5.0 + return (float(reset)/rest)*1.2 + + +def _keyPruefen(headers): + """Vor dem Ablauf des Schluessels warnen, solange noch Zeit zum Tausch ist.""" + ts = _zeit(headers.get("X-API-Key-Expires-At")) + if not ts: + return + ret.keyExpires = ts + tage = (ts - datetime.datetime.now()).total_seconds()/86400.0 + if tage < _WARN_KEY and not _st["keyGewarnt"]: + _st["keyGewarnt"] = True + _LOGGER.warning("Skoda-API-Schluessel laeuft am " + +ts.strftime("%d.%m.%Y")+" ab ("+str(int(tage)) + +" Tage) - in der MySkoda-App erneuern und skoda.conf ueberschreiben.") + + +# --------------------------------------------------------------------------- +# Historie +# --------------------------------------------------------------------------- + +_SPALTEN = ("datetime, cap_chg, cap_status, cap_odo, cap_fuel, cap_ac, " + "soc, range_m, chg_state, chg_type, chg_kw, chg_kmh, chg_rem_min, " + "chg_full_at, saved_loc, target_soc, care_target_soc, care_mode, " + "chg_mode, max_ac, max_ac_a, auto_unlock, " + "locked, doors_locked, doors, windows, lights, sunroof, trunk, bonnet, " + "odo_km, car_type, total_range_km, adblue_km, " + "eng1_type, eng1_soc, eng1_fuel_pct, eng1_range_km, " + "eng2_type, eng2_soc, eng2_fuel_pct, eng2_range_km, " + "park_state, lat, lon, address, " + "ac_state, ac_target_c, ac_win_front, ac_win_rear, aux_state, vent_state, " + "wb_kw, wb_plug, wb_wh_total, wbog_kw, wbog_plug, wbog_wh_total, " + "pv_kw, grid_kw, " + "http_status, api_errors, rl_remaining, key_expires") + + +def _leer(v): + """Leere Zeichenkette als NULL schreiben. + + Kein Wert und ein leerer Wert sind in der Auswertung nicht dasselbe. Mit + NULL genuegt spaeter IS NULL, sonst muesste jede Abfrage zusaetzlich an + den Leerstring denken - und wer das einmal vergisst, zaehlt Fahrzeuge + ohne Verbrenner als Fahrzeuge mit leerem Tank. + """ + return v if v else None + + +def _werte() -> tuple: + return (datetime.datetime.now().replace(microsecond=0), + ret.capChg, ret.capStatus, ret.capOdo, ret.capFuel, ret.capAc, + ret.soc, ret.range_m, _leer(ret.chgState), _leer(ret.chgType), ret.chgKw, + ret.chgKmh, ret.chgRemMin, ret.chgFullAt, ret.savedLoc, + ret.targetSoc, ret.careTargetSoc, ret.careMode, _leer(ret.chgMode), + _leer(ret.maxAc), ret.maxAcA, ret.autoUnlock, + ret.locked, _leer(ret.doorsLocked), _leer(ret.doors), + _leer(ret.windows), _leer(ret.lights), + _leer(ret.sunroof), _leer(ret.trunk), _leer(ret.bonnet), ret.odoKm, + _leer(ret.carType), ret.totalRangeKm, ret.adBlueKm, + _leer(ret.eng1Type), ret.eng1Soc, ret.eng1FuelPct, ret.eng1RangeKm, + _leer(ret.eng2Type), ret.eng2Soc, ret.eng2FuelPct, ret.eng2RangeKm, + _leer(ret.parkState), ret.lat, ret.lon, _leer(ret.address), + _leer(ret.acState), ret.acTargetC, ret.acWinFront, ret.acWinRear, + _leer(ret.auxState), _leer(ret.ventState), + round(_haus["wbKw"], 3), _haus["wbPlug"], _haus["wbWh"], + round(_haus["wbogKw"], 3), _haus["wbogPlug"], _haus["wbogWh"], + round(_haus["pvKw"], 3), round(_haus["gridKw"], 3), + ret.httpStatus, _leer(ret.apiErrors), ret.rlRemaining, ret.keyExpires) + + +def _schreiben(rohtext:str): + """Eine Zeile in skoda und die unveraenderte Antwort in skoda_raw. + + Blockierend - wird nur ueber run_in_executor aufgerufen, damit der + 3-Sekunden-Takt des Managers nicht daran haengt. + + Die Rohantwort mitzuschreiben kostet wenig und rettet spaeter viel: taucht + ein Feld auf, das hier noch nicht zugeordnet ist, laesst es sich aus der + Historie nachtragen, statt erst ab dem Tag der Erkenntnis zu existieren. + """ + werte = _werte() + platz = ",".join(["%s"]*len(werte)) + try: + with mc.connect(**_db) as verbindung: + with verbindung.cursor() as cursor: + cursor.execute("INSERT INTO skoda ("+_SPALTEN+") VALUES ("+platz+");", + werte) + zeile = cursor.lastrowid + if rohtext: + cursor.execute("INSERT INTO skoda_raw (datetime, sample_id, payload) " + "VALUES (%s,%s,%s);", + (datetime.datetime.now().replace(microsecond=0), + zeile, rohtext)) + verbindung.commit() + except Exception as e: + _LOGGER.error("Skoda-Historie nicht geschrieben: "+str(e)) + + +# --------------------------------------------------------------------------- +# Abruf +# --------------------------------------------------------------------------- + +async def _abrufen(): + """Ein Durchgang: anfragen, auswerten, bei Aenderung protokollieren.""" + kopf = {"X-API-Key": _st["apiKey"], "Accept": "application/json"} + url = _URL + _st["vin"] + rohtext = "" + abstand = 0.0 + try: + async with aiohttp.ClientSession() as session: + async with session.get(url, headers=kopf, timeout=_TIMEOUT) as response: + ret.httpStatus = response.status + rohtext = await response.text() + abstand = _kontingent(response.headers) + _keyPruefen(response.headers) + + if response.status == 200: + uebernehmen(json.loads(rohtext)) + ret.error = 0 + ret.lastOk = time.time() + elif response.status == 429: + # Kontingent erschoepft. Retry-After ist verbindlich. + ret.error += 1 + try: + abstand = max(abstand, float(response.headers.get("Retry-After", 60))) + except (TypeError, ValueError): + abstand = max(abstand, 60.0) + _LOGGER.warning("Skoda-API: Kontingent erschoepft, warte " + +str(int(abstand))+" s.") + rohtext = "" + elif response.status in (401, 403): + # Schluessel abgelaufen, widerrufen oder nicht fuer diese VIN + # freigegeben. Das behebt sich nicht von selbst, also selten + # nachfassen statt im Minutentakt gegen die Wand zu laufen. + ret.error += 1 + abstand = max(abstand, float(_I_RUHE)) + _LOGGER.error("Skoda-API weist den Schluessel ab (HTTP " + +str(response.status)+") - in der MySkoda-App " + "erneuern und skoda.conf ueberschreiben.") + rohtext = "" + else: + ret.error += 1 + _LOGGER.warning("Skoda-API antwortet mit HTTP "+str(response.status)) + rohtext = "" + except asyncio.TimeoutError: + ret.error += 1 + _LOGGER.warning("Skoda-API antwortet nicht rechtzeitig.") + except Exception as e: + ret.error += 1 + _LOGGER.warning("Skoda-API nicht erreichbar: "+str(e)) + + jetzt = time.time() + # 401 und 403 zaehlen laut Doku nicht gegen das Kontingent, alles andere + # schon - auch ein 500 aus einer Stoerung bei Skoda. + if ret.httpStatus not in (401, 403): + _gezaehlt(jetzt) + _st["naechster"] = jetzt + max(_I_MIN, _intervall(), abstand, + _budgetSperre(jetzt)) + + if ret.httpStatus != 200: + return + + sig = _signatur() + geaendert = sig != _st["signatur"] + if geaendert: + _st["letzteAend"] = jetzt + # Ohne Aenderung trotzdem gelegentlich eine Zeile: sonst ist hinterher + # nicht zu unterscheiden, ob das Auto stillstand oder das Modul stand. + if geaendert or (jetzt - _st["letzteZeile"]) >= _HEARTBEAT: + _st["signatur"] = sig + _st["letzteZeile"] = jetzt + try: + await asyncio.get_event_loop().run_in_executor(None, _schreiben, rohtext) + except Exception as e: + _LOGGER.error("Skoda-Historie nicht geschrieben: "+str(e)) + + +async def gatherData(wbKw:float=0.0, wbPlug:bool=False, + wbogKw:float=0.0, wbogPlug:bool=False, + pvKw:float=0.0, gridKw:float=0.0, + wbWh:int=0, wbogWh:int=0) -> SkodaData: + """Letzten bekannten Fahrzeugstand liefern, bei Bedarf einen Abruf anstossen. + + Kehrt sofort zurueck. Der eigentliche Abruf laeuft im Hintergrund, damit + eine langsame Cloud-Antwort den 3-Sekunden-Takt des Managers nicht + verzoegert; das Ergebnis steht dann beim naechsten Aufruf bereit. + + Die hausseitigen Werte kommen vom Manager mit und werden neben dem + Fahrzeugstand protokolliert. Erst dadurch wird die Batterie messbar: die + Wallbox zaehlt die eingespeiste Energie, das Fahrzeug meldet den + Ladestand, und aus kWh je SoC-Prozent ergibt sich die nutzbare Kapazitaet + und ihr Verlauf ueber die Jahre. + """ + _haus["wbKw"] = wbKw + _haus["wbPlug"] = bool(wbPlug) + _haus["wbogKw"] = wbogKw + _haus["wbogPlug"] = bool(wbogPlug) + _haus["pvKw"] = pvKw + _haus["gridKw"] = gridKw + # Gesamtzaehlerstaende der Wallboxen in Wh. Die Energie einer Ladung ist + # dann die Differenz zweier Staende statt einer Summe ueber gemittelte + # Leistungswerte - der Fehler an den Raendern des Ladevorgangs entfaellt. + _haus["wbWh"] = int(wbWh or 0) + _haus["wbogWh"] = int(wbogWh or 0) + + # Die Wallbox merkt Anfang und Ende einer Ladung sofort, das Fahrzeug + # erst beim naechsten Abruf. Bei 20 Anfragen je Stunde sind genau diese + # beiden Augenblicke die wertvollsten: der Ladestand davor und danach + # bestimmt die Kapazitaetsrechnung, waehrend ein Punkt mitten in der + # Kurve wenig beitraegt. Beide Flanken stossen deshalb einen Abruf an - + # die Budgetsperre kann ihn trotzdem noch verzoegern. + laedt = bool(wbPlug) and wbKw > 0.5 + if laedt != _st["ladenVorher"]: + _st["ladenVorher"] = laedt + _st["naechster"] = min(_st["naechster"], time.time()) + + ret.alter = round(time.time() - ret.lastOk, 1) if ret.lastOk else 0.0 + + if _st["laeuft"] or time.time() < _st["naechster"]: + return ret + if not _konfig(): + # Noch kein Schluessel hinterlegt. In Ruhe erneut nachsehen, statt die + # Datei alle drei Sekunden zu suchen. + _st["naechster"] = time.time() + 60 + return ret + _st["laeuft"] = True + if True: + + async def lauf(): + try: + await _abrufen() + finally: + _st["laeuft"] = False + + asyncio.ensure_future(lauf()) + return ret + + +if __name__ == "__main__": + # Pruefung ohne Fahrzeug: gespeicherte Antwort einlesen und zeigen, was + # daraus in der Tabelle landen wuerde. + # python3 gatherSkodaData.py antwort.json + import sys + import pprint + logging.basicConfig(level=logging.DEBUG) + with open(sys.argv[1], "r") as f: + uebernehmen(json.load(f)) + pprint.pprint(ret) + print() + for name, wert in zip(_SPALTEN.replace(" ", "").split(","), _werte()): + print(name.ljust(16), wert) diff --git a/gatherWaterData.py b/gatherWaterData.py new file mode 100644 index 0000000..01c3cc5 --- /dev/null +++ b/gatherWaterData.py @@ -0,0 +1,33 @@ +import asyncio +import logging +import sys +import mysql.connector as mc +from mysql.connector import connect, Error +import konfig + +from dataclasses import dataclass + +_LOGGER = logging.getLogger(__name__) + +@dataclass +class WaterData: + waterHeight:int = 0 + temp:float = 0 + +async def getWaterData(): + ret = WaterData + try: + with connect(**konfig.datenbank()) as connection: + query = ("SELECT water_mm, temp_c FROM zisterne WHERE datetime > DATE_SUB(NOW(),INTERVAL 10 MINUTE) ORDER BY ID DESC LIMIT 1;") + with connection.cursor() as cursor: + cursor.execute(query) + myresult = cursor.fetchone() + connection.commit() + ret.waterHeight = myresult[0] + ret.temp = myresult[1]/10 + except: + #_LOGGER.warning("Water data could not be fetched") + return ret + return ret + #result = await vehicle.remote_services.trigger_remote_light_flash() + #print(result.state) \ No newline at end of file diff --git a/goodwe/__init__.py b/goodwe/__init__.py new file mode 100644 index 0000000..63774d2 --- /dev/null +++ b/goodwe/__init__.py @@ -0,0 +1,138 @@ +from __future__ import annotations + +import asyncio +import logging +from typing import Type + +from .dt import DT +from .es import ES +from .et import ET +from .exceptions import InverterError, RequestFailedException +from .goodwe import GoodWeXSProcessor, AbstractDataProcessor, GoodWeInverter +from .inverter import Inverter, OperationMode, Sensor, SensorKind +from .model import DT_MODEL_TAGS, ES_MODEL_TAGS, ET_MODEL_TAGS +from .protocol import ProtocolCommand, UdpInverterProtocol, Aa55ProtocolCommand + +logger = logging.getLogger(__name__) + +# Inverter family names +ET_FAMILY = ["ET", "EH", "BT", "BH"] +ES_FAMILY = ["ES", "EM", "BP"] +DT_FAMILY = ["DT", "MS", "NS", "XS"] + +# Initial discovery command +DISCOVERY_COMMAND = Aa55ProtocolCommand("010200", "0182") + +# supported inverter protocols +_SUPPORTED_PROTOCOLS = [ET, DT, ES] + + +async def connect(host: str, family: str = None, comm_addr: int = 0, timeout: int = 1, retries: int = 3, + do_discover: bool = True) -> Inverter: + """Contact the inverter at the specified host/port and answer appropriate Inverter instance. + + The specific inverter family/type will be detected automatically, but it can be passed explicitly. + Supported inverter family names are ET, EH, BT, BH, ES, EM, BP, DT, MS, D-NS and XS. + + Inverter communication address may be explicitly passed, if not the usual default value + will be used (0xf7 for ET/EH/BT/BH/ES/EM/BP inverters, 0x7f for DT/MS/D-NS/XS inverters). + + Since the UDP communication is by definition unreliable, when no (valid) response is received by the specified + timeout, it is considered lost and the command will be re-tried up to retries times. + + Raise InverterError if unable to contact or recognise supported inverter. + """ + if family in ET_FAMILY: + inv = ET(host, comm_addr, timeout, retries) + elif family in ES_FAMILY: + inv = ES(host, comm_addr, timeout, retries) + elif family in DT_FAMILY: + inv = DT(host, comm_addr, timeout, retries) + elif do_discover: + return await discover(host, timeout, retries) + + logger.debug("Connecting to %s family inverter at %s.", family, host) + await inv.read_device_info() + logger.debug("Connected to inverter %s, S/N:%s.", inv.model_name, inv.serial_number) + return inv + + +async def discover(host: str, timeout: int = 1, retries: int = 3) -> Inverter: + """Contact the inverter at the specified value and answer appropriate Inverter instance + + Raise InverterError if unable to contact or recognise supported inverter + """ + failures = [] + + # Try the common AA55C07F0102000241 command first and detect inverter type from serial_number + try: + logger.debug("Probing inverter at %s.", host) + response = await DISCOVERY_COMMAND.execute(host, timeout, retries) + model_name = response[12:22].decode("ascii").rstrip() + serial_number = response[38:54].decode("ascii") + + inverter_class: Type[Inverter] | None = None + for model_tag in ET_MODEL_TAGS: + if model_tag in serial_number: + logger.debug("Detected ET/EH/BT/BH/GEH inverter %s, S/N:%s.", model_name, serial_number) + inverter_class = ET + for model_tag in ES_MODEL_TAGS: + if model_tag in serial_number: + logger.debug("Detected ES/EM/BP inverter %s, S/N:%s.", model_name, serial_number) + inverter_class = ES + for model_tag in DT_MODEL_TAGS: + if model_tag in serial_number: + logger.debug("Detected DT/MS/D-NS/XS/GEP inverter %s, S/N:%s.", model_name, serial_number) + inverter_class = DT + if inverter_class: + i = inverter_class(host, 0, timeout, retries) + await i.read_device_info() + return i + + except InverterError as ex: + failures.append(ex) + + # Probe inverter specific protocols + for inv in _SUPPORTED_PROTOCOLS: + i = inv(host, 0, timeout, retries) + try: + logger.debug("Probing %s inverter at %s.", inv.__name__, host) + await i.read_device_info() + await i.read_runtime_data() + logger.debug("Detected %s family inverter %s, S/N:%s.", inv.__name__, i.model_name, i.serial_number) + return i + except InverterError as ex: + failures.append(ex) + raise InverterError( + "Unable to connect to the inverter at " + f"host={host}, or your inverter is not supported yet.\n" + f"Failures={str(failures)}" + ) + + +async def search_inverters() -> bytes: + """Scan the network for inverters. + Answer the inverter discovery response string (which includes it IP address) + + Raise InverterError if unable to contact any inverter + """ + logger.debug("Searching inverters by broadcast to port 48899") + loop = asyncio.get_running_loop() + command = ProtocolCommand("WIFIKIT-214028-READ".encode("utf-8"), lambda r: True) + response_future = loop.create_future() + transport, _ = await loop.create_datagram_endpoint( + lambda: UdpInverterProtocol(response_future, command, 1, 3), + remote_addr=("255.255.255.255", 48899), + allow_broadcast=True, + ) + try: + await response_future + result = response_future.result() + if result is not None: + return result + else: + raise InverterError("No response received to broadcast request.") + except asyncio.CancelledError: + raise InverterError("No valid response received to broadcast request.") from None + finally: + transport.close() diff --git a/goodwe/const.py b/goodwe/const.py new file mode 100644 index 0000000..9013ce2 --- /dev/null +++ b/goodwe/const.py @@ -0,0 +1,265 @@ +from typing import Dict + +GOODWE_UDP_PORT = 8899 + +BATTERY_MODES: Dict[int, str] = { + 0: "No battery", + 1: "Standby", + 2: "Discharge", + 3: "Charge", + 4: "To be charged", + 5: "To be discharged", +} + +ENERGY_MODES: Dict[int, str] = { + 0: "Check Mode", + 1: "Wait Mode", + 2: "Normal (On-Grid)", + 4: "Normal (Off-Grid)", + 8: "Flash Mode", + 16: "Fault Mode", + 32: "Battery Standby", + 64: "Battery Charging", + 128: "Battery Discharging", +} + +GRID_MODES: Dict[int, str] = { + 0: "Not connected to grid", + 1: "Connected to grid", + 2: "Fault", +} + +GRID_IN_OUT_MODES: Dict[int, str] = { + 0: "Idle", + 1: "Exporting", + 2: "Importing", +} + +LOAD_MODES: Dict[int, str] = { + 0: "Inverter and the load is disconnected", + 1: "The inverter is connected to a load", +} + +PV_MODES: Dict[int, str] = { + 0: "PV panels not connected", + 1: "PV panels connected, no power", + 2: "PV panels connected, producing power", +} + +WORK_MODES: Dict[int, str] = { + 0: "Wait Mode", + 1: "Normal", + 2: "Error", + 4: "Check Mode", +} + +WORK_MODES_ET: Dict[int, str] = { + 0: "Wait Mode", + 1: "Normal (On-Grid)", + 2: "Normal (Off-Grid)", + 3: "Fault Mode", + 4: "Flash Mode", + 5: "Check Mode", +} + +WORK_MODES_ES: Dict[int, str] = { + 0: "Inverter Off - Standby", + 1: "Inverter On", + 2: "Inverter Abnormal, stopping power", + 3: "Inverter Severly Abnormal, 20 seconds to restart", +} + +SAFETY_COUNTRIES: Dict[int, str] = { + 0: "Italy", + 1: "Czechia", + 2: "Germany", + 3: "Spain", + 4: "Greece Mainland", + 5: "Denmark", + 6: "Belgium", + 7: "Romania", + 8: "G98", + 9: "Australia", + 10: "France", + 11: "China", + 12: "60Hz Grid Default", + 13: "Poland", + 14: "South Africa", + 15: "AustraliaL", + 16: "Brazil", + 17: "Thailand MEA", + 18: "Thailand PEA", + 19: "Mauritius", + 20: "Holland", + 21: "G99", + 22: "China Special", + 23: "French 50Hz", + 24: "French 60Hz", + 25: "Australia Ergon", + 26: "Australia Energex", + 27: "Holland 16/20A", + 28: "Korea", + 29: "China Station", + 30: "Austria", + 31: "India", + 32: "50Hz Grid Default", + 33: "Warehouse", + 34: "Philippines", + 35: "Ireland", + 36: "Taiwan", + 37: "Bulgaria", + 38: "Barbados", + 39: "China Special High", + 40: "G99", + 41: "Sweden", + 42: "Chile", + 43: "Brazil LV", + 44: "NewZealand", + 45: "IEEE1547 208VAC", + 46: "IEEE1547 220VAC", + 47: "IEEE1547 240VAC", + 48: "60Hz LV Default", + 49: "50Hz LV Default", + 50: "AU_WAPN", + 51: "AU_MicroGrid", + 52: "JP_50Hz", + 53: "JP_60Hz", + 54: "India Higher", + 55: "DEWA LV", + 56: "DEWA MV", + 57: "Slovakia", + 58: "GreenGrid", + 59: "Hungary", + 60: "Sri Lanka", + 61: "Spain Islands", + 62: "Ergon30K", + 63: "Energex30K", + 64: "IEEE1547 230/400V", + 65: "IEC61727 60Hz", + 66: "Switzerland", + 67: "CEI-016", + 68: "AU_Horizon", + 69: "Cyprus", + 70: "AU_SAPN", + 71: "AU_Ausgrid", + 72: "AU_Essential", + 73: "AU_Pwcore&CitiPW", + 74: "Hong Kong", + 75: "Poland MV", + 76: "Holland MV", + 77: "Sweden MV", + 78: "VDE4110", + 96: "cUSA_208VacDefault", + 97: "cUSA_240VacDefault", + 98: "cUSA_208VacCA_SCE", + 99: "cUSA_240VacCA_SCE", + 100: "cUSA_208VacCA_SDGE", + 101: "cUSA_240VacCA_SDGE", + 102: "cUSA_208VacCA_PGE", + 103: "cUSA_240VacCA_PGE", + 104: "cUSA_208VacHECO_14HO", + 105: "cUSA_240VacHECO_14HO0x69", + 106: "cUSA_208VacHECO_14HM", + 107: "cUSA_240VacHECO_14HM", +} + +ERROR_CODES: Dict[int, str] = { + 31: 'Internal Communication Failure', + 30: 'EEPROM R/W Failure', + 29: 'Fac Failure', + 28: 'DSP communication failure', + 27: 'PhaseAngleFailure', + 26: '', + 25: 'Relay Check Failure', + 24: '', + 23: 'Vac Consistency Failure', + 22: 'Fac Consistency Failure', + 21: '', + 20: 'Back-Up Over Load', + 19: 'DC Injection High', + 18: 'Isolation Failure', + 17: 'Vac Failure', + 16: 'External Fan Failure', + 15: 'PV Over Voltage', + 14: 'Utility Phase Failure', + 13: 'Over Temperature', + 12: 'InternalFan Failure', + 11: 'DC Bus High', + 10: 'Ground I Failure', + 9: 'Utility Loss', + 8: 'AC HCT Failure', + 7: 'Relay Device Failure', + 6: 'GFCI Device Failure', + 5: '', + 4: 'GFCI Consistency Failure', + 3: 'DCI Consistency Failure', + 2: '', + 1: 'AC HCT Check Failure', + 0: 'GFCI Device Check Failure', +} + +DIAG_STATUS_CODES: Dict[int, str] = { + 0: "Battery voltage low", + 1: "Battery SOC low", + 2: "Battery SOC in back", + 3: "BMS: Discharge disabled", + 4: "Discharge time on", + 5: "Charge time on", + 6: "Discharge Driver On", + 7: "BMS: Discharge current low", + 8: "APP: Discharge current too low", + 9: "Meter communication failure", + 10: "Meter connection reversed", + 11: "Self-use load light", + 12: "EMS: discharge current is zero", + 13: "Discharge BUS high PV voltage", + 14: "Battery Disconnected", + 15: "Battery Overcharged", + 16: "BMS: Temperature too high", + 17: "BMS: Charge too high", + 18: "BMS: Charge disabled", + 19: "Self-use off", + 20: "SOC delta too volatile", + 21: "Battery self discharge too high", + 22: "Battery SOC low (off-grid)", + 23: "Grid wave unstable", + 24: "Export power limit set", + 25: "PF value set", + 26: "Real power limit set", + 27: "DC output on", + 28: "SOC protect off", +} + +BMS_ALARM_CODES: Dict[int, str] = { + 15: 'Charging over-voltage 3', + 14: 'Discharging under-voltage 3', + 13: 'Cell temperature high 3', + 12: 'Communication failure 2', + 11: 'Charging circuit failure', + 10: 'Discharging circuit failure', + 9: 'Battery lock', + 8: 'Battery break', + 7: 'DC bus fault', + 6: 'Precharge fault', + 5: 'Discharging over-current 2', + 4: 'Charging over-current 2', + 3: 'Cell temperature low 2', + 2: 'Cell temperature high 2', + 1: 'Discharging under-voltage 2', + 0: 'Charging over-voltage 2', +} + +BMS_WARNING_CODES: Dict[int, str] = { + 11: 'System temperature high', + 10: 'System temperature low 2', + 9: 'System temperature low 1', + 8: 'Cell imbalance', + 7: 'System reboot', + 6: 'Communication failure 1', + 5: 'Discharging over-current 1', + 4: 'Charging over-current 1', + 3: 'Cell temperature low 1', + 2: 'Cell temperature high 1', + 1: 'Discharging under-voltage 1', + 0: 'Charging over-voltage 1', +} diff --git a/goodwe/dt.py b/goodwe/dt.py new file mode 100644 index 0000000..0537887 --- /dev/null +++ b/goodwe/dt.py @@ -0,0 +1,235 @@ +from __future__ import annotations + +from typing import Tuple + +from .exceptions import InverterError +from .inverter import Inverter +from .inverter import OperationMode +from .inverter import SensorKind as Kind +from .model import is_3_mptt, is_single_phase +from .protocol import ProtocolCommand, ModbusReadCommand, ModbusWriteCommand, ModbusWriteMultiCommand +from .sensor import * + + +class DT(Inverter): + """Class representing inverter of DT/MS/D-NS/XS or GE's GEP(PSB/PSC) families""" + + __all_sensors: Tuple[Sensor, ...] = ( + Timestamp("timestamp", 0, "Timestamp"), + Voltage("vpv1", 6, "PV1 Voltage", Kind.PV), + Current("ipv1", 8, "PV1 Current", Kind.PV), + Calculated("ppv1", + lambda data: round(read_voltage(data, 6) * read_current(data, 8)), + "PV1 Power", "W", Kind.PV), + Voltage("vpv2", 10, "PV2 Voltage", Kind.PV), + Current("ipv2", 12, "PV2 Current", Kind.PV), + Calculated("ppv2", + lambda data: round(read_voltage(data, 10) * read_current(data, 12)), + "PV2 Power", "W", Kind.PV), + Voltage("vpv3", 14, "PV3 Voltage", Kind.PV), + Current("ipv3", 16, "PV3 Current", Kind.PV), + Calculated("ppv3", + lambda data: round(read_voltage(data, 14) * read_current(data, 16)), + "PV3 Power", "W", Kind.PV), + # Voltage("vpv4", 14, "PV4 Voltage", Kind.PV), + # Current("ipv4", 16, "PV4 Current", Kind.PV), + # Voltage("vpv5", 14, "PV5 Voltage", Kind.PV), + # Current("ipv5", 16, "PV5 Current", Kind.PV), + # Voltage("vpv6", 14, "PV6 Voltage", Kind.PV), + # Current("ipv6", 16, "PV7 Current", Kind.PV), + Voltage("vline1", 30, "On-grid L1-L2 Voltage", Kind.AC), + Voltage("vline2", 32, "On-grid L2-L3 Voltage", Kind.AC), + Voltage("vline3", 34, "On-grid L3-L1 Voltage", Kind.AC), + Voltage("vgrid1", 36, "On-grid L1 Voltage", Kind.AC), + Voltage("vgrid2", 38, "On-grid L2 Voltage", Kind.AC), + Voltage("vgrid3", 40, "On-grid L3 Voltage", Kind.AC), + Current("igrid1", 42, "On-grid L1 Current", Kind.AC), + Current("igrid2", 44, "On-grid L2 Current", Kind.AC), + Current("igrid3", 46, "On-grid L3 Current", Kind.AC), + Frequency("fgrid1", 48, "On-grid L1 Frequency", Kind.AC), + Frequency("fgrid2", 50, "On-grid L2 Frequency", Kind.AC), + Frequency("fgrid3", 52, "On-grid L3 Frequency", Kind.AC), + Calculated("pgrid1", + lambda data: round(read_voltage(data, 36) * read_current(data, 42)), + "On-grid L1 Power", "W", Kind.AC), + Calculated("pgrid2", + lambda data: round(read_voltage(data, 38) * read_current(data, 44)), + "On-grid L2 Power", "W", Kind.AC), + Calculated("pgrid3", + lambda data: round(read_voltage(data, 40) * read_current(data, 46)), + "On-grid L3 Power", "W", Kind.AC), + Integer("xx54", 54, "Unknown sensor@54"), + Power("ppv", 56, "PV Power", Kind.PV), + Integer("work_mode", 58, "Work Mode code"), + Enum2("work_mode_label", 58, WORK_MODES, "Work Mode"), + Long("error_codes", 60, "Error Codes"), + Integer("warning_code", 64, "Warning code"), + Integer("xx66", 66, "Unknown sensor@66"), + Integer("xx68", 68, "Unknown sensor@68"), + Integer("xx70", 70, "Unknown sensor@70"), + Integer("xx72", 72, "Unknown sensor@72"), + Integer("xx74", 74, "Unknown sensor@74"), + Integer("xx76", 76, "Unknown sensor@76"), + Integer("xx78", 78, "Unknown sensor@78"), + Integer("xx80", 80, "Unknown sensor@80"), + Temp("temperature", 82, "Inverter Temperature", Kind.AC), + Integer("xx84", 84, "Unknown sensor@84"), + Integer("xx86", 86, "Unknown sensor@86"), + Energy("e_day", 88, "Today's PV Generation", Kind.PV), + Energy4("e_total", 90, "Total PV Generation", Kind.PV), + Long("h_total", 94, "Hours Total", "h", Kind.PV), + Integer("safety_country", 98, "Safety Country code", "", Kind.AC), + Enum2("safety_country_label", 98, SAFETY_COUNTRIES, "Safety Country", Kind.AC), + Integer("xx100", 100, "Unknown sensor@100"), + Integer("xx102", 102, "Unknown sensor@102"), + Integer("xx104", 104, "Unknown sensor@104"), + Integer("xx106", 106, "Unknown sensor@106"), + Integer("xx108", 108, "Unknown sensor@108"), + Integer("xx110", 110, "Unknown sensor@110"), + Integer("xx112", 112, "Unknown sensor@112"), + Integer("xx114", 114, "Unknown sensor@114"), + Integer("xx116", 116, "Unknown sensor@116"), + Integer("xx118", 118, "Unknown sensor@118"), + Integer("xx120", 120, "Unknown sensor@120"), + Integer("xx122", 122, "Unknown sensor@122"), + Integer("funbit", 124, "FunBit", "", Kind.PV), + Voltage("vbus", 126, "Bus Voltage", Kind.PV), + Voltage("vnbus", 128, "NBus Voltage", Kind.PV), + Integer("xx130", 130, "Unknown sensor@130"), + Integer("xx132", 132, "Unknown sensor@132"), + Integer("xx134", 134, "Unknown sensor@134"), + Integer("xx136", 136, "Unknown sensor@136"), + Integer("xx138", 138, "Unknown sensor@138"), + Integer("xx140", 140, "Unknown sensor@140"), + Integer("xx142", 142, "Unknown sensor@142"), + Integer("xx144", 144, "Unknown sensor@144"), + ) + + # Modbus registers of inverter settings, offsets are modbus register addresses + __all_settings: Tuple[Sensor, ...] = ( + Timestamp("time", 40313, "Inverter time"), + + Integer("shadow_scan", 40326, "Shadow Scan", "", Kind.PV), + Integer("grid_export", 40327, "Grid Export Enabled", "", Kind.GRID), + Integer("grid_export_limit", 40328, "Grid Export Limit", "%", Kind.GRID), + ) + + # Settings for single phase inverters + __settings_single_phase: Tuple[Sensor, ...] = ( + Long("grid_export_limit", 40328, "Grid Export Limit", "W", Kind.GRID), + ) + + # Settings for three phase inverters + __settings_three_phase: Tuple[Sensor, ...] = ( + Integer("grid_export_limit", 40336, "Grid Export Limit", "%", Kind.GRID), + ) + + def __init__(self, host: str, comm_addr: int = 0, timeout: int = 1, retries: int = 3): + super().__init__(host, comm_addr, timeout, retries) + if not self.comm_addr: + # Set the default inverter address + self.comm_addr = 0x7f + self._READ_DEVICE_VERSION_INFO: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x7531, 0x0028) + self._READ_DEVICE_RUNNING_DATA: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x7594, 0x0049) + self._sensors = self.__all_sensors + self._settings: dict[str, Sensor] = {s.id_: s for s in self.__all_settings} + + @staticmethod + def _single_phase_only(s: Sensor) -> bool: + """Filter to exclude phase2/3 sensors on single phase inverters""" + return not ((s.id_.endswith('2') or s.id_.endswith('3')) and 'pv' not in s.id_ and not s.id_.startswith('xx')) + + @staticmethod + def _pv1_pv2_only(s: Sensor) -> bool: + """Filter to exclude sensors on < 3 PV inverters""" + return not s.id_.endswith('pv3') + + async def read_device_info(self): + response = await self._read_from_socket(self._READ_DEVICE_VERSION_INFO) + response = response[5:-2] + try: + self.model_name = response[22:32].decode("ascii").rstrip() + except: + print("No model name sent from the inverter.") + self.serial_number = response[6:22].decode("ascii") + self.dsp1_version = read_unsigned_int(response, 66) + self.dsp2_version = read_unsigned_int(response, 68) + self.arm_version = read_unsigned_int(response, 70) + self.firmware = "{}.{}.{:02x}".format(self.dsp1_version, self.dsp2_version, self.arm_version) + + if is_single_phase(self): + # this is single phase inverter, filter out all L2 and L3 sensors + self._sensors = tuple(filter(self._single_phase_only, self.__all_sensors)) + self._settings.update({s.id_: s for s in self.__settings_single_phase}) + else: + self._settings.update({s.id_: s for s in self.__settings_three_phase}) + + if is_3_mptt(self): + # this is 3 PV strings inverter, keep all sensors + pass + else: + # this is only 2 PV strings inverter + self._sensors = tuple(filter(self._pv1_pv2_only, self._sensors)) + pass + + async def read_runtime_data(self, include_unknown_sensors: bool = False) -> Dict[str, Any]: + raw_data = await self._read_from_socket(self._READ_DEVICE_RUNNING_DATA) + data = self._map_response(raw_data[5:-2], self._sensors, include_unknown_sensors) + return data + + async def read_setting(self, setting_id: str) -> Any: + setting = self._settings.get(setting_id) + if not setting: + raise ValueError(f'Unknown setting "{setting_id}"') + count = (setting.size_ + (setting.size_ % 2)) // 2 + raw_data = await self._read_from_socket(ModbusReadCommand(self.comm_addr, setting.offset, count)) + with io.BytesIO(raw_data[5:-2]) as buffer: + return setting.read_value(buffer) + + async def write_setting(self, setting_id: str, value: Any): + setting = self._settings.get(setting_id) + if not setting: + raise ValueError(f'Unknown setting "{setting_id}"') + raw_value = setting.encode_value(value) + if len(raw_value) <= 2: + value = int.from_bytes(raw_value, byteorder="big", signed=True) + await self._read_from_socket(ModbusWriteCommand(self.comm_addr, setting.offset, value)) + else: + await self._read_from_socket(ModbusWriteMultiCommand(self.comm_addr, setting.offset, raw_value)) + + async def read_settings_data(self) -> Dict[str, Any]: + data = {} + for setting in self.settings(): + value = await self.read_setting(setting.id_) + data[setting.id_] = value + return data + + async def get_grid_export_limit(self) -> int: + return await self.read_setting('grid_export_limit') + + async def set_grid_export_limit(self, export_limit: int) -> None: + setting = self._settings.get('grid_export_limit') + if (setting.unit == "%" and 0 <= export_limit <= 100) or (setting.unit != "%" and 0 <= export_limit <= 10000): + return await self.write_setting('grid_export_limit', export_limit) + + async def get_operation_modes(self, include_emulated: bool) -> Tuple[OperationMode, ...]: + return () + + async def get_operation_mode(self) -> OperationMode: + raise InverterError("Operation not supported.") + + async def set_operation_mode(self, operation_mode: OperationMode, eco_mode_power: int = 100, + eco_mode_soc: int = 100) -> None: + raise InverterError("Operation not supported.") + + async def get_ongrid_battery_dod(self) -> int: + raise InverterError("Operation not supported, inverter has no batteries.") + + async def set_ongrid_battery_dod(self, dod: int) -> None: + raise InverterError("Operation not supported, inverter has no batteries.") + + def sensors(self) -> Tuple[Sensor, ...]: + return self._sensors + + def settings(self) -> Tuple[Sensor, ...]: + return tuple(self._settings.values()) diff --git a/goodwe/es.py b/goodwe/es.py new file mode 100644 index 0000000..de4156d --- /dev/null +++ b/goodwe/es.py @@ -0,0 +1,440 @@ +from __future__ import annotations + +import logging +from typing import Tuple, cast + +from .exceptions import InverterError +from .inverter import Inverter +from .inverter import OperationMode +from .inverter import SensorKind as Kind +from .protocol import ProtocolCommand, Aa55ProtocolCommand, Aa55ReadCommand, Aa55WriteCommand, Aa55WriteMultiCommand, \ + ModbusReadCommand, ModbusWriteCommand, ModbusWriteMultiCommand +from .sensor import * + +logger = logging.getLogger(__name__) + + +class ES(Inverter): + """Class representing inverter of ES/EM/BP family""" + + _READ_DEVICE_VERSION_INFO: ProtocolCommand = Aa55ProtocolCommand("010200", "0182") + _READ_DEVICE_RUNNING_DATA: ProtocolCommand = Aa55ProtocolCommand("010600", "0186") + _READ_DEVICE_SETTINGS_DATA: ProtocolCommand = Aa55ProtocolCommand("010900", "0189") + + __sensors: Tuple[Sensor, ...] = ( + Voltage("vpv1", 0, "PV1 Voltage", Kind.PV), # modbus 0x500 + Current("ipv1", 2, "PV1 Current", Kind.PV), + Calculated("ppv1", + lambda data: round(read_voltage(data, 0) * read_current(data, 2)), + "PV1 Power", "W", Kind.PV), + Byte("pv1_mode", 4, "PV1 Mode code", "", Kind.PV), + Enum("pv1_mode_label", 4, PV_MODES, "PV1 Mode", Kind.PV), + Voltage("vpv2", 5, "PV2 Voltage", Kind.PV), + Current("ipv2", 7, "PV2 Current", Kind.PV), + Calculated("ppv2", + lambda data: round(read_voltage(data, 5) * read_current(data, 7)), + "PV2 Power", "W", Kind.PV), + Byte("pv2_mode", 9, "PV2 Mode code", "", Kind.PV), + Enum("pv2_mode_label", 9, PV_MODES, "PV2 Mode", Kind.PV), + Calculated("ppv", + lambda data: round(read_voltage(data, 0) * read_current(data, 2)) + round( + read_voltage(data, 5) * read_current(data, 7)), + "PV Power", "W", Kind.PV), + Voltage("vbattery1", 10, "Battery Voltage", Kind.BAT), # modbus 0x506 + # Voltage("vbattery2", 12, "Battery Voltage 2", Kind.BAT), + Integer("battery_status", 14, "Battery Status", "", Kind.BAT), + Temp("battery_temperature", 16, "Battery Temperature", Kind.BAT), + Calculated("ibattery1", + lambda data: abs(read_current(data, 18)) * (-1 if read_byte(data, 30) == 3 else 1), + "Battery Current", "A", Kind.BAT), + # round(vbattery1 * ibattery1), + Calculated("pbattery1", + lambda data: abs( + round(read_voltage(data, 10) * read_current(data, 18)) + ) * (-1 if read_byte(data, 30) == 3 else 1), + "Battery Power", "W", Kind.BAT), + Integer("battery_charge_limit", 20, "Battery Charge Limit", "A", Kind.BAT), + Integer("battery_discharge_limit", 22, "Battery Discharge Limit", "A", Kind.BAT), + Integer("battery_error", 24, "Battery Error Code", "", Kind.BAT), + Byte("battery_soc", 26, "Battery State of Charge", "%", Kind.BAT), # modbus 0x50E + # Byte("cbattery2", 27, "Battery State of Charge 2", "%", Kind.BAT), + # Byte("cbattery3", 28, "Battery State of Charge 3", "%", Kind.BAT), + Byte("battery_soh", 29, "Battery State of Health", "%", Kind.BAT), + Byte("battery_mode", 30, "Battery Mode code", "", Kind.BAT), + Enum("battery_mode_label", 30, BATTERY_MODES, "Battery Mode", Kind.BAT), + Integer("battery_warning", 31, "Battery Warning", "", Kind.BAT), + Byte("meter_status", 33, "Meter Status code", "", Kind.AC), + Voltage("vgrid", 34, "On-grid Voltage", Kind.AC), + Current("igrid", 36, "On-grid Current", Kind.AC), + Calculated("pgrid", + lambda data: abs(read_bytes2(data, 38)) * (-1 if read_byte(data, 80) == 2 else 1), + "On-grid Export Power", "W", Kind.AC), + Frequency("fgrid", 40, "On-grid Frequency", Kind.AC), + Byte("grid_mode", 42, "Work Mode code", "", Kind.GRID), + Enum("grid_mode_label", 42, WORK_MODES_ES, "Work Mode", Kind.GRID), + Voltage("vload", 43, "Back-up Voltage", Kind.UPS), # modbus 0x51b + Current("iload", 45, "Back-up Current", Kind.UPS), + Power("pload", 47, "On-grid Power", Kind.AC), + Frequency("fload", 49, "Back-up Frequency", Kind.UPS), + Byte("load_mode", 51, "Load Mode code", "", Kind.AC), + Enum("load_mode_label", 51, LOAD_MODES, "Load Mode", Kind.AC), + Byte("work_mode", 52, "Energy Mode code", "", Kind.AC), + Enum("work_mode_label", 52, ENERGY_MODES, "Energy Mode", Kind.AC), + Temp("temperature", 53, "Inverter Temperature"), + Long("error_codes", 55, "Error Codes"), + Energy4("e_total", 59, "Total PV Generation", Kind.PV), + Long("h_total", 63, "Hours Total", "h", Kind.PV), + Energy("e_day", 67, "Today's PV Generation", Kind.PV), + Energy("e_load_day", 69, "Today's Load", Kind.AC), + Energy4("e_load_total", 71, "Total Load", Kind.AC), + Power("total_power", 75, "Total Power", Kind.AC), # modbus 0x52c + Byte("effective_work_mode", 77, "Effective Work Mode code"), + Integer("effective_relay_control", 78, "Effective Relay Control", "", None), + Byte("grid_in_out", 80, "On-grid Mode code", "", Kind.GRID), + Enum("grid_in_out_label", 80, GRID_IN_OUT_MODES, "On-grid Mode", Kind.GRID), + Power("pback_up", 81, "Back-up Power", Kind.UPS), + # pload + pback_up + Calculated("plant_power", + lambda data: round(read_bytes2(data, 47) + read_bytes2(data, 81)), + "Plant Power", "W", Kind.AC), + Decimal("meter_power_factor", 83, 1000, "Meter Power Factor", "", Kind.GRID), # modbus 0x531 + Integer("xx85", 85, "Unknown sensor@85"), + Integer("xx87", 87, "Unknown sensor@87"), + Long("diagnose_result", 89, "Diag Status Code"), + EnumBitmap4("diagnose_result_label", 89, DIAG_STATUS_CODES, "Diag Status"), + # Energy4("e_total_exp", 93, "Total Energy (export)", Kind.GRID), + # Energy4("e_total_imp", 97, "Total Energy (import)", Kind.GRID), + # Voltage("vpv3", 101, "PV3 Voltage", Kind.PV), # modbus 0x500 + # Current("ipv3", 103, "PV3 Current", Kind.PV), + # Byte("pv3_mode", 104, "PV1 Mode", "", Kind.PV), + # Voltage("vgrid_uo", 105, "On-grid Uo Voltage", Kind.AC), + # Current("igrid_uo", 107, "On-grid Uo Current", Kind.AC), + # Voltage("vgrid_wo", 109, "On-grid Wo Voltage", Kind.AC), + # Current("igrid_wo", 111, "On-grid Wo Current", Kind.AC), + # Energy4("e_bat_charge_total", 113, "Total Battery Charge", Kind.BAT), + # Energy4("e_bat_discharge_total", 117, "Total Battery Discharge", Kind.BAT), + + # ppv1 + ppv2 + pbattery - pgrid + Calculated("house_consumption", + lambda data: + round(read_voltage(data, 0) * read_current(data, 2)) + + round(read_voltage(data, 5) * read_current(data, 7)) + + (abs(round(read_voltage(data, 10) * read_current(data, 18))) * + (-1 if read_byte(data, 30) == 3 else 1)) - + (abs(read_bytes2(data, 38)) * (-1 if read_byte(data, 80) == 2 else 1)), + "House Consumption", "W", Kind.AC), + ) + + __all_settings: Tuple[Sensor, ...] = ( + Integer("backup_supply", 12, "Backup Supply"), + Integer("off-grid_charge", 14, "Off-grid Charge"), + Integer("shadow_scan", 16, "Shadow Scan", "", Kind.PV), + Integer("grid_export", 18, "Grid Export Enabled", "", Kind.GRID), + Integer("capacity", 22, "Capacity"), + Integer("charge_v", 24, "Charge Voltage", "V"), + Integer("charge_i", 26, "Charge Current", "A", ), + Integer("discharge_i", 28, "Discharge Current", "A", ), + Integer("discharge_v", 30, "Discharge Voltage", "V"), + Calculated("dod", lambda data: 100 - read_bytes2(data, 32), "Depth of Discharge", "%"), + Integer("battery_activated", 34, "Battery Activated"), + Integer("bp_off_grid_charge", 36, "BP Off-grid Charge"), + Integer("bp_pv_discharge", 38, "BP PV Discharge"), + Integer("bp_bms_protocol", 40, "BP BMS Protocol"), + Integer("power_factor", 42, "Power Factor"), + Integer("grid_export_limit", 52, "Grid Export Limit", "W", Kind.GRID), + Integer("battery_soc_protection", 56, "Battery SoC Protection", "", Kind.BAT), + Integer("work_mode", 66, "Work Mode"), + Integer("grid_quality_check", 68, "Grid Quality Check"), + + EcoModeV1("eco_mode_1", 1793, "Eco Mode Group 1"), # 0x701 + ByteH("eco_mode_1_switch", 1796, "Eco Mode Group 1 Switch", "", Kind.BAT), + EcoModeV1("eco_mode_2", 1797, "Eco Mode Group 2"), + ByteH("eco_mode_2_switch", 1800, "Eco Mode Group 2 Switch", "", Kind.BAT), + EcoModeV1("eco_mode_3", 1801, "Eco Mode Group 3"), + ByteH("eco_mode_3_switch", 1804, "Eco Mode Group 3 Switch", "", Kind.BAT), + EcoModeV1("eco_mode_4", 1805, "Eco Mode Group 4"), + ByteH("eco_mode_4_switch", 1808, "Eco Mode Group 4 Switch", "", Kind.BAT), + ) + + # Settings added in ARM firmware 14 + __settings_arm_fw_14: Tuple[Sensor, ...] = ( + EcoModeV2("eco_mode_1", 47547, "Eco Mode Group 1"), + ByteH("eco_mode_1_switch", 47549, "Eco Mode Group 1 Switch"), + EcoModeV2("eco_mode_2", 47553, "Eco Mode Group 2"), + ByteH("eco_mode_2_switch", 47555, "Eco Mode Group 2 Switch"), + EcoModeV2("eco_mode_3", 47559, "Eco Mode Group 3"), + ByteH("eco_mode_3_switch", 47561, "Eco Mode Group 3 Switch"), + EcoModeV2("eco_mode_4", 47565, "Eco Mode Group 4"), + ByteH("eco_mode_4_switch", 47567, "Eco Mode Group 4 Switch"), + ) + + def __init__(self, host: str, comm_addr: int = 0, timeout: int = 1, retries: int = 3): + super().__init__(host, comm_addr, timeout, retries) + if not self.comm_addr: + # Set the default inverter address + self.comm_addr = 0xf7 + self._settings: dict[str, Sensor] = {s.id_: s for s in self.__all_settings} + + def _supports_eco_mode_v2(self) -> bool: + if self.arm_version < 14: + return False + if "EMU" in self.serial_number: + return self.dsp1_version >= 11 + if "ESU" in self.serial_number: + return self.dsp1_version >= 22 + if "BPS" in self.serial_number: + return self.dsp1_version >= 10 + return False + + async def read_device_info(self): + response = await self._read_from_socket(self._READ_DEVICE_VERSION_INFO) + self.firmware = self._decode(response[7:12]).rstrip() + self.model_name = self._decode(response[12:22]).rstrip() + self.serial_number = response[38:54].decode("ascii") + self.software_version = self._decode(response[58:70]) + try: + if len(self.firmware) >= 2: + self.dsp1_version = int(self.firmware[0:2]) + if len(self.firmware) >= 4: + self.dsp2_version = int(self.firmware[2:4]) + if len(self.firmware) >= 5: + self.arm_version = int(self.firmware[4], base=36) + except ValueError: + logger.exception("Error decoding firmware version %s.", self.firmware) + + if self._supports_eco_mode_v2(): + self._settings.update({s.id_: s for s in self.__settings_arm_fw_14}) + + async def read_runtime_data(self, include_unknown_sensors: bool = False) -> Dict[str, Any]: + raw_data = await self._read_from_socket(self._READ_DEVICE_RUNNING_DATA) + data = self._map_response(raw_data[7:-2], self.__sensors, include_unknown_sensors) + return data + + async def read_setting(self, setting_id: str) -> Any: + if setting_id == 'time': + # Fake setting, just to enable write_setting to work (if checked as pair in read as in HA) + # There does not seem to be time setting/sensor available (or is not known) + return datetime.now() + elif setting_id in ('eco_mode_1', 'eco_mode_2', 'eco_mode_3', 'eco_mode_4'): + setting: Sensor | None = self._settings.get(setting_id) + if not setting: + raise ValueError(f'Unknown setting "{setting_id}"') + count = (setting.size_ + (setting.size_ % 2)) // 2 + if self._is_modbus_setting(setting): + raw_data = await self._read_from_socket(ModbusReadCommand(self.comm_addr, setting.offset, count)) + with io.BytesIO(raw_data[5:-2]) as buffer: + return setting.read_value(buffer) + else: + raw_data = await self._read_from_socket(Aa55ReadCommand(setting.offset, count)) + with io.BytesIO(raw_data[7:-2]) as buffer: + return setting.read_value(buffer) + else: + all_settings = await self.read_settings_data() + return all_settings.get(setting_id) + + async def write_setting(self, setting_id: str, value: Any): + if setting_id == 'time': + await self._read_from_socket( + Aa55ProtocolCommand("030206" + Timestamp("time", 0, "").encode_value(value).hex(), "0382") + ) + else: + setting: Sensor | None = self._settings.get(setting_id) + if not setting: + raise ValueError(f'Unknown setting "{setting_id}"') + if setting.size_ == 1: + # modbus can address/store only 16 bit values, read the other 8 bytes + if self._is_modbus_setting(setting): + register_data = await self._read_from_socket(ModbusReadCommand(self.comm_addr, setting.offset, 1)) + raw_value = setting.encode_value(value, register_data[5:7]) + else: + register_data = await self._read_from_socket(Aa55ReadCommand(self.comm_addr, setting.offset, 1)) + raw_value = setting.encode_value(value, register_data[7:9]) + else: + raw_value = setting.encode_value(value) + if len(raw_value) <= 2: + value = int.from_bytes(raw_value, byteorder="big", signed=True) + if self._is_modbus_setting(setting): + await self._read_from_socket(ModbusWriteCommand(self.comm_addr, setting.offset, value)) + else: + await self._read_from_socket(Aa55WriteCommand(setting.offset, value)) + else: + if self._is_modbus_setting(setting): + await self._read_from_socket(ModbusWriteMultiCommand(self.comm_addr, setting.offset, raw_value)) + else: + await self._read_from_socket(Aa55WriteMultiCommand(setting.offset, raw_value)) + + async def read_settings_data(self) -> Dict[str, Any]: + raw_data = await self._read_from_socket(self._READ_DEVICE_SETTINGS_DATA) + data = self._map_response(raw_data[7:-2], self.settings()) + return data + + async def get_grid_export_limit(self) -> int: + return await self.read_setting('grid_export_limit') + + async def set_grid_export_limit(self, export_limit: int) -> None: + if 0 <= export_limit <= 10000: + await self._read_from_socket( + Aa55ProtocolCommand("033502" + "{:04x}".format(export_limit), "03b5") + ) + + async def get_operation_modes(self, include_emulated: bool) -> Tuple[OperationMode, ...]: + result = [e for e in OperationMode] + result.remove(OperationMode.PEAK_SHAVING) + if not include_emulated: + result.remove(OperationMode.ECO_CHARGE) + result.remove(OperationMode.ECO_DISCHARGE) + return tuple(result) + + async def get_operation_mode(self) -> OperationMode: + mode = OperationMode(await self.read_setting('work_mode')) + if OperationMode.ECO != mode: + return mode + ecomode = await self.read_setting('eco_mode_1') + if ecomode.is_eco_charge_mode(): + return OperationMode.ECO_CHARGE + elif ecomode.is_eco_discharge_mode(): + return OperationMode.ECO_DISCHARGE + else: + return OperationMode.ECO + + async def set_operation_mode(self, operation_mode: OperationMode, eco_mode_power: int = 100, + eco_mode_soc: int = 100) -> None: + if operation_mode == OperationMode.GENERAL: + await self._set_general_mode() + elif operation_mode == OperationMode.OFF_GRID: + await self._set_offgrid_mode() + elif operation_mode == OperationMode.BACKUP: + await self._set_backup_mode() + elif operation_mode == OperationMode.ECO: + await self._set_eco_mode() + elif operation_mode == OperationMode.PEAK_SHAVING: + raise InverterError("Operation not supported.") + elif operation_mode in (OperationMode.ECO_CHARGE, OperationMode.ECO_DISCHARGE): + if eco_mode_power < 0 or eco_mode_power > 100: + raise ValueError() + if eco_mode_soc < 0 or eco_mode_soc > 100: + raise ValueError() + eco_mode: EcoMode = self._convert_eco_mode(EcoModeV2("", 0, "")) + if operation_mode == OperationMode.ECO_CHARGE: + await self.write_setting('eco_mode_1', eco_mode.encode_charge(eco_mode_power, eco_mode_soc)) + else: + await self.write_setting('eco_mode_1', eco_mode.encode_discharge(eco_mode_power)) + await self.write_setting('eco_mode_2_switch', 0) + await self.write_setting('eco_mode_3_switch', 0) + await self.write_setting('eco_mode_4_switch', 0) + await self._set_eco_mode() + + async def get_ongrid_battery_dod(self) -> int: + return await self.read_setting('dod') + + async def set_ongrid_battery_dod(self, dod: int) -> None: + if 0 <= dod <= 89: + await self._read_from_socket(Aa55WriteCommand(0x560, 100 - dod)) + + async def _reset_inverter(self) -> None: + await self._read_from_socket(Aa55ProtocolCommand("031d00", "039d")) + + def sensors(self) -> Tuple[Sensor, ...]: + return self.__sensors + + def settings(self) -> Tuple[Sensor, ...]: + return tuple(self._settings.values()) + + async def _set_general_mode(self) -> None: + if self.arm_version >= 7: + if self._supports_eco_mode_v2(): + await self._clear_battery_mode_param() + else: + await self._set_limit_power_for_charge(0, 0, 0, 0, 0) + await self._set_limit_power_for_discharge(0, 0, 0, 0, 0) + await self._clear_battery_mode_param() + else: + await self._set_limit_power_for_charge(0, 0, 0, 0, 0) + await self._set_limit_power_for_discharge(0, 0, 0, 0, 0) + await self._set_offgrid_work_mode(0) + await self._set_work_mode(0) + + async def _set_offgrid_mode(self) -> None: + if self.arm_version >= 7: + await self._clear_battery_mode_param() + else: + await self._set_limit_power_for_charge(0, 0, 23, 59, 0) + await self._set_limit_power_for_discharge(0, 0, 0, 0, 0) + await self._set_offgrid_work_mode(1) + await self._set_relay_control(3) + await self._set_store_energy_mode(0) + await self._set_work_mode(1) + + async def _set_backup_mode(self) -> None: + if self.arm_version >= 7: + if self._supports_eco_mode_v2(): + await self._clear_battery_mode_param() + else: + await self._clear_battery_mode_param() + await self._set_limit_power_for_charge(0, 0, 23, 59, 10) + else: + await self._set_limit_power_for_charge(0, 0, 23, 59, 10) + await self._set_limit_power_for_discharge(0, 0, 0, 0, 0) + await self._set_offgrid_work_mode(0) + await self._set_work_mode(2) + + async def _set_eco_mode(self) -> None: + await self._set_offgrid_work_mode(0) + await self._set_work_mode(3) + + async def _clear_battery_mode_param(self) -> None: + await self._read_from_socket(Aa55WriteCommand(0x0700, 1)) + + async def _set_limit_power_for_charge(self, startH: int, startM: int, stopH: int, stopM: int, limit: int) -> None: + if limit < 0 or limit > 100: + raise ValueError() + await self._read_from_socket(Aa55ProtocolCommand("032c05" + + "{:02x}".format(startH) + "{:02x}".format(startM) + + "{:02x}".format(stopH) + "{:02x}".format(stopM) + + "{:02x}".format(limit), "03AC")) + + async def _set_limit_power_for_discharge(self, startH: int, startM: int, stopH: int, stopM: int, + limit: int) -> None: + if limit < 0 or limit > 100: + raise ValueError() + await self._read_from_socket(Aa55ProtocolCommand("032d05" + + "{:02x}".format(startH) + "{:02x}".format(startM) + + "{:02x}".format(stopH) + "{:02x}".format(stopM) + + "{:02x}".format(limit), "03AD")) + + async def _set_offgrid_work_mode(self, mode: int) -> None: + await self._read_from_socket(Aa55ProtocolCommand("033601" + "{:02x}".format(mode), "03B6")) + + async def _set_relay_control(self, mode: int) -> None: + param = 0 + if mode == 2: + param = 16 + elif mode == 3: + param = 48 + await self._read_from_socket(Aa55ProtocolCommand("03270200" + "{:02x}".format(param), "03B7")) + + async def _set_store_energy_mode(self, mode: int) -> None: + param = 0 + if mode == 0: + param = 4 + elif mode == 1: + param = 2 + elif mode == 2: + param = 8 + elif mode == 3: + param = 1 + await self._read_from_socket(Aa55ProtocolCommand("032601" + "{:02x}".format(param), "03B6")) + + async def _set_work_mode(self, mode: int) -> None: + await self._read_from_socket(Aa55ProtocolCommand("035901" + "{:02x}".format(mode), "03D9")) + + def _convert_eco_mode(self, sensor: Sensor) -> Sensor | EcoMode: + if EcoModeV1 == type(sensor) and self._supports_eco_mode_v2(): + return cast(EcoModeV1, sensor).as_eco_mode_v2() + elif EcoModeV2 == type(sensor) and not self._supports_eco_mode_v2(): + return cast(EcoModeV2, sensor).as_eco_mode_v1() + else: + return sensor + + def _is_modbus_setting(self, sensor: Sensor) -> bool: + return EcoModeV2 == type(sensor) or sensor.offset > 30000 diff --git a/goodwe/et.py b/goodwe/et.py new file mode 100644 index 0000000..dc80fc4 --- /dev/null +++ b/goodwe/et.py @@ -0,0 +1,508 @@ +from __future__ import annotations + +import logging +from typing import Tuple, cast + +from .inverter import Inverter +from .inverter import OperationMode +from .inverter import SensorKind as Kind +from .model import is_4_mptt, is_single_phase +from .protocol import ProtocolCommand, ModbusReadCommand, ModbusWriteCommand, ModbusWriteMultiCommand +from .sensor import * + +logger = logging.getLogger(__name__) + + +class ET(Inverter): + """Class representing inverter of ET/EH/BT/BH or GE's GEH families""" + + # Modbus registers from offset 0x891c (35100), count 0x7d (125) + __all_sensors: Tuple[Sensor, ...] = ( + Timestamp("timestamp", 0, "Timestamp"), + Voltage("vpv1", 6, "PV1 Voltage", Kind.PV), + Current("ipv1", 8, "PV1 Current", Kind.PV), + Power4("ppv1", 10, "PV1 Power", Kind.PV), + Voltage("vpv2", 14, "PV2 Voltage", Kind.PV), + Current("ipv2", 16, "PV2 Current", Kind.PV), + Power4("ppv2", 18, "PV2 Power", Kind.PV), + Voltage("vpv3", 22, "PV3 Voltage", Kind.PV), # modbus35111 + Current("ipv3", 24, "PV3 Current", Kind.PV), + Power4("ppv3", 26, "PV3 Power", Kind.PV), + Voltage("vpv4", 30, "PV4 Voltage", Kind.PV), + Current("ipv4", 32, "PV4 Current", Kind.PV), + Power4("ppv4", 34, "PV4 Power", Kind.PV), + # ppv1 + ppv2 + ppv3 + ppv4 + Calculated("ppv", + lambda data: + read_bytes4(data, 10) + + read_bytes4(data, 18) + + read_bytes4(data, 26) + + read_bytes4(data, 34), + "PV Power", "W", Kind.PV), + Byte("pv4_mode", 38, "PV4 Mode code", "", Kind.PV), + Enum("pv4_mode_label", 38, PV_MODES, "PV4 Mode", Kind.PV), + Byte("pv3_mode", 39, "PV3 Mode code", "", Kind.PV), + Enum("pv3_mode_label", 39, PV_MODES, "PV3 Mode", Kind.PV), + Byte("pv2_mode", 40, "PV2 Mode code", "", Kind.PV), + Enum("pv2_mode_label", 40, PV_MODES, "PV2 Mode", Kind.PV), + Byte("pv1_mode", 41, "PV1 Mode code", "", Kind.PV), + Enum("pv1_mode_label", 41, PV_MODES, "PV1 Mode", Kind.PV), + Voltage("vgrid", 42, "On-grid L1 Voltage", Kind.AC), # modbus 35121 + Current("igrid", 44, "On-grid L1 Current", Kind.AC), + Frequency("fgrid", 46, "On-grid L1 Frequency", Kind.AC), + # 48 reserved + Power("pgrid", 50, "On-grid L1 Power", Kind.AC), + Voltage("vgrid2", 52, "On-grid L2 Voltage", Kind.AC), + Current("igrid2", 54, "On-grid L2 Current", Kind.AC), + Frequency("fgrid2", 56, "On-grid L2 Frequency", Kind.AC), + # 58 reserved + Power("pgrid2", 60, "On-grid L2 Power", Kind.AC), + Voltage("vgrid3", 62, "On-grid L3 Voltage", Kind.AC), + Current("igrid3", 64, "On-grid L3 Current", Kind.AC), + Frequency("fgrid3", 66, "On-grid L3 Frequency", Kind.AC), + # 68 reserved + Power("pgrid3", 70, "On-grid L3 Power", Kind.AC), + Integer("grid_mode", 72, "Grid Mode code", "", Kind.PV), + Enum2("grid_mode_label", 72, GRID_MODES, "Grid Mode", Kind.PV), + # 74 reserved + Power("total_inverter_power", 76, "Total Power", Kind.AC), + # 78 reserved + Power("active_power", 80, "Active Power", Kind.GRID), + Calculated("grid_in_out", + lambda data: read_grid_mode(data, 80), + "On-grid Mode code", "", Kind.GRID), + EnumCalculated("grid_in_out_label", + lambda data: read_grid_mode(data, 80), GRID_IN_OUT_MODES, + "On-grid Mode", Kind.GRID), + # 82 reserved + Reactive("reactive_power", 84, "Reactive Power", Kind.GRID), + # 86 reserved + Apparent("apparent_power", 88, "Apparent Power", Kind.GRID), + Voltage("backup_v1", 90, "Back-up L1 Voltage", Kind.UPS), # modbus 35145 + Current("backup_i1", 92, "Back-up L1 Current", Kind.UPS), + Frequency("backup_f1", 94, "Back-up L1 Frequency", Kind.UPS), + Integer("load_mode1", 96, "Load Mode L1"), + # 98 reserved + Power("backup_p1", 100, "Back-up L1 Power", Kind.UPS), + Voltage("backup_v2", 102, "Back-up L2 Voltage", Kind.UPS), + Current("backup_i2", 104, "Back-up L2 Current", Kind.UPS), + Frequency("backup_f2", 106, "Back-up L2 Frequency", Kind.UPS), + Integer("load_mode2", 108, "Load Mode L2"), + # 110 reserved + Power("backup_p2", 112, "Back-up L2 Power", Kind.UPS), + Voltage("backup_v3", 114, "Back-up L3 Voltage", Kind.UPS), + Current("backup_i3", 116, "Back-up L3 Current", Kind.UPS), + Frequency("backup_f3", 118, "Back-up L3 Frequency", Kind.UPS), + Integer("load_mode3", 120, "Load Mode L3"), + # 122 reserved + Power("backup_p3", 124, "Back-up L3 Power", Kind.UPS), + # 126 reserved + Power("load_p1", 128, "Load L1", Kind.AC), + # 130 reserved + Power("load_p2", 132, "Load L2", Kind.AC), + # 134 reserved + Power("load_p3", 136, "Load L3", Kind.AC), + # 138 reserved + Power("backup_ptotal", 140, "Back-up Load", Kind.UPS), + # 142 reserved + Power("load_ptotal", 144, "Load", Kind.AC), + Integer("ups_load", 146, "Ups Load", "%", Kind.UPS), + Temp("temperature_air", 148, "Inverter Temperature (Air)", Kind.AC), + Temp("temperature_module", 150, "Inverter Temperature (Module)"), + Temp("temperature", 152, "Inverter Temperature (Radiator)", Kind.AC), + Integer("function_bit", 154, "Function Bit"), + Voltage("bus_voltage", 156, "Bus Voltage", None), + Voltage("nbus_voltage", 158, "NBus Voltage", None), + Voltage("vbattery1", 160, "Battery Voltage", Kind.BAT), # modbus 35180 + Current("ibattery1", 162, "Battery Current", Kind.BAT), + # round(vbattery1 * ibattery1), + Calculated("pbattery1", + lambda data: round(read_voltage(data, 160) * read_current(data, 162)), + "Battery Power", "W", Kind.BAT), + Integer("battery_mode", 168, "Battery Mode code", "", Kind.BAT), + Enum2("battery_mode_label", 168, BATTERY_MODES, "Battery Mode", Kind.BAT), + Integer("warning_code", 170, "Warning code"), + Integer("safety_country", 172, "Safety Country code", "", Kind.AC), + Enum2("safety_country_label", 172, SAFETY_COUNTRIES, "Safety Country", Kind.AC), + Integer("work_mode", 174, "Work Mode code"), + Enum2("work_mode_label", 174, WORK_MODES_ET, "Work Mode"), + Integer("operation_mode", 176, "Operation Mode code"), + Long("error_codes", 178, "Error Codes"), + EnumBitmap4("errors", 178, ERROR_CODES, "Errors"), + Energy4("e_total", 182, "Total PV Generation", Kind.PV), + Energy4("e_day", 186, "Today's PV Generation", Kind.PV), + Energy4("e_total_exp", 190, "Total Energy (export)", Kind.AC), + Long("h_total", 194, "Hours Total", "h", Kind.PV), + Energy("e_day_exp", 198, "Today Energy (export)", Kind.AC), + Energy4("e_total_imp", 200, "Total Energy (import)", Kind.AC), + Energy("e_day_imp", 204, "Today Energy (import)", Kind.AC), + Energy4("e_load_total", 206, "Total Load", Kind.AC), + Energy("e_load_day", 210, "Today Load", Kind.AC), + Energy4("e_bat_charge_total", 212, "Total Battery Charge", Kind.BAT), + Energy("e_bat_charge_day", 216, "Today Battery Charge", Kind.BAT), + Energy4("e_bat_discharge_total", 218, "Total Battery Discharge", Kind.BAT), + Energy("e_bat_discharge_day", 222, "Today Battery Discharge", Kind.BAT), + Long("diagnose_result", 240, "Diag Status Code"), + EnumBitmap4("diagnose_result_label", 240, DIAG_STATUS_CODES, "Diag Status"), + # ppv1 + ppv2 + pbattery - active_power + Calculated("house_consumption", + lambda data: + read_bytes4(data, 10) + + read_bytes4(data, 18) + + read_bytes4(data, 26) + + read_bytes4(data, 34) + + round(read_voltage(data, 160) * read_current(data, 162)) - + read_bytes2(data, 80), + "House Consumption", "W", Kind.AC), + ) + + # Modbus registers from offset 0x9088 (37000) + __all_sensors_battery: Tuple[Sensor, ...] = ( + Integer("battery_bms", 0, "Battery BMS", "", Kind.BAT), + Integer("battery_index", 2, "Battery Index", "", Kind.BAT), + Integer("battery_status", 4, "Battery Status", "", Kind.BAT), + Temp("battery_temperature", 6, "Battery Temperature", Kind.BAT), + Integer("battery_charge_limit", 8, "Battery Charge Limit", "A", Kind.BAT), + Integer("battery_discharge_limit", 10, "Battery Discharge Limit", "A", Kind.BAT), + Integer("battery_error_l", 12, "Battery Error L", "", Kind.BAT), + Integer("battery_soc", 14, "Battery State of Charge", "%", Kind.BAT), + Integer("battery_soh", 16, "Battery State of Health", "%", Kind.BAT), + Integer("battery_modules", 18, "Battery Modules", "", Kind.BAT), # modbus 37009 + Integer("battery_warning_l", 20, "Battery Warning L", "", Kind.BAT), + Integer("battery_protocol", 22, "Battery Protocol", "", Kind.BAT), + Integer("battery_error_h", 24, "Battery Error H", "", Kind.BAT), + EnumBitmap22("battery_error", 24, 12, BMS_ALARM_CODES, "Battery Error", Kind.BAT), + Integer("battery_warning_h", 28, "Battery Warning H", "", Kind.BAT), + EnumBitmap22("battery_warning", 28, 20, BMS_WARNING_CODES, "Battery Warning", Kind.BAT), + Integer("battery_sw_version", 30, "Battery Software Version", "", Kind.BAT), + Integer("battery_hw_version", 32, "Battery Hardware Version", "", Kind.BAT), + Integer("battery_max_cell_temp_id", 34, "Battery Max Cell Temperature ID", "", Kind.BAT), + Integer("battery_min_cell_temp_id", 36, "Battery Min Cell Temperature ID", "", Kind.BAT), + Integer("battery_max_cell_voltage_id", 38, "Battery Max Cell Voltage ID", "", Kind.BAT), + Integer("battery_min_cell_voltage_id", 40, "Battery Min Cell Voltage ID", "", Kind.BAT), + Temp("battery_max_cell_temp", 42, "Battery Max Cell Temperature", Kind.BAT), + Temp("battery_min_cell_temp", 44, "Battery Min Cell Temperature", Kind.BAT), + Voltage("battery_max_cell_voltage", 46, "Battery Max Cell Voltage", Kind.BAT), + Voltage("battery_min_cell_voltage", 48, "Battery Min Cell Voltage", Kind.BAT), + ) + + # Inverter's meter data + # Modbus registers from offset 0x8ca0 (36000) + __all_sensors_meter: Tuple[Sensor, ...] = ( + Integer("commode", 0, "Commode"), + Integer("rssi", 2, "RSSI"), + Integer("manufacture_code", 4, "Manufacture Code"), + Integer("meter_test_status", 6, "Meter Test Status"), # 1: correct,2: reverse,3: incorrect,0: not checked + Integer("meter_comm_status", 8, "Meter Communication Status"), # 1 OK, 0 NotOK + Power("active_power1", 10, "Active Power L1", Kind.GRID), # modbus 36005 + Power("active_power2", 12, "Active Power L2", Kind.GRID), + Power("active_power3", 14, "Active Power L3", Kind.GRID), + Power("active_power_total", 16, "Active Power Total", Kind.GRID), + Reactive("reactive_power_total", 18, "Reactive Power Total", Kind.GRID), + Decimal("meter_power_factor1", 20, 1000, "Meter Power Factor L1", "", Kind.GRID), + Decimal("meter_power_factor2", 22, 1000, "Meter Power Factor L2", "", Kind.GRID), + Decimal("meter_power_factor3", 24, 1000, "Meter Power Factor L3", "", Kind.GRID), + Decimal("meter_power_factor", 26, 1000, "Meter Power Factor", "", Kind.GRID), + Frequency("meter_freq", 28, "Meter Frequency", Kind.GRID), # modbus 36014 + Float("meter_e_total_exp", 30, 1000, "Meter Total Energy (export)", "kWh", Kind.GRID), + Float("meter_e_total_imp", 34, 1000, "Meter Total Energy (import)", "kWh", Kind.GRID), + Power4("meter_active_power1", 38, "Meter Active Power L1", Kind.GRID), + Power4("meter_active_power2", 42, "Meter Active Power L2", Kind.GRID), + Power4("meter_active_power3", 46, "Meter Active Power L3", Kind.GRID), + Power4("meter_active_power_total", 50, "Meter Active Power Total", Kind.GRID), + Reactive4("meter_reactive_power1", 54, "Meter Reactive Power L1", Kind.GRID), + Reactive4("meter_reactive_power2", 58, "Meter Reactive Power L2", Kind.GRID), + Reactive4("meter_reactive_power3", 62, "Meter Reactive Power L2", Kind.GRID), + Reactive4("meter_reactive_power_total", 66, "Meter Reactive Power Total", Kind.GRID), + Apparent4("meter_apparent_power1", 70, "Meter Apparent Power L1", Kind.GRID), + Apparent4("meter_apparent_power2", 74, "Meter Apparent Power L2", Kind.GRID), + Apparent4("meter_apparent_power3", 78, "Meter Apparent Power L3", Kind.GRID), + Apparent4("meter_apparent_power_total", 82, "Meter Apparent Power Total", Kind.GRID), + Integer("meter_type", 86, "Meter Type", "", Kind.GRID), + Integer("meter_sw_version", 88, "Meter Software Version", "", Kind.GRID), + ) + + # Modbus registers of inverter settings, offsets are modbus register addresses + __all_settings: Tuple[Sensor, ...] = ( + Integer("comm_address", 45127, "Communication Address", ""), + + Timestamp("time", 45200, "Inverter time"), + + Integer("sensitivity_check", 45246, "Sensitivity Check Mode", "", Kind.AC), + Integer("cold_start", 45248, "Cold Start", "", Kind.AC), + Integer("shadow_scan", 45251, "Shadow Scan", "", Kind.PV), + Integer("backup_supply", 45252, "Backup Supply", "", Kind.UPS), + Integer("unbalanced_output", 45264, "Unbalanced Output", "", Kind.AC), + Integer("pen_relay", 45288, "PE-N Relay", "", Kind.AC), + + Integer("battery_capacity", 45350, "Battery Capacity", "Ah", Kind.BAT), + Integer("battery_modules", 45351, "Battery Modules", "", Kind.BAT), + Voltage("battery_charge_voltage", 45352, "Battery Charge Voltage", Kind.BAT), + Current("battery_charge_current", 45353, "Battery Charge Current", Kind.BAT), + Voltage("battery_discharge_voltage", 45354, "Battery Discharge Voltage", Kind.BAT), + Current("battery_discharge_current", 45355, "Battery Discharge Current", Kind.BAT), + Integer("battery_discharge_depth", 45356, "Battery Discharge Depth", "%", Kind.BAT), + Voltage("battery_discharge_voltage_offline", 45357, "Battery Discharge Voltage (off-line)", Kind.BAT), + Integer("battery_discharge_depth_offline", 45358, "Battery Discharge Depth (off-line)", "%", Kind.BAT), + + Decimal("power_factor", 45482, 100, "Power Factor"), + + Integer("work_mode", 47000, "Work Mode", "", Kind.AC), + Integer("dred", 47010, "DRED/Remote Shutdown", "", Kind.AC), + + Integer("battery_soc_protection", 47500, "Battery SoC Protection", "", Kind.BAT), + + Integer("grid_export", 47509, "Grid Export Enabled", "", Kind.GRID), + Integer("grid_export_limit", 47510, "Grid Export Limit", "W", Kind.GRID), + + Integer("battery_protocol_code", 47514, "Battery Protocol Code", "", Kind.BAT), + + EcoModeV1("eco_mode_1", 47515, "Eco Mode Group 1"), + ByteH("eco_mode_1_switch", 47518, "Eco Mode Group 1 Switch"), + EcoModeV1("eco_mode_2", 47519, "Eco Mode Group 2"), + ByteH("eco_mode_2_switch", 47522, "Eco Mode Group 2 Switch"), + EcoModeV1("eco_mode_3", 47523, "Eco Mode Group 3"), + ByteH("eco_mode_3_switch", 47526, "Eco Mode Group 3 Switch"), + EcoModeV1("eco_mode_4", 47527, "Eco Mode Group 4"), + ByteH("eco_mode_4_switch", 47530, "Eco Mode Group 4 Switch"), + ) + + # Settings added in ARM firmware 19 + __settings_arm_fw_19: Tuple[Sensor, ...] = ( + Integer("fast_charging", 47545, "Fast Charging Enabled", "", Kind.BAT), + Integer("fast_charging_soc", 47546, "Fast Charging SoC", "%", Kind.BAT), + EcoModeV2("eco_mode_1", 47547, "Eco Mode Group 1"), + ByteH("eco_mode_1_switch", 47549, "Eco Mode Group 1 Switch"), + EcoModeV2("eco_mode_2", 47553, "Eco Mode Group 2"), + ByteH("eco_mode_2_switch", 47555, "Eco Mode Group 2 Switch"), + EcoModeV2("eco_mode_3", 47559, "Eco Mode Group 3"), + ByteH("eco_mode_3_switch", 47561, "Eco Mode Group 3 Switch"), + EcoModeV2("eco_mode_4", 47565, "Eco Mode Group 4"), + ByteH("eco_mode_4_switch", 47567, "Eco Mode Group 4 Switch"), + + Integer("load_control_mode", 47595, "Load Control Mode", "", Kind.AC), + Integer("load_control_switch", 47596, "Load Control Switch", "", Kind.AC), + Integer("load_control_soc", 47596, "Load Control SoC", "", Kind.AC), + + Integer("fast_charging_power", 47603, "Fast Charging Power", "%", Kind.BAT), + ) + + # Settings added in ARM firmware 22 + __settings_arm_fw_22: Tuple[Sensor, ...] = ( + # EcoModeV2("eco_modeV2_5", 47571, "Eco Mode Version 2 Power Group 5"), + # EcoModeV2("eco_modeV2_6", 47577, "Eco Mode Version 2 Power Group 6"), + # EcoModeV2("eco_modeV2_7", 47583, "Eco Mode Version 2 Power Group 7"), + PeakShavingMode("peak_shaving_mode", 47589, "Peak Shaving Mode"), + + Integer("dod_holding", 47602, "DoD Holding", "", Kind.BAT), + ) + + def __init__(self, host: str, comm_addr: int = 0, timeout: int = 1, retries: int = 3): + super().__init__(host, comm_addr, timeout, retries) + if not self.comm_addr: + # Set the default inverter address + self.comm_addr = 0xf7 + self._READ_DEVICE_VERSION_INFO: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x88b8, 0x0021) + self._READ_RUNNING_DATA: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x891c, 0x007d) + self._READ_METER_DATA: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x8ca0, 0x2d) + self._READ_BATTERY_INFO: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x9088, 0x0018) + self._has_battery: bool = True + # By default, we set up only PV1 on PV2 sensors, only few inverters support PV3 and PV4 + # In case they are needed, they are added later in read_device_info + self._sensors = tuple(filter(self._pv1_pv2_only, self.__all_sensors)) + self._sensors_battery = self.__all_sensors_battery + self._sensors_meter = self.__all_sensors_meter + self._settings: dict[str, Sensor] = {s.id_: s for s in self.__all_settings} + + def _supports_eco_mode_v2(self) -> bool: + return self.arm_version >= 19 + + def _supports_peak_shaving(self) -> bool: + return self.arm_version >= 22 + + @staticmethod + def _single_phase_only(s: Sensor) -> bool: + """Filter to exclude phase2/3 sensors on single phase inverters""" + return not ((s.id_.endswith('2') or s.id_.endswith('3')) and 'pv' not in s.id_) + + @staticmethod + def _pv1_pv2_only(s: Sensor) -> bool: + """Filter to exclude sensors on < 4 PV inverters""" + return not (('pv3' in s.id_) or ('pv4' in s.id_)) + + async def read_device_info(self): + response = await self._read_from_socket(self._READ_DEVICE_VERSION_INFO) + response = response[5:-2] + # Modbus registers from offset (35000) + self.modbus_version = read_unsigned_int(response, 0) + self.rated_power = read_unsigned_int(response, 2) + self.ac_output_type = read_unsigned_int(response, 4) # 0: 1-phase, 1: 3-phase (4 wire), 2: 3-phase (3 wire) + self.serial_number = response[6:22].decode("ascii") + self.model_name = response[22:32].decode("ascii").rstrip() + self.dsp1_version = read_unsigned_int(response, 32) + self.dsp2_version = read_unsigned_int(response, 34) + self.dsp_svn_version = read_unsigned_int(response, 36) + self.arm_version = read_unsigned_int(response, 38) + self.arm_svn_version = read_unsigned_int(response, 40) + self.firmware = self._decode(response[42:54]) + self.arm_firmware = self._decode(response[54:66]) + + if is_4_mptt(self): + # this is PV3/PV4 re-include all sensors + self._sensors = tuple(self.__all_sensors) + self._sensors_meter = tuple(self._sensors_meter) + + if is_single_phase(self): + # this is single phase inverter, filter out all L2 and L3 sensors + self._sensors = tuple(filter(self._single_phase_only, self._sensors)) + self._sensors_meter = tuple(filter(self._single_phase_only, self._sensors_meter)) + + if self.arm_version >= 19: + self._settings.update({s.id_: s for s in self.__settings_arm_fw_19}) + if self.arm_version >= 22: + self._settings.update({s.id_: s for s in self.__settings_arm_fw_22}) + + async def read_runtime_data(self, include_unknown_sensors: bool = False) -> Dict[str, Any]: + raw_data = await self._read_from_socket(self._READ_RUNNING_DATA) + data = self._map_response(raw_data[5:-2], self._sensors, include_unknown_sensors) + + self._has_battery = data.get('battery_mode', 0) != 0 + if self._has_battery: + raw_data = await self._read_from_socket(self._READ_BATTERY_INFO) + data.update(self._map_response(raw_data[5:-2], self._sensors_battery, include_unknown_sensors)) + + raw_data = await self._read_from_socket(self._READ_METER_DATA) + data.update(self._map_response(raw_data[5:-2], self._sensors_meter, include_unknown_sensors)) + return data + + async def read_setting(self, setting_id: str) -> Any: + setting = self._settings.get(setting_id) + if not setting: + raise ValueError(f'Unknown setting "{setting_id}"') + count = (setting.size_ + (setting.size_ % 2)) // 2 + raw_data = await self._read_from_socket(ModbusReadCommand(self.comm_addr, setting.offset, count)) + with io.BytesIO(raw_data[5:-2]) as buffer: + return setting.read_value(buffer) + + async def write_setting(self, setting_id: str, value: Any): + setting = self._settings.get(setting_id) + if not setting: + raise ValueError(f'Unknown setting "{setting_id}"') + if setting.size_ == 1: + # modbus can address/store only 16 bit values, read the other 8 bytes + register_data = await self._read_from_socket(ModbusReadCommand(self.comm_addr, setting.offset, 1)) + raw_value = setting.encode_value(value, register_data[5:7]) + else: + raw_value = setting.encode_value(value) + if len(raw_value) <= 2: + value = int.from_bytes(raw_value, byteorder="big", signed=True) + await self._read_from_socket(ModbusWriteCommand(self.comm_addr, setting.offset, value)) + else: + await self._read_from_socket(ModbusWriteMultiCommand(self.comm_addr, setting.offset, raw_value)) + + async def read_settings_data(self) -> Dict[str, Any]: + data = {} + for setting in self.settings(): + try: + value = await self.read_setting(setting.id_) + data[setting.id_] = value + except ValueError: + logger.exception("Error reading setting %s.", setting.id_) + data[setting.id_] = None + return data + + async def get_grid_export_limit(self) -> int: + return await self.read_setting('grid_export_limit') + + async def set_grid_export_limit(self, export_limit: int) -> None: + if 0 <= export_limit <= 10000: + await self.write_setting('grid_export_limit', export_limit) + + async def get_operation_modes(self, include_emulated: bool) -> Tuple[OperationMode, ...]: + result = [e for e in OperationMode] + if not self._supports_peak_shaving(): + result.remove(OperationMode.PEAK_SHAVING) + if not include_emulated: + result.remove(OperationMode.ECO_CHARGE) + result.remove(OperationMode.ECO_DISCHARGE) + return tuple(result) + + async def get_operation_mode(self) -> OperationMode: + mode = OperationMode(await self.read_setting('work_mode')) + if OperationMode.ECO != mode: + return mode + ecomode = await self.read_setting('eco_mode_1') + if ecomode.is_eco_charge_mode(): + return OperationMode.ECO_CHARGE + elif ecomode.is_eco_discharge_mode(): + return OperationMode.ECO_DISCHARGE + else: + return OperationMode.ECO + + async def set_operation_mode(self, operation_mode: OperationMode, eco_mode_power: int = 100, + eco_mode_soc: int = 100) -> None: + if operation_mode == OperationMode.GENERAL: + await self.write_setting('work_mode', 0) + await self._set_offline(False) + await self._clear_battery_mode_param() + elif operation_mode == OperationMode.OFF_GRID: + await self.write_setting('work_mode', 1) + await self._set_offline(True) + await self.write_setting('backup_supply', 1) + await self.write_setting('cold_start', 4) + elif operation_mode == OperationMode.BACKUP: + await self.write_setting('work_mode', 2) + await self._set_offline(False) + await self._clear_battery_mode_param() + elif operation_mode == OperationMode.ECO: + await self.write_setting('work_mode', 3) + await self._set_offline(False) + elif operation_mode == OperationMode.PEAK_SHAVING: + await self.write_setting('work_mode', 4) + await self._set_offline(False) + elif operation_mode in (OperationMode.ECO_CHARGE, OperationMode.ECO_DISCHARGE): + if eco_mode_power < 0 or eco_mode_power > 100: + raise ValueError() + if eco_mode_soc < 0 or eco_mode_soc > 100: + raise ValueError() + eco_mode: EcoMode = self._convert_eco_mode(EcoModeV2("", 0, "")) + if operation_mode == OperationMode.ECO_CHARGE: + await self.write_setting('eco_mode_1', eco_mode.encode_charge(eco_mode_power, eco_mode_soc)) + else: + await self.write_setting('eco_mode_1', eco_mode.encode_discharge(eco_mode_power)) + await self.write_setting('eco_mode_2_switch', 0) + await self.write_setting('eco_mode_3_switch', 0) + await self.write_setting('eco_mode_4_switch', 0) + await self.write_setting('work_mode', 3) + await self._set_offline(False) + + async def get_ongrid_battery_dod(self) -> int: + return 100 - await self.read_setting('battery_discharge_depth') + + async def set_ongrid_battery_dod(self, dod: int) -> None: + if 0 <= dod <= 90: + await self.write_setting('battery_discharge_depth', 100 - dod) + + def sensors(self) -> Tuple[Sensor, ...]: + if self._has_battery: + return self._sensors + self._sensors_battery + self._sensors_meter + else: + return self._sensors + self._sensors_meter + + def settings(self) -> Tuple[Sensor, ...]: + return tuple(self._settings.values()) + + async def _clear_battery_mode_param(self) -> None: + await self._read_from_socket(ModbusWriteCommand(self.comm_addr, 0xb9ad, 1)) + + async def _set_offline(self, mode: bool) -> None: + value = bytes.fromhex('00070000') if mode else bytes.fromhex('00010000') + await self._read_from_socket(ModbusWriteMultiCommand(self.comm_addr, 0xb997, value)) + + def _convert_eco_mode(self, sensor: Sensor) -> Sensor | EcoMode: + if EcoModeV1 == type(sensor) and self._supports_eco_mode_v2(): + return cast(EcoModeV1, sensor).as_eco_mode_v2() + elif EcoModeV2 == type(sensor) and not self._supports_eco_mode_v2(): + return cast(EcoModeV2, sensor).as_eco_mode_v1() + else: + return sensor diff --git a/goodwe/exceptions.py b/goodwe/exceptions.py new file mode 100644 index 0000000..a07840c --- /dev/null +++ b/goodwe/exceptions.py @@ -0,0 +1,37 @@ +class InverterError(Exception): + """Indicates error communicating with inverter""" + + +class RequestFailedException(InverterError): + """ + Indicates request sent to inverter has failed and did not yield in valid response, + even after several retries. + + Attributes: + message -- explanation of the error + consecutive_failures_count -- number requests failed in a consecutive streak + """ + + def __init__(self, message: str = '', consecutive_failures_count: int = 0): + self.message: str = message + self.consecutive_failures_count: int = consecutive_failures_count + + +class RequestRejectedException(InverterError): + """ + Indicates request sent to inverter was rejected and protocol exception response was received. + + Attributes: + message -- rejection reason + """ + + def __init__(self, message: str = ''): + self.message: str = message + + +class ProcessingException(InverterError): + """Indicates an error occurred during processing of inverter data""" + + +class MaxRetriesException(InverterError): + """Indicates the maximum number of retries has been reached""" diff --git a/goodwe/goodwe.py b/goodwe/goodwe.py new file mode 100644 index 0000000..78c2d5c --- /dev/null +++ b/goodwe/goodwe.py @@ -0,0 +1,22 @@ +import logging +from typing import Tuple + +from .exceptions import ProcessingException +from .processor import ProcessorResult, AbstractDataProcessor +from .xs import GoodWeXSProcessor + +logger = logging.getLogger(__name__) + +class GoodWeInverter: + def __init__(self, inverter_address: Tuple[str, int], processor: AbstractDataProcessor): + self.address = inverter_address + self.processor = processor + + async def request_data(self) -> ProcessorResult: + try: + logger.debug('awaiting future') + data = await self.processor.get_runtime_data_command().execute(self.address[0], 1, 3) + return self.processor.process_data(data) + except (TypeError, ValueError) as e: + logger.debug(f'exception occurred during processing inverter data: {e}') + raise ProcessingException diff --git a/goodwe/inverter.py b/goodwe/inverter.py new file mode 100644 index 0000000..a1243c5 --- /dev/null +++ b/goodwe/inverter.py @@ -0,0 +1,305 @@ +from __future__ import annotations + +import asyncio +import io +import logging +from abc import ABC, abstractmethod +from dataclasses import dataclass +from enum import Enum, IntEnum +from typing import Any, Callable, Dict, Tuple, Optional + +from .exceptions import MaxRetriesException, RequestFailedException +from .protocol import ProtocolCommand + +logger = logging.getLogger(__name__) + + +class SensorKind(Enum): + """ + Enumeration of sensor kinds. + + Possible values are: + PV - inverter photo-voltaic (e.g. dc voltage of pv panels) + AC - inverter grid output (e.g. ac voltage of grid connected output) + UPS - inverter ups/eps/backup output (e.g. ac voltage of backup/off-grid connected output) + BAT - battery (e.g. dc voltage of connected battery pack) + GRID - power grid/smart meter (e.g. active power exported to grid) + """ + + PV = 1 + AC = 2 + UPS = 3 + BAT = 4 + GRID = 5 + + +@dataclass +class Sensor: + """Definition of inverter sensor and its attributes""" + + id_: str + offset: int + name: str + size_: int + unit: str + kind: Optional[SensorKind] + + def read_value(self, data: io.BytesIO) -> Any: + """Read the sensor value from data at current position""" + raise NotImplementedError() + + def read(self, data: io.BytesIO) -> Any: + """Read the sensor value from data (at sensor offset)""" + data.seek(self.offset) + return self.read_value(data) + + def encode_value(self, value: Any) -> bytes: + """Encode the (setting mostly) value to (usually) 2 byte raw register value""" + raise NotImplementedError() + + +class OperationMode(IntEnum): + """ + Enumeration of sensor kinds. + + Possible values are: + GENERAL - General mode + OFF_GRID - Off grid mode + BACKUP - Backup mode + ECO - Eco mode + PEAK_SHAVING - Peak shaving mode + ECO_CHARGE - Eco mode with a single "Charge" group valid all the time (from 00:00-23:59, Mon-Sun) + ECO_DISCHARGE - Eco mode with a single "Discharge" group valid all the time (from 00:00-23:59, Mon-Sun) + """ + + GENERAL = 0 + OFF_GRID = 1 + BACKUP = 2 + ECO = 3 + PEAK_SHAVING = 4 + ECO_CHARGE = 5 + ECO_DISCHARGE = 6 + + +class Inverter(ABC): + """ + Common superclass for various inverter models implementations. + Represents the inverter state and its basic behavior + """ + + def __init__(self, host: str, comm_addr: int = 0, timeout: int = 1, retries: int = 3): + self.host: str = host + self.comm_addr: int = comm_addr + self.timeout: int = timeout + self.retries: int = retries + self._running_loop: asyncio.AbstractEventLoop | None = None + self._lock: asyncio.Lock | None = None + self._consecutive_failures_count: int = 0 + + self.model_name: str | None = None + self.serial_number: str | None = None + self.rated_power: int | None = None + self.ac_output_type: int | None = None + self.firmware: str | None = None + self.arm_firmware: str | None = None + self.modbus_version: int | None = None + self.dsp1_version: int = 0 + self.dsp2_version: int = 0 + self.dsp_svn_version: int | None = None + self.arm_version: int = 0 + self.arm_svn_version: int | None = None + + def _ensure_lock(self) -> asyncio.Lock: + """Validate (or create) asyncio Lock. + + The asyncio.Lock must always be created from within's asyncio loop, + so it cannot be eagerly created in constructor. + Additionally, since asyncio.run() creates and closes its own loop, + the lock's scope (its creating loop) mus be verified to support proper + behavior in subsequent asyncio.run() invocations. + """ + if self._lock and self._running_loop == asyncio.get_event_loop(): + return self._lock + else: + logger.debug("Creating lock instance for current event loop.") + self._lock = asyncio.Lock() + self._running_loop = asyncio.get_event_loop() + return self._lock + + async def _read_from_socket(self, command: ProtocolCommand) -> bytes: + async with self._ensure_lock(): + try: + result = await command.execute(self.host, self.timeout, self.retries) + self._consecutive_failures_count = 0 + return result + except MaxRetriesException: + self._consecutive_failures_count += 1 + raise RequestFailedException(f'No valid response received even after {self.retries} retries', + self._consecutive_failures_count) + except RequestFailedException as ex: + self._consecutive_failures_count += 1 + raise RequestFailedException(ex.message, self._consecutive_failures_count) + + @abstractmethod + async def read_device_info(self): + """ + Request the device information from the inverter. + The inverter instance variables will be loaded with relevant data. + """ + raise NotImplementedError() + + @abstractmethod + async def read_runtime_data(self, include_unknown_sensors: bool = False) -> Dict[str, Any]: + """ + Request the runtime data from the inverter. + Answer dictionary of individual sensors and their values. + List of supported sensors (and their definitions) is provided by sensors() method. + + If include_unknown_sensors parameter is set to True, return all runtime values, + including those "xx*" sensors whose meaning is not yet identified. + """ + raise NotImplementedError() + + @abstractmethod + async def read_setting(self, setting_id: str) -> Any: + """ + Read the value of specific inverter setting/configuration parameter. + Setting must be in list provided by settings() method, otherwise ValueError is raised. + """ + raise NotImplementedError() + + @abstractmethod + async def write_setting(self, setting_id: str, value: Any): + """ + Set the value of specific inverter settings/configuration parameter. + Setting must be in list provided by settings() method, otherwise ValueError is raised. + + BEWARE !!! + This method modifies inverter operational parameter (usually accessible to installers only). + Use with caution and at your own risk ! + """ + raise NotImplementedError() + + @abstractmethod + async def read_settings_data(self) -> Dict[str, Any]: + """ + Request the settings data from the inverter. + Answer dictionary of individual settings and their values. + List of supported settings (and their definitions) is provided by settings() method. + """ + raise NotImplementedError() + + async def send_command( + self, command: bytes, validator: Callable[[bytes], bool] = lambda x: True + ) -> bytes: + """ + Send low level udp command (as bytes). + Answer command's raw response data. + """ + return await self._read_from_socket(ProtocolCommand(command, validator)) + + @abstractmethod + async def get_grid_export_limit(self) -> int: + """ + Get the current grid export limit in W + """ + raise NotImplementedError() + + @abstractmethod + async def set_grid_export_limit(self, export_limit: int) -> None: + """ + BEWARE !!! + This method modifies inverter operational parameter accessible to installers only. + Use with caution and at your own risk ! + + Set the grid export limit in W + """ + raise NotImplementedError() + + @abstractmethod + async def get_operation_modes(self, include_emulated: bool) -> Tuple[OperationMode, ...]: + """ + Answer list of supported inverter operation modes + """ + return () + + @abstractmethod + async def get_operation_mode(self) -> OperationMode: + """ + Get the inverter operation mode + """ + raise NotImplementedError() + + @abstractmethod + async def set_operation_mode(self, operation_mode: OperationMode, eco_mode_power: int = 100, + eco_mode_soc: int = 100) -> None: + """ + BEWARE !!! + This method modifies inverter operational parameter accessible to installers only. + Use with caution and at your own risk ! + + Set the inverter operation mode + + The modes ECO_CHARGE and ECO_DISCHARGE are not real inverter operation modes, but a convenience + shortcuts to enter Eco Mode with a single group valid all the time (from 00:00-23:59, Mon-Sun) + charging or discharging with optional charging power and SoC (%) parameters. + """ + raise NotImplementedError() + + @abstractmethod + async def get_ongrid_battery_dod(self) -> int: + """ + Get the On-Grid Battery DoD + 0% - 89% + """ + raise NotImplementedError() + + @abstractmethod + async def set_ongrid_battery_dod(self, dod: int) -> None: + """ + BEWARE !!! + This method modifies On-Grid Battery DoD parameter accessible to installers only. + Use with caution and at your own risk ! + + Set the On-Grid Battery DoD + 0% - 89% + """ + raise NotImplementedError() + + @abstractmethod + def sensors(self) -> Tuple[Sensor, ...]: + """ + Return tuple of sensor definitions + """ + raise NotImplementedError() + + @abstractmethod + def settings(self) -> Tuple[Sensor, ...]: + """ + Return tuple of settings definitions + """ + raise NotImplementedError() + + @staticmethod + def _map_response(resp_data: bytes, sensors: Tuple[Sensor, ...], incl_xx: bool = True) -> Dict[str, Any]: + """Process the response data and return dictionary with runtime values""" + with io.BytesIO(resp_data) as buffer: + result = {} + for sensor in sensors: + if incl_xx or not sensor.id_.startswith("xx"): + try: + result[sensor.id_] = sensor.read(buffer) + except ValueError: + logger.exception("Error reading sensor %s.", sensor.id_) + result[sensor.id_] = None + return result + + @staticmethod + def _decode(data: bytes) -> str: + """Decode the bytes to ascii string""" + try: + if any(x < 32 for x in data): + return data.hex() + return data.decode("ascii") + except ValueError: + return data.hex() diff --git a/goodwe/modbus.py b/goodwe/modbus.py new file mode 100644 index 0000000..daab4d4 --- /dev/null +++ b/goodwe/modbus.py @@ -0,0 +1,149 @@ +import logging +from typing import Union + +from .exceptions import RequestRejectedException + +logger = logging.getLogger(__name__) + +MODBUS_READ_CMD: int = 0x3 +MODBUS_WRITE_CMD: int = 0x6 +MODBUS_WRITE_MULTI_CMD: int = 0x10 + +FAILURE_CODES = { + 1: "ILLEGAL FUNCTION", + 2: "ILLEGAL DATA ADDRESS", + 3: "ILLEGAL DATA VALUE", + 4: "SLAVE DEVICE FAILURE", + 5: "ACKNOWLEDGE", + 6: "SLAVE DEVICE BUSY", + 7: "NEGATIVE ACKNOWLEDGEMENT", + 8: "MEMORY PARITY ERROR", + 10: "GATEWAY PATH UNAVAILABLE", + 11: "GATEWAY TARGET DEVICE FAILED TO RESPOND", +} + + +def _create_crc16_table() -> tuple: + """Construct (modbus) CRC-16 table""" + table = [] + for i in range(256): + buffer = i << 1 + crc = 0 + for _ in range(8, 0, -1): + buffer >>= 1 + if (buffer ^ crc) & 0x0001: + crc = (crc >> 1) ^ 0xA001 + else: + crc >>= 1 + table.append(crc) + return tuple(table) + + +_CRC_16_TABLE = _create_crc16_table() + + +def _modbus_checksum(data: Union[bytearray, bytes]) -> int: + """ + Calculate modbus crc-16 checksum + """ + crc = 0xFFFF + for ch in data: + crc = (crc >> 8) ^ _CRC_16_TABLE[(crc ^ ch) & 0xFF] + return crc + + +def create_modbus_request(comm_addr: int, cmd: int, offset: int, value: int) -> bytes: + """ + Create modbus request. + data[0] is inverter address + data[1] is modbus command + data[2:3] is command offset parameter + data[4:5] is command value parameter + data[6:7] is crc-16 checksum + """ + data: bytearray = bytearray(6) + data[0] = comm_addr + data[1] = cmd + data[2] = (offset >> 8) & 0xFF + data[3] = offset & 0xFF + data[4] = (value >> 8) & 0xFF + data[5] = value & 0xFF + checksum = _modbus_checksum(data) + data.append(checksum & 0xFF) + data.append((checksum >> 8) & 0xFF) + return bytes(data) + + +def create_modbus_multi_request(comm_addr: int, cmd: int, offset: int, values: bytes) -> bytes: + """ + Create modbus (multi value) request. + data[0] is inverter address + data[1] is modbus command + data[2:3] is command offset parameter + data[4:5] is number of registers + data[6] is number of bytes + data[7-n] is data payload + data[n+1:n+2] is crc-16 checksum + """ + data: bytearray = bytearray(7) + data[0] = comm_addr + data[1] = cmd + data[2] = (offset >> 8) & 0xFF + data[3] = offset & 0xFF + data[4] = 0 + data[5] = len(values) // 2 + data[6] = len(values) + data.extend(values) + checksum = _modbus_checksum(data) + data.append(checksum & 0xFF) + data.append((checksum >> 8) & 0xFF) + return bytes(data) + + +def validate_modbus_response(data: bytes, cmd: int, offset: int, value: int) -> bool: + """ + Validate the modbus response. + data[0:1] is header + data[2] is source address + data[3] is command return type + data[4] is response payload length (for read commands) + data[-2:] is crc-16 checksum + """ + if len(data) <= 4: + logger.debug("Response is too short.") + return False + if data[3] == MODBUS_READ_CMD: + if data[4] != value * 2: + logger.debug("Response has unexpected length: %d, expected %d.", data[4], value * 2) + return False + expected_length = data[4] + 7 + if len(data) < expected_length: + logger.debug("Response is too short: %d, expected %d.", len(data), expected_length) + return False + elif data[3] in (MODBUS_WRITE_CMD, MODBUS_WRITE_MULTI_CMD): + if len(data) < 10: + logger.debug("Response has unexpected length: %d, expected %d.", len(data), 10) + return False + expected_length = 10 + response_offset = int.from_bytes(data[4:6], byteorder='big', signed=False) + if response_offset != offset: + logger.debug("Response has wrong offset: %X, expected %X.", response_offset, offset) + return False + response_value = int.from_bytes(data[6:8], byteorder='big', signed=True) + if response_value != value: + logger.debug("Response has wrong value: %X, expected %X.", response_value, value) + return False + else: + expected_length = len(data) + + checksum_offset = expected_length - 2 + if _modbus_checksum(data[2:checksum_offset]) != ((data[checksum_offset + 1] << 8) + data[checksum_offset]): + logger.debug("Response CRC-16 checksum does not match.") + return False + + if data[3] != cmd: + failure_code = FAILURE_CODES.get(data[4], "UNKNOWN") + logger.debug("Response is command failure: %s.", FAILURE_CODES.get(data[4], "UNKNOWN")) + raise RequestRejectedException(failure_code) + + return True diff --git a/goodwe/model.py b/goodwe/model.py new file mode 100644 index 0000000..aca0649 --- /dev/null +++ b/goodwe/model.py @@ -0,0 +1,26 @@ +from .inverter import Inverter + +# Serial number tags to identify inverter type +ET_MODEL_TAGS = ["ETU", "ETL", "ETR", "ETC", "EHU", "EHR", "EHB", "BTU", "BTN", "BTC", "BHU", "AES", "ABP", "HHI", + "HSB", "HUA", "CUA", + "ESN", "EMN", "ERN", "EBN", # ES Gen 2 + "HLB", "HMB", "HBB", "SPN"] # Gen 2 +ES_MODEL_TAGS = ["ESU", "EMU", "ESA", "BPS", "BPU", "EMJ", "IJL"] +DT_MODEL_TAGS = ["DTU", "DTS", "MSU", "MST", "DSN", "DTN", "DST", "NSU", "SSN", "SST", "SSX", "SSY", "PSB", "PSC"] + +SINGLE_PHASE_MODELS = ["DSN", "DST", "NSU", "SSN", "SST", "SSX", "SSY", # DT + "MSU", "MST", "PSB", "PSC", + "EHU", "EHR", "HSB", # ET + "ESN", "EMN", "ERN", "EBN", "HLB", "HMB", "HBB", "SPN"] # ES Gen 2 + + +def is_single_phase(inverter: Inverter) -> bool: + return any(model in inverter.serial_number for model in SINGLE_PHASE_MODELS) + + +def is_3_mptt(inverter: Inverter) -> bool: + return any(model in inverter.serial_number for model in ["MSU", "MST", "PSC"]) + + +def is_4_mptt(inverter: Inverter) -> bool: + return any(model in inverter.serial_number for model in ["HSB"]) diff --git a/goodwe/processor.py b/goodwe/processor.py new file mode 100644 index 0000000..287484e --- /dev/null +++ b/goodwe/processor.py @@ -0,0 +1,37 @@ +from abc import ABC +from dataclasses import dataclass, field +from datetime import datetime + +from goodwe.protocol import ProtocolCommand + + +@dataclass(init=True, order=True) +class ProcessorResult: + sort_index: datetime = field(init=False) + date: datetime + volts_dc: float + current_dc: float + volts_ac: float + current_ac: float + frequency_ac: float + generation_today: float + generation_total: float + rssi: float + operational_hours: float + temperature: float + power: float + status: str + + def __post_init__(self) -> None: + self.sort_index = self.date + + def __str__(self) -> str: + return f'{self.date.strftime("%Y-%m-%d %H:%M:%S")}: (status: {self.status}, power: {self.power})' + + +class AbstractDataProcessor(ABC): + def process_data(self, data: bytes) -> ProcessorResult: + """Process the data provided by the GoodWe inverter and return ProcessorResult""" + + def get_runtime_data_command(self) -> ProtocolCommand: + """Answer protocol command for reading runtime data""" diff --git a/goodwe/protocol.py b/goodwe/protocol.py new file mode 100644 index 0000000..3a0123d --- /dev/null +++ b/goodwe/protocol.py @@ -0,0 +1,270 @@ +from __future__ import annotations + +import asyncio +import logging +from asyncio.futures import Future +from typing import Tuple, Optional, Callable + +from .const import GOODWE_UDP_PORT +from .exceptions import MaxRetriesException, RequestFailedException, RequestRejectedException +from .modbus import create_modbus_request, create_modbus_multi_request, validate_modbus_response, MODBUS_READ_CMD, \ + MODBUS_WRITE_CMD, MODBUS_WRITE_MULTI_CMD + +logger = logging.getLogger(__name__) + + +class UdpInverterProtocol(asyncio.DatagramProtocol): + def __init__( + self, + response_future: Future, + command: ProtocolCommand, + timeout: int, + retries: int + ): + super().__init__() + self.response_future: Future = response_future + self.command: ProtocolCommand = command + self._transport: asyncio.transports.DatagramTransport | None = None + self._retry_timeout: int = timeout + self._max_retries: int = retries + self._retries: int = 0 + + def connection_made(self, transport: asyncio.DatagramTransport) -> None: + """On connection made""" + self._transport = transport + self._send_request() + + def connection_lost(self, exc: Optional[Exception]) -> None: + """On connection lost""" + if exc is not None: + logger.debug("Socket closed with error: %s.", exc) + # Cancel Future on connection lost + if not self.response_future.done(): + self.response_future.cancel() + + def datagram_received(self, data: bytes, addr: Tuple[str, int]) -> None: + """On datagram received""" + try: + if self.command.validator(data): + logger.debug("Received: %s", data.hex()) + self.response_future.set_result(data) + else: + logger.debug("Received invalid response: %s", data.hex()) + self._retries += 1 + self._send_request() + except RequestRejectedException as ex: + logger.debug("Received exception response: %s", data.hex()) + self.response_future.set_exception(ex) + + def error_received(self, exc: Exception) -> None: + """On error received""" + logger.debug("Received error: %s", exc) + self.response_future.set_exception(exc) + + def _send_request(self) -> None: + """Send message via transport""" + logger.debug("Sending: %s%s", self.command, + f' - retry #{self._retries}/{self._max_retries}' if self._retries > 0 else '') + self._transport.sendto(self.command.request) + asyncio.get_event_loop().call_later(self._retry_timeout, self._retry_mechanism) + + def _retry_mechanism(self) -> None: + """Retry mechanism to prevent hanging transport""" + if self.response_future.done(): + self._transport.close() + elif self._retries < self._max_retries: + logger.debug("Failed to receive response to %s in time (%ds).", self.command, self._retry_timeout) + self._retries += 1 + self._send_request() + else: + logger.debug("Max number of retries (%d) reached, request %s failed.", self._max_retries, self.command) + self.response_future.set_exception(MaxRetriesException) + + +class ProtocolCommand: + """Definition of inverter protocol command""" + + def __init__(self, request: bytes, validator: Callable[[bytes], bool]): + self.request: bytes = request + self.validator: Callable[[bytes], bool] = validator + + def __repr__(self): + return self.request.hex() + + async def execute(self, host: str, timeout: int, retries: int) -> bytes: + """ + Execute the udp protocol command on the specified address/port. + Since the UDP communication is by definition unreliable, when no (valid) response is received by specified + timeout, the command will be re-tried up to retries times. + + Return raw response data + """ + loop = asyncio.get_running_loop() + response_future = loop.create_future() + transport, _ = await loop.create_datagram_endpoint( + lambda: UdpInverterProtocol(response_future, self, timeout, retries), + remote_addr=(host, GOODWE_UDP_PORT), + ) + try: + await response_future + result = response_future.result() + if result is not None: + return result + else: + raise RequestFailedException( + "No response received to '" + self.request.hex() + "' request." + ) + except asyncio.CancelledError: + raise RequestFailedException( + "No valid response received to '" + self.request.hex() + "' request." + ) from None + finally: + transport.close() + + +class Aa55ProtocolCommand(ProtocolCommand): + """ + Inverter communication protocol seen mostly on older generations of inverters. + Quite probably it is some variation of the protocol used on RS-485 serial link, + extended/adapted to UDP transport layer. + + Each request starts with header of 0xAA, 0x55, then 0xC0, 0x7F (probably some sort of address/command) + followed by actual payload data. + It is suffixed with 2 bytes of plain checksum of header+payload. + + Response starts again with 0xAA, 0x55, then 0x7F, 0xC0. + 5-6th bytes are some response type, byte 7 is length of the response payload. + The last 2 bytes are again plain checksum of header+payload. + """ + + def __init__(self, payload: str, response_type: str): + super().__init__( + bytes.fromhex( + "AA55C07F" + + payload + + self._checksum(bytes.fromhex("AA55C07F" + payload)).hex() + ), + lambda x: self._validate_response(x, response_type), + ) + + @staticmethod + def _checksum(data: bytes) -> bytes: + checksum = 0 + for each in data: + checksum += each + return checksum.to_bytes(2, byteorder="big", signed=False) + + @staticmethod + def _validate_response(data: bytes, response_type: str) -> bool: + """ + Validate the response. + data[0:3] is header + data[4:5] is response type + data[6] is response payload length + data[-2:] is checksum (plain sum of response data incl. header) + """ + if len(data) <= 8 or len(data) != data[6] + 9: + logger.debug("Response has unexpected length: %d, expected %d.", len(data), data[6] + 9) + return False + elif response_type: + data_rt_int = int.from_bytes(data[4:6], byteorder="big", signed=True) + if int(response_type, 16) != data_rt_int: + logger.debug("Response type unexpected: %04x, expected %s.", data_rt_int, response_type) + return False + checksum = 0 + for each in data[:-2]: + checksum += each + if checksum != int.from_bytes(data[-2:], byteorder="big", signed=True): + logger.debug("Response checksum does not match.") + return False + return True + + +class Aa55ReadCommand(Aa55ProtocolCommand): + """ + Inverter modbus READ command for retrieving modbus registers starting at register # + """ + + def __init__(self, offset: int, count: int): + super().__init__("011A03" + "{:04x}".format(offset) + "{:02x}".format(count), "019A") + + +class Aa55WriteCommand(Aa55ProtocolCommand): + """ + Inverter aa55 WRITE command setting single register # value + """ + + def __init__(self, register: int, value: int): + super().__init__("023905" + "{:04x}".format(register) + "01" + "{:04x}".format(value), "02B9") + + +class Aa55WriteMultiCommand(Aa55ProtocolCommand): + """ + Inverter aa55 WRITE command setting multiple register # value + """ + + def __init__(self, offset: int, values: bytes): + super().__init__("02390B" + "{:04x}".format(offset) + "{:02x}".format(len(values)) + values.hex(), + "02B9") + + +class ModbusProtocolCommand(ProtocolCommand): + """ + Inverter communication protocol seen on newer generation of inverters, based on Modbus + protocol over UDP transport layer. + The modbus communication is rather simple, there are "registers" at specified addresses/offsets, + each represented by 2 bytes. The protocol may query/update individual or range of these registers. + Each register represents some measured value or operational settings. + It's inverter implementation specific which register means what. + Some values may span more registers (i.e. 4bytes measurement value over 2 registers). + + Every request usually starts with communication address (usually 0xF7, but can be changed). + Second byte is the modbus command - 0x03 read multiple, 0x06 write single, 0x10 write multiple. + Bytes 3-4 represent the register address (or start of range) + Bytes 5-6 represent the command parameter (range size or actual value for write). + Last 2 bytes of request is the CRC-16 (modbus flavor) of the request. + + Responses seem to always start with 0xAA, 0x55, then the comm_addr and modbus command. + (If the command fails, the highest bit of command is set to 1 ?) + For read requests, next byte is response payload length, then the actual payload. + Last 2 bytes of response is again the CRC-16 of the response. + """ + + def __init__(self, request: bytes, cmd: int, offset: int, value: int): + super().__init__( + request, + lambda x: validate_modbus_response(x, cmd, offset, value), + ) + + +class ModbusReadCommand(ModbusProtocolCommand): + """ + Inverter modbus READ command for retrieving modbus registers starting at register # + """ + + def __init__(self, comm_addr: int, offset: int, count: int): + super().__init__( + create_modbus_request(comm_addr, MODBUS_READ_CMD, offset, count), + MODBUS_READ_CMD, offset, count) + + +class ModbusWriteCommand(ModbusProtocolCommand): + """ + Inverter modbus WRITE command setting single modbus register # value + """ + + def __init__(self, comm_addr: int, register: int, value: int): + super().__init__( + create_modbus_request(comm_addr, MODBUS_WRITE_CMD, register, value), + MODBUS_WRITE_CMD, register, value) + + +class ModbusWriteMultiCommand(ModbusProtocolCommand): + """ + Inverter modbus WRITE command setting multiple modbus register # value + """ + + def __init__(self, comm_addr: int, offset: int, values: bytes): + super().__init__( + create_modbus_multi_request(comm_addr, MODBUS_WRITE_MULTI_CMD, offset, values), + MODBUS_WRITE_MULTI_CMD, offset, len(values) // 2) diff --git a/goodwe/sensor.py b/goodwe/sensor.py new file mode 100644 index 0000000..b9fe1db --- /dev/null +++ b/goodwe/sensor.py @@ -0,0 +1,761 @@ +from __future__ import annotations + +import io +from abc import ABC, abstractmethod +from datetime import datetime +from struct import unpack +from typing import Any, Callable, Optional + +from .const import * +from .inverter import Sensor, SensorKind + +DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] + + +class Voltage(Sensor): + """Sensor representing voltage [V] value encoded in 2 bytes""" + + def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind]): + super().__init__(id_, offset, name, 2, "V", kind) + + def read_value(self, data: io.BytesIO): + return read_voltage(data) + + def encode_value(self, value: Any) -> bytes: + return encode_voltage(value) + + +class Current(Sensor): + """Sensor representing current [A] value encoded in 2 bytes""" + + def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind]): + super().__init__(id_, offset, name, 2, "A", kind) + + def read_value(self, data: io.BytesIO): + return read_current(data) + + def encode_value(self, value: Any) -> bytes: + return encode_current(value) + + +class Frequency(Sensor): + """Sensor representing frequency [Hz] value encoded in 2 bytes""" + + def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind]): + super().__init__(id_, offset, name, 2, "Hz", kind) + + def read_value(self, data: io.BytesIO): + return read_freq(data) + + +class Power(Sensor): + """Sensor representing power [W] value encoded in 2 bytes""" + + def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind]): + super().__init__(id_, offset, name, 2, "W", kind) + + def read_value(self, data: io.BytesIO): + return read_bytes2(data) + + +class Power4(Sensor): + """Sensor representing power [W] value encoded in 4 bytes""" + + def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind]): + super().__init__(id_, offset, name, 4, "W", kind) + + def read_value(self, data: io.BytesIO): + return read_bytes4(data) + + +class Energy(Sensor): + """Sensor representing energy [kWh] value encoded in 2 bytes""" + + def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind]): + super().__init__(id_, offset, name, 2, "kWh", kind) + + def read_value(self, data: io.BytesIO): + value = read_bytes2(data) + if value == -1: + return None + else: + return float(value) / 10 + + +class Energy4(Sensor): + """Sensor representing energy [kWh] value encoded in 4 bytes""" + + def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind]): + super().__init__(id_, offset, name, 4, "kWh", kind) + + def read_value(self, data: io.BytesIO): + value = read_bytes4(data) + if value == -1: + return None + else: + return float(value) / 10 + + +class Apparent(Sensor): + """Sensor representing apparent power [VA] value encoded in 2 bytes""" + + def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind]): + super().__init__(id_, offset, name, 2, "VA", kind) + + def read_value(self, data: io.BytesIO): + return read_bytes2(data) + + +class Apparent4(Sensor): + """Sensor representing apparent power [VA] value encoded in 4 bytes""" + + def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind]): + super().__init__(id_, offset, name, 2, "VA", kind) + + def read_value(self, data: io.BytesIO): + return read_bytes4(data) + + +class Reactive(Sensor): + """Sensor representing reactive power [var] value encoded in 2 bytes""" + + def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind]): + super().__init__(id_, offset, name, 2, "var", kind) + + def read_value(self, data: io.BytesIO): + return read_bytes2(data) + + +class Reactive4(Sensor): + """Sensor representing reactive power [var] value encoded in 4 bytes""" + + def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind]): + super().__init__(id_, offset, name, 2, "var", kind) + + def read_value(self, data: io.BytesIO): + return read_bytes4(data) + + +class Temp(Sensor): + """Sensor representing temperature [C] value encoded in 2 bytes""" + + def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind] = None): + super().__init__(id_, offset, name, 2, "C", kind) + + def read_value(self, data: io.BytesIO): + return read_temp(data) + + +class Byte(Sensor): + """Sensor representing signed int value encoded in 1 byte""" + + def __init__(self, id_: str, offset: int, name: str, unit: str = "", kind: Optional[SensorKind] = None): + super().__init__(id_, offset, name, 1, unit, kind) + + def read_value(self, data: io.BytesIO): + return read_byte(data) + + def encode_value(self, value: Any) -> bytes: + raise NotImplementedError() + + +class ByteH(Byte): + """Sensor representing signed int value encoded in 1 byte (high 8 bits of 16bit register)""" + + def __init__(self, id_: str, offset: int, name: str, unit: str = "", kind: Optional[SensorKind] = None): + super().__init__(id_, offset, name, unit, kind) + + def encode_value(self, value: Any, register_value: bytes) -> bytes: + word = bytearray(register_value) + word[0] = int.to_bytes(int(value), length=1, byteorder="big", signed=True)[0] + return bytes(word) + + +class ByteL(Byte): + """Sensor representing signed int value encoded in 1 byte (low 8 bits of 16bit register)""" + + def __init__(self, id_: str, offset: int, name: str, unit: str = "", kind: Optional[SensorKind] = None): + super().__init__(id_, offset, name, unit, kind) + + def encode_value(self, value: Any, register_value: bytes) -> bytes: + word = bytearray(register_value) + word[1] = int.to_bytes(int(value), length=1, byteorder="big", signed=True)[0] + return bytes(word) + + +class Integer(Sensor): + """Sensor representing signed int value encoded in 2 bytes""" + + def __init__(self, id_: str, offset: int, name: str, unit: str = "", kind: Optional[SensorKind] = None): + super().__init__(id_, offset, name, 2, unit, kind) + + def read_value(self, data: io.BytesIO): + return read_bytes2(data) + + def encode_value(self, value: Any) -> bytes: + return int.to_bytes(int(value), length=2, byteorder="big", signed=True) + + +class Long(Sensor): + """Sensor representing signed int value encoded in 4 bytes""" + + def __init__(self, id_: str, offset: int, name: str, unit: str = "", kind: Optional[SensorKind] = None): + super().__init__(id_, offset, name, 4, unit, kind) + + def read_value(self, data: io.BytesIO): + return read_bytes4(data) + + def encode_value(self, value: Any) -> bytes: + return int.to_bytes(int(value), length=4, byteorder="big", signed=True) + + +class Decimal(Sensor): + """Sensor representing signed decimal value encoded in 2 bytes""" + + def __init__(self, id_: str, offset: int, scale: int, name: str, unit: str = "", kind: Optional[SensorKind] = None): + super().__init__(id_, offset, name, 2, unit, kind) + self.scale = scale + + def read_value(self, data: io.BytesIO): + return read_decimal2(data, self.scale) + + def encode_value(self, value: Any) -> bytes: + return int.to_bytes(int(value * self.scale), length=2, byteorder="big", signed=True) + + +class Float(Sensor): + """Sensor representing signed int value encoded in 4 bytes""" + + def __init__(self, id_: str, offset: int, scale: int, name: str, unit: str = "", kind: Optional[SensorKind] = None): + super().__init__(id_, offset, name, 4, unit, kind) + self.scale = scale + + def read_value(self, data: io.BytesIO): + return round(read_float4(data) / self.scale, 3) + + +class Timestamp(Sensor): + """Sensor representing datetime value encoded in 6 bytes""" + + def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind] = None): + super().__init__(id_, offset, name, 6, "", kind) + + def read_value(self, data: io.BytesIO): + return read_datetime(data) + + def encode_value(self, value: Any) -> bytes: + return encode_datetime(value) + + +class Enum(Sensor): + """Sensor representing label from enumeration encoded in 1 bytes""" + + def __init__(self, id_: str, offset: int, labels: Dict, name: str, kind: Optional[SensorKind] = None): + super().__init__(id_, offset, name, 1, "", kind) + self._labels: Dict = labels + + def read_value(self, data: io.BytesIO): + return self._labels.get(read_byte(data)) + + +class Enum2(Sensor): + """Sensor representing label from enumeration encoded in 2 bytes""" + + def __init__(self, id_: str, offset: int, labels: Dict, name: str, kind: Optional[SensorKind] = None): + super().__init__(id_, offset, name, 2, "", kind) + self._labels: Dict = labels + + def read_value(self, data: io.BytesIO): + return self._labels.get(read_bytes2(data)) + + +class EnumBitmap4(Sensor): + """Sensor representing label from bitmap encoded in 4 bytes""" + + def __init__(self, id_: str, offset: int, labels: Dict, name: str, kind: Optional[SensorKind] = None): + super().__init__(id_, offset, name, 4, "", kind) + self._labels: Dict = labels + + def read_value(self, data: io.BytesIO) -> Any: + raise NotImplementedError() + + def read(self, data: io.BytesIO): + return decode_bitmap(read_bytes4(data, self.offset), self._labels) + + +class EnumBitmap22(Sensor): + """Sensor representing label from bitmap encoded in 2+2 bytes""" + + def __init__(self, id_: str, offsetH: int, offsetL: int, labels: Dict, name: str, + kind: Optional[SensorKind] = None): + super().__init__(id_, offsetH, name, 2, "", kind) + self._labels: Dict = labels + self._offsetL: int = offsetL + + def read_value(self, data: io.BytesIO) -> Any: + raise NotImplementedError() + + def read(self, data: io.BytesIO): + return decode_bitmap(read_bytes2(data, self.offset) << 16 + read_bytes2(data, self._offsetL), self._labels) + + +class EnumCalculated(Sensor): + """Sensor representing label from enumeration of calculated value""" + + def __init__(self, id_: str, getter: Callable[[io.BytesIO], Any], labels: Dict, name: str, + kind: Optional[SensorKind] = None): + super().__init__(id_, 0, name, 0, "", kind) + self._getter: Callable[[io.BytesIO], Any] = getter + self._labels: Dict = labels + + def read_value(self, data: io.BytesIO) -> Any: + raise NotImplementedError() + + def read(self, data: io.BytesIO): + return self._labels.get(self._getter(data)) + + +class EcoMode(ABC): + """Sensor representing Eco Mode Battery Power Group API""" + + @abstractmethod + def encode_charge(self, eco_mode_power: int, eco_mode_soc: int = 100) -> bytes: + """Answer bytes representing all the time enabled charging eco mode group""" + + @abstractmethod + def encode_discharge(self, eco_mode_power: int) -> bytes: + """Answer bytes representing all the time enabled discharging eco mode group""" + + @abstractmethod + def encode_off(self) -> bytes: + """Answer bytes representing empty and disabled eco mode group""" + + @abstractmethod + def is_eco_charge_mode(self) -> bool: + """Answer if it represents the emulated 24/7 fulltime discharge mode""" + + @abstractmethod + def is_eco_discharge_mode(self) -> bool: + """Answer if it represents the emulated 24/7 fulltime discharge mode""" + + +class EcoModeV1(Sensor, EcoMode): + """Sensor representing Eco Mode Battery Power Group encoded in 8 bytes""" + + def __init__(self, id_: str, offset: int, name: str): + super().__init__(id_, offset, name, 8, "", SensorKind.BAT) + self.start_h: int | None = None + self.start_m: int | None = None + self.end_h: int | None = None + self.end_m: int | None = None + self.power: int | None = None + self.on_off: int | None = None + self.day_bits: int | None = None + self.days: str | None = None + self.soc: int = 100 # just to keep same API with V2 + + def __str__(self): + return f"{self.start_h}:{self.start_m}-{self.end_h}:{self.end_m} {self.days} {self.power}% {'On' if self.on_off != 0 else 'Off'}" + + def read_value(self, data: io.BytesIO): + self.start_h = read_byte(data) + if (self.start_h < 0 or self.start_h > 23) and self.start_h != 48: + raise ValueError(f"{self.id_}: start_h value {self.start_h} out of range.") + self.start_m = read_byte(data) + if self.start_m < 0 or self.start_m > 59: + raise ValueError(f"{self.id_}: start_m value {self.start_m} out of range.") + self.end_h = read_byte(data) + if (self.end_h < 0 or self.end_h > 23) and self.end_h != 48: + raise ValueError(f"{self.id_}: end_h value {self.end_h} out of range.") + self.end_m = read_byte(data) + if self.end_m < 0 or self.end_m > 59: + raise ValueError(f"{self.id_}: end_m value {self.end_m} out of range.") + self.power = read_bytes2(data) # negative=charge, positive=discharge + if self.power < -100 or self.power > 100: + raise ValueError(f"{self.id_}: power value {self.power} out of range.") + self.on_off = read_byte(data) + if self.on_off not in (0, -1): + raise ValueError(f"{self.id_}: on_off value {self.on_off} out of range.") + self.day_bits = read_byte(data) + self.days = decode_day_of_week(self.day_bits) + if self.day_bits < 0: + raise ValueError(f"{self.id_}: day_bits value {self.day_bits} out of range.") + return self + + def encode_value(self, value: Any) -> bytes: + if isinstance(value, bytes) and len(value) == 8: + # try to read_value to check if values are valid + if self.read_value(io.BytesIO(value)): + return value + raise ValueError + + def encode_charge(self, eco_mode_power: int, eco_mode_soc: int = 100) -> bytes: + """Answer bytes representing all the time enabled charging eco mode group""" + return bytes.fromhex("0000173b{:04x}ff7f".format((-1 * abs(eco_mode_power)) & (2 ** 16 - 1))) + + def encode_discharge(self, eco_mode_power: int) -> bytes: + """Answer bytes representing all the time enabled discharging eco mode group""" + return bytes.fromhex("0000173b{:04x}ff7f".format(abs(eco_mode_power))) + + def encode_off(self) -> bytes: + """Answer bytes representing empty and disabled eco mode group""" + return bytes.fromhex("3000300000640000") + + def is_eco_charge_mode(self) -> bool: + """Answer if it represents the emulated 24/7 fulltime discharge mode""" + return self.start_h == 0 \ + and self.start_m == 0 \ + and self.end_h == 23 \ + and self.end_m == 59 \ + and self.on_off != 0 \ + and self.day_bits == 127 \ + and self.power < 0 + + def is_eco_discharge_mode(self) -> bool: + """Answer if it represents the emulated 24/7 fulltime discharge mode""" + return self.start_h == 0 \ + and self.start_m == 0 \ + and self.end_h == 23 \ + and self.end_m == 59 \ + and self.on_off != 0 \ + and self.day_bits == 127 \ + and self.power > 0 + + def as_eco_mode_v2(self) -> EcoModeV2: + """Convert V1 to V2 EcoMode""" + result = EcoModeV2(self.id_, self.offset, self.name) + result.start_h = self.start_h + result.start_m = self.start_m + result.end_h = self.end_h + result.end_m = self.end_m + result.power = self.power + result.on_off = self.on_off + result.day_bits = self.day_bits + result.days = decode_day_of_week(self.day_bits) + result.soc = 100 + return result + + +class EcoModeV2(Sensor, EcoMode): + """Sensor representing Eco Mode Battery Power Group encoded in 12 bytes""" + + def __init__(self, id_: str, offset: int, name: str): + super().__init__(id_, offset, name, 12, "", SensorKind.BAT) + self.start_h: int | None = None + self.start_m: int | None = None + self.end_h: int | None = None + self.end_m: int | None = None + self.on_off: int | None = None + self.day_bits: int | None = None + self.days: str | None = None + self.power: int | None = None + self.soc: int | None = None + # 2 bytes padding 0000 + + def __str__(self): + return f"{self.start_h}:{self.start_m}-{self.end_h}:{self.end_m} {self.days} {self.power}% (SoC {self.soc}%) {'On' if self.on_off != 0 else 'Off'}" + + def read_value(self, data: io.BytesIO): + self.start_h = read_byte(data) + if (self.start_h < 0 or self.start_h > 23) and self.start_h != 48: + raise ValueError(f"{self.id_}: start_h value {self.start_h} out of range.") + self.start_m = read_byte(data) + if self.start_m < 0 or self.start_m > 59: + raise ValueError(f"{self.id_}: start_m value {self.start_m} out of range.") + self.end_h = read_byte(data) + if (self.end_h < 0 or self.end_h > 23) and self.end_h != 48: + raise ValueError(f"{self.id_}: end_h value {self.end_h} out of range.") + self.end_m = read_byte(data) + if self.end_m < 0 or self.end_m > 59: + raise ValueError(f"{self.id_}: end_m value {self.end_m} out of range.") + self.on_off = read_byte(data) + if self.on_off not in (0, -1): + raise ValueError(f"{self.id_}: on_off value {self.on_off} out of range.") + self.day_bits = read_byte(data) + self.days = decode_day_of_week(self.day_bits) + if self.day_bits < 0: + raise ValueError(f"{self.id_}: day_bits value {self.day_bits} out of range.") + self.power = read_bytes2(data) # negative=charge, positive=discharge + if self.power < -100 or self.power > 100: + raise ValueError(f"{self.id_}: power value {self.power} out of range.") + self.soc = read_bytes2(data) + if self.soc < 0 or self.soc > 100: + raise ValueError(f"{self.id_}: SoC value {self.soc} out of range.") + return self + + def encode_value(self, value: Any) -> bytes: + if isinstance(value, bytes) and len(value) == 12: + # try to read_value to check if values are valid + if self.read_value(io.BytesIO(value)): + return value + raise ValueError + + def encode_charge(self, eco_mode_power: int, eco_mode_soc: int = 100) -> bytes: + """Answer bytes representing all the time enabled charging eco mode group""" + return bytes.fromhex( + "0000173bff7f{:04x}{:04x}0000".format((-1 * abs(eco_mode_power)) & (2 ** 16 - 1), eco_mode_soc)) + + def encode_discharge(self, eco_mode_power: int) -> bytes: + """Answer bytes representing all the time enabled discharging eco mode group""" + return bytes.fromhex("0000173bff7f{:04x}00640000".format(abs(eco_mode_power))) + + def encode_off(self) -> bytes: + """Answer bytes representing empty and disabled eco mode group""" + return bytes.fromhex("300030000000006400640000") + + def is_eco_charge_mode(self) -> bool: + """Answer if it represents the emulated 24/7 fulltime discharge mode""" + return self.start_h == 0 \ + and self.start_m == 0 \ + and self.end_h == 23 \ + and self.end_m == 59 \ + and self.on_off != 0 \ + and self.day_bits == 127 \ + and self.power < 0 + + def is_eco_discharge_mode(self) -> bool: + """Answer if it represents the emulated 24/7 fulltime discharge mode""" + return self.start_h == 0 \ + and self.start_m == 0 \ + and self.end_h == 23 \ + and self.end_m == 59 \ + and self.on_off != 0 \ + and self.day_bits == 127 \ + and self.power > 0 + + def as_eco_mode_v1(self) -> EcoModeV1: + """Convert V2 to V1 EcoMode""" + result = EcoModeV1(self.id_, self.offset, self.name) + result.start_h = self.start_h + result.start_m = self.start_m + result.end_h = self.end_h + result.end_m = self.end_m + result.power = self.power + result.on_off = self.on_off + result.day_bits = self.day_bits + result.days = self.days + return result + + +class PeakShavingMode(Sensor): + """Sensor representing Peak Shaving Mode encoded in 12 bytes""" + + def __init__(self, id_: str, offset: int, name: str): + super().__init__(id_, offset, name, 12, "", SensorKind.BAT) + self.start_h: int | None = None + self.start_m: int | None = None + self.end_h: int | None = None + self.end_m: int | None = None + self.on_off: int | None = None + self.day_bits: int | None = None + self.days: str | None = None + self.import_power: float | None = None + self.soc: int | None = None + # 2 bytes padding 0000 + + def __str__(self): + return f"{self.start_h}:{self.start_m}-{self.end_h}:{self.end_m} {self.days} {self.import_power}kW (SoC {self.soc}%) {'On' if self.on_off == -4 else 'Off'}" + + def read_value(self, data: io.BytesIO): + self.start_h = read_byte(data) + if (self.start_h < 0 or self.start_h > 23) and self.start_h != 48: + raise ValueError(f"{self.id_}: start_h value {self.start_h} out of range.") + self.start_m = read_byte(data) + if self.start_m < 0 or self.start_m > 59: + raise ValueError(f"{self.id_}: start_m value {self.start_m} out of range.") + self.end_h = read_byte(data) + if (self.end_h < 0 or self.end_h > 23) and self.end_h != 48: + raise ValueError(f"{self.id_}: end_h value {self.end_h} out of range.") + self.end_m = read_byte(data) + if self.end_m < 0 or self.end_m > 59: + raise ValueError(f"{self.id_}: end_m value {self.end_m} out of range.") + self.on_off = read_byte(data) + if self.on_off not in (-4, 3): + raise ValueError(f"{self.id_}: on_off value {self.on_off} out of range.") + self.day_bits = read_byte(data) + self.days = decode_day_of_week(self.day_bits) + if self.day_bits < 0: + raise ValueError(f"{self.id_}: day_bits value {self.day_bits} out of range.") + self.import_power = read_decimal2(data, 100) + if self.import_power < 0 or self.import_power > 500: + raise ValueError(f"{self.id_}: import_power value {self.import_power} out of range.") + self.soc = read_bytes2(data) + if self.soc < 0 or self.soc > 100: + raise ValueError(f"{self.id_}: soc value {self.soc} out of range.") + return self + + def encode_value(self, value: Any) -> bytes: + if isinstance(value, bytes) and len(value) == 12: + # try to read_value to check if values are valid + if self.read_value(io.BytesIO(value)): + return value + raise ValueError + + def encode_off(self) -> bytes: + """Answer bytes representing empty and disabled eco mode group""" + return bytes.fromhex("300030000000006400640000") + + +class Calculated(Sensor): + """Sensor representing calculated value""" + + def __init__(self, id_: str, getter: Callable[[io.BytesIO], Any], name: str, unit: str, + kind: Optional[SensorKind] = None): + super().__init__(id_, 0, name, 0, unit, kind) + self._getter: Callable[[io.BytesIO], Any] = getter + + def read_value(self, data: io.BytesIO) -> Any: + raise NotImplementedError() + + def read(self, data: io.BytesIO): + return self._getter(data) + + +def read_byte(buffer: io.BytesIO, offset: int = None) -> int: + """Retrieve single byte (signed int) value from buffer""" + if offset is not None: + buffer.seek(offset) + return int.from_bytes(buffer.read(1), byteorder="big", signed=True) + + +def read_bytes2(buffer: io.BytesIO, offset: int = None) -> int: + """Retrieve 2 byte (signed int) value from buffer""" + if offset is not None: + buffer.seek(offset) + return int.from_bytes(buffer.read(2), byteorder="big", signed=True) + + +def read_bytes4(buffer: io.BytesIO, offset: int = None) -> int: + """Retrieve 4 byte (signed int) value from buffer""" + if offset is not None: + buffer.seek(offset) + return int.from_bytes(buffer.read(4), byteorder="big", signed=True) + + +def read_decimal2(buffer: io.BytesIO, scale: int, offset: int = None) -> float: + """Retrieve 2 byte (signed float) value from buffer""" + if offset is not None: + buffer.seek(offset) + return float(int.from_bytes(buffer.read(2), byteorder="big", signed=True)) / scale + + +def read_float4(buffer: io.BytesIO, offset: int = None) -> float: + """Retrieve 4 byte (signed float) value from buffer""" + if offset is not None: + buffer.seek(offset) + data = buffer.read(4) + if len(data) == 4: + return unpack('>f', data)[0] + else: + return float(0) + + +def read_voltage(buffer: io.BytesIO, offset: int = None) -> float: + """Retrieve voltage [V] value (2 bytes) from buffer""" + if offset is not None: + buffer.seek(offset) + value = int.from_bytes(buffer.read(2), byteorder="big", signed=True) + return float(value) / 10 + + +def encode_voltage(value: Any) -> bytes: + """Encode voltage value to raw (2 bytes) payload""" + return int.to_bytes(int(value * 10), length=2, byteorder="big", signed=True) + + +def read_current(buffer: io.BytesIO, offset: int = None) -> float: + """Retrieve current [A] value (2 bytes) from buffer""" + if offset is not None: + buffer.seek(offset) + value = int.from_bytes(buffer.read(2), byteorder="big", signed=True) + return float(value) / 10 + + +def encode_current(value: Any) -> bytes: + """Encode current value to raw (2 bytes) payload""" + return int.to_bytes(int(value * 10), length=2, byteorder="big", signed=True) + + +def read_freq(buffer: io.BytesIO, offset: int = None) -> float: + """Retrieve frequency [Hz] value (2 bytes) from buffer""" + if offset is not None: + buffer.seek(offset) + value = int.from_bytes(buffer.read(2), byteorder="big", signed=True) + return float(value) / 100 + + +def read_temp(buffer: io.BytesIO, offset: int = None) -> float: + """Retrieve temperature [C] value (2 bytes) from buffer""" + if offset is not None: + buffer.seek(offset) + value = int.from_bytes(buffer.read(2), byteorder="big", signed=True) + return float(value) / 10 + + +def read_datetime(buffer: io.BytesIO, offset: int = None) -> datetime: + """Retrieve datetime value (6 bytes) from buffer""" + if offset is not None: + buffer.seek(offset) + year = 2000 + int.from_bytes(buffer.read(1), byteorder='big') + month = int.from_bytes(buffer.read(1), byteorder='big') + day = int.from_bytes(buffer.read(1), byteorder='big') + hour = int.from_bytes(buffer.read(1), byteorder='big') + minute = int.from_bytes(buffer.read(1), byteorder='big') + second = int.from_bytes(buffer.read(1), byteorder='big') + return datetime(year=year, month=month, day=day, hour=hour, minute=minute, second=second) + + +def encode_datetime(value: Any) -> bytes: + """Encode datetime value to raw (6 bytes) payload""" + timestamp = value + if isinstance(value, str): + timestamp = datetime.fromisoformat(value) + + result = bytes([ + timestamp.year - 2000, + timestamp.month, + timestamp.day, + timestamp.hour, + timestamp.minute, + timestamp.second, + ]) + return result + + +def read_grid_mode(buffer: io.BytesIO, offset: int = None) -> int: + """Retrieve 'grid mode' sign value from buffer""" + value = read_bytes2(buffer, offset) + if value < -90: + return 2 + elif value >= 90: + return 1 + else: + return 0 + + +def read_unsigned_int(data: bytes, offset: int) -> int: + """Retrieve 2 byte (unsigned int) value from bytes at specified offset""" + return int.from_bytes(data[offset:offset + 2], byteorder="big", signed=False) + + +def decode_bitmap(value: int, bitmap: Dict[int, str]) -> str: + bits = value + result = [] + for i in range(32): + if bits & 0x1 == 1: + result.append(bitmap.get(i, f'err{i}')) + bits = bits >> 1 + return ", ".join(result) + + +def decode_day_of_week(data: int) -> str: + bits = bin(data)[2:] + daynames = list(DAY_NAMES) + days = "" + for each in bits[::-1]: + if each == '1': + if len(days) > 0: + days += "," + days += daynames[0] + daynames.pop(0) + return days diff --git a/goodwe/xs.py b/goodwe/xs.py new file mode 100644 index 0000000..08fd79c --- /dev/null +++ b/goodwe/xs.py @@ -0,0 +1,43 @@ +import logging + +from .dt import DT +from .processor import ProcessorResult, AbstractDataProcessor +from .protocol import ProtocolCommand +from .sensor import * + +logger = logging.getLogger(__name__) + + +class GoodWeXSProcessor(AbstractDataProcessor): + + def __init__(self): + self.dummy_inverter = DT("localhost") + + def process_data(self, data: bytes) -> ProcessorResult: + """Process the data provided by the GoodWe XS inverter and return ProcessorResult""" + sensors = self.dummy_inverter._map_response(data[5:-2], self.dummy_inverter.sensors()) + + return ProcessorResult( + date=sensors['timestamp'], + volts_dc=sensors['vpv1'], + current_dc=sensors['ipv1'], + volts_ac=sensors['vgrid1'], + current_ac=sensors['igrid1'], + frequency_ac=sensors['fgrid1'], + generation_today=sensors['e_day'], + generation_total=sensors['e_total'], + # this is just response checksum + rssi=self._get_rssi(data), + operational_hours=sensors['h_total'], + temperature=sensors['temperature'], + power=sensors['ppv'], + status=sensors['work_mode_label']) + + def _get_rssi(self, data) -> float: + """Retrieve rssi from GoodWe data""" + with io.BytesIO(data) as buffer: + return read_bytes2(buffer, 149) + + def get_runtime_data_command(self) -> ProtocolCommand: + """Answer protocol command for reading runtime data""" + return self.dummy_inverter._READ_DEVICE_RUNNING_DATA diff --git a/google/__init__.py b/google/__init__.py new file mode 100644 index 0000000..5585614 --- /dev/null +++ b/google/__init__.py @@ -0,0 +1,4 @@ +try: + __import__('pkg_resources').declare_namespace(__name__) +except ImportError: + __path__ = __import__('pkgutil').extend_path(__path__, __name__) diff --git a/google/protobuf/__init__.py b/google/protobuf/__init__.py new file mode 100644 index 0000000..297cccf --- /dev/null +++ b/google/protobuf/__init__.py @@ -0,0 +1,10 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +# Copyright 2007 Google Inc. All Rights Reserved. + +__version__ = '5.27.3' diff --git a/google/protobuf/any_pb2.py b/google/protobuf/any_pb2.py new file mode 100644 index 0000000..4d4b8a0 --- /dev/null +++ b/google/protobuf/any_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: google/protobuf/any.proto +# Protobuf Python Version: 5.27.3 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 3, + '', + 'google/protobuf/any.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19google/protobuf/any.proto\x12\x0fgoogle.protobuf\"6\n\x03\x41ny\x12\x19\n\x08type_url\x18\x01 \x01(\tR\x07typeUrl\x12\x14\n\x05value\x18\x02 \x01(\x0cR\x05valueBv\n\x13\x63om.google.protobufB\x08\x41nyProtoP\x01Z,google.golang.org/protobuf/types/known/anypb\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.any_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\023com.google.protobufB\010AnyProtoP\001Z,google.golang.org/protobuf/types/known/anypb\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' + _globals['_ANY']._serialized_start=46 + _globals['_ANY']._serialized_end=100 +# @@protoc_insertion_point(module_scope) diff --git a/google/protobuf/api_pb2.py b/google/protobuf/api_pb2.py new file mode 100644 index 0000000..8d247d9 --- /dev/null +++ b/google/protobuf/api_pb2.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: google/protobuf/api.proto +# Protobuf Python Version: 5.27.3 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 3, + '', + 'google/protobuf/api.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import source_context_pb2 as google_dot_protobuf_dot_source__context__pb2 +from google.protobuf import type_pb2 as google_dot_protobuf_dot_type__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19google/protobuf/api.proto\x12\x0fgoogle.protobuf\x1a$google/protobuf/source_context.proto\x1a\x1agoogle/protobuf/type.proto\"\xc1\x02\n\x03\x41pi\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x31\n\x07methods\x18\x02 \x03(\x0b\x32\x17.google.protobuf.MethodR\x07methods\x12\x31\n\x07options\x18\x03 \x03(\x0b\x32\x17.google.protobuf.OptionR\x07options\x12\x18\n\x07version\x18\x04 \x01(\tR\x07version\x12\x45\n\x0esource_context\x18\x05 \x01(\x0b\x32\x1e.google.protobuf.SourceContextR\rsourceContext\x12.\n\x06mixins\x18\x06 \x03(\x0b\x32\x16.google.protobuf.MixinR\x06mixins\x12/\n\x06syntax\x18\x07 \x01(\x0e\x32\x17.google.protobuf.SyntaxR\x06syntax\"\xb2\x02\n\x06Method\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12(\n\x10request_type_url\x18\x02 \x01(\tR\x0erequestTypeUrl\x12+\n\x11request_streaming\x18\x03 \x01(\x08R\x10requestStreaming\x12*\n\x11response_type_url\x18\x04 \x01(\tR\x0fresponseTypeUrl\x12-\n\x12response_streaming\x18\x05 \x01(\x08R\x11responseStreaming\x12\x31\n\x07options\x18\x06 \x03(\x0b\x32\x17.google.protobuf.OptionR\x07options\x12/\n\x06syntax\x18\x07 \x01(\x0e\x32\x17.google.protobuf.SyntaxR\x06syntax\"/\n\x05Mixin\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n\x04root\x18\x02 \x01(\tR\x04rootBv\n\x13\x63om.google.protobufB\x08\x41piProtoP\x01Z,google.golang.org/protobuf/types/known/apipb\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.api_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\023com.google.protobufB\010ApiProtoP\001Z,google.golang.org/protobuf/types/known/apipb\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' + _globals['_API']._serialized_start=113 + _globals['_API']._serialized_end=434 + _globals['_METHOD']._serialized_start=437 + _globals['_METHOD']._serialized_end=743 + _globals['_MIXIN']._serialized_start=745 + _globals['_MIXIN']._serialized_end=792 +# @@protoc_insertion_point(module_scope) diff --git a/google/protobuf/compiler/__init__.py b/google/protobuf/compiler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/google/protobuf/compiler/plugin_pb2.py b/google/protobuf/compiler/plugin_pb2.py new file mode 100644 index 0000000..c7ab83b --- /dev/null +++ b/google/protobuf/compiler/plugin_pb2.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: google/protobuf/compiler/plugin.proto +# Protobuf Python Version: 5.27.3 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 3, + '', + 'google/protobuf/compiler/plugin.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%google/protobuf/compiler/plugin.proto\x12\x18google.protobuf.compiler\x1a google/protobuf/descriptor.proto\"c\n\x07Version\x12\x14\n\x05major\x18\x01 \x01(\x05R\x05major\x12\x14\n\x05minor\x18\x02 \x01(\x05R\x05minor\x12\x14\n\x05patch\x18\x03 \x01(\x05R\x05patch\x12\x16\n\x06suffix\x18\x04 \x01(\tR\x06suffix\"\xcf\x02\n\x14\x43odeGeneratorRequest\x12(\n\x10\x66ile_to_generate\x18\x01 \x03(\tR\x0e\x66ileToGenerate\x12\x1c\n\tparameter\x18\x02 \x01(\tR\tparameter\x12\x43\n\nproto_file\x18\x0f \x03(\x0b\x32$.google.protobuf.FileDescriptorProtoR\tprotoFile\x12\\\n\x17source_file_descriptors\x18\x11 \x03(\x0b\x32$.google.protobuf.FileDescriptorProtoR\x15sourceFileDescriptors\x12L\n\x10\x63ompiler_version\x18\x03 \x01(\x0b\x32!.google.protobuf.compiler.VersionR\x0f\x63ompilerVersion\"\x85\x04\n\x15\x43odeGeneratorResponse\x12\x14\n\x05\x65rror\x18\x01 \x01(\tR\x05\x65rror\x12-\n\x12supported_features\x18\x02 \x01(\x04R\x11supportedFeatures\x12\'\n\x0fminimum_edition\x18\x03 \x01(\x05R\x0eminimumEdition\x12\'\n\x0fmaximum_edition\x18\x04 \x01(\x05R\x0emaximumEdition\x12H\n\x04\x66ile\x18\x0f \x03(\x0b\x32\x34.google.protobuf.compiler.CodeGeneratorResponse.FileR\x04\x66ile\x1a\xb1\x01\n\x04\x46ile\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\'\n\x0finsertion_point\x18\x02 \x01(\tR\x0einsertionPoint\x12\x18\n\x07\x63ontent\x18\x0f \x01(\tR\x07\x63ontent\x12R\n\x13generated_code_info\x18\x10 \x01(\x0b\x32\".google.protobuf.GeneratedCodeInfoR\x11generatedCodeInfo\"W\n\x07\x46\x65\x61ture\x12\x10\n\x0c\x46\x45\x41TURE_NONE\x10\x00\x12\x1b\n\x17\x46\x45\x41TURE_PROTO3_OPTIONAL\x10\x01\x12\x1d\n\x19\x46\x45\x41TURE_SUPPORTS_EDITIONS\x10\x02\x42r\n\x1c\x63om.google.protobuf.compilerB\x0cPluginProtosZ)google.golang.org/protobuf/types/pluginpb\xaa\x02\x18Google.Protobuf.Compiler') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.compiler.plugin_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\034com.google.protobuf.compilerB\014PluginProtosZ)google.golang.org/protobuf/types/pluginpb\252\002\030Google.Protobuf.Compiler' + _globals['_VERSION']._serialized_start=101 + _globals['_VERSION']._serialized_end=200 + _globals['_CODEGENERATORREQUEST']._serialized_start=203 + _globals['_CODEGENERATORREQUEST']._serialized_end=538 + _globals['_CODEGENERATORRESPONSE']._serialized_start=541 + _globals['_CODEGENERATORRESPONSE']._serialized_end=1058 + _globals['_CODEGENERATORRESPONSE_FILE']._serialized_start=792 + _globals['_CODEGENERATORRESPONSE_FILE']._serialized_end=969 + _globals['_CODEGENERATORRESPONSE_FEATURE']._serialized_start=971 + _globals['_CODEGENERATORRESPONSE_FEATURE']._serialized_end=1058 +# @@protoc_insertion_point(module_scope) diff --git a/google/protobuf/descriptor.py b/google/protobuf/descriptor.py new file mode 100644 index 0000000..d8c6a43 --- /dev/null +++ b/google/protobuf/descriptor.py @@ -0,0 +1,1511 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Descriptors essentially contain exactly the information found in a .proto +file, in types that make this information accessible in Python. +""" + +__author__ = 'robinson@google.com (Will Robinson)' + +import abc +import binascii +import os +import threading +import warnings + +from google.protobuf.internal import api_implementation + +_USE_C_DESCRIPTORS = False +if api_implementation.Type() != 'python': + # pylint: disable=protected-access + _message = api_implementation._c_module + # TODO: Remove this import after fix api_implementation + if _message is None: + from google.protobuf.pyext import _message + _USE_C_DESCRIPTORS = True + + +class Error(Exception): + """Base error for this module.""" + + +class TypeTransformationError(Error): + """Error transforming between python proto type and corresponding C++ type.""" + + +if _USE_C_DESCRIPTORS: + # This metaclass allows to override the behavior of code like + # isinstance(my_descriptor, FieldDescriptor) + # and make it return True when the descriptor is an instance of the extension + # type written in C++. + class DescriptorMetaclass(type): + + def __instancecheck__(cls, obj): + if super(DescriptorMetaclass, cls).__instancecheck__(obj): + return True + if isinstance(obj, cls._C_DESCRIPTOR_CLASS): + return True + return False +else: + # The standard metaclass; nothing changes. + DescriptorMetaclass = abc.ABCMeta + + +class _Lock(object): + """Wrapper class of threading.Lock(), which is allowed by 'with'.""" + + def __new__(cls): + self = object.__new__(cls) + self._lock = threading.Lock() # pylint: disable=protected-access + return self + + def __enter__(self): + self._lock.acquire() + + def __exit__(self, exc_type, exc_value, exc_tb): + self._lock.release() + + +_lock = threading.Lock() + + +def _Deprecated(name): + if _Deprecated.count > 0: + _Deprecated.count -= 1 + warnings.warn( + 'Call to deprecated create function %s(). Note: Create unlinked ' + 'descriptors is going to go away. Please use get/find descriptors from ' + 'generated code or query the descriptor_pool.' + % name, + category=DeprecationWarning, stacklevel=3) + +# These must match the values in descriptor.proto, but we can't use them +# directly because we sometimes need to reference them in feature helpers +# below *during* the build of descriptor.proto. +_FEATURESET_MESSAGE_ENCODING_DELIMITED = 2 +_FEATURESET_FIELD_PRESENCE_IMPLICIT = 2 +_FEATURESET_FIELD_PRESENCE_LEGACY_REQUIRED = 3 +_FEATURESET_REPEATED_FIELD_ENCODING_PACKED = 1 +_FEATURESET_ENUM_TYPE_CLOSED = 2 + +# Deprecated warnings will print 100 times at most which should be enough for +# users to notice and do not cause timeout. +_Deprecated.count = 100 + + +_internal_create_key = object() + + +class DescriptorBase(metaclass=DescriptorMetaclass): + + """Descriptors base class. + + This class is the base of all descriptor classes. It provides common options + related functionality. + + Attributes: + has_options: True if the descriptor has non-default options. Usually it is + not necessary to read this -- just call GetOptions() which will happily + return the default instance. However, it's sometimes useful for + efficiency, and also useful inside the protobuf implementation to avoid + some bootstrapping issues. + file (FileDescriptor): Reference to file info. + """ + + if _USE_C_DESCRIPTORS: + # The class, or tuple of classes, that are considered as "virtual + # subclasses" of this descriptor class. + _C_DESCRIPTOR_CLASS = () + + def __init__(self, file, options, serialized_options, options_class_name): + """Initialize the descriptor given its options message and the name of the + class of the options message. The name of the class is required in case + the options message is None and has to be created. + """ + self._features = None + self.file = file + self._options = options + self._loaded_options = None + self._options_class_name = options_class_name + self._serialized_options = serialized_options + + # Does this descriptor have non-default options? + self.has_options = (self._options is not None) or ( + self._serialized_options is not None + ) + + @property + @abc.abstractmethod + def _parent(self): + pass + + def _InferLegacyFeatures(self, edition, options, features): + """Infers features from proto2/proto3 syntax so that editions logic can be used everywhere. + + Args: + edition: The edition to infer features for. + options: The options for this descriptor that are being processed. + features: The feature set object to modify with inferred features. + """ + pass + + def _GetFeatures(self): + if not self._features: + self._LazyLoadOptions() + return self._features + + def _ResolveFeatures(self, edition, raw_options): + """Resolves features from the raw options of this descriptor. + + Args: + edition: The edition to use for feature defaults. + raw_options: The options for this descriptor that are being processed. + + Returns: + A fully resolved feature set for making runtime decisions. + """ + # pylint: disable=g-import-not-at-top + from google.protobuf import descriptor_pb2 + + if self._parent: + features = descriptor_pb2.FeatureSet() + features.CopyFrom(self._parent._GetFeatures()) + else: + features = self.file.pool._CreateDefaultFeatures(edition) + unresolved = descriptor_pb2.FeatureSet() + unresolved.CopyFrom(raw_options.features) + self._InferLegacyFeatures(edition, raw_options, unresolved) + features.MergeFrom(unresolved) + + # Use the feature cache to reduce memory bloat. + return self.file.pool._InternFeatures(features) + + def _LazyLoadOptions(self): + """Lazily initializes descriptor options towards the end of the build.""" + if self._loaded_options: + return + + # pylint: disable=g-import-not-at-top + from google.protobuf import descriptor_pb2 + + if not hasattr(descriptor_pb2, self._options_class_name): + raise RuntimeError( + 'Unknown options class name %s!' % self._options_class_name + ) + options_class = getattr(descriptor_pb2, self._options_class_name) + features = None + edition = self.file._edition + + if not self.has_options: + if not self._features: + features = self._ResolveFeatures( + descriptor_pb2.Edition.Value(edition), options_class() + ) + with _lock: + self._loaded_options = options_class() + if not self._features: + self._features = features + else: + if not self._serialized_options: + options = self._options + else: + options = _ParseOptions(options_class(), self._serialized_options) + + if not self._features: + features = self._ResolveFeatures( + descriptor_pb2.Edition.Value(edition), options + ) + with _lock: + self._loaded_options = options + if not self._features: + self._features = features + if options.HasField('features'): + options.ClearField('features') + if not options.SerializeToString(): + self._loaded_options = options_class() + self.has_options = False + + def GetOptions(self): + """Retrieves descriptor options. + + Returns: + The options set on this descriptor. + """ + if not self._loaded_options: + self._LazyLoadOptions() + return self._loaded_options + + +class _NestedDescriptorBase(DescriptorBase): + """Common class for descriptors that can be nested.""" + + def __init__(self, options, options_class_name, name, full_name, + file, containing_type, serialized_start=None, + serialized_end=None, serialized_options=None): + """Constructor. + + Args: + options: Protocol message options or None to use default message options. + options_class_name (str): The class name of the above options. + name (str): Name of this protocol message type. + full_name (str): Fully-qualified name of this protocol message type, which + will include protocol "package" name and the name of any enclosing + types. + containing_type: if provided, this is a nested descriptor, with this + descriptor as parent, otherwise None. + serialized_start: The start index (inclusive) in block in the + file.serialized_pb that describes this descriptor. + serialized_end: The end index (exclusive) in block in the + file.serialized_pb that describes this descriptor. + serialized_options: Protocol message serialized options or None. + """ + super(_NestedDescriptorBase, self).__init__( + file, options, serialized_options, options_class_name + ) + + self.name = name + # TODO: Add function to calculate full_name instead of having it in + # memory? + self.full_name = full_name + self.containing_type = containing_type + + self._serialized_start = serialized_start + self._serialized_end = serialized_end + + def CopyToProto(self, proto): + """Copies this to the matching proto in descriptor_pb2. + + Args: + proto: An empty proto instance from descriptor_pb2. + + Raises: + Error: If self couldn't be serialized, due to to few constructor + arguments. + """ + if (self.file is not None and + self._serialized_start is not None and + self._serialized_end is not None): + proto.ParseFromString(self.file.serialized_pb[ + self._serialized_start:self._serialized_end]) + else: + raise Error('Descriptor does not contain serialization.') + + +class Descriptor(_NestedDescriptorBase): + + """Descriptor for a protocol message type. + + Attributes: + name (str): Name of this protocol message type. + full_name (str): Fully-qualified name of this protocol message type, + which will include protocol "package" name and the name of any + enclosing types. + containing_type (Descriptor): Reference to the descriptor of the type + containing us, or None if this is top-level. + fields (list[FieldDescriptor]): Field descriptors for all fields in + this type. + fields_by_number (dict(int, FieldDescriptor)): Same + :class:`FieldDescriptor` objects as in :attr:`fields`, but indexed + by "number" attribute in each FieldDescriptor. + fields_by_name (dict(str, FieldDescriptor)): Same + :class:`FieldDescriptor` objects as in :attr:`fields`, but indexed by + "name" attribute in each :class:`FieldDescriptor`. + nested_types (list[Descriptor]): Descriptor references + for all protocol message types nested within this one. + nested_types_by_name (dict(str, Descriptor)): Same Descriptor + objects as in :attr:`nested_types`, but indexed by "name" attribute + in each Descriptor. + enum_types (list[EnumDescriptor]): :class:`EnumDescriptor` references + for all enums contained within this type. + enum_types_by_name (dict(str, EnumDescriptor)): Same + :class:`EnumDescriptor` objects as in :attr:`enum_types`, but + indexed by "name" attribute in each EnumDescriptor. + enum_values_by_name (dict(str, EnumValueDescriptor)): Dict mapping + from enum value name to :class:`EnumValueDescriptor` for that value. + extensions (list[FieldDescriptor]): All extensions defined directly + within this message type (NOT within a nested type). + extensions_by_name (dict(str, FieldDescriptor)): Same FieldDescriptor + objects as :attr:`extensions`, but indexed by "name" attribute of each + FieldDescriptor. + is_extendable (bool): Does this type define any extension ranges? + oneofs (list[OneofDescriptor]): The list of descriptors for oneof fields + in this message. + oneofs_by_name (dict(str, OneofDescriptor)): Same objects as in + :attr:`oneofs`, but indexed by "name" attribute. + file (FileDescriptor): Reference to file descriptor. + is_map_entry: If the message type is a map entry. + + """ + + if _USE_C_DESCRIPTORS: + _C_DESCRIPTOR_CLASS = _message.Descriptor + + def __new__( + cls, + name=None, + full_name=None, + filename=None, + containing_type=None, + fields=None, + nested_types=None, + enum_types=None, + extensions=None, + options=None, + serialized_options=None, + is_extendable=True, + extension_ranges=None, + oneofs=None, + file=None, # pylint: disable=redefined-builtin + serialized_start=None, + serialized_end=None, + syntax=None, + is_map_entry=False, + create_key=None): + _message.Message._CheckCalledFromGeneratedFile() + return _message.default_pool.FindMessageTypeByName(full_name) + + # NOTE: The file argument redefining a builtin is nothing we can + # fix right now since we don't know how many clients already rely on the + # name of the argument. + def __init__(self, name, full_name, filename, containing_type, fields, + nested_types, enum_types, extensions, options=None, + serialized_options=None, + is_extendable=True, extension_ranges=None, oneofs=None, + file=None, serialized_start=None, serialized_end=None, # pylint: disable=redefined-builtin + syntax=None, is_map_entry=False, create_key=None): + """Arguments to __init__() are as described in the description + of Descriptor fields above. + + Note that filename is an obsolete argument, that is not used anymore. + Please use file.name to access this as an attribute. + """ + if create_key is not _internal_create_key: + _Deprecated('Descriptor') + + super(Descriptor, self).__init__( + options, 'MessageOptions', name, full_name, file, + containing_type, serialized_start=serialized_start, + serialized_end=serialized_end, serialized_options=serialized_options) + + # We have fields in addition to fields_by_name and fields_by_number, + # so that: + # 1. Clients can index fields by "order in which they're listed." + # 2. Clients can easily iterate over all fields with the terse + # syntax: for f in descriptor.fields: ... + self.fields = fields + for field in self.fields: + field.containing_type = self + field.file = file + self.fields_by_number = dict((f.number, f) for f in fields) + self.fields_by_name = dict((f.name, f) for f in fields) + self._fields_by_camelcase_name = None + + self.nested_types = nested_types + for nested_type in nested_types: + nested_type.containing_type = self + self.nested_types_by_name = dict((t.name, t) for t in nested_types) + + self.enum_types = enum_types + for enum_type in self.enum_types: + enum_type.containing_type = self + self.enum_types_by_name = dict((t.name, t) for t in enum_types) + self.enum_values_by_name = dict( + (v.name, v) for t in enum_types for v in t.values) + + self.extensions = extensions + for extension in self.extensions: + extension.extension_scope = self + self.extensions_by_name = dict((f.name, f) for f in extensions) + self.is_extendable = is_extendable + self.extension_ranges = extension_ranges + self.oneofs = oneofs if oneofs is not None else [] + self.oneofs_by_name = dict((o.name, o) for o in self.oneofs) + for oneof in self.oneofs: + oneof.containing_type = self + oneof.file = file + self._is_map_entry = is_map_entry + + @property + def _parent(self): + return self.containing_type or self.file + + @property + def fields_by_camelcase_name(self): + """Same FieldDescriptor objects as in :attr:`fields`, but indexed by + :attr:`FieldDescriptor.camelcase_name`. + """ + if self._fields_by_camelcase_name is None: + self._fields_by_camelcase_name = dict( + (f.camelcase_name, f) for f in self.fields) + return self._fields_by_camelcase_name + + def EnumValueName(self, enum, value): + """Returns the string name of an enum value. + + This is just a small helper method to simplify a common operation. + + Args: + enum: string name of the Enum. + value: int, value of the enum. + + Returns: + string name of the enum value. + + Raises: + KeyError if either the Enum doesn't exist or the value is not a valid + value for the enum. + """ + return self.enum_types_by_name[enum].values_by_number[value].name + + def CopyToProto(self, proto): + """Copies this to a descriptor_pb2.DescriptorProto. + + Args: + proto: An empty descriptor_pb2.DescriptorProto. + """ + # This function is overridden to give a better doc comment. + super(Descriptor, self).CopyToProto(proto) + + +# TODO: We should have aggressive checking here, +# for example: +# * If you specify a repeated field, you should not be allowed +# to specify a default value. +# * [Other examples here as needed]. +# +# TODO: for this and other *Descriptor classes, we +# might also want to lock things down aggressively (e.g., +# prevent clients from setting the attributes). Having +# stronger invariants here in general will reduce the number +# of runtime checks we must do in reflection.py... +class FieldDescriptor(DescriptorBase): + + """Descriptor for a single field in a .proto file. + + Attributes: + name (str): Name of this field, exactly as it appears in .proto. + full_name (str): Name of this field, including containing scope. This is + particularly relevant for extensions. + index (int): Dense, 0-indexed index giving the order that this + field textually appears within its message in the .proto file. + number (int): Tag number declared for this field in the .proto file. + + type (int): (One of the TYPE_* constants below) Declared type. + cpp_type (int): (One of the CPPTYPE_* constants below) C++ type used to + represent this field. + + label (int): (One of the LABEL_* constants below) Tells whether this + field is optional, required, or repeated. + has_default_value (bool): True if this field has a default value defined, + otherwise false. + default_value (Varies): Default value of this field. Only + meaningful for non-repeated scalar fields. Repeated fields + should always set this to [], and non-repeated composite + fields should always set this to None. + + containing_type (Descriptor): Descriptor of the protocol message + type that contains this field. Set by the Descriptor constructor + if we're passed into one. + Somewhat confusingly, for extension fields, this is the + descriptor of the EXTENDED message, not the descriptor + of the message containing this field. (See is_extension and + extension_scope below). + message_type (Descriptor): If a composite field, a descriptor + of the message type contained in this field. Otherwise, this is None. + enum_type (EnumDescriptor): If this field contains an enum, a + descriptor of that enum. Otherwise, this is None. + + is_extension: True iff this describes an extension field. + extension_scope (Descriptor): Only meaningful if is_extension is True. + Gives the message that immediately contains this extension field. + Will be None iff we're a top-level (file-level) extension field. + + options (descriptor_pb2.FieldOptions): Protocol message field options or + None to use default field options. + + containing_oneof (OneofDescriptor): If the field is a member of a oneof + union, contains its descriptor. Otherwise, None. + + file (FileDescriptor): Reference to file descriptor. + """ + + # Must be consistent with C++ FieldDescriptor::Type enum in + # descriptor.h. + # + # TODO: Find a way to eliminate this repetition. + TYPE_DOUBLE = 1 + TYPE_FLOAT = 2 + TYPE_INT64 = 3 + TYPE_UINT64 = 4 + TYPE_INT32 = 5 + TYPE_FIXED64 = 6 + TYPE_FIXED32 = 7 + TYPE_BOOL = 8 + TYPE_STRING = 9 + TYPE_GROUP = 10 + TYPE_MESSAGE = 11 + TYPE_BYTES = 12 + TYPE_UINT32 = 13 + TYPE_ENUM = 14 + TYPE_SFIXED32 = 15 + TYPE_SFIXED64 = 16 + TYPE_SINT32 = 17 + TYPE_SINT64 = 18 + MAX_TYPE = 18 + + # Must be consistent with C++ FieldDescriptor::CppType enum in + # descriptor.h. + # + # TODO: Find a way to eliminate this repetition. + CPPTYPE_INT32 = 1 + CPPTYPE_INT64 = 2 + CPPTYPE_UINT32 = 3 + CPPTYPE_UINT64 = 4 + CPPTYPE_DOUBLE = 5 + CPPTYPE_FLOAT = 6 + CPPTYPE_BOOL = 7 + CPPTYPE_ENUM = 8 + CPPTYPE_STRING = 9 + CPPTYPE_MESSAGE = 10 + MAX_CPPTYPE = 10 + + _PYTHON_TO_CPP_PROTO_TYPE_MAP = { + TYPE_DOUBLE: CPPTYPE_DOUBLE, + TYPE_FLOAT: CPPTYPE_FLOAT, + TYPE_ENUM: CPPTYPE_ENUM, + TYPE_INT64: CPPTYPE_INT64, + TYPE_SINT64: CPPTYPE_INT64, + TYPE_SFIXED64: CPPTYPE_INT64, + TYPE_UINT64: CPPTYPE_UINT64, + TYPE_FIXED64: CPPTYPE_UINT64, + TYPE_INT32: CPPTYPE_INT32, + TYPE_SFIXED32: CPPTYPE_INT32, + TYPE_SINT32: CPPTYPE_INT32, + TYPE_UINT32: CPPTYPE_UINT32, + TYPE_FIXED32: CPPTYPE_UINT32, + TYPE_BYTES: CPPTYPE_STRING, + TYPE_STRING: CPPTYPE_STRING, + TYPE_BOOL: CPPTYPE_BOOL, + TYPE_MESSAGE: CPPTYPE_MESSAGE, + TYPE_GROUP: CPPTYPE_MESSAGE + } + + # Must be consistent with C++ FieldDescriptor::Label enum in + # descriptor.h. + # + # TODO: Find a way to eliminate this repetition. + LABEL_OPTIONAL = 1 + LABEL_REQUIRED = 2 + LABEL_REPEATED = 3 + MAX_LABEL = 3 + + # Must be consistent with C++ constants kMaxNumber, kFirstReservedNumber, + # and kLastReservedNumber in descriptor.h + MAX_FIELD_NUMBER = (1 << 29) - 1 + FIRST_RESERVED_FIELD_NUMBER = 19000 + LAST_RESERVED_FIELD_NUMBER = 19999 + + if _USE_C_DESCRIPTORS: + _C_DESCRIPTOR_CLASS = _message.FieldDescriptor + + def __new__(cls, name, full_name, index, number, type, cpp_type, label, + default_value, message_type, enum_type, containing_type, + is_extension, extension_scope, options=None, + serialized_options=None, + has_default_value=True, containing_oneof=None, json_name=None, + file=None, create_key=None): # pylint: disable=redefined-builtin + _message.Message._CheckCalledFromGeneratedFile() + if is_extension: + return _message.default_pool.FindExtensionByName(full_name) + else: + return _message.default_pool.FindFieldByName(full_name) + + def __init__(self, name, full_name, index, number, type, cpp_type, label, + default_value, message_type, enum_type, containing_type, + is_extension, extension_scope, options=None, + serialized_options=None, + has_default_value=True, containing_oneof=None, json_name=None, + file=None, create_key=None): # pylint: disable=redefined-builtin + """The arguments are as described in the description of FieldDescriptor + attributes above. + + Note that containing_type may be None, and may be set later if necessary + (to deal with circular references between message types, for example). + Likewise for extension_scope. + """ + if create_key is not _internal_create_key: + _Deprecated('FieldDescriptor') + + super(FieldDescriptor, self).__init__( + file, options, serialized_options, 'FieldOptions' + ) + self.name = name + self.full_name = full_name + self._camelcase_name = None + if json_name is None: + self.json_name = _ToJsonName(name) + else: + self.json_name = json_name + self.index = index + self.number = number + self._type = type + self.cpp_type = cpp_type + self._label = label + self.has_default_value = has_default_value + self.default_value = default_value + self.containing_type = containing_type + self.message_type = message_type + self.enum_type = enum_type + self.is_extension = is_extension + self.extension_scope = extension_scope + self.containing_oneof = containing_oneof + if api_implementation.Type() == 'python': + self._cdescriptor = None + else: + if is_extension: + self._cdescriptor = _message.default_pool.FindExtensionByName(full_name) + else: + self._cdescriptor = _message.default_pool.FindFieldByName(full_name) + + @property + def _parent(self): + if self.containing_oneof: + return self.containing_oneof + if self.is_extension: + return self.extension_scope or self.file + return self.containing_type + + def _InferLegacyFeatures(self, edition, options, features): + # pylint: disable=g-import-not-at-top + from google.protobuf import descriptor_pb2 + + if edition >= descriptor_pb2.Edition.EDITION_2023: + return + + if self._label == FieldDescriptor.LABEL_REQUIRED: + features.field_presence = ( + descriptor_pb2.FeatureSet.FieldPresence.LEGACY_REQUIRED + ) + + if self._type == FieldDescriptor.TYPE_GROUP: + features.message_encoding = ( + descriptor_pb2.FeatureSet.MessageEncoding.DELIMITED + ) + + if options.HasField('packed'): + features.repeated_field_encoding = ( + descriptor_pb2.FeatureSet.RepeatedFieldEncoding.PACKED + if options.packed + else descriptor_pb2.FeatureSet.RepeatedFieldEncoding.EXPANDED + ) + + @property + def type(self): + if ( + self._GetFeatures().message_encoding + == _FEATURESET_MESSAGE_ENCODING_DELIMITED + and self.message_type + and not self.message_type.GetOptions().map_entry + and not self.containing_type.GetOptions().map_entry + ): + return FieldDescriptor.TYPE_GROUP + return self._type + + @type.setter + def type(self, val): + self._type = val + + @property + def label(self): + if ( + self._GetFeatures().field_presence + == _FEATURESET_FIELD_PRESENCE_LEGACY_REQUIRED + ): + return FieldDescriptor.LABEL_REQUIRED + return self._label + + @property + def camelcase_name(self): + """Camelcase name of this field. + + Returns: + str: the name in CamelCase. + """ + if self._camelcase_name is None: + self._camelcase_name = _ToCamelCase(self.name) + return self._camelcase_name + + @property + def has_presence(self): + """Whether the field distinguishes between unpopulated and default values. + + Raises: + RuntimeError: singular field that is not linked with message nor file. + """ + if self.label == FieldDescriptor.LABEL_REPEATED: + return False + if ( + self.cpp_type == FieldDescriptor.CPPTYPE_MESSAGE + or self.is_extension + or self.containing_oneof + ): + return True + + return ( + self._GetFeatures().field_presence + != _FEATURESET_FIELD_PRESENCE_IMPLICIT + ) + + @property + def is_packed(self): + """Returns if the field is packed.""" + if self.label != FieldDescriptor.LABEL_REPEATED: + return False + field_type = self.type + if (field_type == FieldDescriptor.TYPE_STRING or + field_type == FieldDescriptor.TYPE_GROUP or + field_type == FieldDescriptor.TYPE_MESSAGE or + field_type == FieldDescriptor.TYPE_BYTES): + return False + + return ( + self._GetFeatures().repeated_field_encoding + == _FEATURESET_REPEATED_FIELD_ENCODING_PACKED + ) + + @staticmethod + def ProtoTypeToCppProtoType(proto_type): + """Converts from a Python proto type to a C++ Proto Type. + + The Python ProtocolBuffer classes specify both the 'Python' datatype and the + 'C++' datatype - and they're not the same. This helper method should + translate from one to another. + + Args: + proto_type: the Python proto type (descriptor.FieldDescriptor.TYPE_*) + Returns: + int: descriptor.FieldDescriptor.CPPTYPE_*, the C++ type. + Raises: + TypeTransformationError: when the Python proto type isn't known. + """ + try: + return FieldDescriptor._PYTHON_TO_CPP_PROTO_TYPE_MAP[proto_type] + except KeyError: + raise TypeTransformationError('Unknown proto_type: %s' % proto_type) + + +class EnumDescriptor(_NestedDescriptorBase): + + """Descriptor for an enum defined in a .proto file. + + Attributes: + name (str): Name of the enum type. + full_name (str): Full name of the type, including package name + and any enclosing type(s). + + values (list[EnumValueDescriptor]): List of the values + in this enum. + values_by_name (dict(str, EnumValueDescriptor)): Same as :attr:`values`, + but indexed by the "name" field of each EnumValueDescriptor. + values_by_number (dict(int, EnumValueDescriptor)): Same as :attr:`values`, + but indexed by the "number" field of each EnumValueDescriptor. + containing_type (Descriptor): Descriptor of the immediate containing + type of this enum, or None if this is an enum defined at the + top level in a .proto file. Set by Descriptor's constructor + if we're passed into one. + file (FileDescriptor): Reference to file descriptor. + options (descriptor_pb2.EnumOptions): Enum options message or + None to use default enum options. + """ + + if _USE_C_DESCRIPTORS: + _C_DESCRIPTOR_CLASS = _message.EnumDescriptor + + def __new__(cls, name, full_name, filename, values, + containing_type=None, options=None, + serialized_options=None, file=None, # pylint: disable=redefined-builtin + serialized_start=None, serialized_end=None, create_key=None): + _message.Message._CheckCalledFromGeneratedFile() + return _message.default_pool.FindEnumTypeByName(full_name) + + def __init__(self, name, full_name, filename, values, + containing_type=None, options=None, + serialized_options=None, file=None, # pylint: disable=redefined-builtin + serialized_start=None, serialized_end=None, create_key=None): + """Arguments are as described in the attribute description above. + + Note that filename is an obsolete argument, that is not used anymore. + Please use file.name to access this as an attribute. + """ + if create_key is not _internal_create_key: + _Deprecated('EnumDescriptor') + + super(EnumDescriptor, self).__init__( + options, 'EnumOptions', name, full_name, file, + containing_type, serialized_start=serialized_start, + serialized_end=serialized_end, serialized_options=serialized_options) + + self.values = values + for value in self.values: + value.file = file + value.type = self + self.values_by_name = dict((v.name, v) for v in values) + # Values are reversed to ensure that the first alias is retained. + self.values_by_number = dict((v.number, v) for v in reversed(values)) + + @property + def _parent(self): + return self.containing_type or self.file + + @property + def is_closed(self): + """Returns true whether this is a "closed" enum. + + This means that it: + - Has a fixed set of values, rather than being equivalent to an int32. + - Encountering values not in this set causes them to be treated as unknown + fields. + - The first value (i.e., the default) may be nonzero. + + WARNING: Some runtimes currently have a quirk where non-closed enums are + treated as closed when used as the type of fields defined in a + `syntax = proto2;` file. This quirk is not present in all runtimes; as of + writing, we know that: + + - C++, Java, and C++-based Python share this quirk. + - UPB and UPB-based Python do not. + - PHP and Ruby treat all enums as open regardless of declaration. + + Care should be taken when using this function to respect the target + runtime's enum handling quirks. + """ + return self._GetFeatures().enum_type == _FEATURESET_ENUM_TYPE_CLOSED + + def CopyToProto(self, proto): + """Copies this to a descriptor_pb2.EnumDescriptorProto. + + Args: + proto (descriptor_pb2.EnumDescriptorProto): An empty descriptor proto. + """ + # This function is overridden to give a better doc comment. + super(EnumDescriptor, self).CopyToProto(proto) + + +class EnumValueDescriptor(DescriptorBase): + + """Descriptor for a single value within an enum. + + Attributes: + name (str): Name of this value. + index (int): Dense, 0-indexed index giving the order that this + value appears textually within its enum in the .proto file. + number (int): Actual number assigned to this enum value. + type (EnumDescriptor): :class:`EnumDescriptor` to which this value + belongs. Set by :class:`EnumDescriptor`'s constructor if we're + passed into one. + options (descriptor_pb2.EnumValueOptions): Enum value options message or + None to use default enum value options options. + """ + + if _USE_C_DESCRIPTORS: + _C_DESCRIPTOR_CLASS = _message.EnumValueDescriptor + + def __new__(cls, name, index, number, + type=None, # pylint: disable=redefined-builtin + options=None, serialized_options=None, create_key=None): + _message.Message._CheckCalledFromGeneratedFile() + # There is no way we can build a complete EnumValueDescriptor with the + # given parameters (the name of the Enum is not known, for example). + # Fortunately generated files just pass it to the EnumDescriptor() + # constructor, which will ignore it, so returning None is good enough. + return None + + def __init__(self, name, index, number, + type=None, # pylint: disable=redefined-builtin + options=None, serialized_options=None, create_key=None): + """Arguments are as described in the attribute description above.""" + if create_key is not _internal_create_key: + _Deprecated('EnumValueDescriptor') + + super(EnumValueDescriptor, self).__init__( + type.file if type else None, + options, + serialized_options, + 'EnumValueOptions', + ) + self.name = name + self.index = index + self.number = number + self.type = type + + @property + def _parent(self): + return self.type + + +class OneofDescriptor(DescriptorBase): + """Descriptor for a oneof field. + + Attributes: + name (str): Name of the oneof field. + full_name (str): Full name of the oneof field, including package name. + index (int): 0-based index giving the order of the oneof field inside + its containing type. + containing_type (Descriptor): :class:`Descriptor` of the protocol message + type that contains this field. Set by the :class:`Descriptor` constructor + if we're passed into one. + fields (list[FieldDescriptor]): The list of field descriptors this + oneof can contain. + """ + + if _USE_C_DESCRIPTORS: + _C_DESCRIPTOR_CLASS = _message.OneofDescriptor + + def __new__( + cls, name, full_name, index, containing_type, fields, options=None, + serialized_options=None, create_key=None): + _message.Message._CheckCalledFromGeneratedFile() + return _message.default_pool.FindOneofByName(full_name) + + def __init__( + self, name, full_name, index, containing_type, fields, options=None, + serialized_options=None, create_key=None): + """Arguments are as described in the attribute description above.""" + if create_key is not _internal_create_key: + _Deprecated('OneofDescriptor') + + super(OneofDescriptor, self).__init__( + containing_type.file if containing_type else None, + options, + serialized_options, + 'OneofOptions', + ) + self.name = name + self.full_name = full_name + self.index = index + self.containing_type = containing_type + self.fields = fields + + @property + def _parent(self): + return self.containing_type + + +class ServiceDescriptor(_NestedDescriptorBase): + + """Descriptor for a service. + + Attributes: + name (str): Name of the service. + full_name (str): Full name of the service, including package name. + index (int): 0-indexed index giving the order that this services + definition appears within the .proto file. + methods (list[MethodDescriptor]): List of methods provided by this + service. + methods_by_name (dict(str, MethodDescriptor)): Same + :class:`MethodDescriptor` objects as in :attr:`methods_by_name`, but + indexed by "name" attribute in each :class:`MethodDescriptor`. + options (descriptor_pb2.ServiceOptions): Service options message or + None to use default service options. + file (FileDescriptor): Reference to file info. + """ + + if _USE_C_DESCRIPTORS: + _C_DESCRIPTOR_CLASS = _message.ServiceDescriptor + + def __new__( + cls, + name=None, + full_name=None, + index=None, + methods=None, + options=None, + serialized_options=None, + file=None, # pylint: disable=redefined-builtin + serialized_start=None, + serialized_end=None, + create_key=None): + _message.Message._CheckCalledFromGeneratedFile() # pylint: disable=protected-access + return _message.default_pool.FindServiceByName(full_name) + + def __init__(self, name, full_name, index, methods, options=None, + serialized_options=None, file=None, # pylint: disable=redefined-builtin + serialized_start=None, serialized_end=None, create_key=None): + if create_key is not _internal_create_key: + _Deprecated('ServiceDescriptor') + + super(ServiceDescriptor, self).__init__( + options, 'ServiceOptions', name, full_name, file, + None, serialized_start=serialized_start, + serialized_end=serialized_end, serialized_options=serialized_options) + self.index = index + self.methods = methods + self.methods_by_name = dict((m.name, m) for m in methods) + # Set the containing service for each method in this service. + for method in self.methods: + method.file = self.file + method.containing_service = self + + @property + def _parent(self): + return self.file + + def FindMethodByName(self, name): + """Searches for the specified method, and returns its descriptor. + + Args: + name (str): Name of the method. + + Returns: + MethodDescriptor: The descriptor for the requested method. + + Raises: + KeyError: if the method cannot be found in the service. + """ + return self.methods_by_name[name] + + def CopyToProto(self, proto): + """Copies this to a descriptor_pb2.ServiceDescriptorProto. + + Args: + proto (descriptor_pb2.ServiceDescriptorProto): An empty descriptor proto. + """ + # This function is overridden to give a better doc comment. + super(ServiceDescriptor, self).CopyToProto(proto) + + +class MethodDescriptor(DescriptorBase): + + """Descriptor for a method in a service. + + Attributes: + name (str): Name of the method within the service. + full_name (str): Full name of method. + index (int): 0-indexed index of the method inside the service. + containing_service (ServiceDescriptor): The service that contains this + method. + input_type (Descriptor): The descriptor of the message that this method + accepts. + output_type (Descriptor): The descriptor of the message that this method + returns. + client_streaming (bool): Whether this method uses client streaming. + server_streaming (bool): Whether this method uses server streaming. + options (descriptor_pb2.MethodOptions or None): Method options message, or + None to use default method options. + """ + + if _USE_C_DESCRIPTORS: + _C_DESCRIPTOR_CLASS = _message.MethodDescriptor + + def __new__(cls, + name, + full_name, + index, + containing_service, + input_type, + output_type, + client_streaming=False, + server_streaming=False, + options=None, + serialized_options=None, + create_key=None): + _message.Message._CheckCalledFromGeneratedFile() # pylint: disable=protected-access + return _message.default_pool.FindMethodByName(full_name) + + def __init__(self, + name, + full_name, + index, + containing_service, + input_type, + output_type, + client_streaming=False, + server_streaming=False, + options=None, + serialized_options=None, + create_key=None): + """The arguments are as described in the description of MethodDescriptor + attributes above. + + Note that containing_service may be None, and may be set later if necessary. + """ + if create_key is not _internal_create_key: + _Deprecated('MethodDescriptor') + + super(MethodDescriptor, self).__init__( + containing_service.file if containing_service else None, + options, + serialized_options, + 'MethodOptions', + ) + self.name = name + self.full_name = full_name + self.index = index + self.containing_service = containing_service + self.input_type = input_type + self.output_type = output_type + self.client_streaming = client_streaming + self.server_streaming = server_streaming + + @property + def _parent(self): + return self.containing_service + + def CopyToProto(self, proto): + """Copies this to a descriptor_pb2.MethodDescriptorProto. + + Args: + proto (descriptor_pb2.MethodDescriptorProto): An empty descriptor proto. + + Raises: + Error: If self couldn't be serialized, due to too few constructor + arguments. + """ + if self.containing_service is not None: + from google.protobuf import descriptor_pb2 + service_proto = descriptor_pb2.ServiceDescriptorProto() + self.containing_service.CopyToProto(service_proto) + proto.CopyFrom(service_proto.method[self.index]) + else: + raise Error('Descriptor does not contain a service.') + + +class FileDescriptor(DescriptorBase): + """Descriptor for a file. Mimics the descriptor_pb2.FileDescriptorProto. + + Note that :attr:`enum_types_by_name`, :attr:`extensions_by_name`, and + :attr:`dependencies` fields are only set by the + :py:mod:`google.protobuf.message_factory` module, and not by the generated + proto code. + + Attributes: + name (str): Name of file, relative to root of source tree. + package (str): Name of the package + edition (Edition): Enum value indicating edition of the file + serialized_pb (bytes): Byte string of serialized + :class:`descriptor_pb2.FileDescriptorProto`. + dependencies (list[FileDescriptor]): List of other :class:`FileDescriptor` + objects this :class:`FileDescriptor` depends on. + public_dependencies (list[FileDescriptor]): A subset of + :attr:`dependencies`, which were declared as "public". + message_types_by_name (dict(str, Descriptor)): Mapping from message names to + their :class:`Descriptor`. + enum_types_by_name (dict(str, EnumDescriptor)): Mapping from enum names to + their :class:`EnumDescriptor`. + extensions_by_name (dict(str, FieldDescriptor)): Mapping from extension + names declared at file scope to their :class:`FieldDescriptor`. + services_by_name (dict(str, ServiceDescriptor)): Mapping from services' + names to their :class:`ServiceDescriptor`. + pool (DescriptorPool): The pool this descriptor belongs to. When not passed + to the constructor, the global default pool is used. + """ + + if _USE_C_DESCRIPTORS: + _C_DESCRIPTOR_CLASS = _message.FileDescriptor + + def __new__( + cls, + name, + package, + options=None, + serialized_options=None, + serialized_pb=None, + dependencies=None, + public_dependencies=None, + syntax=None, + edition=None, + pool=None, + create_key=None, + ): + # FileDescriptor() is called from various places, not only from generated + # files, to register dynamic proto files and messages. + # pylint: disable=g-explicit-bool-comparison + if serialized_pb: + return _message.default_pool.AddSerializedFile(serialized_pb) + else: + return super(FileDescriptor, cls).__new__(cls) + + def __init__( + self, + name, + package, + options=None, + serialized_options=None, + serialized_pb=None, + dependencies=None, + public_dependencies=None, + syntax=None, + edition=None, + pool=None, + create_key=None, + ): + """Constructor.""" + if create_key is not _internal_create_key: + _Deprecated('FileDescriptor') + + super(FileDescriptor, self).__init__( + self, options, serialized_options, 'FileOptions' + ) + + if edition and edition != 'EDITION_UNKNOWN': + self._edition = edition + elif syntax == 'proto3': + self._edition = 'EDITION_PROTO3' + else: + self._edition = 'EDITION_PROTO2' + + if pool is None: + from google.protobuf import descriptor_pool + pool = descriptor_pool.Default() + self.pool = pool + self.message_types_by_name = {} + self.name = name + self.package = package + self.serialized_pb = serialized_pb + + self.enum_types_by_name = {} + self.extensions_by_name = {} + self.services_by_name = {} + self.dependencies = (dependencies or []) + self.public_dependencies = (public_dependencies or []) + + def CopyToProto(self, proto): + """Copies this to a descriptor_pb2.FileDescriptorProto. + + Args: + proto: An empty descriptor_pb2.FileDescriptorProto. + """ + proto.ParseFromString(self.serialized_pb) + + @property + def _parent(self): + return None + + +def _ParseOptions(message, string): + """Parses serialized options. + + This helper function is used to parse serialized options in generated + proto2 files. It must not be used outside proto2. + """ + message.ParseFromString(string) + return message + + +def _ToCamelCase(name): + """Converts name to camel-case and returns it.""" + capitalize_next = False + result = [] + + for c in name: + if c == '_': + if result: + capitalize_next = True + elif capitalize_next: + result.append(c.upper()) + capitalize_next = False + else: + result += c + + # Lower-case the first letter. + if result and result[0].isupper(): + result[0] = result[0].lower() + return ''.join(result) + + +def _OptionsOrNone(descriptor_proto): + """Returns the value of the field `options`, or None if it is not set.""" + if descriptor_proto.HasField('options'): + return descriptor_proto.options + else: + return None + + +def _ToJsonName(name): + """Converts name to Json name and returns it.""" + capitalize_next = False + result = [] + + for c in name: + if c == '_': + capitalize_next = True + elif capitalize_next: + result.append(c.upper()) + capitalize_next = False + else: + result += c + + return ''.join(result) + + +def MakeDescriptor( + desc_proto, + package='', + build_file_if_cpp=True, + syntax=None, + edition=None, + file_desc=None, +): + """Make a protobuf Descriptor given a DescriptorProto protobuf. + + Handles nested descriptors. Note that this is limited to the scope of defining + a message inside of another message. Composite fields can currently only be + resolved if the message is defined in the same scope as the field. + + Args: + desc_proto: The descriptor_pb2.DescriptorProto protobuf message. + package: Optional package name for the new message Descriptor (string). + build_file_if_cpp: Update the C++ descriptor pool if api matches. Set to + False on recursion, so no duplicates are created. + syntax: The syntax/semantics that should be used. Set to "proto3" to get + proto3 field presence semantics. + edition: The edition that should be used if syntax is "edition". + file_desc: A FileDescriptor to place this descriptor into. + + Returns: + A Descriptor for protobuf messages. + """ + # pylint: disable=g-import-not-at-top + from google.protobuf import descriptor_pb2 + + # Generate a random name for this proto file to prevent conflicts with any + # imported ones. We need to specify a file name so the descriptor pool + # accepts our FileDescriptorProto, but it is not important what that file + # name is actually set to. + proto_name = binascii.hexlify(os.urandom(16)).decode('ascii') + + if package: + file_name = os.path.join(package.replace('.', '/'), proto_name + '.proto') + else: + file_name = proto_name + '.proto' + + if api_implementation.Type() != 'python' and build_file_if_cpp: + # The C++ implementation requires all descriptors to be backed by the same + # definition in the C++ descriptor pool. To do this, we build a + # FileDescriptorProto with the same definition as this descriptor and build + # it into the pool. + file_descriptor_proto = descriptor_pb2.FileDescriptorProto() + file_descriptor_proto.message_type.add().MergeFrom(desc_proto) + + if package: + file_descriptor_proto.package = package + file_descriptor_proto.name = file_name + + _message.default_pool.Add(file_descriptor_proto) + result = _message.default_pool.FindFileByName(file_descriptor_proto.name) + + if _USE_C_DESCRIPTORS: + return result.message_types_by_name[desc_proto.name] + + if file_desc is None: + file_desc = FileDescriptor( + pool=None, + name=file_name, + package=package, + syntax=syntax, + edition=edition, + options=None, + serialized_pb='', + dependencies=[], + public_dependencies=[], + create_key=_internal_create_key, + ) + full_message_name = [desc_proto.name] + if package: full_message_name.insert(0, package) + + # Create Descriptors for enum types + enum_types = {} + for enum_proto in desc_proto.enum_type: + full_name = '.'.join(full_message_name + [enum_proto.name]) + enum_desc = EnumDescriptor( + enum_proto.name, + full_name, + None, + [ + EnumValueDescriptor( + enum_val.name, + ii, + enum_val.number, + create_key=_internal_create_key, + ) + for ii, enum_val in enumerate(enum_proto.value) + ], + file=file_desc, + create_key=_internal_create_key, + ) + enum_types[full_name] = enum_desc + + # Create Descriptors for nested types + nested_types = {} + for nested_proto in desc_proto.nested_type: + full_name = '.'.join(full_message_name + [nested_proto.name]) + # Nested types are just those defined inside of the message, not all types + # used by fields in the message, so no loops are possible here. + nested_desc = MakeDescriptor( + nested_proto, + package='.'.join(full_message_name), + build_file_if_cpp=False, + syntax=syntax, + edition=edition, + file_desc=file_desc, + ) + nested_types[full_name] = nested_desc + + fields = [] + for field_proto in desc_proto.field: + full_name = '.'.join(full_message_name + [field_proto.name]) + enum_desc = None + nested_desc = None + if field_proto.json_name: + json_name = field_proto.json_name + else: + json_name = None + if field_proto.HasField('type_name'): + type_name = field_proto.type_name + full_type_name = '.'.join(full_message_name + + [type_name[type_name.rfind('.')+1:]]) + if full_type_name in nested_types: + nested_desc = nested_types[full_type_name] + elif full_type_name in enum_types: + enum_desc = enum_types[full_type_name] + # Else type_name references a non-local type, which isn't implemented + field = FieldDescriptor( + field_proto.name, + full_name, + field_proto.number - 1, + field_proto.number, + field_proto.type, + FieldDescriptor.ProtoTypeToCppProtoType(field_proto.type), + field_proto.label, + None, + nested_desc, + enum_desc, + None, + False, + None, + options=_OptionsOrNone(field_proto), + has_default_value=False, + json_name=json_name, + file=file_desc, + create_key=_internal_create_key, + ) + fields.append(field) + + desc_name = '.'.join(full_message_name) + return Descriptor( + desc_proto.name, + desc_name, + None, + None, + fields, + list(nested_types.values()), + list(enum_types.values()), + [], + options=_OptionsOrNone(desc_proto), + file=file_desc, + create_key=_internal_create_key, + ) diff --git a/google/protobuf/descriptor.upb.c b/google/protobuf/descriptor.upb.c new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/google/protobuf/descriptor.upb.c @@ -0,0 +1 @@ + diff --git a/google/protobuf/descriptor.upb.h b/google/protobuf/descriptor.upb.h new file mode 100644 index 0000000..d2a8801 --- /dev/null +++ b/google/protobuf/descriptor.upb.h @@ -0,0 +1,6699 @@ +/* This file was generated by upb_generator from the input file: + * + * google/protobuf/descriptor.proto + * + * Do not edit -- your changes will be discarded when the file is + * regenerated. */ + +#ifndef GOOGLE_PROTOBUF_DESCRIPTOR_PROTO_UPB_H_ +#define GOOGLE_PROTOBUF_DESCRIPTOR_PROTO_UPB_H_ + +#include "upb/generated_code_support.h" + +#include "google/protobuf/descriptor.upb_minitable.h" + +// Must be last. +#include "upb/port/def.inc" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct google_protobuf_FileDescriptorSet { upb_Message UPB_PRIVATE(base); } google_protobuf_FileDescriptorSet; +typedef struct google_protobuf_FileDescriptorProto { upb_Message UPB_PRIVATE(base); } google_protobuf_FileDescriptorProto; +typedef struct google_protobuf_DescriptorProto { upb_Message UPB_PRIVATE(base); } google_protobuf_DescriptorProto; +typedef struct google_protobuf_DescriptorProto_ExtensionRange { upb_Message UPB_PRIVATE(base); } google_protobuf_DescriptorProto_ExtensionRange; +typedef struct google_protobuf_DescriptorProto_ReservedRange { upb_Message UPB_PRIVATE(base); } google_protobuf_DescriptorProto_ReservedRange; +typedef struct google_protobuf_ExtensionRangeOptions { upb_Message UPB_PRIVATE(base); } google_protobuf_ExtensionRangeOptions; +typedef struct google_protobuf_ExtensionRangeOptions_Declaration { upb_Message UPB_PRIVATE(base); } google_protobuf_ExtensionRangeOptions_Declaration; +typedef struct google_protobuf_FieldDescriptorProto { upb_Message UPB_PRIVATE(base); } google_protobuf_FieldDescriptorProto; +typedef struct google_protobuf_OneofDescriptorProto { upb_Message UPB_PRIVATE(base); } google_protobuf_OneofDescriptorProto; +typedef struct google_protobuf_EnumDescriptorProto { upb_Message UPB_PRIVATE(base); } google_protobuf_EnumDescriptorProto; +typedef struct google_protobuf_EnumDescriptorProto_EnumReservedRange { upb_Message UPB_PRIVATE(base); } google_protobuf_EnumDescriptorProto_EnumReservedRange; +typedef struct google_protobuf_EnumValueDescriptorProto { upb_Message UPB_PRIVATE(base); } google_protobuf_EnumValueDescriptorProto; +typedef struct google_protobuf_ServiceDescriptorProto { upb_Message UPB_PRIVATE(base); } google_protobuf_ServiceDescriptorProto; +typedef struct google_protobuf_MethodDescriptorProto { upb_Message UPB_PRIVATE(base); } google_protobuf_MethodDescriptorProto; +typedef struct google_protobuf_FileOptions { upb_Message UPB_PRIVATE(base); } google_protobuf_FileOptions; +typedef struct google_protobuf_MessageOptions { upb_Message UPB_PRIVATE(base); } google_protobuf_MessageOptions; +typedef struct google_protobuf_FieldOptions { upb_Message UPB_PRIVATE(base); } google_protobuf_FieldOptions; +typedef struct google_protobuf_FieldOptions_EditionDefault { upb_Message UPB_PRIVATE(base); } google_protobuf_FieldOptions_EditionDefault; +typedef struct google_protobuf_FieldOptions_FeatureSupport { upb_Message UPB_PRIVATE(base); } google_protobuf_FieldOptions_FeatureSupport; +typedef struct google_protobuf_OneofOptions { upb_Message UPB_PRIVATE(base); } google_protobuf_OneofOptions; +typedef struct google_protobuf_EnumOptions { upb_Message UPB_PRIVATE(base); } google_protobuf_EnumOptions; +typedef struct google_protobuf_EnumValueOptions { upb_Message UPB_PRIVATE(base); } google_protobuf_EnumValueOptions; +typedef struct google_protobuf_ServiceOptions { upb_Message UPB_PRIVATE(base); } google_protobuf_ServiceOptions; +typedef struct google_protobuf_MethodOptions { upb_Message UPB_PRIVATE(base); } google_protobuf_MethodOptions; +typedef struct google_protobuf_UninterpretedOption { upb_Message UPB_PRIVATE(base); } google_protobuf_UninterpretedOption; +typedef struct google_protobuf_UninterpretedOption_NamePart { upb_Message UPB_PRIVATE(base); } google_protobuf_UninterpretedOption_NamePart; +typedef struct google_protobuf_FeatureSet { upb_Message UPB_PRIVATE(base); } google_protobuf_FeatureSet; +typedef struct google_protobuf_FeatureSetDefaults { upb_Message UPB_PRIVATE(base); } google_protobuf_FeatureSetDefaults; +typedef struct google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault { upb_Message UPB_PRIVATE(base); } google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault; +typedef struct google_protobuf_SourceCodeInfo { upb_Message UPB_PRIVATE(base); } google_protobuf_SourceCodeInfo; +typedef struct google_protobuf_SourceCodeInfo_Location { upb_Message UPB_PRIVATE(base); } google_protobuf_SourceCodeInfo_Location; +typedef struct google_protobuf_GeneratedCodeInfo { upb_Message UPB_PRIVATE(base); } google_protobuf_GeneratedCodeInfo; +typedef struct google_protobuf_GeneratedCodeInfo_Annotation { upb_Message UPB_PRIVATE(base); } google_protobuf_GeneratedCodeInfo_Annotation; + +typedef enum { + google_protobuf_EDITION_UNKNOWN = 0, + google_protobuf_EDITION_1_TEST_ONLY = 1, + google_protobuf_EDITION_2_TEST_ONLY = 2, + google_protobuf_EDITION_LEGACY = 900, + google_protobuf_EDITION_PROTO2 = 998, + google_protobuf_EDITION_PROTO3 = 999, + google_protobuf_EDITION_2023 = 1000, + google_protobuf_EDITION_2024 = 1001, + google_protobuf_EDITION_99997_TEST_ONLY = 99997, + google_protobuf_EDITION_99998_TEST_ONLY = 99998, + google_protobuf_EDITION_99999_TEST_ONLY = 99999, + google_protobuf_EDITION_MAX = 2147483647 +} google_protobuf_Edition; + +typedef enum { + google_protobuf_ExtensionRangeOptions_DECLARATION = 0, + google_protobuf_ExtensionRangeOptions_UNVERIFIED = 1 +} google_protobuf_ExtensionRangeOptions_VerificationState; + +typedef enum { + google_protobuf_FeatureSet_ENUM_TYPE_UNKNOWN = 0, + google_protobuf_FeatureSet_OPEN = 1, + google_protobuf_FeatureSet_CLOSED = 2 +} google_protobuf_FeatureSet_EnumType; + +typedef enum { + google_protobuf_FeatureSet_FIELD_PRESENCE_UNKNOWN = 0, + google_protobuf_FeatureSet_EXPLICIT = 1, + google_protobuf_FeatureSet_IMPLICIT = 2, + google_protobuf_FeatureSet_LEGACY_REQUIRED = 3 +} google_protobuf_FeatureSet_FieldPresence; + +typedef enum { + google_protobuf_FeatureSet_JSON_FORMAT_UNKNOWN = 0, + google_protobuf_FeatureSet_ALLOW = 1, + google_protobuf_FeatureSet_LEGACY_BEST_EFFORT = 2 +} google_protobuf_FeatureSet_JsonFormat; + +typedef enum { + google_protobuf_FeatureSet_MESSAGE_ENCODING_UNKNOWN = 0, + google_protobuf_FeatureSet_LENGTH_PREFIXED = 1, + google_protobuf_FeatureSet_DELIMITED = 2 +} google_protobuf_FeatureSet_MessageEncoding; + +typedef enum { + google_protobuf_FeatureSet_REPEATED_FIELD_ENCODING_UNKNOWN = 0, + google_protobuf_FeatureSet_PACKED = 1, + google_protobuf_FeatureSet_EXPANDED = 2 +} google_protobuf_FeatureSet_RepeatedFieldEncoding; + +typedef enum { + google_protobuf_FeatureSet_UTF8_VALIDATION_UNKNOWN = 0, + google_protobuf_FeatureSet_VERIFY = 2, + google_protobuf_FeatureSet_NONE = 3 +} google_protobuf_FeatureSet_Utf8Validation; + +typedef enum { + google_protobuf_FieldDescriptorProto_LABEL_OPTIONAL = 1, + google_protobuf_FieldDescriptorProto_LABEL_REQUIRED = 2, + google_protobuf_FieldDescriptorProto_LABEL_REPEATED = 3 +} google_protobuf_FieldDescriptorProto_Label; + +typedef enum { + google_protobuf_FieldDescriptorProto_TYPE_DOUBLE = 1, + google_protobuf_FieldDescriptorProto_TYPE_FLOAT = 2, + google_protobuf_FieldDescriptorProto_TYPE_INT64 = 3, + google_protobuf_FieldDescriptorProto_TYPE_UINT64 = 4, + google_protobuf_FieldDescriptorProto_TYPE_INT32 = 5, + google_protobuf_FieldDescriptorProto_TYPE_FIXED64 = 6, + google_protobuf_FieldDescriptorProto_TYPE_FIXED32 = 7, + google_protobuf_FieldDescriptorProto_TYPE_BOOL = 8, + google_protobuf_FieldDescriptorProto_TYPE_STRING = 9, + google_protobuf_FieldDescriptorProto_TYPE_GROUP = 10, + google_protobuf_FieldDescriptorProto_TYPE_MESSAGE = 11, + google_protobuf_FieldDescriptorProto_TYPE_BYTES = 12, + google_protobuf_FieldDescriptorProto_TYPE_UINT32 = 13, + google_protobuf_FieldDescriptorProto_TYPE_ENUM = 14, + google_protobuf_FieldDescriptorProto_TYPE_SFIXED32 = 15, + google_protobuf_FieldDescriptorProto_TYPE_SFIXED64 = 16, + google_protobuf_FieldDescriptorProto_TYPE_SINT32 = 17, + google_protobuf_FieldDescriptorProto_TYPE_SINT64 = 18 +} google_protobuf_FieldDescriptorProto_Type; + +typedef enum { + google_protobuf_FieldOptions_STRING = 0, + google_protobuf_FieldOptions_CORD = 1, + google_protobuf_FieldOptions_STRING_PIECE = 2 +} google_protobuf_FieldOptions_CType; + +typedef enum { + google_protobuf_FieldOptions_JS_NORMAL = 0, + google_protobuf_FieldOptions_JS_STRING = 1, + google_protobuf_FieldOptions_JS_NUMBER = 2 +} google_protobuf_FieldOptions_JSType; + +typedef enum { + google_protobuf_FieldOptions_RETENTION_UNKNOWN = 0, + google_protobuf_FieldOptions_RETENTION_RUNTIME = 1, + google_protobuf_FieldOptions_RETENTION_SOURCE = 2 +} google_protobuf_FieldOptions_OptionRetention; + +typedef enum { + google_protobuf_FieldOptions_TARGET_TYPE_UNKNOWN = 0, + google_protobuf_FieldOptions_TARGET_TYPE_FILE = 1, + google_protobuf_FieldOptions_TARGET_TYPE_EXTENSION_RANGE = 2, + google_protobuf_FieldOptions_TARGET_TYPE_MESSAGE = 3, + google_protobuf_FieldOptions_TARGET_TYPE_FIELD = 4, + google_protobuf_FieldOptions_TARGET_TYPE_ONEOF = 5, + google_protobuf_FieldOptions_TARGET_TYPE_ENUM = 6, + google_protobuf_FieldOptions_TARGET_TYPE_ENUM_ENTRY = 7, + google_protobuf_FieldOptions_TARGET_TYPE_SERVICE = 8, + google_protobuf_FieldOptions_TARGET_TYPE_METHOD = 9 +} google_protobuf_FieldOptions_OptionTargetType; + +typedef enum { + google_protobuf_FileOptions_SPEED = 1, + google_protobuf_FileOptions_CODE_SIZE = 2, + google_protobuf_FileOptions_LITE_RUNTIME = 3 +} google_protobuf_FileOptions_OptimizeMode; + +typedef enum { + google_protobuf_GeneratedCodeInfo_Annotation_NONE = 0, + google_protobuf_GeneratedCodeInfo_Annotation_SET = 1, + google_protobuf_GeneratedCodeInfo_Annotation_ALIAS = 2 +} google_protobuf_GeneratedCodeInfo_Annotation_Semantic; + +typedef enum { + google_protobuf_MethodOptions_IDEMPOTENCY_UNKNOWN = 0, + google_protobuf_MethodOptions_NO_SIDE_EFFECTS = 1, + google_protobuf_MethodOptions_IDEMPOTENT = 2 +} google_protobuf_MethodOptions_IdempotencyLevel; + + + +/* google.protobuf.FileDescriptorSet */ + +UPB_INLINE google_protobuf_FileDescriptorSet* google_protobuf_FileDescriptorSet_new(upb_Arena* arena) { + return (google_protobuf_FileDescriptorSet*)_upb_Message_New(&google__protobuf__FileDescriptorSet_msg_init, arena); +} +UPB_INLINE google_protobuf_FileDescriptorSet* google_protobuf_FileDescriptorSet_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_FileDescriptorSet* ret = google_protobuf_FileDescriptorSet_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FileDescriptorSet_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_FileDescriptorSet* google_protobuf_FileDescriptorSet_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_FileDescriptorSet* ret = google_protobuf_FileDescriptorSet_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FileDescriptorSet_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_FileDescriptorSet_serialize(const google_protobuf_FileDescriptorSet* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FileDescriptorSet_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_FileDescriptorSet_serialize_ex(const google_protobuf_FileDescriptorSet* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FileDescriptorSet_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_FileDescriptorSet_clear_file(google_protobuf_FileDescriptorSet* msg) { + const upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FileDescriptorProto* const* google_protobuf_FileDescriptorSet_file(const google_protobuf_FileDescriptorSet* msg, size_t* size) { + const upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_FileDescriptorProto* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_FileDescriptorSet_file_upb_array(const google_protobuf_FileDescriptorSet* msg, size_t* size) { + const upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_FileDescriptorSet_file_mutable_upb_array(google_protobuf_FileDescriptorSet* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} + +UPB_INLINE google_protobuf_FileDescriptorProto** google_protobuf_FileDescriptorSet_mutable_file(google_protobuf_FileDescriptorSet* msg, size_t* size) { + upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_FileDescriptorProto**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_FileDescriptorProto** google_protobuf_FileDescriptorSet_resize_file(google_protobuf_FileDescriptorSet* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_FileDescriptorProto**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_FileDescriptorProto* google_protobuf_FileDescriptorSet_add_file(google_protobuf_FileDescriptorSet* msg, upb_Arena* arena) { + upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_FileDescriptorProto* sub = (struct google_protobuf_FileDescriptorProto*)_upb_Message_New(&google__protobuf__FileDescriptorProto_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} + +/* google.protobuf.FileDescriptorProto */ + +UPB_INLINE google_protobuf_FileDescriptorProto* google_protobuf_FileDescriptorProto_new(upb_Arena* arena) { + return (google_protobuf_FileDescriptorProto*)_upb_Message_New(&google__protobuf__FileDescriptorProto_msg_init, arena); +} +UPB_INLINE google_protobuf_FileDescriptorProto* google_protobuf_FileDescriptorProto_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_FileDescriptorProto* ret = google_protobuf_FileDescriptorProto_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FileDescriptorProto_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_FileDescriptorProto* google_protobuf_FileDescriptorProto_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_FileDescriptorProto* ret = google_protobuf_FileDescriptorProto_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FileDescriptorProto_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_FileDescriptorProto_serialize(const google_protobuf_FileDescriptorProto* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FileDescriptorProto_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_FileDescriptorProto_serialize_ex(const google_protobuf_FileDescriptorProto* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FileDescriptorProto_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_FileDescriptorProto_clear_name(google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(52, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FileDescriptorProto_name(const google_protobuf_FileDescriptorProto* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {1, UPB_SIZE(52, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileDescriptorProto_has_name(const google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(52, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileDescriptorProto_clear_package(google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(60, 32), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FileDescriptorProto_package(const google_protobuf_FileDescriptorProto* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {2, UPB_SIZE(60, 32), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileDescriptorProto_has_package(const google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(60, 32), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileDescriptorProto_clear_dependency(google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(12, 48), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView const* google_protobuf_FileDescriptorProto_dependency(const google_protobuf_FileDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {3, UPB_SIZE(12, 48), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (upb_StringView const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_FileDescriptorProto_dependency_upb_array(const google_protobuf_FileDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {3, UPB_SIZE(12, 48), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_FileDescriptorProto_dependency_mutable_upb_array(google_protobuf_FileDescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {3, UPB_SIZE(12, 48), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_FileDescriptorProto_clear_message_type(google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {4, UPB_SIZE(16, 56), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_DescriptorProto* const* google_protobuf_FileDescriptorProto_message_type(const google_protobuf_FileDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {4, UPB_SIZE(16, 56), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_DescriptorProto* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_FileDescriptorProto_message_type_upb_array(const google_protobuf_FileDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {4, UPB_SIZE(16, 56), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_FileDescriptorProto_message_type_mutable_upb_array(google_protobuf_FileDescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {4, UPB_SIZE(16, 56), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_FileDescriptorProto_clear_enum_type(google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {5, UPB_SIZE(20, 64), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_EnumDescriptorProto* const* google_protobuf_FileDescriptorProto_enum_type(const google_protobuf_FileDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {5, UPB_SIZE(20, 64), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_EnumDescriptorProto* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_FileDescriptorProto_enum_type_upb_array(const google_protobuf_FileDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {5, UPB_SIZE(20, 64), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_FileDescriptorProto_enum_type_mutable_upb_array(google_protobuf_FileDescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {5, UPB_SIZE(20, 64), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_FileDescriptorProto_clear_service(google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {6, UPB_SIZE(24, 72), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_ServiceDescriptorProto* const* google_protobuf_FileDescriptorProto_service(const google_protobuf_FileDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {6, UPB_SIZE(24, 72), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_ServiceDescriptorProto* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_FileDescriptorProto_service_upb_array(const google_protobuf_FileDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {6, UPB_SIZE(24, 72), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_FileDescriptorProto_service_mutable_upb_array(google_protobuf_FileDescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {6, UPB_SIZE(24, 72), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_FileDescriptorProto_clear_extension(google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {7, UPB_SIZE(28, 80), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FieldDescriptorProto* const* google_protobuf_FileDescriptorProto_extension(const google_protobuf_FileDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {7, UPB_SIZE(28, 80), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_FieldDescriptorProto* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_FileDescriptorProto_extension_upb_array(const google_protobuf_FileDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {7, UPB_SIZE(28, 80), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_FileDescriptorProto_extension_mutable_upb_array(google_protobuf_FileDescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {7, UPB_SIZE(28, 80), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_FileDescriptorProto_clear_options(google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {8, UPB_SIZE(32, 88), 66, 4, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FileOptions* google_protobuf_FileDescriptorProto_options(const google_protobuf_FileDescriptorProto* msg) { + const google_protobuf_FileOptions* default_val = NULL; + const google_protobuf_FileOptions* ret; + const upb_MiniTableField field = {8, UPB_SIZE(32, 88), 66, 4, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileDescriptorProto_has_options(const google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {8, UPB_SIZE(32, 88), 66, 4, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileDescriptorProto_clear_source_code_info(google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {9, UPB_SIZE(36, 96), 67, 5, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_SourceCodeInfo* google_protobuf_FileDescriptorProto_source_code_info(const google_protobuf_FileDescriptorProto* msg) { + const google_protobuf_SourceCodeInfo* default_val = NULL; + const google_protobuf_SourceCodeInfo* ret; + const upb_MiniTableField field = {9, UPB_SIZE(36, 96), 67, 5, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileDescriptorProto_has_source_code_info(const google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {9, UPB_SIZE(36, 96), 67, 5, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileDescriptorProto_clear_public_dependency(google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {10, UPB_SIZE(40, 104), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t const* google_protobuf_FileDescriptorProto_public_dependency(const google_protobuf_FileDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {10, UPB_SIZE(40, 104), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (int32_t const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_FileDescriptorProto_public_dependency_upb_array(const google_protobuf_FileDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {10, UPB_SIZE(40, 104), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_FileDescriptorProto_public_dependency_mutable_upb_array(google_protobuf_FileDescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {10, UPB_SIZE(40, 104), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_FileDescriptorProto_clear_weak_dependency(google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {11, UPB_SIZE(44, 112), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t const* google_protobuf_FileDescriptorProto_weak_dependency(const google_protobuf_FileDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {11, UPB_SIZE(44, 112), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (int32_t const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_FileDescriptorProto_weak_dependency_upb_array(const google_protobuf_FileDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {11, UPB_SIZE(44, 112), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_FileDescriptorProto_weak_dependency_mutable_upb_array(google_protobuf_FileDescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {11, UPB_SIZE(44, 112), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_FileDescriptorProto_clear_syntax(google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {12, UPB_SIZE(68, 120), 68, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FileDescriptorProto_syntax(const google_protobuf_FileDescriptorProto* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {12, UPB_SIZE(68, 120), 68, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileDescriptorProto_has_syntax(const google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {12, UPB_SIZE(68, 120), 68, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileDescriptorProto_clear_edition(google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {14, UPB_SIZE(48, 12), 69, 6, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FileDescriptorProto_edition(const google_protobuf_FileDescriptorProto* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {14, UPB_SIZE(48, 12), 69, 6, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileDescriptorProto_has_edition(const google_protobuf_FileDescriptorProto* msg) { + const upb_MiniTableField field = {14, UPB_SIZE(48, 12), 69, 6, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE void google_protobuf_FileDescriptorProto_set_name(google_protobuf_FileDescriptorProto *msg, upb_StringView value) { + const upb_MiniTableField field = {1, UPB_SIZE(52, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileDescriptorProto_set_package(google_protobuf_FileDescriptorProto *msg, upb_StringView value) { + const upb_MiniTableField field = {2, UPB_SIZE(60, 32), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE upb_StringView* google_protobuf_FileDescriptorProto_mutable_dependency(google_protobuf_FileDescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {3, UPB_SIZE(12, 48), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (upb_StringView*)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE upb_StringView* google_protobuf_FileDescriptorProto_resize_dependency(google_protobuf_FileDescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {3, UPB_SIZE(12, 48), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (upb_StringView*)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE bool google_protobuf_FileDescriptorProto_add_dependency(google_protobuf_FileDescriptorProto* msg, upb_StringView val, upb_Arena* arena) { + upb_MiniTableField field = {3, UPB_SIZE(12, 48), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return false; + } + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &val, sizeof(val)); + return true; +} +UPB_INLINE google_protobuf_DescriptorProto** google_protobuf_FileDescriptorProto_mutable_message_type(google_protobuf_FileDescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {4, UPB_SIZE(16, 56), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_DescriptorProto**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_DescriptorProto** google_protobuf_FileDescriptorProto_resize_message_type(google_protobuf_FileDescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {4, UPB_SIZE(16, 56), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_DescriptorProto**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_DescriptorProto* google_protobuf_FileDescriptorProto_add_message_type(google_protobuf_FileDescriptorProto* msg, upb_Arena* arena) { + upb_MiniTableField field = {4, UPB_SIZE(16, 56), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_DescriptorProto* sub = (struct google_protobuf_DescriptorProto*)_upb_Message_New(&google__protobuf__DescriptorProto_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE google_protobuf_EnumDescriptorProto** google_protobuf_FileDescriptorProto_mutable_enum_type(google_protobuf_FileDescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {5, UPB_SIZE(20, 64), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_EnumDescriptorProto**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_EnumDescriptorProto** google_protobuf_FileDescriptorProto_resize_enum_type(google_protobuf_FileDescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {5, UPB_SIZE(20, 64), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_EnumDescriptorProto**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_EnumDescriptorProto* google_protobuf_FileDescriptorProto_add_enum_type(google_protobuf_FileDescriptorProto* msg, upb_Arena* arena) { + upb_MiniTableField field = {5, UPB_SIZE(20, 64), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_EnumDescriptorProto* sub = (struct google_protobuf_EnumDescriptorProto*)_upb_Message_New(&google__protobuf__EnumDescriptorProto_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE google_protobuf_ServiceDescriptorProto** google_protobuf_FileDescriptorProto_mutable_service(google_protobuf_FileDescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {6, UPB_SIZE(24, 72), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_ServiceDescriptorProto**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_ServiceDescriptorProto** google_protobuf_FileDescriptorProto_resize_service(google_protobuf_FileDescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {6, UPB_SIZE(24, 72), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_ServiceDescriptorProto**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_ServiceDescriptorProto* google_protobuf_FileDescriptorProto_add_service(google_protobuf_FileDescriptorProto* msg, upb_Arena* arena) { + upb_MiniTableField field = {6, UPB_SIZE(24, 72), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_ServiceDescriptorProto* sub = (struct google_protobuf_ServiceDescriptorProto*)_upb_Message_New(&google__protobuf__ServiceDescriptorProto_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE google_protobuf_FieldDescriptorProto** google_protobuf_FileDescriptorProto_mutable_extension(google_protobuf_FileDescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {7, UPB_SIZE(28, 80), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_FieldDescriptorProto**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_FieldDescriptorProto** google_protobuf_FileDescriptorProto_resize_extension(google_protobuf_FileDescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {7, UPB_SIZE(28, 80), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_FieldDescriptorProto**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_FieldDescriptorProto* google_protobuf_FileDescriptorProto_add_extension(google_protobuf_FileDescriptorProto* msg, upb_Arena* arena) { + upb_MiniTableField field = {7, UPB_SIZE(28, 80), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_FieldDescriptorProto* sub = (struct google_protobuf_FieldDescriptorProto*)_upb_Message_New(&google__protobuf__FieldDescriptorProto_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE void google_protobuf_FileDescriptorProto_set_options(google_protobuf_FileDescriptorProto *msg, google_protobuf_FileOptions* value) { + const upb_MiniTableField field = {8, UPB_SIZE(32, 88), 66, 4, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_FileOptions* google_protobuf_FileDescriptorProto_mutable_options(google_protobuf_FileDescriptorProto* msg, upb_Arena* arena) { + struct google_protobuf_FileOptions* sub = (struct google_protobuf_FileOptions*)google_protobuf_FileDescriptorProto_options(msg); + if (sub == NULL) { + sub = (struct google_protobuf_FileOptions*)_upb_Message_New(&google__protobuf__FileOptions_msg_init, arena); + if (sub) google_protobuf_FileDescriptorProto_set_options(msg, sub); + } + return sub; +} +UPB_INLINE void google_protobuf_FileDescriptorProto_set_source_code_info(google_protobuf_FileDescriptorProto *msg, google_protobuf_SourceCodeInfo* value) { + const upb_MiniTableField field = {9, UPB_SIZE(36, 96), 67, 5, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_SourceCodeInfo* google_protobuf_FileDescriptorProto_mutable_source_code_info(google_protobuf_FileDescriptorProto* msg, upb_Arena* arena) { + struct google_protobuf_SourceCodeInfo* sub = (struct google_protobuf_SourceCodeInfo*)google_protobuf_FileDescriptorProto_source_code_info(msg); + if (sub == NULL) { + sub = (struct google_protobuf_SourceCodeInfo*)_upb_Message_New(&google__protobuf__SourceCodeInfo_msg_init, arena); + if (sub) google_protobuf_FileDescriptorProto_set_source_code_info(msg, sub); + } + return sub; +} +UPB_INLINE int32_t* google_protobuf_FileDescriptorProto_mutable_public_dependency(google_protobuf_FileDescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {10, UPB_SIZE(40, 104), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (int32_t*)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE int32_t* google_protobuf_FileDescriptorProto_resize_public_dependency(google_protobuf_FileDescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {10, UPB_SIZE(40, 104), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (int32_t*)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE bool google_protobuf_FileDescriptorProto_add_public_dependency(google_protobuf_FileDescriptorProto* msg, int32_t val, upb_Arena* arena) { + upb_MiniTableField field = {10, UPB_SIZE(40, 104), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return false; + } + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &val, sizeof(val)); + return true; +} +UPB_INLINE int32_t* google_protobuf_FileDescriptorProto_mutable_weak_dependency(google_protobuf_FileDescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {11, UPB_SIZE(44, 112), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (int32_t*)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE int32_t* google_protobuf_FileDescriptorProto_resize_weak_dependency(google_protobuf_FileDescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {11, UPB_SIZE(44, 112), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (int32_t*)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE bool google_protobuf_FileDescriptorProto_add_weak_dependency(google_protobuf_FileDescriptorProto* msg, int32_t val, upb_Arena* arena) { + upb_MiniTableField field = {11, UPB_SIZE(44, 112), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return false; + } + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &val, sizeof(val)); + return true; +} +UPB_INLINE void google_protobuf_FileDescriptorProto_set_syntax(google_protobuf_FileDescriptorProto *msg, upb_StringView value) { + const upb_MiniTableField field = {12, UPB_SIZE(68, 120), 68, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileDescriptorProto_set_edition(google_protobuf_FileDescriptorProto *msg, int32_t value) { + const upb_MiniTableField field = {14, UPB_SIZE(48, 12), 69, 6, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} + +/* google.protobuf.DescriptorProto */ + +UPB_INLINE google_protobuf_DescriptorProto* google_protobuf_DescriptorProto_new(upb_Arena* arena) { + return (google_protobuf_DescriptorProto*)_upb_Message_New(&google__protobuf__DescriptorProto_msg_init, arena); +} +UPB_INLINE google_protobuf_DescriptorProto* google_protobuf_DescriptorProto_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_DescriptorProto* ret = google_protobuf_DescriptorProto_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__DescriptorProto_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_DescriptorProto* google_protobuf_DescriptorProto_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_DescriptorProto* ret = google_protobuf_DescriptorProto_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__DescriptorProto_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_DescriptorProto_serialize(const google_protobuf_DescriptorProto* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__DescriptorProto_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_DescriptorProto_serialize_ex(const google_protobuf_DescriptorProto* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__DescriptorProto_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_DescriptorProto_clear_name(google_protobuf_DescriptorProto* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(48, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_DescriptorProto_name(const google_protobuf_DescriptorProto* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {1, UPB_SIZE(48, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_DescriptorProto_has_name(const google_protobuf_DescriptorProto* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(48, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_DescriptorProto_clear_field(google_protobuf_DescriptorProto* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FieldDescriptorProto* const* google_protobuf_DescriptorProto_field(const google_protobuf_DescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_FieldDescriptorProto* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_DescriptorProto_field_upb_array(const google_protobuf_DescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_DescriptorProto_field_mutable_upb_array(google_protobuf_DescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_DescriptorProto_clear_nested_type(google_protobuf_DescriptorProto* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 40), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_DescriptorProto* const* google_protobuf_DescriptorProto_nested_type(const google_protobuf_DescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 40), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_DescriptorProto* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_DescriptorProto_nested_type_upb_array(const google_protobuf_DescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 40), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_DescriptorProto_nested_type_mutable_upb_array(google_protobuf_DescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 40), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_DescriptorProto_clear_enum_type(google_protobuf_DescriptorProto* msg) { + const upb_MiniTableField field = {4, UPB_SIZE(20, 48), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_EnumDescriptorProto* const* google_protobuf_DescriptorProto_enum_type(const google_protobuf_DescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {4, UPB_SIZE(20, 48), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_EnumDescriptorProto* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_DescriptorProto_enum_type_upb_array(const google_protobuf_DescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {4, UPB_SIZE(20, 48), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_DescriptorProto_enum_type_mutable_upb_array(google_protobuf_DescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {4, UPB_SIZE(20, 48), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_DescriptorProto_clear_extension_range(google_protobuf_DescriptorProto* msg) { + const upb_MiniTableField field = {5, UPB_SIZE(24, 56), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_DescriptorProto_ExtensionRange* const* google_protobuf_DescriptorProto_extension_range(const google_protobuf_DescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {5, UPB_SIZE(24, 56), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_DescriptorProto_ExtensionRange* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_DescriptorProto_extension_range_upb_array(const google_protobuf_DescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {5, UPB_SIZE(24, 56), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_DescriptorProto_extension_range_mutable_upb_array(google_protobuf_DescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {5, UPB_SIZE(24, 56), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_DescriptorProto_clear_extension(google_protobuf_DescriptorProto* msg) { + const upb_MiniTableField field = {6, UPB_SIZE(28, 64), 0, 4, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FieldDescriptorProto* const* google_protobuf_DescriptorProto_extension(const google_protobuf_DescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {6, UPB_SIZE(28, 64), 0, 4, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_FieldDescriptorProto* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_DescriptorProto_extension_upb_array(const google_protobuf_DescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {6, UPB_SIZE(28, 64), 0, 4, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_DescriptorProto_extension_mutable_upb_array(google_protobuf_DescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {6, UPB_SIZE(28, 64), 0, 4, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_DescriptorProto_clear_options(google_protobuf_DescriptorProto* msg) { + const upb_MiniTableField field = {7, UPB_SIZE(32, 72), 65, 5, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_MessageOptions* google_protobuf_DescriptorProto_options(const google_protobuf_DescriptorProto* msg) { + const google_protobuf_MessageOptions* default_val = NULL; + const google_protobuf_MessageOptions* ret; + const upb_MiniTableField field = {7, UPB_SIZE(32, 72), 65, 5, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_DescriptorProto_has_options(const google_protobuf_DescriptorProto* msg) { + const upb_MiniTableField field = {7, UPB_SIZE(32, 72), 65, 5, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_DescriptorProto_clear_oneof_decl(google_protobuf_DescriptorProto* msg) { + const upb_MiniTableField field = {8, UPB_SIZE(36, 80), 0, 6, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_OneofDescriptorProto* const* google_protobuf_DescriptorProto_oneof_decl(const google_protobuf_DescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {8, UPB_SIZE(36, 80), 0, 6, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_OneofDescriptorProto* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_DescriptorProto_oneof_decl_upb_array(const google_protobuf_DescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {8, UPB_SIZE(36, 80), 0, 6, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_DescriptorProto_oneof_decl_mutable_upb_array(google_protobuf_DescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {8, UPB_SIZE(36, 80), 0, 6, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_DescriptorProto_clear_reserved_range(google_protobuf_DescriptorProto* msg) { + const upb_MiniTableField field = {9, UPB_SIZE(40, 88), 0, 7, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_DescriptorProto_ReservedRange* const* google_protobuf_DescriptorProto_reserved_range(const google_protobuf_DescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {9, UPB_SIZE(40, 88), 0, 7, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_DescriptorProto_ReservedRange* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_DescriptorProto_reserved_range_upb_array(const google_protobuf_DescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {9, UPB_SIZE(40, 88), 0, 7, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_DescriptorProto_reserved_range_mutable_upb_array(google_protobuf_DescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {9, UPB_SIZE(40, 88), 0, 7, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_DescriptorProto_clear_reserved_name(google_protobuf_DescriptorProto* msg) { + const upb_MiniTableField field = {10, UPB_SIZE(44, 96), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView const* google_protobuf_DescriptorProto_reserved_name(const google_protobuf_DescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {10, UPB_SIZE(44, 96), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (upb_StringView const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_DescriptorProto_reserved_name_upb_array(const google_protobuf_DescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {10, UPB_SIZE(44, 96), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_DescriptorProto_reserved_name_mutable_upb_array(google_protobuf_DescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {10, UPB_SIZE(44, 96), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} + +UPB_INLINE void google_protobuf_DescriptorProto_set_name(google_protobuf_DescriptorProto *msg, upb_StringView value) { + const upb_MiniTableField field = {1, UPB_SIZE(48, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE google_protobuf_FieldDescriptorProto** google_protobuf_DescriptorProto_mutable_field(google_protobuf_DescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_FieldDescriptorProto**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_FieldDescriptorProto** google_protobuf_DescriptorProto_resize_field(google_protobuf_DescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_FieldDescriptorProto**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_FieldDescriptorProto* google_protobuf_DescriptorProto_add_field(google_protobuf_DescriptorProto* msg, upb_Arena* arena) { + upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_FieldDescriptorProto* sub = (struct google_protobuf_FieldDescriptorProto*)_upb_Message_New(&google__protobuf__FieldDescriptorProto_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE google_protobuf_DescriptorProto** google_protobuf_DescriptorProto_mutable_nested_type(google_protobuf_DescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {3, UPB_SIZE(16, 40), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_DescriptorProto**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_DescriptorProto** google_protobuf_DescriptorProto_resize_nested_type(google_protobuf_DescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {3, UPB_SIZE(16, 40), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_DescriptorProto**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_DescriptorProto* google_protobuf_DescriptorProto_add_nested_type(google_protobuf_DescriptorProto* msg, upb_Arena* arena) { + upb_MiniTableField field = {3, UPB_SIZE(16, 40), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_DescriptorProto* sub = (struct google_protobuf_DescriptorProto*)_upb_Message_New(&google__protobuf__DescriptorProto_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE google_protobuf_EnumDescriptorProto** google_protobuf_DescriptorProto_mutable_enum_type(google_protobuf_DescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {4, UPB_SIZE(20, 48), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_EnumDescriptorProto**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_EnumDescriptorProto** google_protobuf_DescriptorProto_resize_enum_type(google_protobuf_DescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {4, UPB_SIZE(20, 48), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_EnumDescriptorProto**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_EnumDescriptorProto* google_protobuf_DescriptorProto_add_enum_type(google_protobuf_DescriptorProto* msg, upb_Arena* arena) { + upb_MiniTableField field = {4, UPB_SIZE(20, 48), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_EnumDescriptorProto* sub = (struct google_protobuf_EnumDescriptorProto*)_upb_Message_New(&google__protobuf__EnumDescriptorProto_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE google_protobuf_DescriptorProto_ExtensionRange** google_protobuf_DescriptorProto_mutable_extension_range(google_protobuf_DescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {5, UPB_SIZE(24, 56), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_DescriptorProto_ExtensionRange**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_DescriptorProto_ExtensionRange** google_protobuf_DescriptorProto_resize_extension_range(google_protobuf_DescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {5, UPB_SIZE(24, 56), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_DescriptorProto_ExtensionRange**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_DescriptorProto_ExtensionRange* google_protobuf_DescriptorProto_add_extension_range(google_protobuf_DescriptorProto* msg, upb_Arena* arena) { + upb_MiniTableField field = {5, UPB_SIZE(24, 56), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_DescriptorProto_ExtensionRange* sub = (struct google_protobuf_DescriptorProto_ExtensionRange*)_upb_Message_New(&google__protobuf__DescriptorProto__ExtensionRange_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE google_protobuf_FieldDescriptorProto** google_protobuf_DescriptorProto_mutable_extension(google_protobuf_DescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {6, UPB_SIZE(28, 64), 0, 4, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_FieldDescriptorProto**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_FieldDescriptorProto** google_protobuf_DescriptorProto_resize_extension(google_protobuf_DescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {6, UPB_SIZE(28, 64), 0, 4, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_FieldDescriptorProto**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_FieldDescriptorProto* google_protobuf_DescriptorProto_add_extension(google_protobuf_DescriptorProto* msg, upb_Arena* arena) { + upb_MiniTableField field = {6, UPB_SIZE(28, 64), 0, 4, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_FieldDescriptorProto* sub = (struct google_protobuf_FieldDescriptorProto*)_upb_Message_New(&google__protobuf__FieldDescriptorProto_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE void google_protobuf_DescriptorProto_set_options(google_protobuf_DescriptorProto *msg, google_protobuf_MessageOptions* value) { + const upb_MiniTableField field = {7, UPB_SIZE(32, 72), 65, 5, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_MessageOptions* google_protobuf_DescriptorProto_mutable_options(google_protobuf_DescriptorProto* msg, upb_Arena* arena) { + struct google_protobuf_MessageOptions* sub = (struct google_protobuf_MessageOptions*)google_protobuf_DescriptorProto_options(msg); + if (sub == NULL) { + sub = (struct google_protobuf_MessageOptions*)_upb_Message_New(&google__protobuf__MessageOptions_msg_init, arena); + if (sub) google_protobuf_DescriptorProto_set_options(msg, sub); + } + return sub; +} +UPB_INLINE google_protobuf_OneofDescriptorProto** google_protobuf_DescriptorProto_mutable_oneof_decl(google_protobuf_DescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {8, UPB_SIZE(36, 80), 0, 6, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_OneofDescriptorProto**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_OneofDescriptorProto** google_protobuf_DescriptorProto_resize_oneof_decl(google_protobuf_DescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {8, UPB_SIZE(36, 80), 0, 6, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_OneofDescriptorProto**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_OneofDescriptorProto* google_protobuf_DescriptorProto_add_oneof_decl(google_protobuf_DescriptorProto* msg, upb_Arena* arena) { + upb_MiniTableField field = {8, UPB_SIZE(36, 80), 0, 6, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_OneofDescriptorProto* sub = (struct google_protobuf_OneofDescriptorProto*)_upb_Message_New(&google__protobuf__OneofDescriptorProto_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE google_protobuf_DescriptorProto_ReservedRange** google_protobuf_DescriptorProto_mutable_reserved_range(google_protobuf_DescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {9, UPB_SIZE(40, 88), 0, 7, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_DescriptorProto_ReservedRange**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_DescriptorProto_ReservedRange** google_protobuf_DescriptorProto_resize_reserved_range(google_protobuf_DescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {9, UPB_SIZE(40, 88), 0, 7, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_DescriptorProto_ReservedRange**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_DescriptorProto_ReservedRange* google_protobuf_DescriptorProto_add_reserved_range(google_protobuf_DescriptorProto* msg, upb_Arena* arena) { + upb_MiniTableField field = {9, UPB_SIZE(40, 88), 0, 7, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_DescriptorProto_ReservedRange* sub = (struct google_protobuf_DescriptorProto_ReservedRange*)_upb_Message_New(&google__protobuf__DescriptorProto__ReservedRange_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE upb_StringView* google_protobuf_DescriptorProto_mutable_reserved_name(google_protobuf_DescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {10, UPB_SIZE(44, 96), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (upb_StringView*)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE upb_StringView* google_protobuf_DescriptorProto_resize_reserved_name(google_protobuf_DescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {10, UPB_SIZE(44, 96), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (upb_StringView*)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE bool google_protobuf_DescriptorProto_add_reserved_name(google_protobuf_DescriptorProto* msg, upb_StringView val, upb_Arena* arena) { + upb_MiniTableField field = {10, UPB_SIZE(44, 96), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return false; + } + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &val, sizeof(val)); + return true; +} + +/* google.protobuf.DescriptorProto.ExtensionRange */ + +UPB_INLINE google_protobuf_DescriptorProto_ExtensionRange* google_protobuf_DescriptorProto_ExtensionRange_new(upb_Arena* arena) { + return (google_protobuf_DescriptorProto_ExtensionRange*)_upb_Message_New(&google__protobuf__DescriptorProto__ExtensionRange_msg_init, arena); +} +UPB_INLINE google_protobuf_DescriptorProto_ExtensionRange* google_protobuf_DescriptorProto_ExtensionRange_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_DescriptorProto_ExtensionRange* ret = google_protobuf_DescriptorProto_ExtensionRange_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__DescriptorProto__ExtensionRange_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_DescriptorProto_ExtensionRange* google_protobuf_DescriptorProto_ExtensionRange_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_DescriptorProto_ExtensionRange* ret = google_protobuf_DescriptorProto_ExtensionRange_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__DescriptorProto__ExtensionRange_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_DescriptorProto_ExtensionRange_serialize(const google_protobuf_DescriptorProto_ExtensionRange* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__DescriptorProto__ExtensionRange_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_DescriptorProto_ExtensionRange_serialize_ex(const google_protobuf_DescriptorProto_ExtensionRange* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__DescriptorProto__ExtensionRange_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_DescriptorProto_ExtensionRange_clear_start(google_protobuf_DescriptorProto_ExtensionRange* msg) { + const upb_MiniTableField field = {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_DescriptorProto_ExtensionRange_start(const google_protobuf_DescriptorProto_ExtensionRange* msg) { + int32_t default_val = (int32_t)0; + int32_t ret; + const upb_MiniTableField field = {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_DescriptorProto_ExtensionRange_has_start(const google_protobuf_DescriptorProto_ExtensionRange* msg) { + const upb_MiniTableField field = {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_DescriptorProto_ExtensionRange_clear_end(google_protobuf_DescriptorProto_ExtensionRange* msg) { + const upb_MiniTableField field = {2, 16, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_DescriptorProto_ExtensionRange_end(const google_protobuf_DescriptorProto_ExtensionRange* msg) { + int32_t default_val = (int32_t)0; + int32_t ret; + const upb_MiniTableField field = {2, 16, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_DescriptorProto_ExtensionRange_has_end(const google_protobuf_DescriptorProto_ExtensionRange* msg) { + const upb_MiniTableField field = {2, 16, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_DescriptorProto_ExtensionRange_clear_options(google_protobuf_DescriptorProto_ExtensionRange* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(20, 24), 66, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_ExtensionRangeOptions* google_protobuf_DescriptorProto_ExtensionRange_options(const google_protobuf_DescriptorProto_ExtensionRange* msg) { + const google_protobuf_ExtensionRangeOptions* default_val = NULL; + const google_protobuf_ExtensionRangeOptions* ret; + const upb_MiniTableField field = {3, UPB_SIZE(20, 24), 66, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_DescriptorProto_ExtensionRange_has_options(const google_protobuf_DescriptorProto_ExtensionRange* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(20, 24), 66, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE void google_protobuf_DescriptorProto_ExtensionRange_set_start(google_protobuf_DescriptorProto_ExtensionRange *msg, int32_t value) { + const upb_MiniTableField field = {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_DescriptorProto_ExtensionRange_set_end(google_protobuf_DescriptorProto_ExtensionRange *msg, int32_t value) { + const upb_MiniTableField field = {2, 16, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_DescriptorProto_ExtensionRange_set_options(google_protobuf_DescriptorProto_ExtensionRange *msg, google_protobuf_ExtensionRangeOptions* value) { + const upb_MiniTableField field = {3, UPB_SIZE(20, 24), 66, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_ExtensionRangeOptions* google_protobuf_DescriptorProto_ExtensionRange_mutable_options(google_protobuf_DescriptorProto_ExtensionRange* msg, upb_Arena* arena) { + struct google_protobuf_ExtensionRangeOptions* sub = (struct google_protobuf_ExtensionRangeOptions*)google_protobuf_DescriptorProto_ExtensionRange_options(msg); + if (sub == NULL) { + sub = (struct google_protobuf_ExtensionRangeOptions*)_upb_Message_New(&google__protobuf__ExtensionRangeOptions_msg_init, arena); + if (sub) google_protobuf_DescriptorProto_ExtensionRange_set_options(msg, sub); + } + return sub; +} + +/* google.protobuf.DescriptorProto.ReservedRange */ + +UPB_INLINE google_protobuf_DescriptorProto_ReservedRange* google_protobuf_DescriptorProto_ReservedRange_new(upb_Arena* arena) { + return (google_protobuf_DescriptorProto_ReservedRange*)_upb_Message_New(&google__protobuf__DescriptorProto__ReservedRange_msg_init, arena); +} +UPB_INLINE google_protobuf_DescriptorProto_ReservedRange* google_protobuf_DescriptorProto_ReservedRange_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_DescriptorProto_ReservedRange* ret = google_protobuf_DescriptorProto_ReservedRange_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__DescriptorProto__ReservedRange_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_DescriptorProto_ReservedRange* google_protobuf_DescriptorProto_ReservedRange_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_DescriptorProto_ReservedRange* ret = google_protobuf_DescriptorProto_ReservedRange_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__DescriptorProto__ReservedRange_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_DescriptorProto_ReservedRange_serialize(const google_protobuf_DescriptorProto_ReservedRange* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__DescriptorProto__ReservedRange_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_DescriptorProto_ReservedRange_serialize_ex(const google_protobuf_DescriptorProto_ReservedRange* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__DescriptorProto__ReservedRange_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_DescriptorProto_ReservedRange_clear_start(google_protobuf_DescriptorProto_ReservedRange* msg) { + const upb_MiniTableField field = {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_DescriptorProto_ReservedRange_start(const google_protobuf_DescriptorProto_ReservedRange* msg) { + int32_t default_val = (int32_t)0; + int32_t ret; + const upb_MiniTableField field = {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_DescriptorProto_ReservedRange_has_start(const google_protobuf_DescriptorProto_ReservedRange* msg) { + const upb_MiniTableField field = {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_DescriptorProto_ReservedRange_clear_end(google_protobuf_DescriptorProto_ReservedRange* msg) { + const upb_MiniTableField field = {2, 16, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_DescriptorProto_ReservedRange_end(const google_protobuf_DescriptorProto_ReservedRange* msg) { + int32_t default_val = (int32_t)0; + int32_t ret; + const upb_MiniTableField field = {2, 16, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_DescriptorProto_ReservedRange_has_end(const google_protobuf_DescriptorProto_ReservedRange* msg) { + const upb_MiniTableField field = {2, 16, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE void google_protobuf_DescriptorProto_ReservedRange_set_start(google_protobuf_DescriptorProto_ReservedRange *msg, int32_t value) { + const upb_MiniTableField field = {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_DescriptorProto_ReservedRange_set_end(google_protobuf_DescriptorProto_ReservedRange *msg, int32_t value) { + const upb_MiniTableField field = {2, 16, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} + +/* google.protobuf.ExtensionRangeOptions */ + +UPB_INLINE google_protobuf_ExtensionRangeOptions* google_protobuf_ExtensionRangeOptions_new(upb_Arena* arena) { + return (google_protobuf_ExtensionRangeOptions*)_upb_Message_New(&google__protobuf__ExtensionRangeOptions_msg_init, arena); +} +UPB_INLINE google_protobuf_ExtensionRangeOptions* google_protobuf_ExtensionRangeOptions_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_ExtensionRangeOptions* ret = google_protobuf_ExtensionRangeOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__ExtensionRangeOptions_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_ExtensionRangeOptions* google_protobuf_ExtensionRangeOptions_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_ExtensionRangeOptions* ret = google_protobuf_ExtensionRangeOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__ExtensionRangeOptions_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_ExtensionRangeOptions_serialize(const google_protobuf_ExtensionRangeOptions* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__ExtensionRangeOptions_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_ExtensionRangeOptions_serialize_ex(const google_protobuf_ExtensionRangeOptions* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__ExtensionRangeOptions_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_ExtensionRangeOptions_clear_declaration(google_protobuf_ExtensionRangeOptions* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 16), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_ExtensionRangeOptions_Declaration* const* google_protobuf_ExtensionRangeOptions_declaration(const google_protobuf_ExtensionRangeOptions* msg, size_t* size) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 16), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_ExtensionRangeOptions_Declaration* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_ExtensionRangeOptions_declaration_upb_array(const google_protobuf_ExtensionRangeOptions* msg, size_t* size) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 16), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_ExtensionRangeOptions_declaration_mutable_upb_array(google_protobuf_ExtensionRangeOptions* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 16), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_ExtensionRangeOptions_clear_verification(google_protobuf_ExtensionRangeOptions* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 12), 64, 3, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_ExtensionRangeOptions_verification(const google_protobuf_ExtensionRangeOptions* msg) { + int32_t default_val = 1; + int32_t ret; + const upb_MiniTableField field = {3, UPB_SIZE(16, 12), 64, 3, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_ExtensionRangeOptions_has_verification(const google_protobuf_ExtensionRangeOptions* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 12), 64, 3, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_ExtensionRangeOptions_clear_features(google_protobuf_ExtensionRangeOptions* msg) { + const upb_MiniTableField field = {50, UPB_SIZE(20, 24), 65, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FeatureSet* google_protobuf_ExtensionRangeOptions_features(const google_protobuf_ExtensionRangeOptions* msg) { + const google_protobuf_FeatureSet* default_val = NULL; + const google_protobuf_FeatureSet* ret; + const upb_MiniTableField field = {50, UPB_SIZE(20, 24), 65, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_ExtensionRangeOptions_has_features(const google_protobuf_ExtensionRangeOptions* msg) { + const upb_MiniTableField field = {50, UPB_SIZE(20, 24), 65, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_ExtensionRangeOptions_clear_uninterpreted_option(google_protobuf_ExtensionRangeOptions* msg) { + const upb_MiniTableField field = {999, UPB_SIZE(24, 32), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_UninterpretedOption* const* google_protobuf_ExtensionRangeOptions_uninterpreted_option(const google_protobuf_ExtensionRangeOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(24, 32), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_UninterpretedOption* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_ExtensionRangeOptions_uninterpreted_option_upb_array(const google_protobuf_ExtensionRangeOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(24, 32), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_ExtensionRangeOptions_uninterpreted_option_mutable_upb_array(google_protobuf_ExtensionRangeOptions* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {999, UPB_SIZE(24, 32), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} + +UPB_INLINE google_protobuf_ExtensionRangeOptions_Declaration** google_protobuf_ExtensionRangeOptions_mutable_declaration(google_protobuf_ExtensionRangeOptions* msg, size_t* size) { + upb_MiniTableField field = {2, UPB_SIZE(12, 16), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_ExtensionRangeOptions_Declaration**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_ExtensionRangeOptions_Declaration** google_protobuf_ExtensionRangeOptions_resize_declaration(google_protobuf_ExtensionRangeOptions* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {2, UPB_SIZE(12, 16), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_ExtensionRangeOptions_Declaration**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_ExtensionRangeOptions_Declaration* google_protobuf_ExtensionRangeOptions_add_declaration(google_protobuf_ExtensionRangeOptions* msg, upb_Arena* arena) { + upb_MiniTableField field = {2, UPB_SIZE(12, 16), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_ExtensionRangeOptions_Declaration* sub = (struct google_protobuf_ExtensionRangeOptions_Declaration*)_upb_Message_New(&google__protobuf__ExtensionRangeOptions__Declaration_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE void google_protobuf_ExtensionRangeOptions_set_verification(google_protobuf_ExtensionRangeOptions *msg, int32_t value) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 12), 64, 3, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_ExtensionRangeOptions_set_features(google_protobuf_ExtensionRangeOptions *msg, google_protobuf_FeatureSet* value) { + const upb_MiniTableField field = {50, UPB_SIZE(20, 24), 65, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_FeatureSet* google_protobuf_ExtensionRangeOptions_mutable_features(google_protobuf_ExtensionRangeOptions* msg, upb_Arena* arena) { + struct google_protobuf_FeatureSet* sub = (struct google_protobuf_FeatureSet*)google_protobuf_ExtensionRangeOptions_features(msg); + if (sub == NULL) { + sub = (struct google_protobuf_FeatureSet*)_upb_Message_New(&google__protobuf__FeatureSet_msg_init, arena); + if (sub) google_protobuf_ExtensionRangeOptions_set_features(msg, sub); + } + return sub; +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_ExtensionRangeOptions_mutable_uninterpreted_option(google_protobuf_ExtensionRangeOptions* msg, size_t* size) { + upb_MiniTableField field = {999, UPB_SIZE(24, 32), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_UninterpretedOption**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_ExtensionRangeOptions_resize_uninterpreted_option(google_protobuf_ExtensionRangeOptions* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(24, 32), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_UninterpretedOption**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_UninterpretedOption* google_protobuf_ExtensionRangeOptions_add_uninterpreted_option(google_protobuf_ExtensionRangeOptions* msg, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(24, 32), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_UninterpretedOption* sub = (struct google_protobuf_UninterpretedOption*)_upb_Message_New(&google__protobuf__UninterpretedOption_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} + +/* google.protobuf.ExtensionRangeOptions.Declaration */ + +UPB_INLINE google_protobuf_ExtensionRangeOptions_Declaration* google_protobuf_ExtensionRangeOptions_Declaration_new(upb_Arena* arena) { + return (google_protobuf_ExtensionRangeOptions_Declaration*)_upb_Message_New(&google__protobuf__ExtensionRangeOptions__Declaration_msg_init, arena); +} +UPB_INLINE google_protobuf_ExtensionRangeOptions_Declaration* google_protobuf_ExtensionRangeOptions_Declaration_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_ExtensionRangeOptions_Declaration* ret = google_protobuf_ExtensionRangeOptions_Declaration_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__ExtensionRangeOptions__Declaration_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_ExtensionRangeOptions_Declaration* google_protobuf_ExtensionRangeOptions_Declaration_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_ExtensionRangeOptions_Declaration* ret = google_protobuf_ExtensionRangeOptions_Declaration_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__ExtensionRangeOptions__Declaration_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_ExtensionRangeOptions_Declaration_serialize(const google_protobuf_ExtensionRangeOptions_Declaration* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__ExtensionRangeOptions__Declaration_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_ExtensionRangeOptions_Declaration_serialize_ex(const google_protobuf_ExtensionRangeOptions_Declaration* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__ExtensionRangeOptions__Declaration_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_ExtensionRangeOptions_Declaration_clear_number(google_protobuf_ExtensionRangeOptions_Declaration* msg) { + const upb_MiniTableField field = {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_ExtensionRangeOptions_Declaration_number(const google_protobuf_ExtensionRangeOptions_Declaration* msg) { + int32_t default_val = (int32_t)0; + int32_t ret; + const upb_MiniTableField field = {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_ExtensionRangeOptions_Declaration_has_number(const google_protobuf_ExtensionRangeOptions_Declaration* msg) { + const upb_MiniTableField field = {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_ExtensionRangeOptions_Declaration_clear_full_name(google_protobuf_ExtensionRangeOptions_Declaration* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(20, 24), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_ExtensionRangeOptions_Declaration_full_name(const google_protobuf_ExtensionRangeOptions_Declaration* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {2, UPB_SIZE(20, 24), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_ExtensionRangeOptions_Declaration_has_full_name(const google_protobuf_ExtensionRangeOptions_Declaration* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(20, 24), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_ExtensionRangeOptions_Declaration_clear_type(google_protobuf_ExtensionRangeOptions_Declaration* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(28, 40), 66, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_ExtensionRangeOptions_Declaration_type(const google_protobuf_ExtensionRangeOptions_Declaration* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {3, UPB_SIZE(28, 40), 66, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_ExtensionRangeOptions_Declaration_has_type(const google_protobuf_ExtensionRangeOptions_Declaration* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(28, 40), 66, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_ExtensionRangeOptions_Declaration_clear_reserved(google_protobuf_ExtensionRangeOptions_Declaration* msg) { + const upb_MiniTableField field = {5, 16, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_ExtensionRangeOptions_Declaration_reserved(const google_protobuf_ExtensionRangeOptions_Declaration* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {5, 16, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_ExtensionRangeOptions_Declaration_has_reserved(const google_protobuf_ExtensionRangeOptions_Declaration* msg) { + const upb_MiniTableField field = {5, 16, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_ExtensionRangeOptions_Declaration_clear_repeated(google_protobuf_ExtensionRangeOptions_Declaration* msg) { + const upb_MiniTableField field = {6, 17, 68, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_ExtensionRangeOptions_Declaration_repeated(const google_protobuf_ExtensionRangeOptions_Declaration* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {6, 17, 68, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_ExtensionRangeOptions_Declaration_has_repeated(const google_protobuf_ExtensionRangeOptions_Declaration* msg) { + const upb_MiniTableField field = {6, 17, 68, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE void google_protobuf_ExtensionRangeOptions_Declaration_set_number(google_protobuf_ExtensionRangeOptions_Declaration *msg, int32_t value) { + const upb_MiniTableField field = {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_ExtensionRangeOptions_Declaration_set_full_name(google_protobuf_ExtensionRangeOptions_Declaration *msg, upb_StringView value) { + const upb_MiniTableField field = {2, UPB_SIZE(20, 24), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_ExtensionRangeOptions_Declaration_set_type(google_protobuf_ExtensionRangeOptions_Declaration *msg, upb_StringView value) { + const upb_MiniTableField field = {3, UPB_SIZE(28, 40), 66, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_ExtensionRangeOptions_Declaration_set_reserved(google_protobuf_ExtensionRangeOptions_Declaration *msg, bool value) { + const upb_MiniTableField field = {5, 16, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_ExtensionRangeOptions_Declaration_set_repeated(google_protobuf_ExtensionRangeOptions_Declaration *msg, bool value) { + const upb_MiniTableField field = {6, 17, 68, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} + +/* google.protobuf.FieldDescriptorProto */ + +UPB_INLINE google_protobuf_FieldDescriptorProto* google_protobuf_FieldDescriptorProto_new(upb_Arena* arena) { + return (google_protobuf_FieldDescriptorProto*)_upb_Message_New(&google__protobuf__FieldDescriptorProto_msg_init, arena); +} +UPB_INLINE google_protobuf_FieldDescriptorProto* google_protobuf_FieldDescriptorProto_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_FieldDescriptorProto* ret = google_protobuf_FieldDescriptorProto_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FieldDescriptorProto_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_FieldDescriptorProto* google_protobuf_FieldDescriptorProto_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_FieldDescriptorProto* ret = google_protobuf_FieldDescriptorProto_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FieldDescriptorProto_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_FieldDescriptorProto_serialize(const google_protobuf_FieldDescriptorProto* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FieldDescriptorProto_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_FieldDescriptorProto_serialize_ex(const google_protobuf_FieldDescriptorProto* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FieldDescriptorProto_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_clear_name(google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(36, 32), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FieldDescriptorProto_name(const google_protobuf_FieldDescriptorProto* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {1, UPB_SIZE(36, 32), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldDescriptorProto_has_name(const google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(36, 32), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_clear_extendee(google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(44, 48), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FieldDescriptorProto_extendee(const google_protobuf_FieldDescriptorProto* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {2, UPB_SIZE(44, 48), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldDescriptorProto_has_extendee(const google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(44, 48), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_clear_number(google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {3, 12, 66, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FieldDescriptorProto_number(const google_protobuf_FieldDescriptorProto* msg) { + int32_t default_val = (int32_t)0; + int32_t ret; + const upb_MiniTableField field = {3, 12, 66, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldDescriptorProto_has_number(const google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {3, 12, 66, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_clear_label(google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {4, 16, 67, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FieldDescriptorProto_label(const google_protobuf_FieldDescriptorProto* msg) { + int32_t default_val = 1; + int32_t ret; + const upb_MiniTableField field = {4, 16, 67, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldDescriptorProto_has_label(const google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {4, 16, 67, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_clear_type(google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {5, 20, 68, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FieldDescriptorProto_type(const google_protobuf_FieldDescriptorProto* msg) { + int32_t default_val = 1; + int32_t ret; + const upb_MiniTableField field = {5, 20, 68, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldDescriptorProto_has_type(const google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {5, 20, 68, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_clear_type_name(google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {6, UPB_SIZE(52, 64), 69, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FieldDescriptorProto_type_name(const google_protobuf_FieldDescriptorProto* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {6, UPB_SIZE(52, 64), 69, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldDescriptorProto_has_type_name(const google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {6, UPB_SIZE(52, 64), 69, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_clear_default_value(google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {7, UPB_SIZE(60, 80), 70, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FieldDescriptorProto_default_value(const google_protobuf_FieldDescriptorProto* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {7, UPB_SIZE(60, 80), 70, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldDescriptorProto_has_default_value(const google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {7, UPB_SIZE(60, 80), 70, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_clear_options(google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {8, UPB_SIZE(24, 96), 71, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FieldOptions* google_protobuf_FieldDescriptorProto_options(const google_protobuf_FieldDescriptorProto* msg) { + const google_protobuf_FieldOptions* default_val = NULL; + const google_protobuf_FieldOptions* ret; + const upb_MiniTableField field = {8, UPB_SIZE(24, 96), 71, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldDescriptorProto_has_options(const google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {8, UPB_SIZE(24, 96), 71, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_clear_oneof_index(google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {9, UPB_SIZE(28, 24), 72, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FieldDescriptorProto_oneof_index(const google_protobuf_FieldDescriptorProto* msg) { + int32_t default_val = (int32_t)0; + int32_t ret; + const upb_MiniTableField field = {9, UPB_SIZE(28, 24), 72, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldDescriptorProto_has_oneof_index(const google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {9, UPB_SIZE(28, 24), 72, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_clear_json_name(google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {10, UPB_SIZE(68, 104), 73, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FieldDescriptorProto_json_name(const google_protobuf_FieldDescriptorProto* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {10, UPB_SIZE(68, 104), 73, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldDescriptorProto_has_json_name(const google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {10, UPB_SIZE(68, 104), 73, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_clear_proto3_optional(google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {17, UPB_SIZE(32, 28), 74, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_FieldDescriptorProto_proto3_optional(const google_protobuf_FieldDescriptorProto* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {17, UPB_SIZE(32, 28), 74, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldDescriptorProto_has_proto3_optional(const google_protobuf_FieldDescriptorProto* msg) { + const upb_MiniTableField field = {17, UPB_SIZE(32, 28), 74, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE void google_protobuf_FieldDescriptorProto_set_name(google_protobuf_FieldDescriptorProto *msg, upb_StringView value) { + const upb_MiniTableField field = {1, UPB_SIZE(36, 32), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_set_extendee(google_protobuf_FieldDescriptorProto *msg, upb_StringView value) { + const upb_MiniTableField field = {2, UPB_SIZE(44, 48), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_set_number(google_protobuf_FieldDescriptorProto *msg, int32_t value) { + const upb_MiniTableField field = {3, 12, 66, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_set_label(google_protobuf_FieldDescriptorProto *msg, int32_t value) { + const upb_MiniTableField field = {4, 16, 67, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_set_type(google_protobuf_FieldDescriptorProto *msg, int32_t value) { + const upb_MiniTableField field = {5, 20, 68, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_set_type_name(google_protobuf_FieldDescriptorProto *msg, upb_StringView value) { + const upb_MiniTableField field = {6, UPB_SIZE(52, 64), 69, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_set_default_value(google_protobuf_FieldDescriptorProto *msg, upb_StringView value) { + const upb_MiniTableField field = {7, UPB_SIZE(60, 80), 70, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_set_options(google_protobuf_FieldDescriptorProto *msg, google_protobuf_FieldOptions* value) { + const upb_MiniTableField field = {8, UPB_SIZE(24, 96), 71, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_FieldOptions* google_protobuf_FieldDescriptorProto_mutable_options(google_protobuf_FieldDescriptorProto* msg, upb_Arena* arena) { + struct google_protobuf_FieldOptions* sub = (struct google_protobuf_FieldOptions*)google_protobuf_FieldDescriptorProto_options(msg); + if (sub == NULL) { + sub = (struct google_protobuf_FieldOptions*)_upb_Message_New(&google__protobuf__FieldOptions_msg_init, arena); + if (sub) google_protobuf_FieldDescriptorProto_set_options(msg, sub); + } + return sub; +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_set_oneof_index(google_protobuf_FieldDescriptorProto *msg, int32_t value) { + const upb_MiniTableField field = {9, UPB_SIZE(28, 24), 72, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_set_json_name(google_protobuf_FieldDescriptorProto *msg, upb_StringView value) { + const upb_MiniTableField field = {10, UPB_SIZE(68, 104), 73, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldDescriptorProto_set_proto3_optional(google_protobuf_FieldDescriptorProto *msg, bool value) { + const upb_MiniTableField field = {17, UPB_SIZE(32, 28), 74, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} + +/* google.protobuf.OneofDescriptorProto */ + +UPB_INLINE google_protobuf_OneofDescriptorProto* google_protobuf_OneofDescriptorProto_new(upb_Arena* arena) { + return (google_protobuf_OneofDescriptorProto*)_upb_Message_New(&google__protobuf__OneofDescriptorProto_msg_init, arena); +} +UPB_INLINE google_protobuf_OneofDescriptorProto* google_protobuf_OneofDescriptorProto_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_OneofDescriptorProto* ret = google_protobuf_OneofDescriptorProto_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__OneofDescriptorProto_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_OneofDescriptorProto* google_protobuf_OneofDescriptorProto_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_OneofDescriptorProto* ret = google_protobuf_OneofDescriptorProto_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__OneofDescriptorProto_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_OneofDescriptorProto_serialize(const google_protobuf_OneofDescriptorProto* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__OneofDescriptorProto_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_OneofDescriptorProto_serialize_ex(const google_protobuf_OneofDescriptorProto* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__OneofDescriptorProto_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_OneofDescriptorProto_clear_name(google_protobuf_OneofDescriptorProto* msg) { + const upb_MiniTableField field = {1, 16, 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_OneofDescriptorProto_name(const google_protobuf_OneofDescriptorProto* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {1, 16, 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_OneofDescriptorProto_has_name(const google_protobuf_OneofDescriptorProto* msg) { + const upb_MiniTableField field = {1, 16, 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_OneofDescriptorProto_clear_options(google_protobuf_OneofDescriptorProto* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 32), 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_OneofOptions* google_protobuf_OneofDescriptorProto_options(const google_protobuf_OneofDescriptorProto* msg) { + const google_protobuf_OneofOptions* default_val = NULL; + const google_protobuf_OneofOptions* ret; + const upb_MiniTableField field = {2, UPB_SIZE(12, 32), 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_OneofDescriptorProto_has_options(const google_protobuf_OneofDescriptorProto* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 32), 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE void google_protobuf_OneofDescriptorProto_set_name(google_protobuf_OneofDescriptorProto *msg, upb_StringView value) { + const upb_MiniTableField field = {1, 16, 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_OneofDescriptorProto_set_options(google_protobuf_OneofDescriptorProto *msg, google_protobuf_OneofOptions* value) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 32), 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_OneofOptions* google_protobuf_OneofDescriptorProto_mutable_options(google_protobuf_OneofDescriptorProto* msg, upb_Arena* arena) { + struct google_protobuf_OneofOptions* sub = (struct google_protobuf_OneofOptions*)google_protobuf_OneofDescriptorProto_options(msg); + if (sub == NULL) { + sub = (struct google_protobuf_OneofOptions*)_upb_Message_New(&google__protobuf__OneofOptions_msg_init, arena); + if (sub) google_protobuf_OneofDescriptorProto_set_options(msg, sub); + } + return sub; +} + +/* google.protobuf.EnumDescriptorProto */ + +UPB_INLINE google_protobuf_EnumDescriptorProto* google_protobuf_EnumDescriptorProto_new(upb_Arena* arena) { + return (google_protobuf_EnumDescriptorProto*)_upb_Message_New(&google__protobuf__EnumDescriptorProto_msg_init, arena); +} +UPB_INLINE google_protobuf_EnumDescriptorProto* google_protobuf_EnumDescriptorProto_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_EnumDescriptorProto* ret = google_protobuf_EnumDescriptorProto_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__EnumDescriptorProto_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_EnumDescriptorProto* google_protobuf_EnumDescriptorProto_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_EnumDescriptorProto* ret = google_protobuf_EnumDescriptorProto_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__EnumDescriptorProto_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_EnumDescriptorProto_serialize(const google_protobuf_EnumDescriptorProto* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__EnumDescriptorProto_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_EnumDescriptorProto_serialize_ex(const google_protobuf_EnumDescriptorProto* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__EnumDescriptorProto_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_EnumDescriptorProto_clear_name(google_protobuf_EnumDescriptorProto* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(28, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_EnumDescriptorProto_name(const google_protobuf_EnumDescriptorProto* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {1, UPB_SIZE(28, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_EnumDescriptorProto_has_name(const google_protobuf_EnumDescriptorProto* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(28, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_EnumDescriptorProto_clear_value(google_protobuf_EnumDescriptorProto* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_EnumValueDescriptorProto* const* google_protobuf_EnumDescriptorProto_value(const google_protobuf_EnumDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_EnumValueDescriptorProto* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_EnumDescriptorProto_value_upb_array(const google_protobuf_EnumDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_EnumDescriptorProto_value_mutable_upb_array(google_protobuf_EnumDescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_EnumDescriptorProto_clear_options(google_protobuf_EnumDescriptorProto* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 40), 65, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_EnumOptions* google_protobuf_EnumDescriptorProto_options(const google_protobuf_EnumDescriptorProto* msg) { + const google_protobuf_EnumOptions* default_val = NULL; + const google_protobuf_EnumOptions* ret; + const upb_MiniTableField field = {3, UPB_SIZE(16, 40), 65, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_EnumDescriptorProto_has_options(const google_protobuf_EnumDescriptorProto* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 40), 65, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_EnumDescriptorProto_clear_reserved_range(google_protobuf_EnumDescriptorProto* msg) { + const upb_MiniTableField field = {4, UPB_SIZE(20, 48), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_EnumDescriptorProto_EnumReservedRange* const* google_protobuf_EnumDescriptorProto_reserved_range(const google_protobuf_EnumDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {4, UPB_SIZE(20, 48), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_EnumDescriptorProto_EnumReservedRange* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_EnumDescriptorProto_reserved_range_upb_array(const google_protobuf_EnumDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {4, UPB_SIZE(20, 48), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_EnumDescriptorProto_reserved_range_mutable_upb_array(google_protobuf_EnumDescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {4, UPB_SIZE(20, 48), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_EnumDescriptorProto_clear_reserved_name(google_protobuf_EnumDescriptorProto* msg) { + const upb_MiniTableField field = {5, UPB_SIZE(24, 56), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView const* google_protobuf_EnumDescriptorProto_reserved_name(const google_protobuf_EnumDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {5, UPB_SIZE(24, 56), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (upb_StringView const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_EnumDescriptorProto_reserved_name_upb_array(const google_protobuf_EnumDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {5, UPB_SIZE(24, 56), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_EnumDescriptorProto_reserved_name_mutable_upb_array(google_protobuf_EnumDescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {5, UPB_SIZE(24, 56), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} + +UPB_INLINE void google_protobuf_EnumDescriptorProto_set_name(google_protobuf_EnumDescriptorProto *msg, upb_StringView value) { + const upb_MiniTableField field = {1, UPB_SIZE(28, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE google_protobuf_EnumValueDescriptorProto** google_protobuf_EnumDescriptorProto_mutable_value(google_protobuf_EnumDescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_EnumValueDescriptorProto**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_EnumValueDescriptorProto** google_protobuf_EnumDescriptorProto_resize_value(google_protobuf_EnumDescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_EnumValueDescriptorProto**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_EnumValueDescriptorProto* google_protobuf_EnumDescriptorProto_add_value(google_protobuf_EnumDescriptorProto* msg, upb_Arena* arena) { + upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_EnumValueDescriptorProto* sub = (struct google_protobuf_EnumValueDescriptorProto*)_upb_Message_New(&google__protobuf__EnumValueDescriptorProto_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE void google_protobuf_EnumDescriptorProto_set_options(google_protobuf_EnumDescriptorProto *msg, google_protobuf_EnumOptions* value) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 40), 65, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_EnumOptions* google_protobuf_EnumDescriptorProto_mutable_options(google_protobuf_EnumDescriptorProto* msg, upb_Arena* arena) { + struct google_protobuf_EnumOptions* sub = (struct google_protobuf_EnumOptions*)google_protobuf_EnumDescriptorProto_options(msg); + if (sub == NULL) { + sub = (struct google_protobuf_EnumOptions*)_upb_Message_New(&google__protobuf__EnumOptions_msg_init, arena); + if (sub) google_protobuf_EnumDescriptorProto_set_options(msg, sub); + } + return sub; +} +UPB_INLINE google_protobuf_EnumDescriptorProto_EnumReservedRange** google_protobuf_EnumDescriptorProto_mutable_reserved_range(google_protobuf_EnumDescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {4, UPB_SIZE(20, 48), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_EnumDescriptorProto_EnumReservedRange**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_EnumDescriptorProto_EnumReservedRange** google_protobuf_EnumDescriptorProto_resize_reserved_range(google_protobuf_EnumDescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {4, UPB_SIZE(20, 48), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_EnumDescriptorProto_EnumReservedRange**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_EnumDescriptorProto_EnumReservedRange* google_protobuf_EnumDescriptorProto_add_reserved_range(google_protobuf_EnumDescriptorProto* msg, upb_Arena* arena) { + upb_MiniTableField field = {4, UPB_SIZE(20, 48), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_EnumDescriptorProto_EnumReservedRange* sub = (struct google_protobuf_EnumDescriptorProto_EnumReservedRange*)_upb_Message_New(&google__protobuf__EnumDescriptorProto__EnumReservedRange_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE upb_StringView* google_protobuf_EnumDescriptorProto_mutable_reserved_name(google_protobuf_EnumDescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {5, UPB_SIZE(24, 56), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (upb_StringView*)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE upb_StringView* google_protobuf_EnumDescriptorProto_resize_reserved_name(google_protobuf_EnumDescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {5, UPB_SIZE(24, 56), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (upb_StringView*)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE bool google_protobuf_EnumDescriptorProto_add_reserved_name(google_protobuf_EnumDescriptorProto* msg, upb_StringView val, upb_Arena* arena) { + upb_MiniTableField field = {5, UPB_SIZE(24, 56), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return false; + } + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &val, sizeof(val)); + return true; +} + +/* google.protobuf.EnumDescriptorProto.EnumReservedRange */ + +UPB_INLINE google_protobuf_EnumDescriptorProto_EnumReservedRange* google_protobuf_EnumDescriptorProto_EnumReservedRange_new(upb_Arena* arena) { + return (google_protobuf_EnumDescriptorProto_EnumReservedRange*)_upb_Message_New(&google__protobuf__EnumDescriptorProto__EnumReservedRange_msg_init, arena); +} +UPB_INLINE google_protobuf_EnumDescriptorProto_EnumReservedRange* google_protobuf_EnumDescriptorProto_EnumReservedRange_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_EnumDescriptorProto_EnumReservedRange* ret = google_protobuf_EnumDescriptorProto_EnumReservedRange_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__EnumDescriptorProto__EnumReservedRange_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_EnumDescriptorProto_EnumReservedRange* google_protobuf_EnumDescriptorProto_EnumReservedRange_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_EnumDescriptorProto_EnumReservedRange* ret = google_protobuf_EnumDescriptorProto_EnumReservedRange_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__EnumDescriptorProto__EnumReservedRange_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_EnumDescriptorProto_EnumReservedRange_serialize(const google_protobuf_EnumDescriptorProto_EnumReservedRange* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__EnumDescriptorProto__EnumReservedRange_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_EnumDescriptorProto_EnumReservedRange_serialize_ex(const google_protobuf_EnumDescriptorProto_EnumReservedRange* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__EnumDescriptorProto__EnumReservedRange_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_EnumDescriptorProto_EnumReservedRange_clear_start(google_protobuf_EnumDescriptorProto_EnumReservedRange* msg) { + const upb_MiniTableField field = {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_EnumDescriptorProto_EnumReservedRange_start(const google_protobuf_EnumDescriptorProto_EnumReservedRange* msg) { + int32_t default_val = (int32_t)0; + int32_t ret; + const upb_MiniTableField field = {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_EnumDescriptorProto_EnumReservedRange_has_start(const google_protobuf_EnumDescriptorProto_EnumReservedRange* msg) { + const upb_MiniTableField field = {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_EnumDescriptorProto_EnumReservedRange_clear_end(google_protobuf_EnumDescriptorProto_EnumReservedRange* msg) { + const upb_MiniTableField field = {2, 16, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_EnumDescriptorProto_EnumReservedRange_end(const google_protobuf_EnumDescriptorProto_EnumReservedRange* msg) { + int32_t default_val = (int32_t)0; + int32_t ret; + const upb_MiniTableField field = {2, 16, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_EnumDescriptorProto_EnumReservedRange_has_end(const google_protobuf_EnumDescriptorProto_EnumReservedRange* msg) { + const upb_MiniTableField field = {2, 16, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE void google_protobuf_EnumDescriptorProto_EnumReservedRange_set_start(google_protobuf_EnumDescriptorProto_EnumReservedRange *msg, int32_t value) { + const upb_MiniTableField field = {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_EnumDescriptorProto_EnumReservedRange_set_end(google_protobuf_EnumDescriptorProto_EnumReservedRange *msg, int32_t value) { + const upb_MiniTableField field = {2, 16, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} + +/* google.protobuf.EnumValueDescriptorProto */ + +UPB_INLINE google_protobuf_EnumValueDescriptorProto* google_protobuf_EnumValueDescriptorProto_new(upb_Arena* arena) { + return (google_protobuf_EnumValueDescriptorProto*)_upb_Message_New(&google__protobuf__EnumValueDescriptorProto_msg_init, arena); +} +UPB_INLINE google_protobuf_EnumValueDescriptorProto* google_protobuf_EnumValueDescriptorProto_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_EnumValueDescriptorProto* ret = google_protobuf_EnumValueDescriptorProto_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__EnumValueDescriptorProto_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_EnumValueDescriptorProto* google_protobuf_EnumValueDescriptorProto_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_EnumValueDescriptorProto* ret = google_protobuf_EnumValueDescriptorProto_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__EnumValueDescriptorProto_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_EnumValueDescriptorProto_serialize(const google_protobuf_EnumValueDescriptorProto* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__EnumValueDescriptorProto_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_EnumValueDescriptorProto_serialize_ex(const google_protobuf_EnumValueDescriptorProto* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__EnumValueDescriptorProto_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_EnumValueDescriptorProto_clear_name(google_protobuf_EnumValueDescriptorProto* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(20, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_EnumValueDescriptorProto_name(const google_protobuf_EnumValueDescriptorProto* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {1, UPB_SIZE(20, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_EnumValueDescriptorProto_has_name(const google_protobuf_EnumValueDescriptorProto* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(20, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_EnumValueDescriptorProto_clear_number(google_protobuf_EnumValueDescriptorProto* msg) { + const upb_MiniTableField field = {2, 12, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_EnumValueDescriptorProto_number(const google_protobuf_EnumValueDescriptorProto* msg) { + int32_t default_val = (int32_t)0; + int32_t ret; + const upb_MiniTableField field = {2, 12, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_EnumValueDescriptorProto_has_number(const google_protobuf_EnumValueDescriptorProto* msg) { + const upb_MiniTableField field = {2, 12, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_EnumValueDescriptorProto_clear_options(google_protobuf_EnumValueDescriptorProto* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 32), 66, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_EnumValueOptions* google_protobuf_EnumValueDescriptorProto_options(const google_protobuf_EnumValueDescriptorProto* msg) { + const google_protobuf_EnumValueOptions* default_val = NULL; + const google_protobuf_EnumValueOptions* ret; + const upb_MiniTableField field = {3, UPB_SIZE(16, 32), 66, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_EnumValueDescriptorProto_has_options(const google_protobuf_EnumValueDescriptorProto* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 32), 66, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE void google_protobuf_EnumValueDescriptorProto_set_name(google_protobuf_EnumValueDescriptorProto *msg, upb_StringView value) { + const upb_MiniTableField field = {1, UPB_SIZE(20, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_EnumValueDescriptorProto_set_number(google_protobuf_EnumValueDescriptorProto *msg, int32_t value) { + const upb_MiniTableField field = {2, 12, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_EnumValueDescriptorProto_set_options(google_protobuf_EnumValueDescriptorProto *msg, google_protobuf_EnumValueOptions* value) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 32), 66, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_EnumValueOptions* google_protobuf_EnumValueDescriptorProto_mutable_options(google_protobuf_EnumValueDescriptorProto* msg, upb_Arena* arena) { + struct google_protobuf_EnumValueOptions* sub = (struct google_protobuf_EnumValueOptions*)google_protobuf_EnumValueDescriptorProto_options(msg); + if (sub == NULL) { + sub = (struct google_protobuf_EnumValueOptions*)_upb_Message_New(&google__protobuf__EnumValueOptions_msg_init, arena); + if (sub) google_protobuf_EnumValueDescriptorProto_set_options(msg, sub); + } + return sub; +} + +/* google.protobuf.ServiceDescriptorProto */ + +UPB_INLINE google_protobuf_ServiceDescriptorProto* google_protobuf_ServiceDescriptorProto_new(upb_Arena* arena) { + return (google_protobuf_ServiceDescriptorProto*)_upb_Message_New(&google__protobuf__ServiceDescriptorProto_msg_init, arena); +} +UPB_INLINE google_protobuf_ServiceDescriptorProto* google_protobuf_ServiceDescriptorProto_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_ServiceDescriptorProto* ret = google_protobuf_ServiceDescriptorProto_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__ServiceDescriptorProto_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_ServiceDescriptorProto* google_protobuf_ServiceDescriptorProto_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_ServiceDescriptorProto* ret = google_protobuf_ServiceDescriptorProto_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__ServiceDescriptorProto_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_ServiceDescriptorProto_serialize(const google_protobuf_ServiceDescriptorProto* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__ServiceDescriptorProto_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_ServiceDescriptorProto_serialize_ex(const google_protobuf_ServiceDescriptorProto* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__ServiceDescriptorProto_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_ServiceDescriptorProto_clear_name(google_protobuf_ServiceDescriptorProto* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(20, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_ServiceDescriptorProto_name(const google_protobuf_ServiceDescriptorProto* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {1, UPB_SIZE(20, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_ServiceDescriptorProto_has_name(const google_protobuf_ServiceDescriptorProto* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(20, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_ServiceDescriptorProto_clear_method(google_protobuf_ServiceDescriptorProto* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_MethodDescriptorProto* const* google_protobuf_ServiceDescriptorProto_method(const google_protobuf_ServiceDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_MethodDescriptorProto* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_ServiceDescriptorProto_method_upb_array(const google_protobuf_ServiceDescriptorProto* msg, size_t* size) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_ServiceDescriptorProto_method_mutable_upb_array(google_protobuf_ServiceDescriptorProto* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_ServiceDescriptorProto_clear_options(google_protobuf_ServiceDescriptorProto* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 40), 65, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_ServiceOptions* google_protobuf_ServiceDescriptorProto_options(const google_protobuf_ServiceDescriptorProto* msg) { + const google_protobuf_ServiceOptions* default_val = NULL; + const google_protobuf_ServiceOptions* ret; + const upb_MiniTableField field = {3, UPB_SIZE(16, 40), 65, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_ServiceDescriptorProto_has_options(const google_protobuf_ServiceDescriptorProto* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 40), 65, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE void google_protobuf_ServiceDescriptorProto_set_name(google_protobuf_ServiceDescriptorProto *msg, upb_StringView value) { + const upb_MiniTableField field = {1, UPB_SIZE(20, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE google_protobuf_MethodDescriptorProto** google_protobuf_ServiceDescriptorProto_mutable_method(google_protobuf_ServiceDescriptorProto* msg, size_t* size) { + upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_MethodDescriptorProto**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_MethodDescriptorProto** google_protobuf_ServiceDescriptorProto_resize_method(google_protobuf_ServiceDescriptorProto* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_MethodDescriptorProto**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_MethodDescriptorProto* google_protobuf_ServiceDescriptorProto_add_method(google_protobuf_ServiceDescriptorProto* msg, upb_Arena* arena) { + upb_MiniTableField field = {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_MethodDescriptorProto* sub = (struct google_protobuf_MethodDescriptorProto*)_upb_Message_New(&google__protobuf__MethodDescriptorProto_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE void google_protobuf_ServiceDescriptorProto_set_options(google_protobuf_ServiceDescriptorProto *msg, google_protobuf_ServiceOptions* value) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 40), 65, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_ServiceOptions* google_protobuf_ServiceDescriptorProto_mutable_options(google_protobuf_ServiceDescriptorProto* msg, upb_Arena* arena) { + struct google_protobuf_ServiceOptions* sub = (struct google_protobuf_ServiceOptions*)google_protobuf_ServiceDescriptorProto_options(msg); + if (sub == NULL) { + sub = (struct google_protobuf_ServiceOptions*)_upb_Message_New(&google__protobuf__ServiceOptions_msg_init, arena); + if (sub) google_protobuf_ServiceDescriptorProto_set_options(msg, sub); + } + return sub; +} + +/* google.protobuf.MethodDescriptorProto */ + +UPB_INLINE google_protobuf_MethodDescriptorProto* google_protobuf_MethodDescriptorProto_new(upb_Arena* arena) { + return (google_protobuf_MethodDescriptorProto*)_upb_Message_New(&google__protobuf__MethodDescriptorProto_msg_init, arena); +} +UPB_INLINE google_protobuf_MethodDescriptorProto* google_protobuf_MethodDescriptorProto_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_MethodDescriptorProto* ret = google_protobuf_MethodDescriptorProto_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__MethodDescriptorProto_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_MethodDescriptorProto* google_protobuf_MethodDescriptorProto_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_MethodDescriptorProto* ret = google_protobuf_MethodDescriptorProto_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__MethodDescriptorProto_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_MethodDescriptorProto_serialize(const google_protobuf_MethodDescriptorProto* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__MethodDescriptorProto_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_MethodDescriptorProto_serialize_ex(const google_protobuf_MethodDescriptorProto* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__MethodDescriptorProto_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_MethodDescriptorProto_clear_name(google_protobuf_MethodDescriptorProto* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(20, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_MethodDescriptorProto_name(const google_protobuf_MethodDescriptorProto* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {1, UPB_SIZE(20, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_MethodDescriptorProto_has_name(const google_protobuf_MethodDescriptorProto* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(20, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_MethodDescriptorProto_clear_input_type(google_protobuf_MethodDescriptorProto* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(28, 32), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_MethodDescriptorProto_input_type(const google_protobuf_MethodDescriptorProto* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {2, UPB_SIZE(28, 32), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_MethodDescriptorProto_has_input_type(const google_protobuf_MethodDescriptorProto* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(28, 32), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_MethodDescriptorProto_clear_output_type(google_protobuf_MethodDescriptorProto* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(36, 48), 66, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_MethodDescriptorProto_output_type(const google_protobuf_MethodDescriptorProto* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {3, UPB_SIZE(36, 48), 66, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_MethodDescriptorProto_has_output_type(const google_protobuf_MethodDescriptorProto* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(36, 48), 66, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_MethodDescriptorProto_clear_options(google_protobuf_MethodDescriptorProto* msg) { + const upb_MiniTableField field = {4, UPB_SIZE(12, 64), 67, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_MethodOptions* google_protobuf_MethodDescriptorProto_options(const google_protobuf_MethodDescriptorProto* msg) { + const google_protobuf_MethodOptions* default_val = NULL; + const google_protobuf_MethodOptions* ret; + const upb_MiniTableField field = {4, UPB_SIZE(12, 64), 67, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_MethodDescriptorProto_has_options(const google_protobuf_MethodDescriptorProto* msg) { + const upb_MiniTableField field = {4, UPB_SIZE(12, 64), 67, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_MethodDescriptorProto_clear_client_streaming(google_protobuf_MethodDescriptorProto* msg) { + const upb_MiniTableField field = {5, UPB_SIZE(16, 9), 68, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_MethodDescriptorProto_client_streaming(const google_protobuf_MethodDescriptorProto* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {5, UPB_SIZE(16, 9), 68, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_MethodDescriptorProto_has_client_streaming(const google_protobuf_MethodDescriptorProto* msg) { + const upb_MiniTableField field = {5, UPB_SIZE(16, 9), 68, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_MethodDescriptorProto_clear_server_streaming(google_protobuf_MethodDescriptorProto* msg) { + const upb_MiniTableField field = {6, UPB_SIZE(17, 10), 69, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_MethodDescriptorProto_server_streaming(const google_protobuf_MethodDescriptorProto* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {6, UPB_SIZE(17, 10), 69, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_MethodDescriptorProto_has_server_streaming(const google_protobuf_MethodDescriptorProto* msg) { + const upb_MiniTableField field = {6, UPB_SIZE(17, 10), 69, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE void google_protobuf_MethodDescriptorProto_set_name(google_protobuf_MethodDescriptorProto *msg, upb_StringView value) { + const upb_MiniTableField field = {1, UPB_SIZE(20, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_MethodDescriptorProto_set_input_type(google_protobuf_MethodDescriptorProto *msg, upb_StringView value) { + const upb_MiniTableField field = {2, UPB_SIZE(28, 32), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_MethodDescriptorProto_set_output_type(google_protobuf_MethodDescriptorProto *msg, upb_StringView value) { + const upb_MiniTableField field = {3, UPB_SIZE(36, 48), 66, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_MethodDescriptorProto_set_options(google_protobuf_MethodDescriptorProto *msg, google_protobuf_MethodOptions* value) { + const upb_MiniTableField field = {4, UPB_SIZE(12, 64), 67, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_MethodOptions* google_protobuf_MethodDescriptorProto_mutable_options(google_protobuf_MethodDescriptorProto* msg, upb_Arena* arena) { + struct google_protobuf_MethodOptions* sub = (struct google_protobuf_MethodOptions*)google_protobuf_MethodDescriptorProto_options(msg); + if (sub == NULL) { + sub = (struct google_protobuf_MethodOptions*)_upb_Message_New(&google__protobuf__MethodOptions_msg_init, arena); + if (sub) google_protobuf_MethodDescriptorProto_set_options(msg, sub); + } + return sub; +} +UPB_INLINE void google_protobuf_MethodDescriptorProto_set_client_streaming(google_protobuf_MethodDescriptorProto *msg, bool value) { + const upb_MiniTableField field = {5, UPB_SIZE(16, 9), 68, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_MethodDescriptorProto_set_server_streaming(google_protobuf_MethodDescriptorProto *msg, bool value) { + const upb_MiniTableField field = {6, UPB_SIZE(17, 10), 69, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} + +/* google.protobuf.FileOptions */ + +UPB_INLINE google_protobuf_FileOptions* google_protobuf_FileOptions_new(upb_Arena* arena) { + return (google_protobuf_FileOptions*)_upb_Message_New(&google__protobuf__FileOptions_msg_init, arena); +} +UPB_INLINE google_protobuf_FileOptions* google_protobuf_FileOptions_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_FileOptions* ret = google_protobuf_FileOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FileOptions_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_FileOptions* google_protobuf_FileOptions_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_FileOptions* ret = google_protobuf_FileOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FileOptions_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_FileOptions_serialize(const google_protobuf_FileOptions* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FileOptions_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_FileOptions_serialize_ex(const google_protobuf_FileOptions* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FileOptions_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_FileOptions_clear_java_package(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(32, 24), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FileOptions_java_package(const google_protobuf_FileOptions* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {1, UPB_SIZE(32, 24), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_java_package(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(32, 24), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_java_outer_classname(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {8, 40, 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FileOptions_java_outer_classname(const google_protobuf_FileOptions* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {8, 40, 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_java_outer_classname(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {8, 40, 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_optimize_for(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {9, 12, 66, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FileOptions_optimize_for(const google_protobuf_FileOptions* msg) { + int32_t default_val = 1; + int32_t ret; + const upb_MiniTableField field = {9, 12, 66, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_optimize_for(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {9, 12, 66, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_java_multiple_files(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {10, 16, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_FileOptions_java_multiple_files(const google_protobuf_FileOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {10, 16, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_java_multiple_files(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {10, 16, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_go_package(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {11, UPB_SIZE(48, 56), 68, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FileOptions_go_package(const google_protobuf_FileOptions* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {11, UPB_SIZE(48, 56), 68, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_go_package(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {11, UPB_SIZE(48, 56), 68, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_cc_generic_services(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {16, 17, 69, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_FileOptions_cc_generic_services(const google_protobuf_FileOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {16, 17, 69, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_cc_generic_services(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {16, 17, 69, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_java_generic_services(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {17, 18, 70, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_FileOptions_java_generic_services(const google_protobuf_FileOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {17, 18, 70, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_java_generic_services(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {17, 18, 70, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_py_generic_services(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {18, 19, 71, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_FileOptions_py_generic_services(const google_protobuf_FileOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {18, 19, 71, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_py_generic_services(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {18, 19, 71, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_java_generate_equals_and_hash(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {20, 20, 72, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_FileOptions_java_generate_equals_and_hash(const google_protobuf_FileOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {20, 20, 72, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_java_generate_equals_and_hash(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {20, 20, 72, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_deprecated(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {23, 21, 73, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_FileOptions_deprecated(const google_protobuf_FileOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {23, 21, 73, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_deprecated(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {23, 21, 73, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_java_string_check_utf8(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {27, 22, 74, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_FileOptions_java_string_check_utf8(const google_protobuf_FileOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {27, 22, 74, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_java_string_check_utf8(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {27, 22, 74, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_cc_enable_arenas(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {31, 23, 75, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_FileOptions_cc_enable_arenas(const google_protobuf_FileOptions* msg) { + bool default_val = true; + bool ret; + const upb_MiniTableField field = {31, 23, 75, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_cc_enable_arenas(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {31, 23, 75, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_objc_class_prefix(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {36, UPB_SIZE(56, 72), 76, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FileOptions_objc_class_prefix(const google_protobuf_FileOptions* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {36, UPB_SIZE(56, 72), 76, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_objc_class_prefix(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {36, UPB_SIZE(56, 72), 76, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_csharp_namespace(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {37, UPB_SIZE(64, 88), 77, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FileOptions_csharp_namespace(const google_protobuf_FileOptions* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {37, UPB_SIZE(64, 88), 77, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_csharp_namespace(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {37, UPB_SIZE(64, 88), 77, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_swift_prefix(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {39, UPB_SIZE(72, 104), 78, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FileOptions_swift_prefix(const google_protobuf_FileOptions* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {39, UPB_SIZE(72, 104), 78, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_swift_prefix(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {39, UPB_SIZE(72, 104), 78, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_php_class_prefix(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {40, UPB_SIZE(80, 120), 79, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FileOptions_php_class_prefix(const google_protobuf_FileOptions* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {40, UPB_SIZE(80, 120), 79, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_php_class_prefix(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {40, UPB_SIZE(80, 120), 79, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_php_namespace(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {41, UPB_SIZE(88, 136), 80, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FileOptions_php_namespace(const google_protobuf_FileOptions* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {41, UPB_SIZE(88, 136), 80, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_php_namespace(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {41, UPB_SIZE(88, 136), 80, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_php_metadata_namespace(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {44, UPB_SIZE(96, 152), 81, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FileOptions_php_metadata_namespace(const google_protobuf_FileOptions* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {44, UPB_SIZE(96, 152), 81, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_php_metadata_namespace(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {44, UPB_SIZE(96, 152), 81, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_ruby_package(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {45, UPB_SIZE(104, 168), 82, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FileOptions_ruby_package(const google_protobuf_FileOptions* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {45, UPB_SIZE(104, 168), 82, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_ruby_package(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {45, UPB_SIZE(104, 168), 82, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_features(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {50, UPB_SIZE(24, 184), 83, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FeatureSet* google_protobuf_FileOptions_features(const google_protobuf_FileOptions* msg) { + const google_protobuf_FeatureSet* default_val = NULL; + const google_protobuf_FeatureSet* ret; + const upb_MiniTableField field = {50, UPB_SIZE(24, 184), 83, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FileOptions_has_features(const google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {50, UPB_SIZE(24, 184), 83, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FileOptions_clear_uninterpreted_option(google_protobuf_FileOptions* msg) { + const upb_MiniTableField field = {999, UPB_SIZE(28, 192), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_UninterpretedOption* const* google_protobuf_FileOptions_uninterpreted_option(const google_protobuf_FileOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(28, 192), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_UninterpretedOption* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_FileOptions_uninterpreted_option_upb_array(const google_protobuf_FileOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(28, 192), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_FileOptions_uninterpreted_option_mutable_upb_array(google_protobuf_FileOptions* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {999, UPB_SIZE(28, 192), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} + +UPB_INLINE void google_protobuf_FileOptions_set_java_package(google_protobuf_FileOptions *msg, upb_StringView value) { + const upb_MiniTableField field = {1, UPB_SIZE(32, 24), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_java_outer_classname(google_protobuf_FileOptions *msg, upb_StringView value) { + const upb_MiniTableField field = {8, 40, 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_optimize_for(google_protobuf_FileOptions *msg, int32_t value) { + const upb_MiniTableField field = {9, 12, 66, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_java_multiple_files(google_protobuf_FileOptions *msg, bool value) { + const upb_MiniTableField field = {10, 16, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_go_package(google_protobuf_FileOptions *msg, upb_StringView value) { + const upb_MiniTableField field = {11, UPB_SIZE(48, 56), 68, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_cc_generic_services(google_protobuf_FileOptions *msg, bool value) { + const upb_MiniTableField field = {16, 17, 69, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_java_generic_services(google_protobuf_FileOptions *msg, bool value) { + const upb_MiniTableField field = {17, 18, 70, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_py_generic_services(google_protobuf_FileOptions *msg, bool value) { + const upb_MiniTableField field = {18, 19, 71, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_java_generate_equals_and_hash(google_protobuf_FileOptions *msg, bool value) { + const upb_MiniTableField field = {20, 20, 72, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_deprecated(google_protobuf_FileOptions *msg, bool value) { + const upb_MiniTableField field = {23, 21, 73, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_java_string_check_utf8(google_protobuf_FileOptions *msg, bool value) { + const upb_MiniTableField field = {27, 22, 74, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_cc_enable_arenas(google_protobuf_FileOptions *msg, bool value) { + const upb_MiniTableField field = {31, 23, 75, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_objc_class_prefix(google_protobuf_FileOptions *msg, upb_StringView value) { + const upb_MiniTableField field = {36, UPB_SIZE(56, 72), 76, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_csharp_namespace(google_protobuf_FileOptions *msg, upb_StringView value) { + const upb_MiniTableField field = {37, UPB_SIZE(64, 88), 77, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_swift_prefix(google_protobuf_FileOptions *msg, upb_StringView value) { + const upb_MiniTableField field = {39, UPB_SIZE(72, 104), 78, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_php_class_prefix(google_protobuf_FileOptions *msg, upb_StringView value) { + const upb_MiniTableField field = {40, UPB_SIZE(80, 120), 79, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_php_namespace(google_protobuf_FileOptions *msg, upb_StringView value) { + const upb_MiniTableField field = {41, UPB_SIZE(88, 136), 80, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_php_metadata_namespace(google_protobuf_FileOptions *msg, upb_StringView value) { + const upb_MiniTableField field = {44, UPB_SIZE(96, 152), 81, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_ruby_package(google_protobuf_FileOptions *msg, upb_StringView value) { + const upb_MiniTableField field = {45, UPB_SIZE(104, 168), 82, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FileOptions_set_features(google_protobuf_FileOptions *msg, google_protobuf_FeatureSet* value) { + const upb_MiniTableField field = {50, UPB_SIZE(24, 184), 83, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_FeatureSet* google_protobuf_FileOptions_mutable_features(google_protobuf_FileOptions* msg, upb_Arena* arena) { + struct google_protobuf_FeatureSet* sub = (struct google_protobuf_FeatureSet*)google_protobuf_FileOptions_features(msg); + if (sub == NULL) { + sub = (struct google_protobuf_FeatureSet*)_upb_Message_New(&google__protobuf__FeatureSet_msg_init, arena); + if (sub) google_protobuf_FileOptions_set_features(msg, sub); + } + return sub; +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_FileOptions_mutable_uninterpreted_option(google_protobuf_FileOptions* msg, size_t* size) { + upb_MiniTableField field = {999, UPB_SIZE(28, 192), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_UninterpretedOption**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_FileOptions_resize_uninterpreted_option(google_protobuf_FileOptions* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(28, 192), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_UninterpretedOption**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_UninterpretedOption* google_protobuf_FileOptions_add_uninterpreted_option(google_protobuf_FileOptions* msg, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(28, 192), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_UninterpretedOption* sub = (struct google_protobuf_UninterpretedOption*)_upb_Message_New(&google__protobuf__UninterpretedOption_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} + +/* google.protobuf.MessageOptions */ + +UPB_INLINE google_protobuf_MessageOptions* google_protobuf_MessageOptions_new(upb_Arena* arena) { + return (google_protobuf_MessageOptions*)_upb_Message_New(&google__protobuf__MessageOptions_msg_init, arena); +} +UPB_INLINE google_protobuf_MessageOptions* google_protobuf_MessageOptions_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_MessageOptions* ret = google_protobuf_MessageOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__MessageOptions_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_MessageOptions* google_protobuf_MessageOptions_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_MessageOptions* ret = google_protobuf_MessageOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__MessageOptions_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_MessageOptions_serialize(const google_protobuf_MessageOptions* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__MessageOptions_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_MessageOptions_serialize_ex(const google_protobuf_MessageOptions* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__MessageOptions_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_MessageOptions_clear_message_set_wire_format(google_protobuf_MessageOptions* msg) { + const upb_MiniTableField field = {1, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_MessageOptions_message_set_wire_format(const google_protobuf_MessageOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {1, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_MessageOptions_has_message_set_wire_format(const google_protobuf_MessageOptions* msg) { + const upb_MiniTableField field = {1, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_MessageOptions_clear_no_standard_descriptor_accessor(google_protobuf_MessageOptions* msg) { + const upb_MiniTableField field = {2, 10, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_MessageOptions_no_standard_descriptor_accessor(const google_protobuf_MessageOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {2, 10, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_MessageOptions_has_no_standard_descriptor_accessor(const google_protobuf_MessageOptions* msg) { + const upb_MiniTableField field = {2, 10, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_MessageOptions_clear_deprecated(google_protobuf_MessageOptions* msg) { + const upb_MiniTableField field = {3, 11, 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_MessageOptions_deprecated(const google_protobuf_MessageOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {3, 11, 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_MessageOptions_has_deprecated(const google_protobuf_MessageOptions* msg) { + const upb_MiniTableField field = {3, 11, 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_MessageOptions_clear_map_entry(google_protobuf_MessageOptions* msg) { + const upb_MiniTableField field = {7, 12, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_MessageOptions_map_entry(const google_protobuf_MessageOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {7, 12, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_MessageOptions_has_map_entry(const google_protobuf_MessageOptions* msg) { + const upb_MiniTableField field = {7, 12, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_MessageOptions_clear_deprecated_legacy_json_field_conflicts(google_protobuf_MessageOptions* msg) { + const upb_MiniTableField field = {11, 13, 68, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_MessageOptions_deprecated_legacy_json_field_conflicts(const google_protobuf_MessageOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {11, 13, 68, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_MessageOptions_has_deprecated_legacy_json_field_conflicts(const google_protobuf_MessageOptions* msg) { + const upb_MiniTableField field = {11, 13, 68, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_MessageOptions_clear_features(google_protobuf_MessageOptions* msg) { + const upb_MiniTableField field = {12, 16, 69, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FeatureSet* google_protobuf_MessageOptions_features(const google_protobuf_MessageOptions* msg) { + const google_protobuf_FeatureSet* default_val = NULL; + const google_protobuf_FeatureSet* ret; + const upb_MiniTableField field = {12, 16, 69, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_MessageOptions_has_features(const google_protobuf_MessageOptions* msg) { + const upb_MiniTableField field = {12, 16, 69, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_MessageOptions_clear_uninterpreted_option(google_protobuf_MessageOptions* msg) { + const upb_MiniTableField field = {999, UPB_SIZE(20, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_UninterpretedOption* const* google_protobuf_MessageOptions_uninterpreted_option(const google_protobuf_MessageOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(20, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_UninterpretedOption* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_MessageOptions_uninterpreted_option_upb_array(const google_protobuf_MessageOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(20, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_MessageOptions_uninterpreted_option_mutable_upb_array(google_protobuf_MessageOptions* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {999, UPB_SIZE(20, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} + +UPB_INLINE void google_protobuf_MessageOptions_set_message_set_wire_format(google_protobuf_MessageOptions *msg, bool value) { + const upb_MiniTableField field = {1, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_MessageOptions_set_no_standard_descriptor_accessor(google_protobuf_MessageOptions *msg, bool value) { + const upb_MiniTableField field = {2, 10, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_MessageOptions_set_deprecated(google_protobuf_MessageOptions *msg, bool value) { + const upb_MiniTableField field = {3, 11, 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_MessageOptions_set_map_entry(google_protobuf_MessageOptions *msg, bool value) { + const upb_MiniTableField field = {7, 12, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_MessageOptions_set_deprecated_legacy_json_field_conflicts(google_protobuf_MessageOptions *msg, bool value) { + const upb_MiniTableField field = {11, 13, 68, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_MessageOptions_set_features(google_protobuf_MessageOptions *msg, google_protobuf_FeatureSet* value) { + const upb_MiniTableField field = {12, 16, 69, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_FeatureSet* google_protobuf_MessageOptions_mutable_features(google_protobuf_MessageOptions* msg, upb_Arena* arena) { + struct google_protobuf_FeatureSet* sub = (struct google_protobuf_FeatureSet*)google_protobuf_MessageOptions_features(msg); + if (sub == NULL) { + sub = (struct google_protobuf_FeatureSet*)_upb_Message_New(&google__protobuf__FeatureSet_msg_init, arena); + if (sub) google_protobuf_MessageOptions_set_features(msg, sub); + } + return sub; +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_MessageOptions_mutable_uninterpreted_option(google_protobuf_MessageOptions* msg, size_t* size) { + upb_MiniTableField field = {999, UPB_SIZE(20, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_UninterpretedOption**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_MessageOptions_resize_uninterpreted_option(google_protobuf_MessageOptions* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(20, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_UninterpretedOption**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_UninterpretedOption* google_protobuf_MessageOptions_add_uninterpreted_option(google_protobuf_MessageOptions* msg, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(20, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_UninterpretedOption* sub = (struct google_protobuf_UninterpretedOption*)_upb_Message_New(&google__protobuf__UninterpretedOption_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} + +/* google.protobuf.FieldOptions */ + +UPB_INLINE google_protobuf_FieldOptions* google_protobuf_FieldOptions_new(upb_Arena* arena) { + return (google_protobuf_FieldOptions*)_upb_Message_New(&google__protobuf__FieldOptions_msg_init, arena); +} +UPB_INLINE google_protobuf_FieldOptions* google_protobuf_FieldOptions_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_FieldOptions* ret = google_protobuf_FieldOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FieldOptions_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_FieldOptions* google_protobuf_FieldOptions_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_FieldOptions* ret = google_protobuf_FieldOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FieldOptions_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_FieldOptions_serialize(const google_protobuf_FieldOptions* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FieldOptions_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_FieldOptions_serialize_ex(const google_protobuf_FieldOptions* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FieldOptions_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_FieldOptions_clear_ctype(google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {1, 12, 64, 4, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FieldOptions_ctype(const google_protobuf_FieldOptions* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {1, 12, 64, 4, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_has_ctype(const google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {1, 12, 64, 4, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldOptions_clear_packed(google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {2, 16, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_FieldOptions_packed(const google_protobuf_FieldOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {2, 16, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_has_packed(const google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {2, 16, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldOptions_clear_deprecated(google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {3, 17, 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_FieldOptions_deprecated(const google_protobuf_FieldOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {3, 17, 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_has_deprecated(const google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {3, 17, 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldOptions_clear_lazy(google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {5, 18, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_FieldOptions_lazy(const google_protobuf_FieldOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {5, 18, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_has_lazy(const google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {5, 18, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldOptions_clear_jstype(google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {6, 20, 68, 5, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FieldOptions_jstype(const google_protobuf_FieldOptions* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {6, 20, 68, 5, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_has_jstype(const google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {6, 20, 68, 5, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldOptions_clear_weak(google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {10, 24, 69, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_FieldOptions_weak(const google_protobuf_FieldOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {10, 24, 69, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_has_weak(const google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {10, 24, 69, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldOptions_clear_unverified_lazy(google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {15, 25, 70, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_FieldOptions_unverified_lazy(const google_protobuf_FieldOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {15, 25, 70, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_has_unverified_lazy(const google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {15, 25, 70, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldOptions_clear_debug_redact(google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {16, 26, 71, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_FieldOptions_debug_redact(const google_protobuf_FieldOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {16, 26, 71, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_has_debug_redact(const google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {16, 26, 71, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldOptions_clear_retention(google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {17, 28, 72, 6, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FieldOptions_retention(const google_protobuf_FieldOptions* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {17, 28, 72, 6, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_has_retention(const google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {17, 28, 72, 6, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldOptions_clear_targets(google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {19, 32, 0, 7, 14, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t const* google_protobuf_FieldOptions_targets(const google_protobuf_FieldOptions* msg, size_t* size) { + const upb_MiniTableField field = {19, 32, 0, 7, 14, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (int32_t const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_FieldOptions_targets_upb_array(const google_protobuf_FieldOptions* msg, size_t* size) { + const upb_MiniTableField field = {19, 32, 0, 7, 14, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_FieldOptions_targets_mutable_upb_array(google_protobuf_FieldOptions* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {19, 32, 0, 7, 14, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_FieldOptions_clear_edition_defaults(google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {20, UPB_SIZE(36, 40), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FieldOptions_EditionDefault* const* google_protobuf_FieldOptions_edition_defaults(const google_protobuf_FieldOptions* msg, size_t* size) { + const upb_MiniTableField field = {20, UPB_SIZE(36, 40), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_FieldOptions_EditionDefault* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_FieldOptions_edition_defaults_upb_array(const google_protobuf_FieldOptions* msg, size_t* size) { + const upb_MiniTableField field = {20, UPB_SIZE(36, 40), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_FieldOptions_edition_defaults_mutable_upb_array(google_protobuf_FieldOptions* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {20, UPB_SIZE(36, 40), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_FieldOptions_clear_features(google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {21, UPB_SIZE(40, 48), 73, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FeatureSet* google_protobuf_FieldOptions_features(const google_protobuf_FieldOptions* msg) { + const google_protobuf_FeatureSet* default_val = NULL; + const google_protobuf_FeatureSet* ret; + const upb_MiniTableField field = {21, UPB_SIZE(40, 48), 73, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_has_features(const google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {21, UPB_SIZE(40, 48), 73, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldOptions_clear_feature_support(google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {22, UPB_SIZE(44, 56), 74, 2, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FieldOptions_FeatureSupport* google_protobuf_FieldOptions_feature_support(const google_protobuf_FieldOptions* msg) { + const google_protobuf_FieldOptions_FeatureSupport* default_val = NULL; + const google_protobuf_FieldOptions_FeatureSupport* ret; + const upb_MiniTableField field = {22, UPB_SIZE(44, 56), 74, 2, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_has_feature_support(const google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {22, UPB_SIZE(44, 56), 74, 2, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldOptions_clear_uninterpreted_option(google_protobuf_FieldOptions* msg) { + const upb_MiniTableField field = {999, UPB_SIZE(48, 64), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_UninterpretedOption* const* google_protobuf_FieldOptions_uninterpreted_option(const google_protobuf_FieldOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(48, 64), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_UninterpretedOption* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_FieldOptions_uninterpreted_option_upb_array(const google_protobuf_FieldOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(48, 64), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_FieldOptions_uninterpreted_option_mutable_upb_array(google_protobuf_FieldOptions* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {999, UPB_SIZE(48, 64), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} + +UPB_INLINE void google_protobuf_FieldOptions_set_ctype(google_protobuf_FieldOptions *msg, int32_t value) { + const upb_MiniTableField field = {1, 12, 64, 4, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldOptions_set_packed(google_protobuf_FieldOptions *msg, bool value) { + const upb_MiniTableField field = {2, 16, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldOptions_set_deprecated(google_protobuf_FieldOptions *msg, bool value) { + const upb_MiniTableField field = {3, 17, 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldOptions_set_lazy(google_protobuf_FieldOptions *msg, bool value) { + const upb_MiniTableField field = {5, 18, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldOptions_set_jstype(google_protobuf_FieldOptions *msg, int32_t value) { + const upb_MiniTableField field = {6, 20, 68, 5, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldOptions_set_weak(google_protobuf_FieldOptions *msg, bool value) { + const upb_MiniTableField field = {10, 24, 69, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldOptions_set_unverified_lazy(google_protobuf_FieldOptions *msg, bool value) { + const upb_MiniTableField field = {15, 25, 70, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldOptions_set_debug_redact(google_protobuf_FieldOptions *msg, bool value) { + const upb_MiniTableField field = {16, 26, 71, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldOptions_set_retention(google_protobuf_FieldOptions *msg, int32_t value) { + const upb_MiniTableField field = {17, 28, 72, 6, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE int32_t* google_protobuf_FieldOptions_mutable_targets(google_protobuf_FieldOptions* msg, size_t* size) { + upb_MiniTableField field = {19, 32, 0, 7, 14, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (int32_t*)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE int32_t* google_protobuf_FieldOptions_resize_targets(google_protobuf_FieldOptions* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {19, 32, 0, 7, 14, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (int32_t*)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE bool google_protobuf_FieldOptions_add_targets(google_protobuf_FieldOptions* msg, int32_t val, upb_Arena* arena) { + upb_MiniTableField field = {19, 32, 0, 7, 14, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return false; + } + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &val, sizeof(val)); + return true; +} +UPB_INLINE google_protobuf_FieldOptions_EditionDefault** google_protobuf_FieldOptions_mutable_edition_defaults(google_protobuf_FieldOptions* msg, size_t* size) { + upb_MiniTableField field = {20, UPB_SIZE(36, 40), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_FieldOptions_EditionDefault**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_FieldOptions_EditionDefault** google_protobuf_FieldOptions_resize_edition_defaults(google_protobuf_FieldOptions* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {20, UPB_SIZE(36, 40), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_FieldOptions_EditionDefault**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_FieldOptions_EditionDefault* google_protobuf_FieldOptions_add_edition_defaults(google_protobuf_FieldOptions* msg, upb_Arena* arena) { + upb_MiniTableField field = {20, UPB_SIZE(36, 40), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_FieldOptions_EditionDefault* sub = (struct google_protobuf_FieldOptions_EditionDefault*)_upb_Message_New(&google__protobuf__FieldOptions__EditionDefault_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE void google_protobuf_FieldOptions_set_features(google_protobuf_FieldOptions *msg, google_protobuf_FeatureSet* value) { + const upb_MiniTableField field = {21, UPB_SIZE(40, 48), 73, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_FeatureSet* google_protobuf_FieldOptions_mutable_features(google_protobuf_FieldOptions* msg, upb_Arena* arena) { + struct google_protobuf_FeatureSet* sub = (struct google_protobuf_FeatureSet*)google_protobuf_FieldOptions_features(msg); + if (sub == NULL) { + sub = (struct google_protobuf_FeatureSet*)_upb_Message_New(&google__protobuf__FeatureSet_msg_init, arena); + if (sub) google_protobuf_FieldOptions_set_features(msg, sub); + } + return sub; +} +UPB_INLINE void google_protobuf_FieldOptions_set_feature_support(google_protobuf_FieldOptions *msg, google_protobuf_FieldOptions_FeatureSupport* value) { + const upb_MiniTableField field = {22, UPB_SIZE(44, 56), 74, 2, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_FieldOptions_FeatureSupport* google_protobuf_FieldOptions_mutable_feature_support(google_protobuf_FieldOptions* msg, upb_Arena* arena) { + struct google_protobuf_FieldOptions_FeatureSupport* sub = (struct google_protobuf_FieldOptions_FeatureSupport*)google_protobuf_FieldOptions_feature_support(msg); + if (sub == NULL) { + sub = (struct google_protobuf_FieldOptions_FeatureSupport*)_upb_Message_New(&google__protobuf__FieldOptions__FeatureSupport_msg_init, arena); + if (sub) google_protobuf_FieldOptions_set_feature_support(msg, sub); + } + return sub; +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_FieldOptions_mutable_uninterpreted_option(google_protobuf_FieldOptions* msg, size_t* size) { + upb_MiniTableField field = {999, UPB_SIZE(48, 64), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_UninterpretedOption**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_FieldOptions_resize_uninterpreted_option(google_protobuf_FieldOptions* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(48, 64), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_UninterpretedOption**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_UninterpretedOption* google_protobuf_FieldOptions_add_uninterpreted_option(google_protobuf_FieldOptions* msg, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(48, 64), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_UninterpretedOption* sub = (struct google_protobuf_UninterpretedOption*)_upb_Message_New(&google__protobuf__UninterpretedOption_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} + +/* google.protobuf.FieldOptions.EditionDefault */ + +UPB_INLINE google_protobuf_FieldOptions_EditionDefault* google_protobuf_FieldOptions_EditionDefault_new(upb_Arena* arena) { + return (google_protobuf_FieldOptions_EditionDefault*)_upb_Message_New(&google__protobuf__FieldOptions__EditionDefault_msg_init, arena); +} +UPB_INLINE google_protobuf_FieldOptions_EditionDefault* google_protobuf_FieldOptions_EditionDefault_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_FieldOptions_EditionDefault* ret = google_protobuf_FieldOptions_EditionDefault_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FieldOptions__EditionDefault_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_FieldOptions_EditionDefault* google_protobuf_FieldOptions_EditionDefault_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_FieldOptions_EditionDefault* ret = google_protobuf_FieldOptions_EditionDefault_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FieldOptions__EditionDefault_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_FieldOptions_EditionDefault_serialize(const google_protobuf_FieldOptions_EditionDefault* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FieldOptions__EditionDefault_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_FieldOptions_EditionDefault_serialize_ex(const google_protobuf_FieldOptions_EditionDefault* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FieldOptions__EditionDefault_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_FieldOptions_EditionDefault_clear_value(google_protobuf_FieldOptions_EditionDefault* msg) { + const upb_MiniTableField field = {2, 16, 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FieldOptions_EditionDefault_value(const google_protobuf_FieldOptions_EditionDefault* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {2, 16, 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_EditionDefault_has_value(const google_protobuf_FieldOptions_EditionDefault* msg) { + const upb_MiniTableField field = {2, 16, 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldOptions_EditionDefault_clear_edition(google_protobuf_FieldOptions_EditionDefault* msg) { + const upb_MiniTableField field = {3, 12, 65, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FieldOptions_EditionDefault_edition(const google_protobuf_FieldOptions_EditionDefault* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {3, 12, 65, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_EditionDefault_has_edition(const google_protobuf_FieldOptions_EditionDefault* msg) { + const upb_MiniTableField field = {3, 12, 65, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE void google_protobuf_FieldOptions_EditionDefault_set_value(google_protobuf_FieldOptions_EditionDefault *msg, upb_StringView value) { + const upb_MiniTableField field = {2, 16, 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldOptions_EditionDefault_set_edition(google_protobuf_FieldOptions_EditionDefault *msg, int32_t value) { + const upb_MiniTableField field = {3, 12, 65, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} + +/* google.protobuf.FieldOptions.FeatureSupport */ + +UPB_INLINE google_protobuf_FieldOptions_FeatureSupport* google_protobuf_FieldOptions_FeatureSupport_new(upb_Arena* arena) { + return (google_protobuf_FieldOptions_FeatureSupport*)_upb_Message_New(&google__protobuf__FieldOptions__FeatureSupport_msg_init, arena); +} +UPB_INLINE google_protobuf_FieldOptions_FeatureSupport* google_protobuf_FieldOptions_FeatureSupport_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_FieldOptions_FeatureSupport* ret = google_protobuf_FieldOptions_FeatureSupport_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FieldOptions__FeatureSupport_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_FieldOptions_FeatureSupport* google_protobuf_FieldOptions_FeatureSupport_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_FieldOptions_FeatureSupport* ret = google_protobuf_FieldOptions_FeatureSupport_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FieldOptions__FeatureSupport_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_FieldOptions_FeatureSupport_serialize(const google_protobuf_FieldOptions_FeatureSupport* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FieldOptions__FeatureSupport_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_FieldOptions_FeatureSupport_serialize_ex(const google_protobuf_FieldOptions_FeatureSupport* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FieldOptions__FeatureSupport_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_FieldOptions_FeatureSupport_clear_edition_introduced(google_protobuf_FieldOptions_FeatureSupport* msg) { + const upb_MiniTableField field = {1, 12, 64, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FieldOptions_FeatureSupport_edition_introduced(const google_protobuf_FieldOptions_FeatureSupport* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {1, 12, 64, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_FeatureSupport_has_edition_introduced(const google_protobuf_FieldOptions_FeatureSupport* msg) { + const upb_MiniTableField field = {1, 12, 64, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldOptions_FeatureSupport_clear_edition_deprecated(google_protobuf_FieldOptions_FeatureSupport* msg) { + const upb_MiniTableField field = {2, 16, 65, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FieldOptions_FeatureSupport_edition_deprecated(const google_protobuf_FieldOptions_FeatureSupport* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {2, 16, 65, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_FeatureSupport_has_edition_deprecated(const google_protobuf_FieldOptions_FeatureSupport* msg) { + const upb_MiniTableField field = {2, 16, 65, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldOptions_FeatureSupport_clear_deprecation_warning(google_protobuf_FieldOptions_FeatureSupport* msg) { + const upb_MiniTableField field = {3, 24, 66, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_FieldOptions_FeatureSupport_deprecation_warning(const google_protobuf_FieldOptions_FeatureSupport* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {3, 24, 66, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_FeatureSupport_has_deprecation_warning(const google_protobuf_FieldOptions_FeatureSupport* msg) { + const upb_MiniTableField field = {3, 24, 66, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FieldOptions_FeatureSupport_clear_edition_removed(google_protobuf_FieldOptions_FeatureSupport* msg) { + const upb_MiniTableField field = {4, 20, 67, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FieldOptions_FeatureSupport_edition_removed(const google_protobuf_FieldOptions_FeatureSupport* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {4, 20, 67, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FieldOptions_FeatureSupport_has_edition_removed(const google_protobuf_FieldOptions_FeatureSupport* msg) { + const upb_MiniTableField field = {4, 20, 67, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE void google_protobuf_FieldOptions_FeatureSupport_set_edition_introduced(google_protobuf_FieldOptions_FeatureSupport *msg, int32_t value) { + const upb_MiniTableField field = {1, 12, 64, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldOptions_FeatureSupport_set_edition_deprecated(google_protobuf_FieldOptions_FeatureSupport *msg, int32_t value) { + const upb_MiniTableField field = {2, 16, 65, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldOptions_FeatureSupport_set_deprecation_warning(google_protobuf_FieldOptions_FeatureSupport *msg, upb_StringView value) { + const upb_MiniTableField field = {3, 24, 66, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FieldOptions_FeatureSupport_set_edition_removed(google_protobuf_FieldOptions_FeatureSupport *msg, int32_t value) { + const upb_MiniTableField field = {4, 20, 67, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} + +/* google.protobuf.OneofOptions */ + +UPB_INLINE google_protobuf_OneofOptions* google_protobuf_OneofOptions_new(upb_Arena* arena) { + return (google_protobuf_OneofOptions*)_upb_Message_New(&google__protobuf__OneofOptions_msg_init, arena); +} +UPB_INLINE google_protobuf_OneofOptions* google_protobuf_OneofOptions_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_OneofOptions* ret = google_protobuf_OneofOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__OneofOptions_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_OneofOptions* google_protobuf_OneofOptions_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_OneofOptions* ret = google_protobuf_OneofOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__OneofOptions_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_OneofOptions_serialize(const google_protobuf_OneofOptions* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__OneofOptions_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_OneofOptions_serialize_ex(const google_protobuf_OneofOptions* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__OneofOptions_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_OneofOptions_clear_features(google_protobuf_OneofOptions* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 16), 64, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FeatureSet* google_protobuf_OneofOptions_features(const google_protobuf_OneofOptions* msg) { + const google_protobuf_FeatureSet* default_val = NULL; + const google_protobuf_FeatureSet* ret; + const upb_MiniTableField field = {1, UPB_SIZE(12, 16), 64, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_OneofOptions_has_features(const google_protobuf_OneofOptions* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 16), 64, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_OneofOptions_clear_uninterpreted_option(google_protobuf_OneofOptions* msg) { + const upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_UninterpretedOption* const* google_protobuf_OneofOptions_uninterpreted_option(const google_protobuf_OneofOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_UninterpretedOption* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_OneofOptions_uninterpreted_option_upb_array(const google_protobuf_OneofOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_OneofOptions_uninterpreted_option_mutable_upb_array(google_protobuf_OneofOptions* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} + +UPB_INLINE void google_protobuf_OneofOptions_set_features(google_protobuf_OneofOptions *msg, google_protobuf_FeatureSet* value) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 16), 64, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_FeatureSet* google_protobuf_OneofOptions_mutable_features(google_protobuf_OneofOptions* msg, upb_Arena* arena) { + struct google_protobuf_FeatureSet* sub = (struct google_protobuf_FeatureSet*)google_protobuf_OneofOptions_features(msg); + if (sub == NULL) { + sub = (struct google_protobuf_FeatureSet*)_upb_Message_New(&google__protobuf__FeatureSet_msg_init, arena); + if (sub) google_protobuf_OneofOptions_set_features(msg, sub); + } + return sub; +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_OneofOptions_mutable_uninterpreted_option(google_protobuf_OneofOptions* msg, size_t* size) { + upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_UninterpretedOption**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_OneofOptions_resize_uninterpreted_option(google_protobuf_OneofOptions* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_UninterpretedOption**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_UninterpretedOption* google_protobuf_OneofOptions_add_uninterpreted_option(google_protobuf_OneofOptions* msg, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_UninterpretedOption* sub = (struct google_protobuf_UninterpretedOption*)_upb_Message_New(&google__protobuf__UninterpretedOption_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} + +/* google.protobuf.EnumOptions */ + +UPB_INLINE google_protobuf_EnumOptions* google_protobuf_EnumOptions_new(upb_Arena* arena) { + return (google_protobuf_EnumOptions*)_upb_Message_New(&google__protobuf__EnumOptions_msg_init, arena); +} +UPB_INLINE google_protobuf_EnumOptions* google_protobuf_EnumOptions_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_EnumOptions* ret = google_protobuf_EnumOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__EnumOptions_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_EnumOptions* google_protobuf_EnumOptions_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_EnumOptions* ret = google_protobuf_EnumOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__EnumOptions_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_EnumOptions_serialize(const google_protobuf_EnumOptions* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__EnumOptions_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_EnumOptions_serialize_ex(const google_protobuf_EnumOptions* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__EnumOptions_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_EnumOptions_clear_allow_alias(google_protobuf_EnumOptions* msg) { + const upb_MiniTableField field = {2, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_EnumOptions_allow_alias(const google_protobuf_EnumOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {2, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_EnumOptions_has_allow_alias(const google_protobuf_EnumOptions* msg) { + const upb_MiniTableField field = {2, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_EnumOptions_clear_deprecated(google_protobuf_EnumOptions* msg) { + const upb_MiniTableField field = {3, 10, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_EnumOptions_deprecated(const google_protobuf_EnumOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {3, 10, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_EnumOptions_has_deprecated(const google_protobuf_EnumOptions* msg) { + const upb_MiniTableField field = {3, 10, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_EnumOptions_clear_deprecated_legacy_json_field_conflicts(google_protobuf_EnumOptions* msg) { + const upb_MiniTableField field = {6, 11, 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_EnumOptions_deprecated_legacy_json_field_conflicts(const google_protobuf_EnumOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {6, 11, 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_EnumOptions_has_deprecated_legacy_json_field_conflicts(const google_protobuf_EnumOptions* msg) { + const upb_MiniTableField field = {6, 11, 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_EnumOptions_clear_features(google_protobuf_EnumOptions* msg) { + const upb_MiniTableField field = {7, UPB_SIZE(12, 16), 67, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FeatureSet* google_protobuf_EnumOptions_features(const google_protobuf_EnumOptions* msg) { + const google_protobuf_FeatureSet* default_val = NULL; + const google_protobuf_FeatureSet* ret; + const upb_MiniTableField field = {7, UPB_SIZE(12, 16), 67, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_EnumOptions_has_features(const google_protobuf_EnumOptions* msg) { + const upb_MiniTableField field = {7, UPB_SIZE(12, 16), 67, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_EnumOptions_clear_uninterpreted_option(google_protobuf_EnumOptions* msg) { + const upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_UninterpretedOption* const* google_protobuf_EnumOptions_uninterpreted_option(const google_protobuf_EnumOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_UninterpretedOption* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_EnumOptions_uninterpreted_option_upb_array(const google_protobuf_EnumOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_EnumOptions_uninterpreted_option_mutable_upb_array(google_protobuf_EnumOptions* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} + +UPB_INLINE void google_protobuf_EnumOptions_set_allow_alias(google_protobuf_EnumOptions *msg, bool value) { + const upb_MiniTableField field = {2, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_EnumOptions_set_deprecated(google_protobuf_EnumOptions *msg, bool value) { + const upb_MiniTableField field = {3, 10, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_EnumOptions_set_deprecated_legacy_json_field_conflicts(google_protobuf_EnumOptions *msg, bool value) { + const upb_MiniTableField field = {6, 11, 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_EnumOptions_set_features(google_protobuf_EnumOptions *msg, google_protobuf_FeatureSet* value) { + const upb_MiniTableField field = {7, UPB_SIZE(12, 16), 67, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_FeatureSet* google_protobuf_EnumOptions_mutable_features(google_protobuf_EnumOptions* msg, upb_Arena* arena) { + struct google_protobuf_FeatureSet* sub = (struct google_protobuf_FeatureSet*)google_protobuf_EnumOptions_features(msg); + if (sub == NULL) { + sub = (struct google_protobuf_FeatureSet*)_upb_Message_New(&google__protobuf__FeatureSet_msg_init, arena); + if (sub) google_protobuf_EnumOptions_set_features(msg, sub); + } + return sub; +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_EnumOptions_mutable_uninterpreted_option(google_protobuf_EnumOptions* msg, size_t* size) { + upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_UninterpretedOption**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_EnumOptions_resize_uninterpreted_option(google_protobuf_EnumOptions* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_UninterpretedOption**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_UninterpretedOption* google_protobuf_EnumOptions_add_uninterpreted_option(google_protobuf_EnumOptions* msg, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_UninterpretedOption* sub = (struct google_protobuf_UninterpretedOption*)_upb_Message_New(&google__protobuf__UninterpretedOption_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} + +/* google.protobuf.EnumValueOptions */ + +UPB_INLINE google_protobuf_EnumValueOptions* google_protobuf_EnumValueOptions_new(upb_Arena* arena) { + return (google_protobuf_EnumValueOptions*)_upb_Message_New(&google__protobuf__EnumValueOptions_msg_init, arena); +} +UPB_INLINE google_protobuf_EnumValueOptions* google_protobuf_EnumValueOptions_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_EnumValueOptions* ret = google_protobuf_EnumValueOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__EnumValueOptions_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_EnumValueOptions* google_protobuf_EnumValueOptions_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_EnumValueOptions* ret = google_protobuf_EnumValueOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__EnumValueOptions_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_EnumValueOptions_serialize(const google_protobuf_EnumValueOptions* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__EnumValueOptions_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_EnumValueOptions_serialize_ex(const google_protobuf_EnumValueOptions* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__EnumValueOptions_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_EnumValueOptions_clear_deprecated(google_protobuf_EnumValueOptions* msg) { + const upb_MiniTableField field = {1, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_EnumValueOptions_deprecated(const google_protobuf_EnumValueOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {1, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_EnumValueOptions_has_deprecated(const google_protobuf_EnumValueOptions* msg) { + const upb_MiniTableField field = {1, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_EnumValueOptions_clear_features(google_protobuf_EnumValueOptions* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 16), 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FeatureSet* google_protobuf_EnumValueOptions_features(const google_protobuf_EnumValueOptions* msg) { + const google_protobuf_FeatureSet* default_val = NULL; + const google_protobuf_FeatureSet* ret; + const upb_MiniTableField field = {2, UPB_SIZE(12, 16), 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_EnumValueOptions_has_features(const google_protobuf_EnumValueOptions* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 16), 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_EnumValueOptions_clear_debug_redact(google_protobuf_EnumValueOptions* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 10), 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_EnumValueOptions_debug_redact(const google_protobuf_EnumValueOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {3, UPB_SIZE(16, 10), 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_EnumValueOptions_has_debug_redact(const google_protobuf_EnumValueOptions* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 10), 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_EnumValueOptions_clear_feature_support(google_protobuf_EnumValueOptions* msg) { + const upb_MiniTableField field = {4, UPB_SIZE(20, 24), 67, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FieldOptions_FeatureSupport* google_protobuf_EnumValueOptions_feature_support(const google_protobuf_EnumValueOptions* msg) { + const google_protobuf_FieldOptions_FeatureSupport* default_val = NULL; + const google_protobuf_FieldOptions_FeatureSupport* ret; + const upb_MiniTableField field = {4, UPB_SIZE(20, 24), 67, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_EnumValueOptions_has_feature_support(const google_protobuf_EnumValueOptions* msg) { + const upb_MiniTableField field = {4, UPB_SIZE(20, 24), 67, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_EnumValueOptions_clear_uninterpreted_option(google_protobuf_EnumValueOptions* msg) { + const upb_MiniTableField field = {999, UPB_SIZE(24, 32), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_UninterpretedOption* const* google_protobuf_EnumValueOptions_uninterpreted_option(const google_protobuf_EnumValueOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(24, 32), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_UninterpretedOption* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_EnumValueOptions_uninterpreted_option_upb_array(const google_protobuf_EnumValueOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(24, 32), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_EnumValueOptions_uninterpreted_option_mutable_upb_array(google_protobuf_EnumValueOptions* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {999, UPB_SIZE(24, 32), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} + +UPB_INLINE void google_protobuf_EnumValueOptions_set_deprecated(google_protobuf_EnumValueOptions *msg, bool value) { + const upb_MiniTableField field = {1, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_EnumValueOptions_set_features(google_protobuf_EnumValueOptions *msg, google_protobuf_FeatureSet* value) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 16), 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_FeatureSet* google_protobuf_EnumValueOptions_mutable_features(google_protobuf_EnumValueOptions* msg, upb_Arena* arena) { + struct google_protobuf_FeatureSet* sub = (struct google_protobuf_FeatureSet*)google_protobuf_EnumValueOptions_features(msg); + if (sub == NULL) { + sub = (struct google_protobuf_FeatureSet*)_upb_Message_New(&google__protobuf__FeatureSet_msg_init, arena); + if (sub) google_protobuf_EnumValueOptions_set_features(msg, sub); + } + return sub; +} +UPB_INLINE void google_protobuf_EnumValueOptions_set_debug_redact(google_protobuf_EnumValueOptions *msg, bool value) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 10), 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_EnumValueOptions_set_feature_support(google_protobuf_EnumValueOptions *msg, google_protobuf_FieldOptions_FeatureSupport* value) { + const upb_MiniTableField field = {4, UPB_SIZE(20, 24), 67, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_FieldOptions_FeatureSupport* google_protobuf_EnumValueOptions_mutable_feature_support(google_protobuf_EnumValueOptions* msg, upb_Arena* arena) { + struct google_protobuf_FieldOptions_FeatureSupport* sub = (struct google_protobuf_FieldOptions_FeatureSupport*)google_protobuf_EnumValueOptions_feature_support(msg); + if (sub == NULL) { + sub = (struct google_protobuf_FieldOptions_FeatureSupport*)_upb_Message_New(&google__protobuf__FieldOptions__FeatureSupport_msg_init, arena); + if (sub) google_protobuf_EnumValueOptions_set_feature_support(msg, sub); + } + return sub; +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_EnumValueOptions_mutable_uninterpreted_option(google_protobuf_EnumValueOptions* msg, size_t* size) { + upb_MiniTableField field = {999, UPB_SIZE(24, 32), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_UninterpretedOption**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_EnumValueOptions_resize_uninterpreted_option(google_protobuf_EnumValueOptions* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(24, 32), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_UninterpretedOption**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_UninterpretedOption* google_protobuf_EnumValueOptions_add_uninterpreted_option(google_protobuf_EnumValueOptions* msg, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(24, 32), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_UninterpretedOption* sub = (struct google_protobuf_UninterpretedOption*)_upb_Message_New(&google__protobuf__UninterpretedOption_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} + +/* google.protobuf.ServiceOptions */ + +UPB_INLINE google_protobuf_ServiceOptions* google_protobuf_ServiceOptions_new(upb_Arena* arena) { + return (google_protobuf_ServiceOptions*)_upb_Message_New(&google__protobuf__ServiceOptions_msg_init, arena); +} +UPB_INLINE google_protobuf_ServiceOptions* google_protobuf_ServiceOptions_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_ServiceOptions* ret = google_protobuf_ServiceOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__ServiceOptions_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_ServiceOptions* google_protobuf_ServiceOptions_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_ServiceOptions* ret = google_protobuf_ServiceOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__ServiceOptions_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_ServiceOptions_serialize(const google_protobuf_ServiceOptions* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__ServiceOptions_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_ServiceOptions_serialize_ex(const google_protobuf_ServiceOptions* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__ServiceOptions_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_ServiceOptions_clear_deprecated(google_protobuf_ServiceOptions* msg) { + const upb_MiniTableField field = {33, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_ServiceOptions_deprecated(const google_protobuf_ServiceOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {33, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_ServiceOptions_has_deprecated(const google_protobuf_ServiceOptions* msg) { + const upb_MiniTableField field = {33, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_ServiceOptions_clear_features(google_protobuf_ServiceOptions* msg) { + const upb_MiniTableField field = {34, UPB_SIZE(12, 16), 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FeatureSet* google_protobuf_ServiceOptions_features(const google_protobuf_ServiceOptions* msg) { + const google_protobuf_FeatureSet* default_val = NULL; + const google_protobuf_FeatureSet* ret; + const upb_MiniTableField field = {34, UPB_SIZE(12, 16), 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_ServiceOptions_has_features(const google_protobuf_ServiceOptions* msg) { + const upb_MiniTableField field = {34, UPB_SIZE(12, 16), 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_ServiceOptions_clear_uninterpreted_option(google_protobuf_ServiceOptions* msg) { + const upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_UninterpretedOption* const* google_protobuf_ServiceOptions_uninterpreted_option(const google_protobuf_ServiceOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_UninterpretedOption* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_ServiceOptions_uninterpreted_option_upb_array(const google_protobuf_ServiceOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_ServiceOptions_uninterpreted_option_mutable_upb_array(google_protobuf_ServiceOptions* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} + +UPB_INLINE void google_protobuf_ServiceOptions_set_deprecated(google_protobuf_ServiceOptions *msg, bool value) { + const upb_MiniTableField field = {33, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_ServiceOptions_set_features(google_protobuf_ServiceOptions *msg, google_protobuf_FeatureSet* value) { + const upb_MiniTableField field = {34, UPB_SIZE(12, 16), 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_FeatureSet* google_protobuf_ServiceOptions_mutable_features(google_protobuf_ServiceOptions* msg, upb_Arena* arena) { + struct google_protobuf_FeatureSet* sub = (struct google_protobuf_FeatureSet*)google_protobuf_ServiceOptions_features(msg); + if (sub == NULL) { + sub = (struct google_protobuf_FeatureSet*)_upb_Message_New(&google__protobuf__FeatureSet_msg_init, arena); + if (sub) google_protobuf_ServiceOptions_set_features(msg, sub); + } + return sub; +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_ServiceOptions_mutable_uninterpreted_option(google_protobuf_ServiceOptions* msg, size_t* size) { + upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_UninterpretedOption**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_ServiceOptions_resize_uninterpreted_option(google_protobuf_ServiceOptions* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_UninterpretedOption**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_UninterpretedOption* google_protobuf_ServiceOptions_add_uninterpreted_option(google_protobuf_ServiceOptions* msg, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_UninterpretedOption* sub = (struct google_protobuf_UninterpretedOption*)_upb_Message_New(&google__protobuf__UninterpretedOption_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} + +/* google.protobuf.MethodOptions */ + +UPB_INLINE google_protobuf_MethodOptions* google_protobuf_MethodOptions_new(upb_Arena* arena) { + return (google_protobuf_MethodOptions*)_upb_Message_New(&google__protobuf__MethodOptions_msg_init, arena); +} +UPB_INLINE google_protobuf_MethodOptions* google_protobuf_MethodOptions_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_MethodOptions* ret = google_protobuf_MethodOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__MethodOptions_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_MethodOptions* google_protobuf_MethodOptions_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_MethodOptions* ret = google_protobuf_MethodOptions_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__MethodOptions_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_MethodOptions_serialize(const google_protobuf_MethodOptions* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__MethodOptions_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_MethodOptions_serialize_ex(const google_protobuf_MethodOptions* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__MethodOptions_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_MethodOptions_clear_deprecated(google_protobuf_MethodOptions* msg) { + const upb_MiniTableField field = {33, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_MethodOptions_deprecated(const google_protobuf_MethodOptions* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {33, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_MethodOptions_has_deprecated(const google_protobuf_MethodOptions* msg) { + const upb_MiniTableField field = {33, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_MethodOptions_clear_idempotency_level(google_protobuf_MethodOptions* msg) { + const upb_MiniTableField field = {34, 12, 65, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_MethodOptions_idempotency_level(const google_protobuf_MethodOptions* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {34, 12, 65, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_MethodOptions_has_idempotency_level(const google_protobuf_MethodOptions* msg) { + const upb_MiniTableField field = {34, 12, 65, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_MethodOptions_clear_features(google_protobuf_MethodOptions* msg) { + const upb_MiniTableField field = {35, 16, 66, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FeatureSet* google_protobuf_MethodOptions_features(const google_protobuf_MethodOptions* msg) { + const google_protobuf_FeatureSet* default_val = NULL; + const google_protobuf_FeatureSet* ret; + const upb_MiniTableField field = {35, 16, 66, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_MethodOptions_has_features(const google_protobuf_MethodOptions* msg) { + const upb_MiniTableField field = {35, 16, 66, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_MethodOptions_clear_uninterpreted_option(google_protobuf_MethodOptions* msg) { + const upb_MiniTableField field = {999, UPB_SIZE(20, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_UninterpretedOption* const* google_protobuf_MethodOptions_uninterpreted_option(const google_protobuf_MethodOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(20, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_UninterpretedOption* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_MethodOptions_uninterpreted_option_upb_array(const google_protobuf_MethodOptions* msg, size_t* size) { + const upb_MiniTableField field = {999, UPB_SIZE(20, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_MethodOptions_uninterpreted_option_mutable_upb_array(google_protobuf_MethodOptions* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {999, UPB_SIZE(20, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} + +UPB_INLINE void google_protobuf_MethodOptions_set_deprecated(google_protobuf_MethodOptions *msg, bool value) { + const upb_MiniTableField field = {33, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_MethodOptions_set_idempotency_level(google_protobuf_MethodOptions *msg, int32_t value) { + const upb_MiniTableField field = {34, 12, 65, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_MethodOptions_set_features(google_protobuf_MethodOptions *msg, google_protobuf_FeatureSet* value) { + const upb_MiniTableField field = {35, 16, 66, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_FeatureSet* google_protobuf_MethodOptions_mutable_features(google_protobuf_MethodOptions* msg, upb_Arena* arena) { + struct google_protobuf_FeatureSet* sub = (struct google_protobuf_FeatureSet*)google_protobuf_MethodOptions_features(msg); + if (sub == NULL) { + sub = (struct google_protobuf_FeatureSet*)_upb_Message_New(&google__protobuf__FeatureSet_msg_init, arena); + if (sub) google_protobuf_MethodOptions_set_features(msg, sub); + } + return sub; +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_MethodOptions_mutable_uninterpreted_option(google_protobuf_MethodOptions* msg, size_t* size) { + upb_MiniTableField field = {999, UPB_SIZE(20, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_UninterpretedOption**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_UninterpretedOption** google_protobuf_MethodOptions_resize_uninterpreted_option(google_protobuf_MethodOptions* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(20, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_UninterpretedOption**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_UninterpretedOption* google_protobuf_MethodOptions_add_uninterpreted_option(google_protobuf_MethodOptions* msg, upb_Arena* arena) { + upb_MiniTableField field = {999, UPB_SIZE(20, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_UninterpretedOption* sub = (struct google_protobuf_UninterpretedOption*)_upb_Message_New(&google__protobuf__UninterpretedOption_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} + +/* google.protobuf.UninterpretedOption */ + +UPB_INLINE google_protobuf_UninterpretedOption* google_protobuf_UninterpretedOption_new(upb_Arena* arena) { + return (google_protobuf_UninterpretedOption*)_upb_Message_New(&google__protobuf__UninterpretedOption_msg_init, arena); +} +UPB_INLINE google_protobuf_UninterpretedOption* google_protobuf_UninterpretedOption_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_UninterpretedOption* ret = google_protobuf_UninterpretedOption_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__UninterpretedOption_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_UninterpretedOption* google_protobuf_UninterpretedOption_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_UninterpretedOption* ret = google_protobuf_UninterpretedOption_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__UninterpretedOption_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_UninterpretedOption_serialize(const google_protobuf_UninterpretedOption* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__UninterpretedOption_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_UninterpretedOption_serialize_ex(const google_protobuf_UninterpretedOption* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__UninterpretedOption_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_UninterpretedOption_clear_name(google_protobuf_UninterpretedOption* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 16), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_UninterpretedOption_NamePart* const* google_protobuf_UninterpretedOption_name(const google_protobuf_UninterpretedOption* msg, size_t* size) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 16), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_UninterpretedOption_NamePart* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_UninterpretedOption_name_upb_array(const google_protobuf_UninterpretedOption* msg, size_t* size) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 16), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_UninterpretedOption_name_mutable_upb_array(google_protobuf_UninterpretedOption* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {2, UPB_SIZE(12, 16), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_UninterpretedOption_clear_identifier_value(google_protobuf_UninterpretedOption* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 24), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_UninterpretedOption_identifier_value(const google_protobuf_UninterpretedOption* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {3, UPB_SIZE(16, 24), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_UninterpretedOption_has_identifier_value(const google_protobuf_UninterpretedOption* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 24), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_UninterpretedOption_clear_positive_int_value(google_protobuf_UninterpretedOption* msg) { + const upb_MiniTableField field = {4, UPB_SIZE(24, 40), 65, kUpb_NoSub, 4, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE uint64_t google_protobuf_UninterpretedOption_positive_int_value(const google_protobuf_UninterpretedOption* msg) { + uint64_t default_val = (uint64_t)0ull; + uint64_t ret; + const upb_MiniTableField field = {4, UPB_SIZE(24, 40), 65, kUpb_NoSub, 4, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_UninterpretedOption_has_positive_int_value(const google_protobuf_UninterpretedOption* msg) { + const upb_MiniTableField field = {4, UPB_SIZE(24, 40), 65, kUpb_NoSub, 4, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_UninterpretedOption_clear_negative_int_value(google_protobuf_UninterpretedOption* msg) { + const upb_MiniTableField field = {5, UPB_SIZE(32, 48), 66, kUpb_NoSub, 3, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int64_t google_protobuf_UninterpretedOption_negative_int_value(const google_protobuf_UninterpretedOption* msg) { + int64_t default_val = (int64_t)0ll; + int64_t ret; + const upb_MiniTableField field = {5, UPB_SIZE(32, 48), 66, kUpb_NoSub, 3, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_UninterpretedOption_has_negative_int_value(const google_protobuf_UninterpretedOption* msg) { + const upb_MiniTableField field = {5, UPB_SIZE(32, 48), 66, kUpb_NoSub, 3, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_UninterpretedOption_clear_double_value(google_protobuf_UninterpretedOption* msg) { + const upb_MiniTableField field = {6, UPB_SIZE(40, 56), 67, kUpb_NoSub, 1, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE double google_protobuf_UninterpretedOption_double_value(const google_protobuf_UninterpretedOption* msg) { + double default_val = 0; + double ret; + const upb_MiniTableField field = {6, UPB_SIZE(40, 56), 67, kUpb_NoSub, 1, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_UninterpretedOption_has_double_value(const google_protobuf_UninterpretedOption* msg) { + const upb_MiniTableField field = {6, UPB_SIZE(40, 56), 67, kUpb_NoSub, 1, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_UninterpretedOption_clear_string_value(google_protobuf_UninterpretedOption* msg) { + const upb_MiniTableField field = {7, UPB_SIZE(48, 64), 68, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_UninterpretedOption_string_value(const google_protobuf_UninterpretedOption* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {7, UPB_SIZE(48, 64), 68, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_UninterpretedOption_has_string_value(const google_protobuf_UninterpretedOption* msg) { + const upb_MiniTableField field = {7, UPB_SIZE(48, 64), 68, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_UninterpretedOption_clear_aggregate_value(google_protobuf_UninterpretedOption* msg) { + const upb_MiniTableField field = {8, UPB_SIZE(56, 80), 69, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_UninterpretedOption_aggregate_value(const google_protobuf_UninterpretedOption* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {8, UPB_SIZE(56, 80), 69, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_UninterpretedOption_has_aggregate_value(const google_protobuf_UninterpretedOption* msg) { + const upb_MiniTableField field = {8, UPB_SIZE(56, 80), 69, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE google_protobuf_UninterpretedOption_NamePart** google_protobuf_UninterpretedOption_mutable_name(google_protobuf_UninterpretedOption* msg, size_t* size) { + upb_MiniTableField field = {2, UPB_SIZE(12, 16), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_UninterpretedOption_NamePart**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_UninterpretedOption_NamePart** google_protobuf_UninterpretedOption_resize_name(google_protobuf_UninterpretedOption* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {2, UPB_SIZE(12, 16), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_UninterpretedOption_NamePart**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_UninterpretedOption_NamePart* google_protobuf_UninterpretedOption_add_name(google_protobuf_UninterpretedOption* msg, upb_Arena* arena) { + upb_MiniTableField field = {2, UPB_SIZE(12, 16), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_UninterpretedOption_NamePart* sub = (struct google_protobuf_UninterpretedOption_NamePart*)_upb_Message_New(&google__protobuf__UninterpretedOption__NamePart_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE void google_protobuf_UninterpretedOption_set_identifier_value(google_protobuf_UninterpretedOption *msg, upb_StringView value) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 24), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_UninterpretedOption_set_positive_int_value(google_protobuf_UninterpretedOption *msg, uint64_t value) { + const upb_MiniTableField field = {4, UPB_SIZE(24, 40), 65, kUpb_NoSub, 4, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_UninterpretedOption_set_negative_int_value(google_protobuf_UninterpretedOption *msg, int64_t value) { + const upb_MiniTableField field = {5, UPB_SIZE(32, 48), 66, kUpb_NoSub, 3, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_UninterpretedOption_set_double_value(google_protobuf_UninterpretedOption *msg, double value) { + const upb_MiniTableField field = {6, UPB_SIZE(40, 56), 67, kUpb_NoSub, 1, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_UninterpretedOption_set_string_value(google_protobuf_UninterpretedOption *msg, upb_StringView value) { + const upb_MiniTableField field = {7, UPB_SIZE(48, 64), 68, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_UninterpretedOption_set_aggregate_value(google_protobuf_UninterpretedOption *msg, upb_StringView value) { + const upb_MiniTableField field = {8, UPB_SIZE(56, 80), 69, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} + +/* google.protobuf.UninterpretedOption.NamePart */ + +UPB_INLINE google_protobuf_UninterpretedOption_NamePart* google_protobuf_UninterpretedOption_NamePart_new(upb_Arena* arena) { + return (google_protobuf_UninterpretedOption_NamePart*)_upb_Message_New(&google__protobuf__UninterpretedOption__NamePart_msg_init, arena); +} +UPB_INLINE google_protobuf_UninterpretedOption_NamePart* google_protobuf_UninterpretedOption_NamePart_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_UninterpretedOption_NamePart* ret = google_protobuf_UninterpretedOption_NamePart_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__UninterpretedOption__NamePart_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_UninterpretedOption_NamePart* google_protobuf_UninterpretedOption_NamePart_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_UninterpretedOption_NamePart* ret = google_protobuf_UninterpretedOption_NamePart_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__UninterpretedOption__NamePart_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_UninterpretedOption_NamePart_serialize(const google_protobuf_UninterpretedOption_NamePart* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__UninterpretedOption__NamePart_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_UninterpretedOption_NamePart_serialize_ex(const google_protobuf_UninterpretedOption_NamePart* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__UninterpretedOption__NamePart_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_UninterpretedOption_NamePart_clear_name_part(google_protobuf_UninterpretedOption_NamePart* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_UninterpretedOption_NamePart_name_part(const google_protobuf_UninterpretedOption_NamePart* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {1, UPB_SIZE(12, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_UninterpretedOption_NamePart_has_name_part(const google_protobuf_UninterpretedOption_NamePart* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_UninterpretedOption_NamePart_clear_is_extension(google_protobuf_UninterpretedOption_NamePart* msg) { + const upb_MiniTableField field = {2, 9, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE bool google_protobuf_UninterpretedOption_NamePart_is_extension(const google_protobuf_UninterpretedOption_NamePart* msg) { + bool default_val = false; + bool ret; + const upb_MiniTableField field = {2, 9, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_UninterpretedOption_NamePart_has_is_extension(const google_protobuf_UninterpretedOption_NamePart* msg) { + const upb_MiniTableField field = {2, 9, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE void google_protobuf_UninterpretedOption_NamePart_set_name_part(google_protobuf_UninterpretedOption_NamePart *msg, upb_StringView value) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_UninterpretedOption_NamePart_set_is_extension(google_protobuf_UninterpretedOption_NamePart *msg, bool value) { + const upb_MiniTableField field = {2, 9, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} + +/* google.protobuf.FeatureSet */ + +UPB_INLINE google_protobuf_FeatureSet* google_protobuf_FeatureSet_new(upb_Arena* arena) { + return (google_protobuf_FeatureSet*)_upb_Message_New(&google__protobuf__FeatureSet_msg_init, arena); +} +UPB_INLINE google_protobuf_FeatureSet* google_protobuf_FeatureSet_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_FeatureSet* ret = google_protobuf_FeatureSet_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FeatureSet_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_FeatureSet* google_protobuf_FeatureSet_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_FeatureSet* ret = google_protobuf_FeatureSet_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FeatureSet_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_FeatureSet_serialize(const google_protobuf_FeatureSet* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FeatureSet_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_FeatureSet_serialize_ex(const google_protobuf_FeatureSet* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FeatureSet_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_FeatureSet_clear_field_presence(google_protobuf_FeatureSet* msg) { + const upb_MiniTableField field = {1, 12, 64, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FeatureSet_field_presence(const google_protobuf_FeatureSet* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {1, 12, 64, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FeatureSet_has_field_presence(const google_protobuf_FeatureSet* msg) { + const upb_MiniTableField field = {1, 12, 64, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FeatureSet_clear_enum_type(google_protobuf_FeatureSet* msg) { + const upb_MiniTableField field = {2, 16, 65, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FeatureSet_enum_type(const google_protobuf_FeatureSet* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {2, 16, 65, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FeatureSet_has_enum_type(const google_protobuf_FeatureSet* msg) { + const upb_MiniTableField field = {2, 16, 65, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FeatureSet_clear_repeated_field_encoding(google_protobuf_FeatureSet* msg) { + const upb_MiniTableField field = {3, 20, 66, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FeatureSet_repeated_field_encoding(const google_protobuf_FeatureSet* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {3, 20, 66, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FeatureSet_has_repeated_field_encoding(const google_protobuf_FeatureSet* msg) { + const upb_MiniTableField field = {3, 20, 66, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FeatureSet_clear_utf8_validation(google_protobuf_FeatureSet* msg) { + const upb_MiniTableField field = {4, 24, 67, 3, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FeatureSet_utf8_validation(const google_protobuf_FeatureSet* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {4, 24, 67, 3, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FeatureSet_has_utf8_validation(const google_protobuf_FeatureSet* msg) { + const upb_MiniTableField field = {4, 24, 67, 3, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FeatureSet_clear_message_encoding(google_protobuf_FeatureSet* msg) { + const upb_MiniTableField field = {5, 28, 68, 4, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FeatureSet_message_encoding(const google_protobuf_FeatureSet* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {5, 28, 68, 4, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FeatureSet_has_message_encoding(const google_protobuf_FeatureSet* msg) { + const upb_MiniTableField field = {5, 28, 68, 4, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FeatureSet_clear_json_format(google_protobuf_FeatureSet* msg) { + const upb_MiniTableField field = {6, 32, 69, 5, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FeatureSet_json_format(const google_protobuf_FeatureSet* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {6, 32, 69, 5, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FeatureSet_has_json_format(const google_protobuf_FeatureSet* msg) { + const upb_MiniTableField field = {6, 32, 69, 5, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE void google_protobuf_FeatureSet_set_field_presence(google_protobuf_FeatureSet *msg, int32_t value) { + const upb_MiniTableField field = {1, 12, 64, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FeatureSet_set_enum_type(google_protobuf_FeatureSet *msg, int32_t value) { + const upb_MiniTableField field = {2, 16, 65, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FeatureSet_set_repeated_field_encoding(google_protobuf_FeatureSet *msg, int32_t value) { + const upb_MiniTableField field = {3, 20, 66, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FeatureSet_set_utf8_validation(google_protobuf_FeatureSet *msg, int32_t value) { + const upb_MiniTableField field = {4, 24, 67, 3, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FeatureSet_set_message_encoding(google_protobuf_FeatureSet *msg, int32_t value) { + const upb_MiniTableField field = {5, 28, 68, 4, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FeatureSet_set_json_format(google_protobuf_FeatureSet *msg, int32_t value) { + const upb_MiniTableField field = {6, 32, 69, 5, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} + +/* google.protobuf.FeatureSetDefaults */ + +UPB_INLINE google_protobuf_FeatureSetDefaults* google_protobuf_FeatureSetDefaults_new(upb_Arena* arena) { + return (google_protobuf_FeatureSetDefaults*)_upb_Message_New(&google__protobuf__FeatureSetDefaults_msg_init, arena); +} +UPB_INLINE google_protobuf_FeatureSetDefaults* google_protobuf_FeatureSetDefaults_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_FeatureSetDefaults* ret = google_protobuf_FeatureSetDefaults_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FeatureSetDefaults_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_FeatureSetDefaults* google_protobuf_FeatureSetDefaults_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_FeatureSetDefaults* ret = google_protobuf_FeatureSetDefaults_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FeatureSetDefaults_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_FeatureSetDefaults_serialize(const google_protobuf_FeatureSetDefaults* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FeatureSetDefaults_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_FeatureSetDefaults_serialize_ex(const google_protobuf_FeatureSetDefaults* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FeatureSetDefaults_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_FeatureSetDefaults_clear_defaults(google_protobuf_FeatureSetDefaults* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 24), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* const* google_protobuf_FeatureSetDefaults_defaults(const google_protobuf_FeatureSetDefaults* msg, size_t* size) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 24), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_FeatureSetDefaults_defaults_upb_array(const google_protobuf_FeatureSetDefaults* msg, size_t* size) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 24), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_FeatureSetDefaults_defaults_mutable_upb_array(google_protobuf_FeatureSetDefaults* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 24), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_FeatureSetDefaults_clear_minimum_edition(google_protobuf_FeatureSetDefaults* msg) { + const upb_MiniTableField field = {4, UPB_SIZE(16, 12), 64, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FeatureSetDefaults_minimum_edition(const google_protobuf_FeatureSetDefaults* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {4, UPB_SIZE(16, 12), 64, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FeatureSetDefaults_has_minimum_edition(const google_protobuf_FeatureSetDefaults* msg) { + const upb_MiniTableField field = {4, UPB_SIZE(16, 12), 64, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FeatureSetDefaults_clear_maximum_edition(google_protobuf_FeatureSetDefaults* msg) { + const upb_MiniTableField field = {5, UPB_SIZE(20, 16), 65, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FeatureSetDefaults_maximum_edition(const google_protobuf_FeatureSetDefaults* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {5, UPB_SIZE(20, 16), 65, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FeatureSetDefaults_has_maximum_edition(const google_protobuf_FeatureSetDefaults* msg) { + const upb_MiniTableField field = {5, UPB_SIZE(20, 16), 65, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault** google_protobuf_FeatureSetDefaults_mutable_defaults(google_protobuf_FeatureSetDefaults* msg, size_t* size) { + upb_MiniTableField field = {1, UPB_SIZE(12, 24), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault** google_protobuf_FeatureSetDefaults_resize_defaults(google_protobuf_FeatureSetDefaults* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {1, UPB_SIZE(12, 24), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* google_protobuf_FeatureSetDefaults_add_defaults(google_protobuf_FeatureSetDefaults* msg, upb_Arena* arena) { + upb_MiniTableField field = {1, UPB_SIZE(12, 24), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* sub = (struct google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault*)_upb_Message_New(&google__protobuf__FeatureSetDefaults__FeatureSetEditionDefault_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} +UPB_INLINE void google_protobuf_FeatureSetDefaults_set_minimum_edition(google_protobuf_FeatureSetDefaults *msg, int32_t value) { + const upb_MiniTableField field = {4, UPB_SIZE(16, 12), 64, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FeatureSetDefaults_set_maximum_edition(google_protobuf_FeatureSetDefaults *msg, int32_t value) { + const upb_MiniTableField field = {5, UPB_SIZE(20, 16), 65, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} + +/* google.protobuf.FeatureSetDefaults.FeatureSetEditionDefault */ + +UPB_INLINE google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_new(upb_Arena* arena) { + return (google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault*)_upb_Message_New(&google__protobuf__FeatureSetDefaults__FeatureSetEditionDefault_msg_init, arena); +} +UPB_INLINE google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* ret = google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FeatureSetDefaults__FeatureSetEditionDefault_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* ret = google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__FeatureSetDefaults__FeatureSetEditionDefault_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_serialize(const google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FeatureSetDefaults__FeatureSetEditionDefault_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_serialize_ex(const google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__FeatureSetDefaults__FeatureSetEditionDefault_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_clear_edition(google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* msg) { + const upb_MiniTableField field = {3, 12, 64, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_edition(const google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {3, 12, 64, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_has_edition(const google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* msg) { + const upb_MiniTableField field = {3, 12, 64, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_clear_overridable_features(google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* msg) { + const upb_MiniTableField field = {4, 16, 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FeatureSet* google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_overridable_features(const google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* msg) { + const google_protobuf_FeatureSet* default_val = NULL; + const google_protobuf_FeatureSet* ret; + const upb_MiniTableField field = {4, 16, 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_has_overridable_features(const google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* msg) { + const upb_MiniTableField field = {4, 16, 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_clear_fixed_features(google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* msg) { + const upb_MiniTableField field = {5, UPB_SIZE(20, 24), 66, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_FeatureSet* google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_fixed_features(const google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* msg) { + const google_protobuf_FeatureSet* default_val = NULL; + const google_protobuf_FeatureSet* ret; + const upb_MiniTableField field = {5, UPB_SIZE(20, 24), 66, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_has_fixed_features(const google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* msg) { + const upb_MiniTableField field = {5, UPB_SIZE(20, 24), 66, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE void google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_set_edition(google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault *msg, int32_t value) { + const upb_MiniTableField field = {3, 12, 64, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_set_overridable_features(google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault *msg, google_protobuf_FeatureSet* value) { + const upb_MiniTableField field = {4, 16, 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_FeatureSet* google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_mutable_overridable_features(google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* msg, upb_Arena* arena) { + struct google_protobuf_FeatureSet* sub = (struct google_protobuf_FeatureSet*)google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_overridable_features(msg); + if (sub == NULL) { + sub = (struct google_protobuf_FeatureSet*)_upb_Message_New(&google__protobuf__FeatureSet_msg_init, arena); + if (sub) google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_set_overridable_features(msg, sub); + } + return sub; +} +UPB_INLINE void google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_set_fixed_features(google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault *msg, google_protobuf_FeatureSet* value) { + const upb_MiniTableField field = {5, UPB_SIZE(20, 24), 66, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE struct google_protobuf_FeatureSet* google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_mutable_fixed_features(google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault* msg, upb_Arena* arena) { + struct google_protobuf_FeatureSet* sub = (struct google_protobuf_FeatureSet*)google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_fixed_features(msg); + if (sub == NULL) { + sub = (struct google_protobuf_FeatureSet*)_upb_Message_New(&google__protobuf__FeatureSet_msg_init, arena); + if (sub) google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_set_fixed_features(msg, sub); + } + return sub; +} + +/* google.protobuf.SourceCodeInfo */ + +UPB_INLINE google_protobuf_SourceCodeInfo* google_protobuf_SourceCodeInfo_new(upb_Arena* arena) { + return (google_protobuf_SourceCodeInfo*)_upb_Message_New(&google__protobuf__SourceCodeInfo_msg_init, arena); +} +UPB_INLINE google_protobuf_SourceCodeInfo* google_protobuf_SourceCodeInfo_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_SourceCodeInfo* ret = google_protobuf_SourceCodeInfo_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__SourceCodeInfo_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_SourceCodeInfo* google_protobuf_SourceCodeInfo_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_SourceCodeInfo* ret = google_protobuf_SourceCodeInfo_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__SourceCodeInfo_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_SourceCodeInfo_serialize(const google_protobuf_SourceCodeInfo* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__SourceCodeInfo_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_SourceCodeInfo_serialize_ex(const google_protobuf_SourceCodeInfo* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__SourceCodeInfo_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_SourceCodeInfo_clear_location(google_protobuf_SourceCodeInfo* msg) { + const upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_SourceCodeInfo_Location* const* google_protobuf_SourceCodeInfo_location(const google_protobuf_SourceCodeInfo* msg, size_t* size) { + const upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_SourceCodeInfo_Location* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_SourceCodeInfo_location_upb_array(const google_protobuf_SourceCodeInfo* msg, size_t* size) { + const upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_SourceCodeInfo_location_mutable_upb_array(google_protobuf_SourceCodeInfo* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} + +UPB_INLINE google_protobuf_SourceCodeInfo_Location** google_protobuf_SourceCodeInfo_mutable_location(google_protobuf_SourceCodeInfo* msg, size_t* size) { + upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_SourceCodeInfo_Location**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_SourceCodeInfo_Location** google_protobuf_SourceCodeInfo_resize_location(google_protobuf_SourceCodeInfo* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_SourceCodeInfo_Location**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_SourceCodeInfo_Location* google_protobuf_SourceCodeInfo_add_location(google_protobuf_SourceCodeInfo* msg, upb_Arena* arena) { + upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_SourceCodeInfo_Location* sub = (struct google_protobuf_SourceCodeInfo_Location*)_upb_Message_New(&google__protobuf__SourceCodeInfo__Location_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} + +/* google.protobuf.SourceCodeInfo.Location */ + +UPB_INLINE google_protobuf_SourceCodeInfo_Location* google_protobuf_SourceCodeInfo_Location_new(upb_Arena* arena) { + return (google_protobuf_SourceCodeInfo_Location*)_upb_Message_New(&google__protobuf__SourceCodeInfo__Location_msg_init, arena); +} +UPB_INLINE google_protobuf_SourceCodeInfo_Location* google_protobuf_SourceCodeInfo_Location_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_SourceCodeInfo_Location* ret = google_protobuf_SourceCodeInfo_Location_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__SourceCodeInfo__Location_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_SourceCodeInfo_Location* google_protobuf_SourceCodeInfo_Location_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_SourceCodeInfo_Location* ret = google_protobuf_SourceCodeInfo_Location_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__SourceCodeInfo__Location_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_SourceCodeInfo_Location_serialize(const google_protobuf_SourceCodeInfo_Location* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__SourceCodeInfo__Location_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_SourceCodeInfo_Location_serialize_ex(const google_protobuf_SourceCodeInfo_Location* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__SourceCodeInfo__Location_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_SourceCodeInfo_Location_clear_path(google_protobuf_SourceCodeInfo_Location* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 16), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t const* google_protobuf_SourceCodeInfo_Location_path(const google_protobuf_SourceCodeInfo_Location* msg, size_t* size) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 16), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (int32_t const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_SourceCodeInfo_Location_path_upb_array(const google_protobuf_SourceCodeInfo_Location* msg, size_t* size) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 16), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_SourceCodeInfo_Location_path_mutable_upb_array(google_protobuf_SourceCodeInfo_Location* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 16), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_SourceCodeInfo_Location_clear_span(google_protobuf_SourceCodeInfo_Location* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(16, 24), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t const* google_protobuf_SourceCodeInfo_Location_span(const google_protobuf_SourceCodeInfo_Location* msg, size_t* size) { + const upb_MiniTableField field = {2, UPB_SIZE(16, 24), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (int32_t const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_SourceCodeInfo_Location_span_upb_array(const google_protobuf_SourceCodeInfo_Location* msg, size_t* size) { + const upb_MiniTableField field = {2, UPB_SIZE(16, 24), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_SourceCodeInfo_Location_span_mutable_upb_array(google_protobuf_SourceCodeInfo_Location* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {2, UPB_SIZE(16, 24), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_SourceCodeInfo_Location_clear_leading_comments(google_protobuf_SourceCodeInfo_Location* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(24, 32), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_SourceCodeInfo_Location_leading_comments(const google_protobuf_SourceCodeInfo_Location* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {3, UPB_SIZE(24, 32), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_SourceCodeInfo_Location_has_leading_comments(const google_protobuf_SourceCodeInfo_Location* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(24, 32), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_SourceCodeInfo_Location_clear_trailing_comments(google_protobuf_SourceCodeInfo_Location* msg) { + const upb_MiniTableField field = {4, UPB_SIZE(32, 48), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_SourceCodeInfo_Location_trailing_comments(const google_protobuf_SourceCodeInfo_Location* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {4, UPB_SIZE(32, 48), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_SourceCodeInfo_Location_has_trailing_comments(const google_protobuf_SourceCodeInfo_Location* msg) { + const upb_MiniTableField field = {4, UPB_SIZE(32, 48), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_SourceCodeInfo_Location_clear_leading_detached_comments(google_protobuf_SourceCodeInfo_Location* msg) { + const upb_MiniTableField field = {6, UPB_SIZE(20, 64), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView const* google_protobuf_SourceCodeInfo_Location_leading_detached_comments(const google_protobuf_SourceCodeInfo_Location* msg, size_t* size) { + const upb_MiniTableField field = {6, UPB_SIZE(20, 64), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (upb_StringView const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_SourceCodeInfo_Location_leading_detached_comments_upb_array(const google_protobuf_SourceCodeInfo_Location* msg, size_t* size) { + const upb_MiniTableField field = {6, UPB_SIZE(20, 64), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_SourceCodeInfo_Location_leading_detached_comments_mutable_upb_array(google_protobuf_SourceCodeInfo_Location* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {6, UPB_SIZE(20, 64), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} + +UPB_INLINE int32_t* google_protobuf_SourceCodeInfo_Location_mutable_path(google_protobuf_SourceCodeInfo_Location* msg, size_t* size) { + upb_MiniTableField field = {1, UPB_SIZE(12, 16), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (int32_t*)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE int32_t* google_protobuf_SourceCodeInfo_Location_resize_path(google_protobuf_SourceCodeInfo_Location* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {1, UPB_SIZE(12, 16), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (int32_t*)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE bool google_protobuf_SourceCodeInfo_Location_add_path(google_protobuf_SourceCodeInfo_Location* msg, int32_t val, upb_Arena* arena) { + upb_MiniTableField field = {1, UPB_SIZE(12, 16), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return false; + } + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &val, sizeof(val)); + return true; +} +UPB_INLINE int32_t* google_protobuf_SourceCodeInfo_Location_mutable_span(google_protobuf_SourceCodeInfo_Location* msg, size_t* size) { + upb_MiniTableField field = {2, UPB_SIZE(16, 24), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (int32_t*)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE int32_t* google_protobuf_SourceCodeInfo_Location_resize_span(google_protobuf_SourceCodeInfo_Location* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {2, UPB_SIZE(16, 24), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (int32_t*)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE bool google_protobuf_SourceCodeInfo_Location_add_span(google_protobuf_SourceCodeInfo_Location* msg, int32_t val, upb_Arena* arena) { + upb_MiniTableField field = {2, UPB_SIZE(16, 24), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return false; + } + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &val, sizeof(val)); + return true; +} +UPB_INLINE void google_protobuf_SourceCodeInfo_Location_set_leading_comments(google_protobuf_SourceCodeInfo_Location *msg, upb_StringView value) { + const upb_MiniTableField field = {3, UPB_SIZE(24, 32), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_SourceCodeInfo_Location_set_trailing_comments(google_protobuf_SourceCodeInfo_Location *msg, upb_StringView value) { + const upb_MiniTableField field = {4, UPB_SIZE(32, 48), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE upb_StringView* google_protobuf_SourceCodeInfo_Location_mutable_leading_detached_comments(google_protobuf_SourceCodeInfo_Location* msg, size_t* size) { + upb_MiniTableField field = {6, UPB_SIZE(20, 64), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (upb_StringView*)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE upb_StringView* google_protobuf_SourceCodeInfo_Location_resize_leading_detached_comments(google_protobuf_SourceCodeInfo_Location* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {6, UPB_SIZE(20, 64), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (upb_StringView*)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE bool google_protobuf_SourceCodeInfo_Location_add_leading_detached_comments(google_protobuf_SourceCodeInfo_Location* msg, upb_StringView val, upb_Arena* arena) { + upb_MiniTableField field = {6, UPB_SIZE(20, 64), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return false; + } + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &val, sizeof(val)); + return true; +} + +/* google.protobuf.GeneratedCodeInfo */ + +UPB_INLINE google_protobuf_GeneratedCodeInfo* google_protobuf_GeneratedCodeInfo_new(upb_Arena* arena) { + return (google_protobuf_GeneratedCodeInfo*)_upb_Message_New(&google__protobuf__GeneratedCodeInfo_msg_init, arena); +} +UPB_INLINE google_protobuf_GeneratedCodeInfo* google_protobuf_GeneratedCodeInfo_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_GeneratedCodeInfo* ret = google_protobuf_GeneratedCodeInfo_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__GeneratedCodeInfo_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_GeneratedCodeInfo* google_protobuf_GeneratedCodeInfo_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_GeneratedCodeInfo* ret = google_protobuf_GeneratedCodeInfo_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__GeneratedCodeInfo_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_GeneratedCodeInfo_serialize(const google_protobuf_GeneratedCodeInfo* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__GeneratedCodeInfo_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_GeneratedCodeInfo_serialize_ex(const google_protobuf_GeneratedCodeInfo* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__GeneratedCodeInfo_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_GeneratedCodeInfo_clear_annotation(google_protobuf_GeneratedCodeInfo* msg) { + const upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE const google_protobuf_GeneratedCodeInfo_Annotation* const* google_protobuf_GeneratedCodeInfo_annotation(const google_protobuf_GeneratedCodeInfo* msg, size_t* size) { + const upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (const google_protobuf_GeneratedCodeInfo_Annotation* const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_GeneratedCodeInfo_annotation_upb_array(const google_protobuf_GeneratedCodeInfo* msg, size_t* size) { + const upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_GeneratedCodeInfo_annotation_mutable_upb_array(google_protobuf_GeneratedCodeInfo* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} + +UPB_INLINE google_protobuf_GeneratedCodeInfo_Annotation** google_protobuf_GeneratedCodeInfo_mutable_annotation(google_protobuf_GeneratedCodeInfo* msg, size_t* size) { + upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (google_protobuf_GeneratedCodeInfo_Annotation**)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE google_protobuf_GeneratedCodeInfo_Annotation** google_protobuf_GeneratedCodeInfo_resize_annotation(google_protobuf_GeneratedCodeInfo* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (google_protobuf_GeneratedCodeInfo_Annotation**)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE struct google_protobuf_GeneratedCodeInfo_Annotation* google_protobuf_GeneratedCodeInfo_add_annotation(google_protobuf_GeneratedCodeInfo* msg, upb_Arena* arena) { + upb_MiniTableField field = {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return NULL; + } + struct google_protobuf_GeneratedCodeInfo_Annotation* sub = (struct google_protobuf_GeneratedCodeInfo_Annotation*)_upb_Message_New(&google__protobuf__GeneratedCodeInfo__Annotation_msg_init, arena); + if (!arr || !sub) return NULL; + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &sub, sizeof(sub)); + return sub; +} + +/* google.protobuf.GeneratedCodeInfo.Annotation */ + +UPB_INLINE google_protobuf_GeneratedCodeInfo_Annotation* google_protobuf_GeneratedCodeInfo_Annotation_new(upb_Arena* arena) { + return (google_protobuf_GeneratedCodeInfo_Annotation*)_upb_Message_New(&google__protobuf__GeneratedCodeInfo__Annotation_msg_init, arena); +} +UPB_INLINE google_protobuf_GeneratedCodeInfo_Annotation* google_protobuf_GeneratedCodeInfo_Annotation_parse(const char* buf, size_t size, upb_Arena* arena) { + google_protobuf_GeneratedCodeInfo_Annotation* ret = google_protobuf_GeneratedCodeInfo_Annotation_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__GeneratedCodeInfo__Annotation_msg_init, NULL, 0, arena) != + kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE google_protobuf_GeneratedCodeInfo_Annotation* google_protobuf_GeneratedCodeInfo_Annotation_parse_ex(const char* buf, size_t size, + const upb_ExtensionRegistry* extreg, + int options, upb_Arena* arena) { + google_protobuf_GeneratedCodeInfo_Annotation* ret = google_protobuf_GeneratedCodeInfo_Annotation_new(arena); + if (!ret) return NULL; + if (upb_Decode(buf, size, UPB_UPCAST(ret), &google__protobuf__GeneratedCodeInfo__Annotation_msg_init, extreg, options, + arena) != kUpb_DecodeStatus_Ok) { + return NULL; + } + return ret; +} +UPB_INLINE char* google_protobuf_GeneratedCodeInfo_Annotation_serialize(const google_protobuf_GeneratedCodeInfo_Annotation* msg, upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__GeneratedCodeInfo__Annotation_msg_init, 0, arena, &ptr, len); + return ptr; +} +UPB_INLINE char* google_protobuf_GeneratedCodeInfo_Annotation_serialize_ex(const google_protobuf_GeneratedCodeInfo_Annotation* msg, int options, + upb_Arena* arena, size_t* len) { + char* ptr; + (void)upb_Encode(UPB_UPCAST(msg), &google__protobuf__GeneratedCodeInfo__Annotation_msg_init, options, arena, &ptr, len); + return ptr; +} +UPB_INLINE void google_protobuf_GeneratedCodeInfo_Annotation_clear_path(google_protobuf_GeneratedCodeInfo_Annotation* msg) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 24), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t const* google_protobuf_GeneratedCodeInfo_Annotation_path(const google_protobuf_GeneratedCodeInfo_Annotation* msg, size_t* size) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 24), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (int32_t const*)upb_Array_DataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE const upb_Array* _google_protobuf_GeneratedCodeInfo_Annotation_path_upb_array(const google_protobuf_GeneratedCodeInfo_Annotation* msg, size_t* size) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 24), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + const upb_Array* arr = upb_Message_GetArray(UPB_UPCAST(msg), &field); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE upb_Array* _google_protobuf_GeneratedCodeInfo_Annotation_path_mutable_upb_array(google_protobuf_GeneratedCodeInfo_Annotation* msg, size_t* size, upb_Arena* arena) { + const upb_MiniTableField field = {1, UPB_SIZE(12, 24), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray(UPB_UPCAST(msg), + &field, arena); + if (size) { + *size = arr ? arr->UPB_PRIVATE(size) : 0; + } + return arr; +} +UPB_INLINE void google_protobuf_GeneratedCodeInfo_Annotation_clear_source_file(google_protobuf_GeneratedCodeInfo_Annotation* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(28, 32), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE upb_StringView google_protobuf_GeneratedCodeInfo_Annotation_source_file(const google_protobuf_GeneratedCodeInfo_Annotation* msg) { + upb_StringView default_val = upb_StringView_FromString(""); + upb_StringView ret; + const upb_MiniTableField field = {2, UPB_SIZE(28, 32), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_GeneratedCodeInfo_Annotation_has_source_file(const google_protobuf_GeneratedCodeInfo_Annotation* msg) { + const upb_MiniTableField field = {2, UPB_SIZE(28, 32), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_GeneratedCodeInfo_Annotation_clear_begin(google_protobuf_GeneratedCodeInfo_Annotation* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 12), 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_GeneratedCodeInfo_Annotation_begin(const google_protobuf_GeneratedCodeInfo_Annotation* msg) { + int32_t default_val = (int32_t)0; + int32_t ret; + const upb_MiniTableField field = {3, UPB_SIZE(16, 12), 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_GeneratedCodeInfo_Annotation_has_begin(const google_protobuf_GeneratedCodeInfo_Annotation* msg) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 12), 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_GeneratedCodeInfo_Annotation_clear_end(google_protobuf_GeneratedCodeInfo_Annotation* msg) { + const upb_MiniTableField field = {4, UPB_SIZE(20, 16), 66, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_GeneratedCodeInfo_Annotation_end(const google_protobuf_GeneratedCodeInfo_Annotation* msg) { + int32_t default_val = (int32_t)0; + int32_t ret; + const upb_MiniTableField field = {4, UPB_SIZE(20, 16), 66, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_GeneratedCodeInfo_Annotation_has_end(const google_protobuf_GeneratedCodeInfo_Annotation* msg) { + const upb_MiniTableField field = {4, UPB_SIZE(20, 16), 66, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE void google_protobuf_GeneratedCodeInfo_Annotation_clear_semantic(google_protobuf_GeneratedCodeInfo_Annotation* msg) { + const upb_MiniTableField field = {5, UPB_SIZE(24, 20), 67, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_ClearBaseField(UPB_UPCAST(msg), &field); +} +UPB_INLINE int32_t google_protobuf_GeneratedCodeInfo_Annotation_semantic(const google_protobuf_GeneratedCodeInfo_Annotation* msg) { + int32_t default_val = 0; + int32_t ret; + const upb_MiniTableField field = {5, UPB_SIZE(24, 20), 67, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + _upb_Message_GetNonExtensionField(UPB_UPCAST(msg), &field, + &default_val, &ret); + return ret; +} +UPB_INLINE bool google_protobuf_GeneratedCodeInfo_Annotation_has_semantic(const google_protobuf_GeneratedCodeInfo_Annotation* msg) { + const upb_MiniTableField field = {5, UPB_SIZE(24, 20), 67, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + return upb_Message_HasBaseField(UPB_UPCAST(msg), &field); +} + +UPB_INLINE int32_t* google_protobuf_GeneratedCodeInfo_Annotation_mutable_path(google_protobuf_GeneratedCodeInfo_Annotation* msg, size_t* size) { + upb_MiniTableField field = {1, UPB_SIZE(12, 24), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetMutableArray(UPB_UPCAST(msg), &field); + if (arr) { + if (size) *size = arr->UPB_PRIVATE(size); + return (int32_t*)upb_Array_MutableDataPtr(arr); + } else { + if (size) *size = 0; + return NULL; + } +} +UPB_INLINE int32_t* google_protobuf_GeneratedCodeInfo_Annotation_resize_path(google_protobuf_GeneratedCodeInfo_Annotation* msg, size_t size, upb_Arena* arena) { + upb_MiniTableField field = {1, UPB_SIZE(12, 24), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + return (int32_t*)upb_Message_ResizeArrayUninitialized(UPB_UPCAST(msg), + &field, size, arena); +} +UPB_INLINE bool google_protobuf_GeneratedCodeInfo_Annotation_add_path(google_protobuf_GeneratedCodeInfo_Annotation* msg, int32_t val, upb_Arena* arena) { + upb_MiniTableField field = {1, UPB_SIZE(12, 24), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}; + upb_Array* arr = upb_Message_GetOrCreateMutableArray( + UPB_UPCAST(msg), &field, arena); + if (!arr || !UPB_PRIVATE(_upb_Array_ResizeUninitialized)( + arr, arr->UPB_PRIVATE(size) + 1, arena)) { + return false; + } + UPB_PRIVATE(_upb_Array_Set) + (arr, arr->UPB_PRIVATE(size) - 1, &val, sizeof(val)); + return true; +} +UPB_INLINE void google_protobuf_GeneratedCodeInfo_Annotation_set_source_file(google_protobuf_GeneratedCodeInfo_Annotation *msg, upb_StringView value) { + const upb_MiniTableField field = {2, UPB_SIZE(28, 32), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_GeneratedCodeInfo_Annotation_set_begin(google_protobuf_GeneratedCodeInfo_Annotation *msg, int32_t value) { + const upb_MiniTableField field = {3, UPB_SIZE(16, 12), 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_GeneratedCodeInfo_Annotation_set_end(google_protobuf_GeneratedCodeInfo_Annotation *msg, int32_t value) { + const upb_MiniTableField field = {4, UPB_SIZE(20, 16), 66, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} +UPB_INLINE void google_protobuf_GeneratedCodeInfo_Annotation_set_semantic(google_protobuf_GeneratedCodeInfo_Annotation *msg, int32_t value) { + const upb_MiniTableField field = {5, UPB_SIZE(24, 20), 67, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}; + upb_Message_SetBaseField((upb_Message *)msg, &field, &value); +} + +/* Max size 32 is google.protobuf.FileOptions */ +/* Max size 64 is google.protobuf.FileOptions */ +#define _UPB_MAXOPT_SIZE UPB_SIZE(112, 200) + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#include "upb/port/undef.inc" + +#endif /* GOOGLE_PROTOBUF_DESCRIPTOR_PROTO_UPB_H_ */ diff --git a/google/protobuf/descriptor.upb_minitable.c b/google/protobuf/descriptor.upb_minitable.c new file mode 100644 index 0000000..9bd32e1 --- /dev/null +++ b/google/protobuf/descriptor.upb_minitable.c @@ -0,0 +1,1364 @@ +/* This file was generated by upb_generator from the input file: + * + * google/protobuf/descriptor.proto + * + * Do not edit -- your changes will be discarded when the file is + * regenerated. */ + +#include +#include "upb/generated_code_support.h" +#include "google/protobuf/descriptor.upb_minitable.h" + +// Must be last. +#include "upb/port/def.inc" + +static const upb_MiniTableSub google_protobuf_FileDescriptorSet_submsgs[1] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__FileDescriptorProto_msg_init}, +}; + +static const upb_MiniTableField google_protobuf_FileDescriptorSet__fields[1] = { + {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__FileDescriptorSet_msg_init = { + &google_protobuf_FileDescriptorSet_submsgs[0], + &google_protobuf_FileDescriptorSet__fields[0], + 16, 1, kUpb_ExtMode_NonExtendable, 1, UPB_FASTTABLE_MASK(8), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.FileDescriptorSet", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x000800003f00000a, &upb_prm_1bt_max192b}, + }) +}; + +static const upb_MiniTableSub google_protobuf_FileDescriptorProto_submsgs[7] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__DescriptorProto_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__EnumDescriptorProto_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__ServiceDescriptorProto_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__FieldDescriptorProto_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__FileOptions_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__SourceCodeInfo_msg_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_Edition_enum_init}, +}; + +static const upb_MiniTableField google_protobuf_FileDescriptorProto__fields[13] = { + {1, UPB_SIZE(52, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {2, UPB_SIZE(60, 32), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {3, UPB_SIZE(12, 48), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {4, UPB_SIZE(16, 56), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {5, UPB_SIZE(20, 64), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {6, UPB_SIZE(24, 72), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {7, UPB_SIZE(28, 80), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {8, UPB_SIZE(32, 88), 66, 4, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {9, UPB_SIZE(36, 96), 67, 5, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {10, UPB_SIZE(40, 104), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {11, UPB_SIZE(44, 112), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {12, UPB_SIZE(68, 120), 68, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {14, UPB_SIZE(48, 12), 69, 6, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__FileDescriptorProto_msg_init = { + &google_protobuf_FileDescriptorProto_submsgs[0], + &google_protobuf_FileDescriptorProto__fields[0], + UPB_SIZE(80, 136), 13, kUpb_ExtMode_NonExtendable, 12, UPB_FASTTABLE_MASK(120), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.FileDescriptorProto", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x003000003f00001a, &upb_prs_1bt}, + {0x003800003f000022, &upb_prm_1bt_max128b}, + {0x004000003f01002a, &upb_prm_1bt_max128b}, + {0x004800003f020032, &upb_prm_1bt_max64b}, + {0x005000003f03003a, &upb_prm_1bt_max128b}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x006800003f000050, &upb_prv4_1bt}, + {0x007000003f000058, &upb_prv4_1bt}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + }) +}; + +static const upb_MiniTableSub google_protobuf_DescriptorProto_submsgs[8] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__FieldDescriptorProto_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__DescriptorProto_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__EnumDescriptorProto_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__DescriptorProto__ExtensionRange_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__FieldDescriptorProto_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__MessageOptions_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__OneofDescriptorProto_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__DescriptorProto__ReservedRange_msg_init}, +}; + +static const upb_MiniTableField google_protobuf_DescriptorProto__fields[10] = { + {1, UPB_SIZE(48, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {3, UPB_SIZE(16, 40), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {4, UPB_SIZE(20, 48), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {5, UPB_SIZE(24, 56), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {6, UPB_SIZE(28, 64), 0, 4, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {7, UPB_SIZE(32, 72), 65, 5, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {8, UPB_SIZE(36, 80), 0, 6, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {9, UPB_SIZE(40, 88), 0, 7, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {10, UPB_SIZE(44, 96), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__DescriptorProto_msg_init = { + &google_protobuf_DescriptorProto_submsgs[0], + &google_protobuf_DescriptorProto__fields[0], + UPB_SIZE(56, 104), 10, kUpb_ExtMode_NonExtendable, 10, UPB_FASTTABLE_MASK(120), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.DescriptorProto", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x002000003f000012, &upb_prm_1bt_max128b}, + {0x002800003f01001a, &upb_prm_1bt_max128b}, + {0x003000003f020022, &upb_prm_1bt_max128b}, + {0x003800003f03002a, &upb_prm_1bt_max64b}, + {0x004000003f040032, &upb_prm_1bt_max128b}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x005000003f060042, &upb_prm_1bt_max64b}, + {0x005800003f07004a, &upb_prm_1bt_max64b}, + {0x006000003f000052, &upb_prs_1bt}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + }) +}; + +static const upb_MiniTableSub google_protobuf_DescriptorProto_ExtensionRange_submsgs[1] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__ExtensionRangeOptions_msg_init}, +}; + +static const upb_MiniTableField google_protobuf_DescriptorProto_ExtensionRange__fields[3] = { + {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {2, 16, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {3, UPB_SIZE(20, 24), 66, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__DescriptorProto__ExtensionRange_msg_init = { + &google_protobuf_DescriptorProto_ExtensionRange_submsgs[0], + &google_protobuf_DescriptorProto_ExtensionRange__fields[0], + UPB_SIZE(24, 32), 3, kUpb_ExtMode_NonExtendable, 3, UPB_FASTTABLE_MASK(255), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.DescriptorProto.ExtensionRange", +#endif +}; + +static const upb_MiniTableField google_protobuf_DescriptorProto_ReservedRange__fields[2] = { + {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {2, 16, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__DescriptorProto__ReservedRange_msg_init = { + NULL, + &google_protobuf_DescriptorProto_ReservedRange__fields[0], + 24, 2, kUpb_ExtMode_NonExtendable, 2, UPB_FASTTABLE_MASK(255), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.DescriptorProto.ReservedRange", +#endif +}; + +static const upb_MiniTableSub google_protobuf_ExtensionRangeOptions_submsgs[4] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__ExtensionRangeOptions__Declaration_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__FeatureSet_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__UninterpretedOption_msg_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_ExtensionRangeOptions_VerificationState_enum_init}, +}; + +static const upb_MiniTableField google_protobuf_ExtensionRangeOptions__fields[4] = { + {2, UPB_SIZE(12, 16), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {3, UPB_SIZE(16, 12), 64, 3, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {50, UPB_SIZE(20, 24), 65, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {999, UPB_SIZE(24, 32), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__ExtensionRangeOptions_msg_init = { + &google_protobuf_ExtensionRangeOptions_submsgs[0], + &google_protobuf_ExtensionRangeOptions__fields[0], + UPB_SIZE(32, 40), 4, kUpb_ExtMode_Extendable, 0, UPB_FASTTABLE_MASK(248), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.ExtensionRangeOptions", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x001000003f000012, &upb_prm_1bt_max64b}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x002000003f023eba, &upb_prm_2bt_max128b}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + }) +}; + +static const upb_MiniTableField google_protobuf_ExtensionRangeOptions_Declaration__fields[5] = { + {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {2, UPB_SIZE(20, 24), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {3, UPB_SIZE(28, 40), 66, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {5, 16, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {6, 17, 68, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__ExtensionRangeOptions__Declaration_msg_init = { + NULL, + &google_protobuf_ExtensionRangeOptions_Declaration__fields[0], + UPB_SIZE(40, 56), 5, kUpb_ExtMode_NonExtendable, 3, UPB_FASTTABLE_MASK(255), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.ExtensionRangeOptions.Declaration", +#endif +}; + +static const upb_MiniTableSub google_protobuf_FieldDescriptorProto_submsgs[3] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__FieldOptions_msg_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_FieldDescriptorProto_Label_enum_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_FieldDescriptorProto_Type_enum_init}, +}; + +static const upb_MiniTableField google_protobuf_FieldDescriptorProto__fields[11] = { + {1, UPB_SIZE(36, 32), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {2, UPB_SIZE(44, 48), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {3, 12, 66, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {4, 16, 67, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {5, 20, 68, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {6, UPB_SIZE(52, 64), 69, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {7, UPB_SIZE(60, 80), 70, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {8, UPB_SIZE(24, 96), 71, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {9, UPB_SIZE(28, 24), 72, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {10, UPB_SIZE(68, 104), 73, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {17, UPB_SIZE(32, 28), 74, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__FieldDescriptorProto_msg_init = { + &google_protobuf_FieldDescriptorProto_submsgs[0], + &google_protobuf_FieldDescriptorProto__fields[0], + UPB_SIZE(80, 120), 11, kUpb_ExtMode_NonExtendable, 10, UPB_FASTTABLE_MASK(255), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.FieldDescriptorProto", +#endif +}; + +static const upb_MiniTableSub google_protobuf_OneofDescriptorProto_submsgs[1] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__OneofOptions_msg_init}, +}; + +static const upb_MiniTableField google_protobuf_OneofDescriptorProto__fields[2] = { + {1, 16, 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {2, UPB_SIZE(12, 32), 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__OneofDescriptorProto_msg_init = { + &google_protobuf_OneofDescriptorProto_submsgs[0], + &google_protobuf_OneofDescriptorProto__fields[0], + UPB_SIZE(24, 40), 2, kUpb_ExtMode_NonExtendable, 2, UPB_FASTTABLE_MASK(255), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.OneofDescriptorProto", +#endif +}; + +static const upb_MiniTableSub google_protobuf_EnumDescriptorProto_submsgs[3] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__EnumValueDescriptorProto_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__EnumOptions_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__EnumDescriptorProto__EnumReservedRange_msg_init}, +}; + +static const upb_MiniTableField google_protobuf_EnumDescriptorProto__fields[5] = { + {1, UPB_SIZE(28, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {3, UPB_SIZE(16, 40), 65, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {4, UPB_SIZE(20, 48), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {5, UPB_SIZE(24, 56), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__EnumDescriptorProto_msg_init = { + &google_protobuf_EnumDescriptorProto_submsgs[0], + &google_protobuf_EnumDescriptorProto__fields[0], + UPB_SIZE(40, 64), 5, kUpb_ExtMode_NonExtendable, 5, UPB_FASTTABLE_MASK(56), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.EnumDescriptorProto", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x002000003f000012, &upb_prm_1bt_max64b}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x003000003f020022, &upb_prm_1bt_max64b}, + {0x003800003f00002a, &upb_prs_1bt}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + }) +}; + +static const upb_MiniTableField google_protobuf_EnumDescriptorProto_EnumReservedRange__fields[2] = { + {1, 12, 64, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {2, 16, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__EnumDescriptorProto__EnumReservedRange_msg_init = { + NULL, + &google_protobuf_EnumDescriptorProto_EnumReservedRange__fields[0], + 24, 2, kUpb_ExtMode_NonExtendable, 2, UPB_FASTTABLE_MASK(255), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.EnumDescriptorProto.EnumReservedRange", +#endif +}; + +static const upb_MiniTableSub google_protobuf_EnumValueDescriptorProto_submsgs[1] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__EnumValueOptions_msg_init}, +}; + +static const upb_MiniTableField google_protobuf_EnumValueDescriptorProto__fields[3] = { + {1, UPB_SIZE(20, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {2, 12, 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {3, UPB_SIZE(16, 32), 66, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__EnumValueDescriptorProto_msg_init = { + &google_protobuf_EnumValueDescriptorProto_submsgs[0], + &google_protobuf_EnumValueDescriptorProto__fields[0], + UPB_SIZE(32, 40), 3, kUpb_ExtMode_NonExtendable, 3, UPB_FASTTABLE_MASK(255), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.EnumValueDescriptorProto", +#endif +}; + +static const upb_MiniTableSub google_protobuf_ServiceDescriptorProto_submsgs[2] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__MethodDescriptorProto_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__ServiceOptions_msg_init}, +}; + +static const upb_MiniTableField google_protobuf_ServiceDescriptorProto__fields[3] = { + {1, UPB_SIZE(20, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {2, UPB_SIZE(12, 32), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {3, UPB_SIZE(16, 40), 65, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__ServiceDescriptorProto_msg_init = { + &google_protobuf_ServiceDescriptorProto_submsgs[0], + &google_protobuf_ServiceDescriptorProto__fields[0], + UPB_SIZE(32, 48), 3, kUpb_ExtMode_NonExtendable, 3, UPB_FASTTABLE_MASK(24), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.ServiceDescriptorProto", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x002000003f000012, &upb_prm_1bt_max128b}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + }) +}; + +static const upb_MiniTableSub google_protobuf_MethodDescriptorProto_submsgs[1] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__MethodOptions_msg_init}, +}; + +static const upb_MiniTableField google_protobuf_MethodDescriptorProto__fields[6] = { + {1, UPB_SIZE(20, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {2, UPB_SIZE(28, 32), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {3, UPB_SIZE(36, 48), 66, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {4, UPB_SIZE(12, 64), 67, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {5, UPB_SIZE(16, 9), 68, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {6, UPB_SIZE(17, 10), 69, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__MethodDescriptorProto_msg_init = { + &google_protobuf_MethodDescriptorProto_submsgs[0], + &google_protobuf_MethodDescriptorProto__fields[0], + UPB_SIZE(48, 72), 6, kUpb_ExtMode_NonExtendable, 6, UPB_FASTTABLE_MASK(255), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.MethodDescriptorProto", +#endif +}; + +static const upb_MiniTableSub google_protobuf_FileOptions_submsgs[3] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__FeatureSet_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__UninterpretedOption_msg_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_FileOptions_OptimizeMode_enum_init}, +}; + +static const upb_MiniTableField google_protobuf_FileOptions__fields[21] = { + {1, UPB_SIZE(32, 24), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {8, 40, 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {9, 12, 66, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {10, 16, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {11, UPB_SIZE(48, 56), 68, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {16, 17, 69, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {17, 18, 70, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {18, 19, 71, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {20, 20, 72, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {23, 21, 73, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {27, 22, 74, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {31, 23, 75, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {36, UPB_SIZE(56, 72), 76, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {37, UPB_SIZE(64, 88), 77, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {39, UPB_SIZE(72, 104), 78, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {40, UPB_SIZE(80, 120), 79, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {41, UPB_SIZE(88, 136), 80, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {44, UPB_SIZE(96, 152), 81, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {45, UPB_SIZE(104, 168), 82, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {50, UPB_SIZE(24, 184), 83, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {999, UPB_SIZE(28, 192), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__FileOptions_msg_init = { + &google_protobuf_FileOptions_submsgs[0], + &google_protobuf_FileOptions__fields[0], + UPB_SIZE(112, 200), 21, kUpb_ExtMode_Extendable, 1, UPB_FASTTABLE_MASK(248), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.FileOptions", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x00c000003f013eba, &upb_prm_2bt_max128b}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + }) +}; + +static const upb_MiniTableSub google_protobuf_MessageOptions_submsgs[2] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__FeatureSet_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__UninterpretedOption_msg_init}, +}; + +static const upb_MiniTableField google_protobuf_MessageOptions__fields[7] = { + {1, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {2, 10, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {3, 11, 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {7, 12, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {11, 13, 68, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {12, 16, 69, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {999, UPB_SIZE(20, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__MessageOptions_msg_init = { + &google_protobuf_MessageOptions_submsgs[0], + &google_protobuf_MessageOptions__fields[0], + UPB_SIZE(24, 32), 7, kUpb_ExtMode_Extendable, 3, UPB_FASTTABLE_MASK(248), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.MessageOptions", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x001800003f013eba, &upb_prm_2bt_max128b}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + }) +}; + +static const upb_MiniTableSub google_protobuf_FieldOptions_submsgs[8] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__FieldOptions__EditionDefault_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__FeatureSet_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__FieldOptions__FeatureSupport_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__UninterpretedOption_msg_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_FieldOptions_CType_enum_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_FieldOptions_JSType_enum_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_FieldOptions_OptionRetention_enum_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_FieldOptions_OptionTargetType_enum_init}, +}; + +static const upb_MiniTableField google_protobuf_FieldOptions__fields[14] = { + {1, 12, 64, 4, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {2, 16, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {3, 17, 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {5, 18, 67, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {6, 20, 68, 5, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {10, 24, 69, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {15, 25, 70, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {16, 26, 71, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {17, 28, 72, 6, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {19, 32, 0, 7, 14, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {20, UPB_SIZE(36, 40), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {21, UPB_SIZE(40, 48), 73, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {22, UPB_SIZE(44, 56), 74, 2, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {999, UPB_SIZE(48, 64), 0, 3, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__FieldOptions_msg_init = { + &google_protobuf_FieldOptions_submsgs[0], + &google_protobuf_FieldOptions__fields[0], + UPB_SIZE(56, 72), 14, kUpb_ExtMode_Extendable, 3, UPB_FASTTABLE_MASK(248), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.FieldOptions", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x002800003f0001a2, &upb_prm_2bt_max64b}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x004000003f033eba, &upb_prm_2bt_max128b}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + }) +}; + +static const upb_MiniTableSub google_protobuf_FieldOptions_EditionDefault_submsgs[1] = { + {.UPB_PRIVATE(subenum) = &google_protobuf_Edition_enum_init}, +}; + +static const upb_MiniTableField google_protobuf_FieldOptions_EditionDefault__fields[2] = { + {2, 16, 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {3, 12, 65, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__FieldOptions__EditionDefault_msg_init = { + &google_protobuf_FieldOptions_EditionDefault_submsgs[0], + &google_protobuf_FieldOptions_EditionDefault__fields[0], + UPB_SIZE(24, 32), 2, kUpb_ExtMode_NonExtendable, 0, UPB_FASTTABLE_MASK(255), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.FieldOptions.EditionDefault", +#endif +}; + +static const upb_MiniTableSub google_protobuf_FieldOptions_FeatureSupport_submsgs[3] = { + {.UPB_PRIVATE(subenum) = &google_protobuf_Edition_enum_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_Edition_enum_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_Edition_enum_init}, +}; + +static const upb_MiniTableField google_protobuf_FieldOptions_FeatureSupport__fields[4] = { + {1, 12, 64, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {2, 16, 65, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {3, 24, 66, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {4, 20, 67, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__FieldOptions__FeatureSupport_msg_init = { + &google_protobuf_FieldOptions_FeatureSupport_submsgs[0], + &google_protobuf_FieldOptions_FeatureSupport__fields[0], + UPB_SIZE(32, 40), 4, kUpb_ExtMode_NonExtendable, 4, UPB_FASTTABLE_MASK(255), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.FieldOptions.FeatureSupport", +#endif +}; + +static const upb_MiniTableSub google_protobuf_OneofOptions_submsgs[2] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__FeatureSet_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__UninterpretedOption_msg_init}, +}; + +static const upb_MiniTableField google_protobuf_OneofOptions__fields[2] = { + {1, UPB_SIZE(12, 16), 64, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__OneofOptions_msg_init = { + &google_protobuf_OneofOptions_submsgs[0], + &google_protobuf_OneofOptions__fields[0], + UPB_SIZE(24, 32), 2, kUpb_ExtMode_Extendable, 1, UPB_FASTTABLE_MASK(248), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.OneofOptions", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x001800003f013eba, &upb_prm_2bt_max128b}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + }) +}; + +static const upb_MiniTableSub google_protobuf_EnumOptions_submsgs[2] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__FeatureSet_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__UninterpretedOption_msg_init}, +}; + +static const upb_MiniTableField google_protobuf_EnumOptions__fields[5] = { + {2, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {3, 10, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {6, 11, 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {7, UPB_SIZE(12, 16), 67, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__EnumOptions_msg_init = { + &google_protobuf_EnumOptions_submsgs[0], + &google_protobuf_EnumOptions__fields[0], + UPB_SIZE(24, 32), 5, kUpb_ExtMode_Extendable, 0, UPB_FASTTABLE_MASK(248), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.EnumOptions", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x001800003f013eba, &upb_prm_2bt_max128b}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + }) +}; + +static const upb_MiniTableSub google_protobuf_EnumValueOptions_submsgs[3] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__FeatureSet_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__FieldOptions__FeatureSupport_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__UninterpretedOption_msg_init}, +}; + +static const upb_MiniTableField google_protobuf_EnumValueOptions__fields[5] = { + {1, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {2, UPB_SIZE(12, 16), 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {3, UPB_SIZE(16, 10), 66, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {4, UPB_SIZE(20, 24), 67, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {999, UPB_SIZE(24, 32), 0, 2, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__EnumValueOptions_msg_init = { + &google_protobuf_EnumValueOptions_submsgs[0], + &google_protobuf_EnumValueOptions__fields[0], + UPB_SIZE(32, 40), 5, kUpb_ExtMode_Extendable, 4, UPB_FASTTABLE_MASK(248), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.EnumValueOptions", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x002000003f023eba, &upb_prm_2bt_max128b}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + }) +}; + +static const upb_MiniTableSub google_protobuf_ServiceOptions_submsgs[2] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__FeatureSet_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__UninterpretedOption_msg_init}, +}; + +static const upb_MiniTableField google_protobuf_ServiceOptions__fields[3] = { + {33, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {34, UPB_SIZE(12, 16), 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {999, UPB_SIZE(16, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__ServiceOptions_msg_init = { + &google_protobuf_ServiceOptions_submsgs[0], + &google_protobuf_ServiceOptions__fields[0], + UPB_SIZE(24, 32), 3, kUpb_ExtMode_Extendable, 0, UPB_FASTTABLE_MASK(248), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.ServiceOptions", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x001800003f013eba, &upb_prm_2bt_max128b}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + }) +}; + +static const upb_MiniTableSub google_protobuf_MethodOptions_submsgs[3] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__FeatureSet_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__UninterpretedOption_msg_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_MethodOptions_IdempotencyLevel_enum_init}, +}; + +static const upb_MiniTableField google_protobuf_MethodOptions__fields[4] = { + {33, 9, 64, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, + {34, 12, 65, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {35, 16, 66, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {999, UPB_SIZE(20, 24), 0, 1, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__MethodOptions_msg_init = { + &google_protobuf_MethodOptions_submsgs[0], + &google_protobuf_MethodOptions__fields[0], + UPB_SIZE(24, 32), 4, kUpb_ExtMode_Extendable, 0, UPB_FASTTABLE_MASK(248), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.MethodOptions", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x001800003f013eba, &upb_prm_2bt_max128b}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + }) +}; + +static const upb_MiniTableSub google_protobuf_UninterpretedOption_submsgs[1] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__UninterpretedOption__NamePart_msg_init}, +}; + +static const upb_MiniTableField google_protobuf_UninterpretedOption__fields[7] = { + {2, UPB_SIZE(12, 16), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {3, UPB_SIZE(16, 24), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {4, UPB_SIZE(24, 40), 65, kUpb_NoSub, 4, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)}, + {5, UPB_SIZE(32, 48), 66, kUpb_NoSub, 3, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)}, + {6, UPB_SIZE(40, 56), 67, kUpb_NoSub, 1, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)}, + {7, UPB_SIZE(48, 64), 68, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {8, UPB_SIZE(56, 80), 69, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__UninterpretedOption_msg_init = { + &google_protobuf_UninterpretedOption_submsgs[0], + &google_protobuf_UninterpretedOption__fields[0], + UPB_SIZE(64, 96), 7, kUpb_ExtMode_NonExtendable, 0, UPB_FASTTABLE_MASK(24), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.UninterpretedOption", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x001000003f000012, &upb_prm_1bt_max64b}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + }) +}; + +static const upb_MiniTableField google_protobuf_UninterpretedOption_NamePart__fields[2] = { + {1, UPB_SIZE(12, 16), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {2, 9, 65, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__UninterpretedOption__NamePart_msg_init = { + NULL, + &google_protobuf_UninterpretedOption_NamePart__fields[0], + UPB_SIZE(24, 32), 2, kUpb_ExtMode_NonExtendable, 2, UPB_FASTTABLE_MASK(255), 2, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.UninterpretedOption.NamePart", +#endif +}; + +static const upb_MiniTableSub google_protobuf_FeatureSet_submsgs[6] = { + {.UPB_PRIVATE(subenum) = &google_protobuf_FeatureSet_FieldPresence_enum_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_FeatureSet_EnumType_enum_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_FeatureSet_RepeatedFieldEncoding_enum_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_FeatureSet_Utf8Validation_enum_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_FeatureSet_MessageEncoding_enum_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_FeatureSet_JsonFormat_enum_init}, +}; + +static const upb_MiniTableField google_protobuf_FeatureSet__fields[6] = { + {1, 12, 64, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {2, 16, 65, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {3, 20, 66, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {4, 24, 67, 3, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {5, 28, 68, 4, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {6, 32, 69, 5, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__FeatureSet_msg_init = { + &google_protobuf_FeatureSet_submsgs[0], + &google_protobuf_FeatureSet__fields[0], + 40, 6, kUpb_ExtMode_Extendable, 6, UPB_FASTTABLE_MASK(255), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.FeatureSet", +#endif +}; + +static const upb_MiniTableSub google_protobuf_FeatureSetDefaults_submsgs[3] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__FeatureSetDefaults__FeatureSetEditionDefault_msg_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_Edition_enum_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_Edition_enum_init}, +}; + +static const upb_MiniTableField google_protobuf_FeatureSetDefaults__fields[3] = { + {1, UPB_SIZE(12, 24), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {4, UPB_SIZE(16, 12), 64, 1, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {5, UPB_SIZE(20, 16), 65, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__FeatureSetDefaults_msg_init = { + &google_protobuf_FeatureSetDefaults_submsgs[0], + &google_protobuf_FeatureSetDefaults__fields[0], + UPB_SIZE(24, 32), 3, kUpb_ExtMode_NonExtendable, 1, UPB_FASTTABLE_MASK(8), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.FeatureSetDefaults", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x001800003f00000a, &upb_prm_1bt_max64b}, + }) +}; + +static const upb_MiniTableSub google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_submsgs[3] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__FeatureSet_msg_init}, + {.UPB_PRIVATE(submsg) = &google__protobuf__FeatureSet_msg_init}, + {.UPB_PRIVATE(subenum) = &google_protobuf_Edition_enum_init}, +}; + +static const upb_MiniTableField google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault__fields[3] = { + {3, 12, 64, 2, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {4, 16, 65, 0, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {5, UPB_SIZE(20, 24), 66, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__FeatureSetDefaults__FeatureSetEditionDefault_msg_init = { + &google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_submsgs[0], + &google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault__fields[0], + UPB_SIZE(24, 32), 3, kUpb_ExtMode_NonExtendable, 0, UPB_FASTTABLE_MASK(255), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.FeatureSetDefaults.FeatureSetEditionDefault", +#endif +}; + +static const upb_MiniTableSub google_protobuf_SourceCodeInfo_submsgs[1] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__SourceCodeInfo__Location_msg_init}, +}; + +static const upb_MiniTableField google_protobuf_SourceCodeInfo__fields[1] = { + {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__SourceCodeInfo_msg_init = { + &google_protobuf_SourceCodeInfo_submsgs[0], + &google_protobuf_SourceCodeInfo__fields[0], + 16, 1, kUpb_ExtMode_NonExtendable, 1, UPB_FASTTABLE_MASK(8), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.SourceCodeInfo", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x000800003f00000a, &upb_prm_1bt_max128b}, + }) +}; + +static const upb_MiniTableField google_protobuf_SourceCodeInfo_Location__fields[5] = { + {1, UPB_SIZE(12, 16), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {2, UPB_SIZE(16, 24), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {3, UPB_SIZE(24, 32), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {4, UPB_SIZE(32, 48), 65, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {6, UPB_SIZE(20, 64), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsAlternate | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__SourceCodeInfo__Location_msg_init = { + NULL, + &google_protobuf_SourceCodeInfo_Location__fields[0], + UPB_SIZE(40, 72), 5, kUpb_ExtMode_NonExtendable, 4, UPB_FASTTABLE_MASK(56), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.SourceCodeInfo.Location", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x001000003f00000a, &upb_ppv4_1bt}, + {0x001800003f000012, &upb_ppv4_1bt}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x004000003f000032, &upb_prs_1bt}, + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + }) +}; + +static const upb_MiniTableSub google_protobuf_GeneratedCodeInfo_submsgs[1] = { + {.UPB_PRIVATE(submsg) = &google__protobuf__GeneratedCodeInfo__Annotation_msg_init}, +}; + +static const upb_MiniTableField google_protobuf_GeneratedCodeInfo__fields[1] = { + {1, 8, 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__GeneratedCodeInfo_msg_init = { + &google_protobuf_GeneratedCodeInfo_submsgs[0], + &google_protobuf_GeneratedCodeInfo__fields[0], + 16, 1, kUpb_ExtMode_NonExtendable, 1, UPB_FASTTABLE_MASK(8), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.GeneratedCodeInfo", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x000800003f00000a, &upb_prm_1bt_max64b}, + }) +}; + +static const upb_MiniTableSub google_protobuf_GeneratedCodeInfo_Annotation_submsgs[1] = { + {.UPB_PRIVATE(subenum) = &google_protobuf_GeneratedCodeInfo_Annotation_Semantic_enum_init}, +}; + +static const upb_MiniTableField google_protobuf_GeneratedCodeInfo_Annotation__fields[5] = { + {1, UPB_SIZE(12, 24), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Array | (int)kUpb_LabelFlags_IsPacked | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)}, + {2, UPB_SIZE(28, 32), 64, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | (int)kUpb_LabelFlags_IsAlternate | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)}, + {3, UPB_SIZE(16, 12), 65, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {4, UPB_SIZE(20, 16), 66, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, + {5, UPB_SIZE(24, 20), 67, 0, 14, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)}, +}; + +const upb_MiniTable google__protobuf__GeneratedCodeInfo__Annotation_msg_init = { + &google_protobuf_GeneratedCodeInfo_Annotation_submsgs[0], + &google_protobuf_GeneratedCodeInfo_Annotation__fields[0], + UPB_SIZE(40, 48), 5, kUpb_ExtMode_NonExtendable, 5, UPB_FASTTABLE_MASK(8), 0, +#ifdef UPB_TRACING_ENABLED + "google.protobuf.GeneratedCodeInfo.Annotation", +#endif + UPB_FASTTABLE_INIT({ + {0x0000000000000000, &_upb_FastDecoder_DecodeGeneric}, + {0x001800003f00000a, &upb_ppv4_1bt}, + }) +}; + +static const upb_MiniTable *messages_layout[33] = { + &google__protobuf__FileDescriptorSet_msg_init, + &google__protobuf__FileDescriptorProto_msg_init, + &google__protobuf__DescriptorProto_msg_init, + &google__protobuf__DescriptorProto__ExtensionRange_msg_init, + &google__protobuf__DescriptorProto__ReservedRange_msg_init, + &google__protobuf__ExtensionRangeOptions_msg_init, + &google__protobuf__ExtensionRangeOptions__Declaration_msg_init, + &google__protobuf__FieldDescriptorProto_msg_init, + &google__protobuf__OneofDescriptorProto_msg_init, + &google__protobuf__EnumDescriptorProto_msg_init, + &google__protobuf__EnumDescriptorProto__EnumReservedRange_msg_init, + &google__protobuf__EnumValueDescriptorProto_msg_init, + &google__protobuf__ServiceDescriptorProto_msg_init, + &google__protobuf__MethodDescriptorProto_msg_init, + &google__protobuf__FileOptions_msg_init, + &google__protobuf__MessageOptions_msg_init, + &google__protobuf__FieldOptions_msg_init, + &google__protobuf__FieldOptions__EditionDefault_msg_init, + &google__protobuf__FieldOptions__FeatureSupport_msg_init, + &google__protobuf__OneofOptions_msg_init, + &google__protobuf__EnumOptions_msg_init, + &google__protobuf__EnumValueOptions_msg_init, + &google__protobuf__ServiceOptions_msg_init, + &google__protobuf__MethodOptions_msg_init, + &google__protobuf__UninterpretedOption_msg_init, + &google__protobuf__UninterpretedOption__NamePart_msg_init, + &google__protobuf__FeatureSet_msg_init, + &google__protobuf__FeatureSetDefaults_msg_init, + &google__protobuf__FeatureSetDefaults__FeatureSetEditionDefault_msg_init, + &google__protobuf__SourceCodeInfo_msg_init, + &google__protobuf__SourceCodeInfo__Location_msg_init, + &google__protobuf__GeneratedCodeInfo_msg_init, + &google__protobuf__GeneratedCodeInfo__Annotation_msg_init, +}; + +const upb_MiniTableEnum google_protobuf_Edition_enum_init = { + 64, + 9, + { + 0x7, + 0x0, + 0x384, + 0x3e6, + 0x3e7, + 0x3e8, + 0x3e9, + 0x1869d, + 0x1869e, + 0x1869f, + 0x7fffffff, + }, +}; + +const upb_MiniTableEnum google_protobuf_ExtensionRangeOptions_VerificationState_enum_init = { + 64, + 0, + { + 0x3, + 0x0, + }, +}; + +const upb_MiniTableEnum google_protobuf_FeatureSet_EnumType_enum_init = { + 64, + 0, + { + 0x7, + 0x0, + }, +}; + +const upb_MiniTableEnum google_protobuf_FeatureSet_FieldPresence_enum_init = { + 64, + 0, + { + 0xf, + 0x0, + }, +}; + +const upb_MiniTableEnum google_protobuf_FeatureSet_JsonFormat_enum_init = { + 64, + 0, + { + 0x7, + 0x0, + }, +}; + +const upb_MiniTableEnum google_protobuf_FeatureSet_MessageEncoding_enum_init = { + 64, + 0, + { + 0x7, + 0x0, + }, +}; + +const upb_MiniTableEnum google_protobuf_FeatureSet_RepeatedFieldEncoding_enum_init = { + 64, + 0, + { + 0x7, + 0x0, + }, +}; + +const upb_MiniTableEnum google_protobuf_FeatureSet_Utf8Validation_enum_init = { + 64, + 0, + { + 0xd, + 0x0, + }, +}; + +const upb_MiniTableEnum google_protobuf_FieldDescriptorProto_Label_enum_init = { + 64, + 0, + { + 0xe, + 0x0, + }, +}; + +const upb_MiniTableEnum google_protobuf_FieldDescriptorProto_Type_enum_init = { + 64, + 0, + { + 0x7fffe, + 0x0, + }, +}; + +const upb_MiniTableEnum google_protobuf_FieldOptions_CType_enum_init = { + 64, + 0, + { + 0x7, + 0x0, + }, +}; + +const upb_MiniTableEnum google_protobuf_FieldOptions_JSType_enum_init = { + 64, + 0, + { + 0x7, + 0x0, + }, +}; + +const upb_MiniTableEnum google_protobuf_FieldOptions_OptionRetention_enum_init = { + 64, + 0, + { + 0x7, + 0x0, + }, +}; + +const upb_MiniTableEnum google_protobuf_FieldOptions_OptionTargetType_enum_init = { + 64, + 0, + { + 0x3ff, + 0x0, + }, +}; + +const upb_MiniTableEnum google_protobuf_FileOptions_OptimizeMode_enum_init = { + 64, + 0, + { + 0xe, + 0x0, + }, +}; + +const upb_MiniTableEnum google_protobuf_GeneratedCodeInfo_Annotation_Semantic_enum_init = { + 64, + 0, + { + 0x7, + 0x0, + }, +}; + +const upb_MiniTableEnum google_protobuf_MethodOptions_IdempotencyLevel_enum_init = { + 64, + 0, + { + 0x7, + 0x0, + }, +}; + +static const upb_MiniTableEnum *enums_layout[17] = { + &google_protobuf_Edition_enum_init, + &google_protobuf_ExtensionRangeOptions_VerificationState_enum_init, + &google_protobuf_FeatureSet_EnumType_enum_init, + &google_protobuf_FeatureSet_FieldPresence_enum_init, + &google_protobuf_FeatureSet_JsonFormat_enum_init, + &google_protobuf_FeatureSet_MessageEncoding_enum_init, + &google_protobuf_FeatureSet_RepeatedFieldEncoding_enum_init, + &google_protobuf_FeatureSet_Utf8Validation_enum_init, + &google_protobuf_FieldDescriptorProto_Label_enum_init, + &google_protobuf_FieldDescriptorProto_Type_enum_init, + &google_protobuf_FieldOptions_CType_enum_init, + &google_protobuf_FieldOptions_JSType_enum_init, + &google_protobuf_FieldOptions_OptionRetention_enum_init, + &google_protobuf_FieldOptions_OptionTargetType_enum_init, + &google_protobuf_FileOptions_OptimizeMode_enum_init, + &google_protobuf_GeneratedCodeInfo_Annotation_Semantic_enum_init, + &google_protobuf_MethodOptions_IdempotencyLevel_enum_init, +}; + +const upb_MiniTableFile google_protobuf_descriptor_proto_upb_file_layout = { + messages_layout, + enums_layout, + NULL, + 33, + 17, + 0, +}; + +#include "upb/port/undef.inc" + diff --git a/google/protobuf/descriptor.upb_minitable.h b/google/protobuf/descriptor.upb_minitable.h new file mode 100644 index 0000000..f48a64d --- /dev/null +++ b/google/protobuf/descriptor.upb_minitable.h @@ -0,0 +1,79 @@ +/* This file was generated by upb_generator from the input file: + * + * google/protobuf/descriptor.proto + * + * Do not edit -- your changes will be discarded when the file is + * regenerated. */ + +#ifndef GOOGLE_PROTOBUF_DESCRIPTOR_PROTO_UPB_MINITABLE_H_ +#define GOOGLE_PROTOBUF_DESCRIPTOR_PROTO_UPB_MINITABLE_H_ + +#include "upb/generated_code_support.h" + +// Must be last. +#include "upb/port/def.inc" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const upb_MiniTable google__protobuf__FileDescriptorSet_msg_init; +extern const upb_MiniTable google__protobuf__FileDescriptorProto_msg_init; +extern const upb_MiniTable google__protobuf__DescriptorProto_msg_init; +extern const upb_MiniTable google__protobuf__DescriptorProto__ExtensionRange_msg_init; +extern const upb_MiniTable google__protobuf__DescriptorProto__ReservedRange_msg_init; +extern const upb_MiniTable google__protobuf__ExtensionRangeOptions_msg_init; +extern const upb_MiniTable google__protobuf__ExtensionRangeOptions__Declaration_msg_init; +extern const upb_MiniTable google__protobuf__FieldDescriptorProto_msg_init; +extern const upb_MiniTable google__protobuf__OneofDescriptorProto_msg_init; +extern const upb_MiniTable google__protobuf__EnumDescriptorProto_msg_init; +extern const upb_MiniTable google__protobuf__EnumDescriptorProto__EnumReservedRange_msg_init; +extern const upb_MiniTable google__protobuf__EnumValueDescriptorProto_msg_init; +extern const upb_MiniTable google__protobuf__ServiceDescriptorProto_msg_init; +extern const upb_MiniTable google__protobuf__MethodDescriptorProto_msg_init; +extern const upb_MiniTable google__protobuf__FileOptions_msg_init; +extern const upb_MiniTable google__protobuf__MessageOptions_msg_init; +extern const upb_MiniTable google__protobuf__FieldOptions_msg_init; +extern const upb_MiniTable google__protobuf__FieldOptions__EditionDefault_msg_init; +extern const upb_MiniTable google__protobuf__FieldOptions__FeatureSupport_msg_init; +extern const upb_MiniTable google__protobuf__OneofOptions_msg_init; +extern const upb_MiniTable google__protobuf__EnumOptions_msg_init; +extern const upb_MiniTable google__protobuf__EnumValueOptions_msg_init; +extern const upb_MiniTable google__protobuf__ServiceOptions_msg_init; +extern const upb_MiniTable google__protobuf__MethodOptions_msg_init; +extern const upb_MiniTable google__protobuf__UninterpretedOption_msg_init; +extern const upb_MiniTable google__protobuf__UninterpretedOption__NamePart_msg_init; +extern const upb_MiniTable google__protobuf__FeatureSet_msg_init; +extern const upb_MiniTable google__protobuf__FeatureSetDefaults_msg_init; +extern const upb_MiniTable google__protobuf__FeatureSetDefaults__FeatureSetEditionDefault_msg_init; +extern const upb_MiniTable google__protobuf__SourceCodeInfo_msg_init; +extern const upb_MiniTable google__protobuf__SourceCodeInfo__Location_msg_init; +extern const upb_MiniTable google__protobuf__GeneratedCodeInfo_msg_init; +extern const upb_MiniTable google__protobuf__GeneratedCodeInfo__Annotation_msg_init; + +extern const upb_MiniTableEnum google_protobuf_Edition_enum_init; +extern const upb_MiniTableEnum google_protobuf_ExtensionRangeOptions_VerificationState_enum_init; +extern const upb_MiniTableEnum google_protobuf_FeatureSet_EnumType_enum_init; +extern const upb_MiniTableEnum google_protobuf_FeatureSet_FieldPresence_enum_init; +extern const upb_MiniTableEnum google_protobuf_FeatureSet_JsonFormat_enum_init; +extern const upb_MiniTableEnum google_protobuf_FeatureSet_MessageEncoding_enum_init; +extern const upb_MiniTableEnum google_protobuf_FeatureSet_RepeatedFieldEncoding_enum_init; +extern const upb_MiniTableEnum google_protobuf_FeatureSet_Utf8Validation_enum_init; +extern const upb_MiniTableEnum google_protobuf_FieldDescriptorProto_Label_enum_init; +extern const upb_MiniTableEnum google_protobuf_FieldDescriptorProto_Type_enum_init; +extern const upb_MiniTableEnum google_protobuf_FieldOptions_CType_enum_init; +extern const upb_MiniTableEnum google_protobuf_FieldOptions_JSType_enum_init; +extern const upb_MiniTableEnum google_protobuf_FieldOptions_OptionRetention_enum_init; +extern const upb_MiniTableEnum google_protobuf_FieldOptions_OptionTargetType_enum_init; +extern const upb_MiniTableEnum google_protobuf_FileOptions_OptimizeMode_enum_init; +extern const upb_MiniTableEnum google_protobuf_GeneratedCodeInfo_Annotation_Semantic_enum_init; +extern const upb_MiniTableEnum google_protobuf_MethodOptions_IdempotencyLevel_enum_init; +extern const upb_MiniTableFile google_protobuf_descriptor_proto_upb_file_layout; + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#include "upb/port/undef.inc" + +#endif /* GOOGLE_PROTOBUF_DESCRIPTOR_PROTO_UPB_MINITABLE_H_ */ diff --git a/google/protobuf/descriptor.upbdefs.c b/google/protobuf/descriptor.upbdefs.c new file mode 100644 index 0000000..47d0623 --- /dev/null +++ b/google/protobuf/descriptor.upbdefs.c @@ -0,0 +1,514 @@ +/* This file was generated by upb_generator from the input file: + * + * google/protobuf/descriptor.proto + * + * Do not edit -- your changes will be discarded when the file is + * regenerated. */ + +#include "upb/reflection/def.h" +#include "google/protobuf/descriptor.upbdefs.h" +#include "google/protobuf/descriptor.upb_minitable.h" + +static const char descriptor[12268] = {'\n', ' ', 'g', 'o', 'o', 'g', 'l', 'e', '/', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '/', 'd', 'e', 's', 'c', 'r', 'i', 'p', +'t', 'o', 'r', '.', 'p', 'r', 'o', 't', 'o', '\022', '\017', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', +'f', '\"', 'M', '\n', '\021', 'F', 'i', 'l', 'e', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'S', 'e', 't', '\022', '8', '\n', +'\004', 'f', 'i', 'l', 'e', '\030', '\001', ' ', '\003', '(', '\013', '2', '$', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', +'o', 'b', 'u', 'f', '.', 'F', 'i', 'l', 'e', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', 'o', 'R', +'\004', 'f', 'i', 'l', 'e', '\"', '\230', '\005', '\n', '\023', 'F', 'i', 'l', 'e', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', +'r', 'o', 't', 'o', '\022', '\022', '\n', '\004', 'n', 'a', 'm', 'e', '\030', '\001', ' ', '\001', '(', '\t', 'R', '\004', 'n', 'a', 'm', 'e', '\022', +'\030', '\n', '\007', 'p', 'a', 'c', 'k', 'a', 'g', 'e', '\030', '\002', ' ', '\001', '(', '\t', 'R', '\007', 'p', 'a', 'c', 'k', 'a', 'g', 'e', +'\022', '\036', '\n', '\n', 'd', 'e', 'p', 'e', 'n', 'd', 'e', 'n', 'c', 'y', '\030', '\003', ' ', '\003', '(', '\t', 'R', '\n', 'd', 'e', 'p', +'e', 'n', 'd', 'e', 'n', 'c', 'y', '\022', '+', '\n', '\021', 'p', 'u', 'b', 'l', 'i', 'c', '_', 'd', 'e', 'p', 'e', 'n', 'd', 'e', +'n', 'c', 'y', '\030', '\n', ' ', '\003', '(', '\005', 'R', '\020', 'p', 'u', 'b', 'l', 'i', 'c', 'D', 'e', 'p', 'e', 'n', 'd', 'e', 'n', +'c', 'y', '\022', '\'', '\n', '\017', 'w', 'e', 'a', 'k', '_', 'd', 'e', 'p', 'e', 'n', 'd', 'e', 'n', 'c', 'y', '\030', '\013', ' ', '\003', +'(', '\005', 'R', '\016', 'w', 'e', 'a', 'k', 'D', 'e', 'p', 'e', 'n', 'd', 'e', 'n', 'c', 'y', '\022', 'C', '\n', '\014', 'm', 'e', 's', +'s', 'a', 'g', 'e', '_', 't', 'y', 'p', 'e', '\030', '\004', ' ', '\003', '(', '\013', '2', ' ', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', +'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', 'o', 'R', +'\013', 'm', 'e', 's', 's', 'a', 'g', 'e', 'T', 'y', 'p', 'e', '\022', 'A', '\n', '\t', 'e', 'n', 'u', 'm', '_', 't', 'y', 'p', 'e', +'\030', '\005', ' ', '\003', '(', '\013', '2', '$', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', +'E', 'n', 'u', 'm', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', 'o', 'R', '\010', 'e', 'n', 'u', 'm', +'T', 'y', 'p', 'e', '\022', 'A', '\n', '\007', 's', 'e', 'r', 'v', 'i', 'c', 'e', '\030', '\006', ' ', '\003', '(', '\013', '2', '\'', '.', 'g', +'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 'D', 'e', 's', +'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', 'o', 'R', '\007', 's', 'e', 'r', 'v', 'i', 'c', 'e', '\022', 'C', '\n', '\t', +'e', 'x', 't', 'e', 'n', 's', 'i', 'o', 'n', '\030', '\007', ' ', '\003', '(', '\013', '2', '%', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', +'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'i', 'e', 'l', 'd', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', +'r', 'o', 't', 'o', 'R', '\t', 'e', 'x', 't', 'e', 'n', 's', 'i', 'o', 'n', '\022', '6', '\n', '\007', 'o', 'p', 't', 'i', 'o', 'n', +'s', '\030', '\010', ' ', '\001', '(', '\013', '2', '\034', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', +'.', 'F', 'i', 'l', 'e', 'O', 'p', 't', 'i', 'o', 'n', 's', 'R', '\007', 'o', 'p', 't', 'i', 'o', 'n', 's', '\022', 'I', '\n', '\020', +'s', 'o', 'u', 'r', 'c', 'e', '_', 'c', 'o', 'd', 'e', '_', 'i', 'n', 'f', 'o', '\030', '\t', ' ', '\001', '(', '\013', '2', '\037', '.', +'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'S', 'o', 'u', 'r', 'c', 'e', 'C', 'o', 'd', +'e', 'I', 'n', 'f', 'o', 'R', '\016', 's', 'o', 'u', 'r', 'c', 'e', 'C', 'o', 'd', 'e', 'I', 'n', 'f', 'o', '\022', '\026', '\n', '\006', +'s', 'y', 'n', 't', 'a', 'x', '\030', '\014', ' ', '\001', '(', '\t', 'R', '\006', 's', 'y', 'n', 't', 'a', 'x', '\022', '2', '\n', '\007', 'e', +'d', 'i', 't', 'i', 'o', 'n', '\030', '\016', ' ', '\001', '(', '\016', '2', '\030', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', +'t', 'o', 'b', 'u', 'f', '.', 'E', 'd', 'i', 't', 'i', 'o', 'n', 'R', '\007', 'e', 'd', 'i', 't', 'i', 'o', 'n', '\"', '\271', '\006', +'\n', '\017', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', 'o', '\022', '\022', '\n', '\004', 'n', 'a', 'm', 'e', +'\030', '\001', ' ', '\001', '(', '\t', 'R', '\004', 'n', 'a', 'm', 'e', '\022', ';', '\n', '\005', 'f', 'i', 'e', 'l', 'd', '\030', '\002', ' ', '\003', +'(', '\013', '2', '%', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'i', 'e', 'l', +'d', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', 'o', 'R', '\005', 'f', 'i', 'e', 'l', 'd', '\022', 'C', +'\n', '\t', 'e', 'x', 't', 'e', 'n', 's', 'i', 'o', 'n', '\030', '\006', ' ', '\003', '(', '\013', '2', '%', '.', 'g', 'o', 'o', 'g', 'l', +'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'i', 'e', 'l', 'd', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', +'r', 'P', 'r', 'o', 't', 'o', 'R', '\t', 'e', 'x', 't', 'e', 'n', 's', 'i', 'o', 'n', '\022', 'A', '\n', '\013', 'n', 'e', 's', 't', +'e', 'd', '_', 't', 'y', 'p', 'e', '\030', '\003', ' ', '\003', '(', '\013', '2', ' ', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', +'o', 't', 'o', 'b', 'u', 'f', '.', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', 'o', 'R', '\n', 'n', +'e', 's', 't', 'e', 'd', 'T', 'y', 'p', 'e', '\022', 'A', '\n', '\t', 'e', 'n', 'u', 'm', '_', 't', 'y', 'p', 'e', '\030', '\004', ' ', +'\003', '(', '\013', '2', '$', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'E', 'n', 'u', +'m', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', 'o', 'R', '\010', 'e', 'n', 'u', 'm', 'T', 'y', 'p', +'e', '\022', 'X', '\n', '\017', 'e', 'x', 't', 'e', 'n', 's', 'i', 'o', 'n', '_', 'r', 'a', 'n', 'g', 'e', '\030', '\005', ' ', '\003', '(', +'\013', '2', '/', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'D', 'e', 's', 'c', 'r', +'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', 'o', '.', 'E', 'x', 't', 'e', 'n', 's', 'i', 'o', 'n', 'R', 'a', 'n', 'g', 'e', +'R', '\016', 'e', 'x', 't', 'e', 'n', 's', 'i', 'o', 'n', 'R', 'a', 'n', 'g', 'e', '\022', 'D', '\n', '\n', 'o', 'n', 'e', 'o', 'f', +'_', 'd', 'e', 'c', 'l', '\030', '\010', ' ', '\003', '(', '\013', '2', '%', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', +'o', 'b', 'u', 'f', '.', 'O', 'n', 'e', 'o', 'f', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', 'o', +'R', '\t', 'o', 'n', 'e', 'o', 'f', 'D', 'e', 'c', 'l', '\022', '9', '\n', '\007', 'o', 'p', 't', 'i', 'o', 'n', 's', '\030', '\007', ' ', +'\001', '(', '\013', '2', '\037', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'M', 'e', 's', +'s', 'a', 'g', 'e', 'O', 'p', 't', 'i', 'o', 'n', 's', 'R', '\007', 'o', 'p', 't', 'i', 'o', 'n', 's', '\022', 'U', '\n', '\016', 'r', +'e', 's', 'e', 'r', 'v', 'e', 'd', '_', 'r', 'a', 'n', 'g', 'e', '\030', '\t', ' ', '\003', '(', '\013', '2', '.', '.', 'g', 'o', 'o', +'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', +'o', 't', 'o', '.', 'R', 'e', 's', 'e', 'r', 'v', 'e', 'd', 'R', 'a', 'n', 'g', 'e', 'R', '\r', 'r', 'e', 's', 'e', 'r', 'v', +'e', 'd', 'R', 'a', 'n', 'g', 'e', '\022', '#', '\n', '\r', 'r', 'e', 's', 'e', 'r', 'v', 'e', 'd', '_', 'n', 'a', 'm', 'e', '\030', +'\n', ' ', '\003', '(', '\t', 'R', '\014', 'r', 'e', 's', 'e', 'r', 'v', 'e', 'd', 'N', 'a', 'm', 'e', '\032', 'z', '\n', '\016', 'E', 'x', +'t', 'e', 'n', 's', 'i', 'o', 'n', 'R', 'a', 'n', 'g', 'e', '\022', '\024', '\n', '\005', 's', 't', 'a', 'r', 't', '\030', '\001', ' ', '\001', +'(', '\005', 'R', '\005', 's', 't', 'a', 'r', 't', '\022', '\020', '\n', '\003', 'e', 'n', 'd', '\030', '\002', ' ', '\001', '(', '\005', 'R', '\003', 'e', +'n', 'd', '\022', '@', '\n', '\007', 'o', 'p', 't', 'i', 'o', 'n', 's', '\030', '\003', ' ', '\001', '(', '\013', '2', '&', '.', 'g', 'o', 'o', +'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'E', 'x', 't', 'e', 'n', 's', 'i', 'o', 'n', 'R', 'a', 'n', +'g', 'e', 'O', 'p', 't', 'i', 'o', 'n', 's', 'R', '\007', 'o', 'p', 't', 'i', 'o', 'n', 's', '\032', '7', '\n', '\r', 'R', 'e', 's', +'e', 'r', 'v', 'e', 'd', 'R', 'a', 'n', 'g', 'e', '\022', '\024', '\n', '\005', 's', 't', 'a', 'r', 't', '\030', '\001', ' ', '\001', '(', '\005', +'R', '\005', 's', 't', 'a', 'r', 't', '\022', '\020', '\n', '\003', 'e', 'n', 'd', '\030', '\002', ' ', '\001', '(', '\005', 'R', '\003', 'e', 'n', 'd', +'\"', '\314', '\004', '\n', '\025', 'E', 'x', 't', 'e', 'n', 's', 'i', 'o', 'n', 'R', 'a', 'n', 'g', 'e', 'O', 'p', 't', 'i', 'o', 'n', +'s', '\022', 'X', '\n', '\024', 'u', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', '_', 'o', 'p', 't', 'i', 'o', 'n', +'\030', '\347', '\007', ' ', '\003', '(', '\013', '2', '$', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', +'.', 'U', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', 'O', 'p', 't', 'i', 'o', 'n', 'R', '\023', 'u', 'n', 'i', +'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', 'O', 'p', 't', 'i', 'o', 'n', '\022', 'Y', '\n', '\013', 'd', 'e', 'c', 'l', 'a', +'r', 'a', 't', 'i', 'o', 'n', '\030', '\002', ' ', '\003', '(', '\013', '2', '2', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', +'t', 'o', 'b', 'u', 'f', '.', 'E', 'x', 't', 'e', 'n', 's', 'i', 'o', 'n', 'R', 'a', 'n', 'g', 'e', 'O', 'p', 't', 'i', 'o', +'n', 's', '.', 'D', 'e', 'c', 'l', 'a', 'r', 'a', 't', 'i', 'o', 'n', 'B', '\003', '\210', '\001', '\002', 'R', '\013', 'd', 'e', 'c', 'l', +'a', 'r', 'a', 't', 'i', 'o', 'n', '\022', '7', '\n', '\010', 'f', 'e', 'a', 't', 'u', 'r', 'e', 's', '\030', '2', ' ', '\001', '(', '\013', +'2', '\033', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'e', 'a', 't', 'u', 'r', +'e', 'S', 'e', 't', 'R', '\010', 'f', 'e', 'a', 't', 'u', 'r', 'e', 's', '\022', 'm', '\n', '\014', 'v', 'e', 'r', 'i', 'f', 'i', 'c', +'a', 't', 'i', 'o', 'n', '\030', '\003', ' ', '\001', '(', '\016', '2', '8', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', +'o', 'b', 'u', 'f', '.', 'E', 'x', 't', 'e', 'n', 's', 'i', 'o', 'n', 'R', 'a', 'n', 'g', 'e', 'O', 'p', 't', 'i', 'o', 'n', +'s', '.', 'V', 'e', 'r', 'i', 'f', 'i', 'c', 'a', 't', 'i', 'o', 'n', 'S', 't', 'a', 't', 'e', ':', '\n', 'U', 'N', 'V', 'E', +'R', 'I', 'F', 'I', 'E', 'D', 'B', '\003', '\210', '\001', '\002', 'R', '\014', 'v', 'e', 'r', 'i', 'f', 'i', 'c', 'a', 't', 'i', 'o', 'n', +'\032', '\224', '\001', '\n', '\013', 'D', 'e', 'c', 'l', 'a', 'r', 'a', 't', 'i', 'o', 'n', '\022', '\026', '\n', '\006', 'n', 'u', 'm', 'b', 'e', +'r', '\030', '\001', ' ', '\001', '(', '\005', 'R', '\006', 'n', 'u', 'm', 'b', 'e', 'r', '\022', '\033', '\n', '\t', 'f', 'u', 'l', 'l', '_', 'n', +'a', 'm', 'e', '\030', '\002', ' ', '\001', '(', '\t', 'R', '\010', 'f', 'u', 'l', 'l', 'N', 'a', 'm', 'e', '\022', '\022', '\n', '\004', 't', 'y', +'p', 'e', '\030', '\003', ' ', '\001', '(', '\t', 'R', '\004', 't', 'y', 'p', 'e', '\022', '\032', '\n', '\010', 'r', 'e', 's', 'e', 'r', 'v', 'e', +'d', '\030', '\005', ' ', '\001', '(', '\010', 'R', '\010', 'r', 'e', 's', 'e', 'r', 'v', 'e', 'd', '\022', '\032', '\n', '\010', 'r', 'e', 'p', 'e', +'a', 't', 'e', 'd', '\030', '\006', ' ', '\001', '(', '\010', 'R', '\010', 'r', 'e', 'p', 'e', 'a', 't', 'e', 'd', 'J', '\004', '\010', '\004', '\020', +'\005', '\"', '4', '\n', '\021', 'V', 'e', 'r', 'i', 'f', 'i', 'c', 'a', 't', 'i', 'o', 'n', 'S', 't', 'a', 't', 'e', '\022', '\017', '\n', +'\013', 'D', 'E', 'C', 'L', 'A', 'R', 'A', 'T', 'I', 'O', 'N', '\020', '\000', '\022', '\016', '\n', '\n', 'U', 'N', 'V', 'E', 'R', 'I', 'F', +'I', 'E', 'D', '\020', '\001', '*', '\t', '\010', '\350', '\007', '\020', '\200', '\200', '\200', '\200', '\002', '\"', '\301', '\006', '\n', '\024', 'F', 'i', 'e', 'l', +'d', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', 'o', '\022', '\022', '\n', '\004', 'n', 'a', 'm', 'e', '\030', +'\001', ' ', '\001', '(', '\t', 'R', '\004', 'n', 'a', 'm', 'e', '\022', '\026', '\n', '\006', 'n', 'u', 'm', 'b', 'e', 'r', '\030', '\003', ' ', '\001', +'(', '\005', 'R', '\006', 'n', 'u', 'm', 'b', 'e', 'r', '\022', 'A', '\n', '\005', 'l', 'a', 'b', 'e', 'l', '\030', '\004', ' ', '\001', '(', '\016', +'2', '+', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'i', 'e', 'l', 'd', 'D', +'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', 'o', '.', 'L', 'a', 'b', 'e', 'l', 'R', '\005', 'l', 'a', 'b', +'e', 'l', '\022', '>', '\n', '\004', 't', 'y', 'p', 'e', '\030', '\005', ' ', '\001', '(', '\016', '2', '*', '.', 'g', 'o', 'o', 'g', 'l', 'e', +'.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'i', 'e', 'l', 'd', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', +'P', 'r', 'o', 't', 'o', '.', 'T', 'y', 'p', 'e', 'R', '\004', 't', 'y', 'p', 'e', '\022', '\033', '\n', '\t', 't', 'y', 'p', 'e', '_', +'n', 'a', 'm', 'e', '\030', '\006', ' ', '\001', '(', '\t', 'R', '\010', 't', 'y', 'p', 'e', 'N', 'a', 'm', 'e', '\022', '\032', '\n', '\010', 'e', +'x', 't', 'e', 'n', 'd', 'e', 'e', '\030', '\002', ' ', '\001', '(', '\t', 'R', '\010', 'e', 'x', 't', 'e', 'n', 'd', 'e', 'e', '\022', '#', +'\n', '\r', 'd', 'e', 'f', 'a', 'u', 'l', 't', '_', 'v', 'a', 'l', 'u', 'e', '\030', '\007', ' ', '\001', '(', '\t', 'R', '\014', 'd', 'e', +'f', 'a', 'u', 'l', 't', 'V', 'a', 'l', 'u', 'e', '\022', '\037', '\n', '\013', 'o', 'n', 'e', 'o', 'f', '_', 'i', 'n', 'd', 'e', 'x', +'\030', '\t', ' ', '\001', '(', '\005', 'R', '\n', 'o', 'n', 'e', 'o', 'f', 'I', 'n', 'd', 'e', 'x', '\022', '\033', '\n', '\t', 'j', 's', 'o', +'n', '_', 'n', 'a', 'm', 'e', '\030', '\n', ' ', '\001', '(', '\t', 'R', '\010', 'j', 's', 'o', 'n', 'N', 'a', 'm', 'e', '\022', '7', '\n', +'\007', 'o', 'p', 't', 'i', 'o', 'n', 's', '\030', '\010', ' ', '\001', '(', '\013', '2', '\035', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', +'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'i', 'e', 'l', 'd', 'O', 'p', 't', 'i', 'o', 'n', 's', 'R', '\007', 'o', 'p', 't', +'i', 'o', 'n', 's', '\022', '\'', '\n', '\017', 'p', 'r', 'o', 't', 'o', '3', '_', 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l', '\030', '\021', +' ', '\001', '(', '\010', 'R', '\016', 'p', 'r', 'o', 't', 'o', '3', 'O', 'p', 't', 'i', 'o', 'n', 'a', 'l', '\"', '\266', '\002', '\n', '\004', +'T', 'y', 'p', 'e', '\022', '\017', '\n', '\013', 'T', 'Y', 'P', 'E', '_', 'D', 'O', 'U', 'B', 'L', 'E', '\020', '\001', '\022', '\016', '\n', '\n', +'T', 'Y', 'P', 'E', '_', 'F', 'L', 'O', 'A', 'T', '\020', '\002', '\022', '\016', '\n', '\n', 'T', 'Y', 'P', 'E', '_', 'I', 'N', 'T', '6', +'4', '\020', '\003', '\022', '\017', '\n', '\013', 'T', 'Y', 'P', 'E', '_', 'U', 'I', 'N', 'T', '6', '4', '\020', '\004', '\022', '\016', '\n', '\n', 'T', +'Y', 'P', 'E', '_', 'I', 'N', 'T', '3', '2', '\020', '\005', '\022', '\020', '\n', '\014', 'T', 'Y', 'P', 'E', '_', 'F', 'I', 'X', 'E', 'D', +'6', '4', '\020', '\006', '\022', '\020', '\n', '\014', 'T', 'Y', 'P', 'E', '_', 'F', 'I', 'X', 'E', 'D', '3', '2', '\020', '\007', '\022', '\r', '\n', +'\t', 'T', 'Y', 'P', 'E', '_', 'B', 'O', 'O', 'L', '\020', '\010', '\022', '\017', '\n', '\013', 'T', 'Y', 'P', 'E', '_', 'S', 'T', 'R', 'I', +'N', 'G', '\020', '\t', '\022', '\016', '\n', '\n', 'T', 'Y', 'P', 'E', '_', 'G', 'R', 'O', 'U', 'P', '\020', '\n', '\022', '\020', '\n', '\014', 'T', +'Y', 'P', 'E', '_', 'M', 'E', 'S', 'S', 'A', 'G', 'E', '\020', '\013', '\022', '\016', '\n', '\n', 'T', 'Y', 'P', 'E', '_', 'B', 'Y', 'T', +'E', 'S', '\020', '\014', '\022', '\017', '\n', '\013', 'T', 'Y', 'P', 'E', '_', 'U', 'I', 'N', 'T', '3', '2', '\020', '\r', '\022', '\r', '\n', '\t', +'T', 'Y', 'P', 'E', '_', 'E', 'N', 'U', 'M', '\020', '\016', '\022', '\021', '\n', '\r', 'T', 'Y', 'P', 'E', '_', 'S', 'F', 'I', 'X', 'E', +'D', '3', '2', '\020', '\017', '\022', '\021', '\n', '\r', 'T', 'Y', 'P', 'E', '_', 'S', 'F', 'I', 'X', 'E', 'D', '6', '4', '\020', '\020', '\022', +'\017', '\n', '\013', 'T', 'Y', 'P', 'E', '_', 'S', 'I', 'N', 'T', '3', '2', '\020', '\021', '\022', '\017', '\n', '\013', 'T', 'Y', 'P', 'E', '_', +'S', 'I', 'N', 'T', '6', '4', '\020', '\022', '\"', 'C', '\n', '\005', 'L', 'a', 'b', 'e', 'l', '\022', '\022', '\n', '\016', 'L', 'A', 'B', 'E', +'L', '_', 'O', 'P', 'T', 'I', 'O', 'N', 'A', 'L', '\020', '\001', '\022', '\022', '\n', '\016', 'L', 'A', 'B', 'E', 'L', '_', 'R', 'E', 'P', +'E', 'A', 'T', 'E', 'D', '\020', '\003', '\022', '\022', '\n', '\016', 'L', 'A', 'B', 'E', 'L', '_', 'R', 'E', 'Q', 'U', 'I', 'R', 'E', 'D', +'\020', '\002', '\"', 'c', '\n', '\024', 'O', 'n', 'e', 'o', 'f', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', +'o', '\022', '\022', '\n', '\004', 'n', 'a', 'm', 'e', '\030', '\001', ' ', '\001', '(', '\t', 'R', '\004', 'n', 'a', 'm', 'e', '\022', '7', '\n', '\007', +'o', 'p', 't', 'i', 'o', 'n', 's', '\030', '\002', ' ', '\001', '(', '\013', '2', '\035', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', +'o', 't', 'o', 'b', 'u', 'f', '.', 'O', 'n', 'e', 'o', 'f', 'O', 'p', 't', 'i', 'o', 'n', 's', 'R', '\007', 'o', 'p', 't', 'i', +'o', 'n', 's', '\"', '\343', '\002', '\n', '\023', 'E', 'n', 'u', 'm', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', +'t', 'o', '\022', '\022', '\n', '\004', 'n', 'a', 'm', 'e', '\030', '\001', ' ', '\001', '(', '\t', 'R', '\004', 'n', 'a', 'm', 'e', '\022', '?', '\n', +'\005', 'v', 'a', 'l', 'u', 'e', '\030', '\002', ' ', '\003', '(', '\013', '2', ')', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', +'t', 'o', 'b', 'u', 'f', '.', 'E', 'n', 'u', 'm', 'V', 'a', 'l', 'u', 'e', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', +'P', 'r', 'o', 't', 'o', 'R', '\005', 'v', 'a', 'l', 'u', 'e', '\022', '6', '\n', '\007', 'o', 'p', 't', 'i', 'o', 'n', 's', '\030', '\003', +' ', '\001', '(', '\013', '2', '\034', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'E', 'n', +'u', 'm', 'O', 'p', 't', 'i', 'o', 'n', 's', 'R', '\007', 'o', 'p', 't', 'i', 'o', 'n', 's', '\022', ']', '\n', '\016', 'r', 'e', 's', +'e', 'r', 'v', 'e', 'd', '_', 'r', 'a', 'n', 'g', 'e', '\030', '\004', ' ', '\003', '(', '\013', '2', '6', '.', 'g', 'o', 'o', 'g', 'l', +'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'E', 'n', 'u', 'm', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', +'P', 'r', 'o', 't', 'o', '.', 'E', 'n', 'u', 'm', 'R', 'e', 's', 'e', 'r', 'v', 'e', 'd', 'R', 'a', 'n', 'g', 'e', 'R', '\r', +'r', 'e', 's', 'e', 'r', 'v', 'e', 'd', 'R', 'a', 'n', 'g', 'e', '\022', '#', '\n', '\r', 'r', 'e', 's', 'e', 'r', 'v', 'e', 'd', +'_', 'n', 'a', 'm', 'e', '\030', '\005', ' ', '\003', '(', '\t', 'R', '\014', 'r', 'e', 's', 'e', 'r', 'v', 'e', 'd', 'N', 'a', 'm', 'e', +'\032', ';', '\n', '\021', 'E', 'n', 'u', 'm', 'R', 'e', 's', 'e', 'r', 'v', 'e', 'd', 'R', 'a', 'n', 'g', 'e', '\022', '\024', '\n', '\005', +'s', 't', 'a', 'r', 't', '\030', '\001', ' ', '\001', '(', '\005', 'R', '\005', 's', 't', 'a', 'r', 't', '\022', '\020', '\n', '\003', 'e', 'n', 'd', +'\030', '\002', ' ', '\001', '(', '\005', 'R', '\003', 'e', 'n', 'd', '\"', '\203', '\001', '\n', '\030', 'E', 'n', 'u', 'm', 'V', 'a', 'l', 'u', 'e', +'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', 'o', '\022', '\022', '\n', '\004', 'n', 'a', 'm', 'e', '\030', '\001', +' ', '\001', '(', '\t', 'R', '\004', 'n', 'a', 'm', 'e', '\022', '\026', '\n', '\006', 'n', 'u', 'm', 'b', 'e', 'r', '\030', '\002', ' ', '\001', '(', +'\005', 'R', '\006', 'n', 'u', 'm', 'b', 'e', 'r', '\022', ';', '\n', '\007', 'o', 'p', 't', 'i', 'o', 'n', 's', '\030', '\003', ' ', '\001', '(', +'\013', '2', '!', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'E', 'n', 'u', 'm', 'V', +'a', 'l', 'u', 'e', 'O', 'p', 't', 'i', 'o', 'n', 's', 'R', '\007', 'o', 'p', 't', 'i', 'o', 'n', 's', '\"', '\247', '\001', '\n', '\026', +'S', 'e', 'r', 'v', 'i', 'c', 'e', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', 'o', '\022', '\022', '\n', +'\004', 'n', 'a', 'm', 'e', '\030', '\001', ' ', '\001', '(', '\t', 'R', '\004', 'n', 'a', 'm', 'e', '\022', '>', '\n', '\006', 'm', 'e', 't', 'h', +'o', 'd', '\030', '\002', ' ', '\003', '(', '\013', '2', '&', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', +'f', '.', 'M', 'e', 't', 'h', 'o', 'd', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', 'o', 'R', '\006', +'m', 'e', 't', 'h', 'o', 'd', '\022', '9', '\n', '\007', 'o', 'p', 't', 'i', 'o', 'n', 's', '\030', '\003', ' ', '\001', '(', '\013', '2', '\037', +'.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 'O', +'p', 't', 'i', 'o', 'n', 's', 'R', '\007', 'o', 'p', 't', 'i', 'o', 'n', 's', '\"', '\211', '\002', '\n', '\025', 'M', 'e', 't', 'h', 'o', +'d', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'P', 'r', 'o', 't', 'o', '\022', '\022', '\n', '\004', 'n', 'a', 'm', 'e', '\030', +'\001', ' ', '\001', '(', '\t', 'R', '\004', 'n', 'a', 'm', 'e', '\022', '\035', '\n', '\n', 'i', 'n', 'p', 'u', 't', '_', 't', 'y', 'p', 'e', +'\030', '\002', ' ', '\001', '(', '\t', 'R', '\t', 'i', 'n', 'p', 'u', 't', 'T', 'y', 'p', 'e', '\022', '\037', '\n', '\013', 'o', 'u', 't', 'p', +'u', 't', '_', 't', 'y', 'p', 'e', '\030', '\003', ' ', '\001', '(', '\t', 'R', '\n', 'o', 'u', 't', 'p', 'u', 't', 'T', 'y', 'p', 'e', +'\022', '8', '\n', '\007', 'o', 'p', 't', 'i', 'o', 'n', 's', '\030', '\004', ' ', '\001', '(', '\013', '2', '\036', '.', 'g', 'o', 'o', 'g', 'l', +'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'M', 'e', 't', 'h', 'o', 'd', 'O', 'p', 't', 'i', 'o', 'n', 's', 'R', +'\007', 'o', 'p', 't', 'i', 'o', 'n', 's', '\022', '0', '\n', '\020', 'c', 'l', 'i', 'e', 'n', 't', '_', 's', 't', 'r', 'e', 'a', 'm', +'i', 'n', 'g', '\030', '\005', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', 'l', 's', 'e', 'R', '\017', 'c', 'l', 'i', 'e', 'n', 't', 'S', +'t', 'r', 'e', 'a', 'm', 'i', 'n', 'g', '\022', '0', '\n', '\020', 's', 'e', 'r', 'v', 'e', 'r', '_', 's', 't', 'r', 'e', 'a', 'm', +'i', 'n', 'g', '\030', '\006', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', 'l', 's', 'e', 'R', '\017', 's', 'e', 'r', 'v', 'e', 'r', 'S', +'t', 'r', 'e', 'a', 'm', 'i', 'n', 'g', '\"', '\255', '\t', '\n', '\013', 'F', 'i', 'l', 'e', 'O', 'p', 't', 'i', 'o', 'n', 's', '\022', +'!', '\n', '\014', 'j', 'a', 'v', 'a', '_', 'p', 'a', 'c', 'k', 'a', 'g', 'e', '\030', '\001', ' ', '\001', '(', '\t', 'R', '\013', 'j', 'a', +'v', 'a', 'P', 'a', 'c', 'k', 'a', 'g', 'e', '\022', '0', '\n', '\024', 'j', 'a', 'v', 'a', '_', 'o', 'u', 't', 'e', 'r', '_', 'c', +'l', 'a', 's', 's', 'n', 'a', 'm', 'e', '\030', '\010', ' ', '\001', '(', '\t', 'R', '\022', 'j', 'a', 'v', 'a', 'O', 'u', 't', 'e', 'r', +'C', 'l', 'a', 's', 's', 'n', 'a', 'm', 'e', '\022', '5', '\n', '\023', 'j', 'a', 'v', 'a', '_', 'm', 'u', 'l', 't', 'i', 'p', 'l', +'e', '_', 'f', 'i', 'l', 'e', 's', '\030', '\n', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', 'l', 's', 'e', 'R', '\021', 'j', 'a', 'v', +'a', 'M', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'F', 'i', 'l', 'e', 's', '\022', 'D', '\n', '\035', 'j', 'a', 'v', 'a', '_', 'g', 'e', +'n', 'e', 'r', 'a', 't', 'e', '_', 'e', 'q', 'u', 'a', 'l', 's', '_', 'a', 'n', 'd', '_', 'h', 'a', 's', 'h', '\030', '\024', ' ', +'\001', '(', '\010', 'B', '\002', '\030', '\001', 'R', '\031', 'j', 'a', 'v', 'a', 'G', 'e', 'n', 'e', 'r', 'a', 't', 'e', 'E', 'q', 'u', 'a', +'l', 's', 'A', 'n', 'd', 'H', 'a', 's', 'h', '\022', ':', '\n', '\026', 'j', 'a', 'v', 'a', '_', 's', 't', 'r', 'i', 'n', 'g', '_', +'c', 'h', 'e', 'c', 'k', '_', 'u', 't', 'f', '8', '\030', '\033', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', 'l', 's', 'e', 'R', '\023', +'j', 'a', 'v', 'a', 'S', 't', 'r', 'i', 'n', 'g', 'C', 'h', 'e', 'c', 'k', 'U', 't', 'f', '8', '\022', 'S', '\n', '\014', 'o', 'p', +'t', 'i', 'm', 'i', 'z', 'e', '_', 'f', 'o', 'r', '\030', '\t', ' ', '\001', '(', '\016', '2', ')', '.', 'g', 'o', 'o', 'g', 'l', 'e', +'.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'i', 'l', 'e', 'O', 'p', 't', 'i', 'o', 'n', 's', '.', 'O', 'p', 't', +'i', 'm', 'i', 'z', 'e', 'M', 'o', 'd', 'e', ':', '\005', 'S', 'P', 'E', 'E', 'D', 'R', '\013', 'o', 'p', 't', 'i', 'm', 'i', 'z', +'e', 'F', 'o', 'r', '\022', '\035', '\n', '\n', 'g', 'o', '_', 'p', 'a', 'c', 'k', 'a', 'g', 'e', '\030', '\013', ' ', '\001', '(', '\t', 'R', +'\t', 'g', 'o', 'P', 'a', 'c', 'k', 'a', 'g', 'e', '\022', '5', '\n', '\023', 'c', 'c', '_', 'g', 'e', 'n', 'e', 'r', 'i', 'c', '_', +'s', 'e', 'r', 'v', 'i', 'c', 'e', 's', '\030', '\020', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', 'l', 's', 'e', 'R', '\021', 'c', 'c', +'G', 'e', 'n', 'e', 'r', 'i', 'c', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 's', '\022', '9', '\n', '\025', 'j', 'a', 'v', 'a', '_', 'g', +'e', 'n', 'e', 'r', 'i', 'c', '_', 's', 'e', 'r', 'v', 'i', 'c', 'e', 's', '\030', '\021', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', +'l', 's', 'e', 'R', '\023', 'j', 'a', 'v', 'a', 'G', 'e', 'n', 'e', 'r', 'i', 'c', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 's', '\022', +'5', '\n', '\023', 'p', 'y', '_', 'g', 'e', 'n', 'e', 'r', 'i', 'c', '_', 's', 'e', 'r', 'v', 'i', 'c', 'e', 's', '\030', '\022', ' ', +'\001', '(', '\010', ':', '\005', 'f', 'a', 'l', 's', 'e', 'R', '\021', 'p', 'y', 'G', 'e', 'n', 'e', 'r', 'i', 'c', 'S', 'e', 'r', 'v', +'i', 'c', 'e', 's', '\022', '%', '\n', '\n', 'd', 'e', 'p', 'r', 'e', 'c', 'a', 't', 'e', 'd', '\030', '\027', ' ', '\001', '(', '\010', ':', +'\005', 'f', 'a', 'l', 's', 'e', 'R', '\n', 'd', 'e', 'p', 'r', 'e', 'c', 'a', 't', 'e', 'd', '\022', '.', '\n', '\020', 'c', 'c', '_', +'e', 'n', 'a', 'b', 'l', 'e', '_', 'a', 'r', 'e', 'n', 'a', 's', '\030', '\037', ' ', '\001', '(', '\010', ':', '\004', 't', 'r', 'u', 'e', +'R', '\016', 'c', 'c', 'E', 'n', 'a', 'b', 'l', 'e', 'A', 'r', 'e', 'n', 'a', 's', '\022', '*', '\n', '\021', 'o', 'b', 'j', 'c', '_', +'c', 'l', 'a', 's', 's', '_', 'p', 'r', 'e', 'f', 'i', 'x', '\030', '$', ' ', '\001', '(', '\t', 'R', '\017', 'o', 'b', 'j', 'c', 'C', +'l', 'a', 's', 's', 'P', 'r', 'e', 'f', 'i', 'x', '\022', ')', '\n', '\020', 'c', 's', 'h', 'a', 'r', 'p', '_', 'n', 'a', 'm', 'e', +'s', 'p', 'a', 'c', 'e', '\030', '%', ' ', '\001', '(', '\t', 'R', '\017', 'c', 's', 'h', 'a', 'r', 'p', 'N', 'a', 'm', 'e', 's', 'p', +'a', 'c', 'e', '\022', '!', '\n', '\014', 's', 'w', 'i', 'f', 't', '_', 'p', 'r', 'e', 'f', 'i', 'x', '\030', '\'', ' ', '\001', '(', '\t', +'R', '\013', 's', 'w', 'i', 'f', 't', 'P', 'r', 'e', 'f', 'i', 'x', '\022', '(', '\n', '\020', 'p', 'h', 'p', '_', 'c', 'l', 'a', 's', +'s', '_', 'p', 'r', 'e', 'f', 'i', 'x', '\030', '(', ' ', '\001', '(', '\t', 'R', '\016', 'p', 'h', 'p', 'C', 'l', 'a', 's', 's', 'P', +'r', 'e', 'f', 'i', 'x', '\022', '#', '\n', '\r', 'p', 'h', 'p', '_', 'n', 'a', 'm', 'e', 's', 'p', 'a', 'c', 'e', '\030', ')', ' ', +'\001', '(', '\t', 'R', '\014', 'p', 'h', 'p', 'N', 'a', 'm', 'e', 's', 'p', 'a', 'c', 'e', '\022', '4', '\n', '\026', 'p', 'h', 'p', '_', +'m', 'e', 't', 'a', 'd', 'a', 't', 'a', '_', 'n', 'a', 'm', 'e', 's', 'p', 'a', 'c', 'e', '\030', ',', ' ', '\001', '(', '\t', 'R', +'\024', 'p', 'h', 'p', 'M', 'e', 't', 'a', 'd', 'a', 't', 'a', 'N', 'a', 'm', 'e', 's', 'p', 'a', 'c', 'e', '\022', '!', '\n', '\014', +'r', 'u', 'b', 'y', '_', 'p', 'a', 'c', 'k', 'a', 'g', 'e', '\030', '-', ' ', '\001', '(', '\t', 'R', '\013', 'r', 'u', 'b', 'y', 'P', +'a', 'c', 'k', 'a', 'g', 'e', '\022', '7', '\n', '\010', 'f', 'e', 'a', 't', 'u', 'r', 'e', 's', '\030', '2', ' ', '\001', '(', '\013', '2', +'\033', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'e', 'a', 't', 'u', 'r', 'e', +'S', 'e', 't', 'R', '\010', 'f', 'e', 'a', 't', 'u', 'r', 'e', 's', '\022', 'X', '\n', '\024', 'u', 'n', 'i', 'n', 't', 'e', 'r', 'p', +'r', 'e', 't', 'e', 'd', '_', 'o', 'p', 't', 'i', 'o', 'n', '\030', '\347', '\007', ' ', '\003', '(', '\013', '2', '$', '.', 'g', 'o', 'o', +'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'U', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', +'d', 'O', 'p', 't', 'i', 'o', 'n', 'R', '\023', 'u', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', 'O', 'p', 't', +'i', 'o', 'n', '\"', ':', '\n', '\014', 'O', 'p', 't', 'i', 'm', 'i', 'z', 'e', 'M', 'o', 'd', 'e', '\022', '\t', '\n', '\005', 'S', 'P', +'E', 'E', 'D', '\020', '\001', '\022', '\r', '\n', '\t', 'C', 'O', 'D', 'E', '_', 'S', 'I', 'Z', 'E', '\020', '\002', '\022', '\020', '\n', '\014', 'L', +'I', 'T', 'E', '_', 'R', 'U', 'N', 'T', 'I', 'M', 'E', '\020', '\003', '*', '\t', '\010', '\350', '\007', '\020', '\200', '\200', '\200', '\200', '\002', 'J', +'\004', '\010', '*', '\020', '+', 'J', '\004', '\010', '&', '\020', '\'', 'R', '\024', 'p', 'h', 'p', '_', 'g', 'e', 'n', 'e', 'r', 'i', 'c', '_', +'s', 'e', 'r', 'v', 'i', 'c', 'e', 's', '\"', '\364', '\003', '\n', '\016', 'M', 'e', 's', 's', 'a', 'g', 'e', 'O', 'p', 't', 'i', 'o', +'n', 's', '\022', '<', '\n', '\027', 'm', 'e', 's', 's', 'a', 'g', 'e', '_', 's', 'e', 't', '_', 'w', 'i', 'r', 'e', '_', 'f', 'o', +'r', 'm', 'a', 't', '\030', '\001', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', 'l', 's', 'e', 'R', '\024', 'm', 'e', 's', 's', 'a', 'g', +'e', 'S', 'e', 't', 'W', 'i', 'r', 'e', 'F', 'o', 'r', 'm', 'a', 't', '\022', 'L', '\n', '\037', 'n', 'o', '_', 's', 't', 'a', 'n', +'d', 'a', 'r', 'd', '_', 'd', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', '_', 'a', 'c', 'c', 'e', 's', 's', 'o', 'r', '\030', +'\002', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', 'l', 's', 'e', 'R', '\034', 'n', 'o', 'S', 't', 'a', 'n', 'd', 'a', 'r', 'd', 'D', +'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', 'A', 'c', 'c', 'e', 's', 's', 'o', 'r', '\022', '%', '\n', '\n', 'd', 'e', 'p', 'r', +'e', 'c', 'a', 't', 'e', 'd', '\030', '\003', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', 'l', 's', 'e', 'R', '\n', 'd', 'e', 'p', 'r', +'e', 'c', 'a', 't', 'e', 'd', '\022', '\033', '\n', '\t', 'm', 'a', 'p', '_', 'e', 'n', 't', 'r', 'y', '\030', '\007', ' ', '\001', '(', '\010', +'R', '\010', 'm', 'a', 'p', 'E', 'n', 't', 'r', 'y', '\022', 'V', '\n', '&', 'd', 'e', 'p', 'r', 'e', 'c', 'a', 't', 'e', 'd', '_', +'l', 'e', 'g', 'a', 'c', 'y', '_', 'j', 's', 'o', 'n', '_', 'f', 'i', 'e', 'l', 'd', '_', 'c', 'o', 'n', 'f', 'l', 'i', 'c', +'t', 's', '\030', '\013', ' ', '\001', '(', '\010', 'B', '\002', '\030', '\001', 'R', '\"', 'd', 'e', 'p', 'r', 'e', 'c', 'a', 't', 'e', 'd', 'L', +'e', 'g', 'a', 'c', 'y', 'J', 's', 'o', 'n', 'F', 'i', 'e', 'l', 'd', 'C', 'o', 'n', 'f', 'l', 'i', 'c', 't', 's', '\022', '7', +'\n', '\010', 'f', 'e', 'a', 't', 'u', 'r', 'e', 's', '\030', '\014', ' ', '\001', '(', '\013', '2', '\033', '.', 'g', 'o', 'o', 'g', 'l', 'e', +'.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'e', 't', 'R', '\010', 'f', 'e', 'a', +'t', 'u', 'r', 'e', 's', '\022', 'X', '\n', '\024', 'u', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', '_', 'o', 'p', +'t', 'i', 'o', 'n', '\030', '\347', '\007', ' ', '\003', '(', '\013', '2', '$', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', +'o', 'b', 'u', 'f', '.', 'U', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', 'O', 'p', 't', 'i', 'o', 'n', 'R', +'\023', 'u', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', 'O', 'p', 't', 'i', 'o', 'n', '*', '\t', '\010', '\350', '\007', +'\020', '\200', '\200', '\200', '\200', '\002', 'J', '\004', '\010', '\004', '\020', '\005', 'J', '\004', '\010', '\005', '\020', '\006', 'J', '\004', '\010', '\006', '\020', '\007', 'J', +'\004', '\010', '\010', '\020', '\t', 'J', '\004', '\010', '\t', '\020', '\n', '\"', '\235', '\r', '\n', '\014', 'F', 'i', 'e', 'l', 'd', 'O', 'p', 't', 'i', +'o', 'n', 's', '\022', 'A', '\n', '\005', 'c', 't', 'y', 'p', 'e', '\030', '\001', ' ', '\001', '(', '\016', '2', '#', '.', 'g', 'o', 'o', 'g', +'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'i', 'e', 'l', 'd', 'O', 'p', 't', 'i', 'o', 'n', 's', '.', +'C', 'T', 'y', 'p', 'e', ':', '\006', 'S', 'T', 'R', 'I', 'N', 'G', 'R', '\005', 'c', 't', 'y', 'p', 'e', '\022', '\026', '\n', '\006', 'p', +'a', 'c', 'k', 'e', 'd', '\030', '\002', ' ', '\001', '(', '\010', 'R', '\006', 'p', 'a', 'c', 'k', 'e', 'd', '\022', 'G', '\n', '\006', 'j', 's', +'t', 'y', 'p', 'e', '\030', '\006', ' ', '\001', '(', '\016', '2', '$', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', +'b', 'u', 'f', '.', 'F', 'i', 'e', 'l', 'd', 'O', 'p', 't', 'i', 'o', 'n', 's', '.', 'J', 'S', 'T', 'y', 'p', 'e', ':', '\t', +'J', 'S', '_', 'N', 'O', 'R', 'M', 'A', 'L', 'R', '\006', 'j', 's', 't', 'y', 'p', 'e', '\022', '\031', '\n', '\004', 'l', 'a', 'z', 'y', +'\030', '\005', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', 'l', 's', 'e', 'R', '\004', 'l', 'a', 'z', 'y', '\022', '.', '\n', '\017', 'u', 'n', +'v', 'e', 'r', 'i', 'f', 'i', 'e', 'd', '_', 'l', 'a', 'z', 'y', '\030', '\017', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', 'l', 's', +'e', 'R', '\016', 'u', 'n', 'v', 'e', 'r', 'i', 'f', 'i', 'e', 'd', 'L', 'a', 'z', 'y', '\022', '%', '\n', '\n', 'd', 'e', 'p', 'r', +'e', 'c', 'a', 't', 'e', 'd', '\030', '\003', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', 'l', 's', 'e', 'R', '\n', 'd', 'e', 'p', 'r', +'e', 'c', 'a', 't', 'e', 'd', '\022', '\031', '\n', '\004', 'w', 'e', 'a', 'k', '\030', '\n', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', 'l', +'s', 'e', 'R', '\004', 'w', 'e', 'a', 'k', '\022', '(', '\n', '\014', 'd', 'e', 'b', 'u', 'g', '_', 'r', 'e', 'd', 'a', 'c', 't', '\030', +'\020', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', 'l', 's', 'e', 'R', '\013', 'd', 'e', 'b', 'u', 'g', 'R', 'e', 'd', 'a', 'c', 't', +'\022', 'K', '\n', '\t', 'r', 'e', 't', 'e', 'n', 't', 'i', 'o', 'n', '\030', '\021', ' ', '\001', '(', '\016', '2', '-', '.', 'g', 'o', 'o', +'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'i', 'e', 'l', 'd', 'O', 'p', 't', 'i', 'o', 'n', 's', +'.', 'O', 'p', 't', 'i', 'o', 'n', 'R', 'e', 't', 'e', 'n', 't', 'i', 'o', 'n', 'R', '\t', 'r', 'e', 't', 'e', 'n', 't', 'i', +'o', 'n', '\022', 'H', '\n', '\007', 't', 'a', 'r', 'g', 'e', 't', 's', '\030', '\023', ' ', '\003', '(', '\016', '2', '.', '.', 'g', 'o', 'o', +'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'i', 'e', 'l', 'd', 'O', 'p', 't', 'i', 'o', 'n', 's', +'.', 'O', 'p', 't', 'i', 'o', 'n', 'T', 'a', 'r', 'g', 'e', 't', 'T', 'y', 'p', 'e', 'R', '\007', 't', 'a', 'r', 'g', 'e', 't', +'s', '\022', 'W', '\n', '\020', 'e', 'd', 'i', 't', 'i', 'o', 'n', '_', 'd', 'e', 'f', 'a', 'u', 'l', 't', 's', '\030', '\024', ' ', '\003', +'(', '\013', '2', ',', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'i', 'e', 'l', +'d', 'O', 'p', 't', 'i', 'o', 'n', 's', '.', 'E', 'd', 'i', 't', 'i', 'o', 'n', 'D', 'e', 'f', 'a', 'u', 'l', 't', 'R', '\017', +'e', 'd', 'i', 't', 'i', 'o', 'n', 'D', 'e', 'f', 'a', 'u', 'l', 't', 's', '\022', '7', '\n', '\010', 'f', 'e', 'a', 't', 'u', 'r', +'e', 's', '\030', '\025', ' ', '\001', '(', '\013', '2', '\033', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', +'f', '.', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'e', 't', 'R', '\010', 'f', 'e', 'a', 't', 'u', 'r', 'e', 's', '\022', 'U', '\n', +'\017', 'f', 'e', 'a', 't', 'u', 'r', 'e', '_', 's', 'u', 'p', 'p', 'o', 'r', 't', '\030', '\026', ' ', '\001', '(', '\013', '2', ',', '.', +'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'i', 'e', 'l', 'd', 'O', 'p', 't', 'i', +'o', 'n', 's', '.', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'u', 'p', 'p', 'o', 'r', 't', 'R', '\016', 'f', 'e', 'a', 't', 'u', +'r', 'e', 'S', 'u', 'p', 'p', 'o', 'r', 't', '\022', 'X', '\n', '\024', 'u', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', +'d', '_', 'o', 'p', 't', 'i', 'o', 'n', '\030', '\347', '\007', ' ', '\003', '(', '\013', '2', '$', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', +'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'U', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', 'O', 'p', 't', +'i', 'o', 'n', 'R', '\023', 'u', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', 'O', 'p', 't', 'i', 'o', 'n', '\032', +'Z', '\n', '\016', 'E', 'd', 'i', 't', 'i', 'o', 'n', 'D', 'e', 'f', 'a', 'u', 'l', 't', '\022', '2', '\n', '\007', 'e', 'd', 'i', 't', +'i', 'o', 'n', '\030', '\003', ' ', '\001', '(', '\016', '2', '\030', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', +'u', 'f', '.', 'E', 'd', 'i', 't', 'i', 'o', 'n', 'R', '\007', 'e', 'd', 'i', 't', 'i', 'o', 'n', '\022', '\024', '\n', '\005', 'v', 'a', +'l', 'u', 'e', '\030', '\002', ' ', '\001', '(', '\t', 'R', '\005', 'v', 'a', 'l', 'u', 'e', '\032', '\226', '\002', '\n', '\016', 'F', 'e', 'a', 't', +'u', 'r', 'e', 'S', 'u', 'p', 'p', 'o', 'r', 't', '\022', 'G', '\n', '\022', 'e', 'd', 'i', 't', 'i', 'o', 'n', '_', 'i', 'n', 't', +'r', 'o', 'd', 'u', 'c', 'e', 'd', '\030', '\001', ' ', '\001', '(', '\016', '2', '\030', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', +'o', 't', 'o', 'b', 'u', 'f', '.', 'E', 'd', 'i', 't', 'i', 'o', 'n', 'R', '\021', 'e', 'd', 'i', 't', 'i', 'o', 'n', 'I', 'n', +'t', 'r', 'o', 'd', 'u', 'c', 'e', 'd', '\022', 'G', '\n', '\022', 'e', 'd', 'i', 't', 'i', 'o', 'n', '_', 'd', 'e', 'p', 'r', 'e', +'c', 'a', 't', 'e', 'd', '\030', '\002', ' ', '\001', '(', '\016', '2', '\030', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', +'o', 'b', 'u', 'f', '.', 'E', 'd', 'i', 't', 'i', 'o', 'n', 'R', '\021', 'e', 'd', 'i', 't', 'i', 'o', 'n', 'D', 'e', 'p', 'r', +'e', 'c', 'a', 't', 'e', 'd', '\022', '/', '\n', '\023', 'd', 'e', 'p', 'r', 'e', 'c', 'a', 't', 'i', 'o', 'n', '_', 'w', 'a', 'r', +'n', 'i', 'n', 'g', '\030', '\003', ' ', '\001', '(', '\t', 'R', '\022', 'd', 'e', 'p', 'r', 'e', 'c', 'a', 't', 'i', 'o', 'n', 'W', 'a', +'r', 'n', 'i', 'n', 'g', '\022', 'A', '\n', '\017', 'e', 'd', 'i', 't', 'i', 'o', 'n', '_', 'r', 'e', 'm', 'o', 'v', 'e', 'd', '\030', +'\004', ' ', '\001', '(', '\016', '2', '\030', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'E', +'d', 'i', 't', 'i', 'o', 'n', 'R', '\016', 'e', 'd', 'i', 't', 'i', 'o', 'n', 'R', 'e', 'm', 'o', 'v', 'e', 'd', '\"', '/', '\n', +'\005', 'C', 'T', 'y', 'p', 'e', '\022', '\n', '\n', '\006', 'S', 'T', 'R', 'I', 'N', 'G', '\020', '\000', '\022', '\010', '\n', '\004', 'C', 'O', 'R', +'D', '\020', '\001', '\022', '\020', '\n', '\014', 'S', 'T', 'R', 'I', 'N', 'G', '_', 'P', 'I', 'E', 'C', 'E', '\020', '\002', '\"', '5', '\n', '\006', +'J', 'S', 'T', 'y', 'p', 'e', '\022', '\r', '\n', '\t', 'J', 'S', '_', 'N', 'O', 'R', 'M', 'A', 'L', '\020', '\000', '\022', '\r', '\n', '\t', +'J', 'S', '_', 'S', 'T', 'R', 'I', 'N', 'G', '\020', '\001', '\022', '\r', '\n', '\t', 'J', 'S', '_', 'N', 'U', 'M', 'B', 'E', 'R', '\020', +'\002', '\"', 'U', '\n', '\017', 'O', 'p', 't', 'i', 'o', 'n', 'R', 'e', 't', 'e', 'n', 't', 'i', 'o', 'n', '\022', '\025', '\n', '\021', 'R', +'E', 'T', 'E', 'N', 'T', 'I', 'O', 'N', '_', 'U', 'N', 'K', 'N', 'O', 'W', 'N', '\020', '\000', '\022', '\025', '\n', '\021', 'R', 'E', 'T', +'E', 'N', 'T', 'I', 'O', 'N', '_', 'R', 'U', 'N', 'T', 'I', 'M', 'E', '\020', '\001', '\022', '\024', '\n', '\020', 'R', 'E', 'T', 'E', 'N', +'T', 'I', 'O', 'N', '_', 'S', 'O', 'U', 'R', 'C', 'E', '\020', '\002', '\"', '\214', '\002', '\n', '\020', 'O', 'p', 't', 'i', 'o', 'n', 'T', +'a', 'r', 'g', 'e', 't', 'T', 'y', 'p', 'e', '\022', '\027', '\n', '\023', 'T', 'A', 'R', 'G', 'E', 'T', '_', 'T', 'Y', 'P', 'E', '_', +'U', 'N', 'K', 'N', 'O', 'W', 'N', '\020', '\000', '\022', '\024', '\n', '\020', 'T', 'A', 'R', 'G', 'E', 'T', '_', 'T', 'Y', 'P', 'E', '_', +'F', 'I', 'L', 'E', '\020', '\001', '\022', '\037', '\n', '\033', 'T', 'A', 'R', 'G', 'E', 'T', '_', 'T', 'Y', 'P', 'E', '_', 'E', 'X', 'T', +'E', 'N', 'S', 'I', 'O', 'N', '_', 'R', 'A', 'N', 'G', 'E', '\020', '\002', '\022', '\027', '\n', '\023', 'T', 'A', 'R', 'G', 'E', 'T', '_', +'T', 'Y', 'P', 'E', '_', 'M', 'E', 'S', 'S', 'A', 'G', 'E', '\020', '\003', '\022', '\025', '\n', '\021', 'T', 'A', 'R', 'G', 'E', 'T', '_', +'T', 'Y', 'P', 'E', '_', 'F', 'I', 'E', 'L', 'D', '\020', '\004', '\022', '\025', '\n', '\021', 'T', 'A', 'R', 'G', 'E', 'T', '_', 'T', 'Y', +'P', 'E', '_', 'O', 'N', 'E', 'O', 'F', '\020', '\005', '\022', '\024', '\n', '\020', 'T', 'A', 'R', 'G', 'E', 'T', '_', 'T', 'Y', 'P', 'E', +'_', 'E', 'N', 'U', 'M', '\020', '\006', '\022', '\032', '\n', '\026', 'T', 'A', 'R', 'G', 'E', 'T', '_', 'T', 'Y', 'P', 'E', '_', 'E', 'N', +'U', 'M', '_', 'E', 'N', 'T', 'R', 'Y', '\020', '\007', '\022', '\027', '\n', '\023', 'T', 'A', 'R', 'G', 'E', 'T', '_', 'T', 'Y', 'P', 'E', +'_', 'S', 'E', 'R', 'V', 'I', 'C', 'E', '\020', '\010', '\022', '\026', '\n', '\022', 'T', 'A', 'R', 'G', 'E', 'T', '_', 'T', 'Y', 'P', 'E', +'_', 'M', 'E', 'T', 'H', 'O', 'D', '\020', '\t', '*', '\t', '\010', '\350', '\007', '\020', '\200', '\200', '\200', '\200', '\002', 'J', '\004', '\010', '\004', '\020', +'\005', 'J', '\004', '\010', '\022', '\020', '\023', '\"', '\254', '\001', '\n', '\014', 'O', 'n', 'e', 'o', 'f', 'O', 'p', 't', 'i', 'o', 'n', 's', '\022', +'7', '\n', '\010', 'f', 'e', 'a', 't', 'u', 'r', 'e', 's', '\030', '\001', ' ', '\001', '(', '\013', '2', '\033', '.', 'g', 'o', 'o', 'g', 'l', +'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'e', 't', 'R', '\010', 'f', 'e', +'a', 't', 'u', 'r', 'e', 's', '\022', 'X', '\n', '\024', 'u', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', '_', 'o', +'p', 't', 'i', 'o', 'n', '\030', '\347', '\007', ' ', '\003', '(', '\013', '2', '$', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', +'t', 'o', 'b', 'u', 'f', '.', 'U', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', 'O', 'p', 't', 'i', 'o', 'n', +'R', '\023', 'u', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', 'O', 'p', 't', 'i', 'o', 'n', '*', '\t', '\010', '\350', +'\007', '\020', '\200', '\200', '\200', '\200', '\002', '\"', '\321', '\002', '\n', '\013', 'E', 'n', 'u', 'm', 'O', 'p', 't', 'i', 'o', 'n', 's', '\022', '\037', +'\n', '\013', 'a', 'l', 'l', 'o', 'w', '_', 'a', 'l', 'i', 'a', 's', '\030', '\002', ' ', '\001', '(', '\010', 'R', '\n', 'a', 'l', 'l', 'o', +'w', 'A', 'l', 'i', 'a', 's', '\022', '%', '\n', '\n', 'd', 'e', 'p', 'r', 'e', 'c', 'a', 't', 'e', 'd', '\030', '\003', ' ', '\001', '(', +'\010', ':', '\005', 'f', 'a', 'l', 's', 'e', 'R', '\n', 'd', 'e', 'p', 'r', 'e', 'c', 'a', 't', 'e', 'd', '\022', 'V', '\n', '&', 'd', +'e', 'p', 'r', 'e', 'c', 'a', 't', 'e', 'd', '_', 'l', 'e', 'g', 'a', 'c', 'y', '_', 'j', 's', 'o', 'n', '_', 'f', 'i', 'e', +'l', 'd', '_', 'c', 'o', 'n', 'f', 'l', 'i', 'c', 't', 's', '\030', '\006', ' ', '\001', '(', '\010', 'B', '\002', '\030', '\001', 'R', '\"', 'd', +'e', 'p', 'r', 'e', 'c', 'a', 't', 'e', 'd', 'L', 'e', 'g', 'a', 'c', 'y', 'J', 's', 'o', 'n', 'F', 'i', 'e', 'l', 'd', 'C', +'o', 'n', 'f', 'l', 'i', 'c', 't', 's', '\022', '7', '\n', '\010', 'f', 'e', 'a', 't', 'u', 'r', 'e', 's', '\030', '\007', ' ', '\001', '(', +'\013', '2', '\033', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'e', 'a', 't', 'u', +'r', 'e', 'S', 'e', 't', 'R', '\010', 'f', 'e', 'a', 't', 'u', 'r', 'e', 's', '\022', 'X', '\n', '\024', 'u', 'n', 'i', 'n', 't', 'e', +'r', 'p', 'r', 'e', 't', 'e', 'd', '_', 'o', 'p', 't', 'i', 'o', 'n', '\030', '\347', '\007', ' ', '\003', '(', '\013', '2', '$', '.', 'g', +'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'U', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', +'t', 'e', 'd', 'O', 'p', 't', 'i', 'o', 'n', 'R', '\023', 'u', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', 'O', +'p', 't', 'i', 'o', 'n', '*', '\t', '\010', '\350', '\007', '\020', '\200', '\200', '\200', '\200', '\002', 'J', '\004', '\010', '\005', '\020', '\006', '\"', '\330', '\002', +'\n', '\020', 'E', 'n', 'u', 'm', 'V', 'a', 'l', 'u', 'e', 'O', 'p', 't', 'i', 'o', 'n', 's', '\022', '%', '\n', '\n', 'd', 'e', 'p', +'r', 'e', 'c', 'a', 't', 'e', 'd', '\030', '\001', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', 'l', 's', 'e', 'R', '\n', 'd', 'e', 'p', +'r', 'e', 'c', 'a', 't', 'e', 'd', '\022', '7', '\n', '\010', 'f', 'e', 'a', 't', 'u', 'r', 'e', 's', '\030', '\002', ' ', '\001', '(', '\013', +'2', '\033', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'e', 'a', 't', 'u', 'r', +'e', 'S', 'e', 't', 'R', '\010', 'f', 'e', 'a', 't', 'u', 'r', 'e', 's', '\022', '(', '\n', '\014', 'd', 'e', 'b', 'u', 'g', '_', 'r', +'e', 'd', 'a', 'c', 't', '\030', '\003', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', 'l', 's', 'e', 'R', '\013', 'd', 'e', 'b', 'u', 'g', +'R', 'e', 'd', 'a', 'c', 't', '\022', 'U', '\n', '\017', 'f', 'e', 'a', 't', 'u', 'r', 'e', '_', 's', 'u', 'p', 'p', 'o', 'r', 't', +'\030', '\004', ' ', '\001', '(', '\013', '2', ',', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', +'F', 'i', 'e', 'l', 'd', 'O', 'p', 't', 'i', 'o', 'n', 's', '.', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'u', 'p', 'p', 'o', +'r', 't', 'R', '\016', 'f', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'u', 'p', 'p', 'o', 'r', 't', '\022', 'X', '\n', '\024', 'u', 'n', 'i', +'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', '_', 'o', 'p', 't', 'i', 'o', 'n', '\030', '\347', '\007', ' ', '\003', '(', '\013', '2', +'$', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'U', 'n', 'i', 'n', 't', 'e', 'r', +'p', 'r', 'e', 't', 'e', 'd', 'O', 'p', 't', 'i', 'o', 'n', 'R', '\023', 'u', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', +'e', 'd', 'O', 'p', 't', 'i', 'o', 'n', '*', '\t', '\010', '\350', '\007', '\020', '\200', '\200', '\200', '\200', '\002', '\"', '\325', '\001', '\n', '\016', 'S', +'e', 'r', 'v', 'i', 'c', 'e', 'O', 'p', 't', 'i', 'o', 'n', 's', '\022', '7', '\n', '\010', 'f', 'e', 'a', 't', 'u', 'r', 'e', 's', +'\030', '\"', ' ', '\001', '(', '\013', '2', '\033', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', +'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'e', 't', 'R', '\010', 'f', 'e', 'a', 't', 'u', 'r', 'e', 's', '\022', '%', '\n', '\n', 'd', +'e', 'p', 'r', 'e', 'c', 'a', 't', 'e', 'd', '\030', '!', ' ', '\001', '(', '\010', ':', '\005', 'f', 'a', 'l', 's', 'e', 'R', '\n', 'd', +'e', 'p', 'r', 'e', 'c', 'a', 't', 'e', 'd', '\022', 'X', '\n', '\024', 'u', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', +'d', '_', 'o', 'p', 't', 'i', 'o', 'n', '\030', '\347', '\007', ' ', '\003', '(', '\013', '2', '$', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', +'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'U', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', 'O', 'p', 't', +'i', 'o', 'n', 'R', '\023', 'u', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', 'O', 'p', 't', 'i', 'o', 'n', '*', +'\t', '\010', '\350', '\007', '\020', '\200', '\200', '\200', '\200', '\002', '\"', '\231', '\003', '\n', '\r', 'M', 'e', 't', 'h', 'o', 'd', 'O', 'p', 't', 'i', +'o', 'n', 's', '\022', '%', '\n', '\n', 'd', 'e', 'p', 'r', 'e', 'c', 'a', 't', 'e', 'd', '\030', '!', ' ', '\001', '(', '\010', ':', '\005', +'f', 'a', 'l', 's', 'e', 'R', '\n', 'd', 'e', 'p', 'r', 'e', 'c', 'a', 't', 'e', 'd', '\022', 'q', '\n', '\021', 'i', 'd', 'e', 'm', +'p', 'o', 't', 'e', 'n', 'c', 'y', '_', 'l', 'e', 'v', 'e', 'l', '\030', '\"', ' ', '\001', '(', '\016', '2', '/', '.', 'g', 'o', 'o', +'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'M', 'e', 't', 'h', 'o', 'd', 'O', 'p', 't', 'i', 'o', 'n', +'s', '.', 'I', 'd', 'e', 'm', 'p', 'o', 't', 'e', 'n', 'c', 'y', 'L', 'e', 'v', 'e', 'l', ':', '\023', 'I', 'D', 'E', 'M', 'P', +'O', 'T', 'E', 'N', 'C', 'Y', '_', 'U', 'N', 'K', 'N', 'O', 'W', 'N', 'R', '\020', 'i', 'd', 'e', 'm', 'p', 'o', 't', 'e', 'n', +'c', 'y', 'L', 'e', 'v', 'e', 'l', '\022', '7', '\n', '\010', 'f', 'e', 'a', 't', 'u', 'r', 'e', 's', '\030', '#', ' ', '\001', '(', '\013', +'2', '\033', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'e', 'a', 't', 'u', 'r', +'e', 'S', 'e', 't', 'R', '\010', 'f', 'e', 'a', 't', 'u', 'r', 'e', 's', '\022', 'X', '\n', '\024', 'u', 'n', 'i', 'n', 't', 'e', 'r', +'p', 'r', 'e', 't', 'e', 'd', '_', 'o', 'p', 't', 'i', 'o', 'n', '\030', '\347', '\007', ' ', '\003', '(', '\013', '2', '$', '.', 'g', 'o', +'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'U', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', +'e', 'd', 'O', 'p', 't', 'i', 'o', 'n', 'R', '\023', 'u', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', 'O', 'p', +'t', 'i', 'o', 'n', '\"', 'P', '\n', '\020', 'I', 'd', 'e', 'm', 'p', 'o', 't', 'e', 'n', 'c', 'y', 'L', 'e', 'v', 'e', 'l', '\022', +'\027', '\n', '\023', 'I', 'D', 'E', 'M', 'P', 'O', 'T', 'E', 'N', 'C', 'Y', '_', 'U', 'N', 'K', 'N', 'O', 'W', 'N', '\020', '\000', '\022', +'\023', '\n', '\017', 'N', 'O', '_', 'S', 'I', 'D', 'E', '_', 'E', 'F', 'F', 'E', 'C', 'T', 'S', '\020', '\001', '\022', '\016', '\n', '\n', 'I', +'D', 'E', 'M', 'P', 'O', 'T', 'E', 'N', 'T', '\020', '\002', '*', '\t', '\010', '\350', '\007', '\020', '\200', '\200', '\200', '\200', '\002', '\"', '\232', '\003', +'\n', '\023', 'U', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', 'O', 'p', 't', 'i', 'o', 'n', '\022', 'A', '\n', '\004', +'n', 'a', 'm', 'e', '\030', '\002', ' ', '\003', '(', '\013', '2', '-', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', +'b', 'u', 'f', '.', 'U', 'n', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', 'e', 'd', 'O', 'p', 't', 'i', 'o', 'n', '.', 'N', +'a', 'm', 'e', 'P', 'a', 'r', 't', 'R', '\004', 'n', 'a', 'm', 'e', '\022', ')', '\n', '\020', 'i', 'd', 'e', 'n', 't', 'i', 'f', 'i', +'e', 'r', '_', 'v', 'a', 'l', 'u', 'e', '\030', '\003', ' ', '\001', '(', '\t', 'R', '\017', 'i', 'd', 'e', 'n', 't', 'i', 'f', 'i', 'e', +'r', 'V', 'a', 'l', 'u', 'e', '\022', ',', '\n', '\022', 'p', 'o', 's', 'i', 't', 'i', 'v', 'e', '_', 'i', 'n', 't', '_', 'v', 'a', +'l', 'u', 'e', '\030', '\004', ' ', '\001', '(', '\004', 'R', '\020', 'p', 'o', 's', 'i', 't', 'i', 'v', 'e', 'I', 'n', 't', 'V', 'a', 'l', +'u', 'e', '\022', ',', '\n', '\022', 'n', 'e', 'g', 'a', 't', 'i', 'v', 'e', '_', 'i', 'n', 't', '_', 'v', 'a', 'l', 'u', 'e', '\030', +'\005', ' ', '\001', '(', '\003', 'R', '\020', 'n', 'e', 'g', 'a', 't', 'i', 'v', 'e', 'I', 'n', 't', 'V', 'a', 'l', 'u', 'e', '\022', '!', +'\n', '\014', 'd', 'o', 'u', 'b', 'l', 'e', '_', 'v', 'a', 'l', 'u', 'e', '\030', '\006', ' ', '\001', '(', '\001', 'R', '\013', 'd', 'o', 'u', +'b', 'l', 'e', 'V', 'a', 'l', 'u', 'e', '\022', '!', '\n', '\014', 's', 't', 'r', 'i', 'n', 'g', '_', 'v', 'a', 'l', 'u', 'e', '\030', +'\007', ' ', '\001', '(', '\014', 'R', '\013', 's', 't', 'r', 'i', 'n', 'g', 'V', 'a', 'l', 'u', 'e', '\022', '\'', '\n', '\017', 'a', 'g', 'g', +'r', 'e', 'g', 'a', 't', 'e', '_', 'v', 'a', 'l', 'u', 'e', '\030', '\010', ' ', '\001', '(', '\t', 'R', '\016', 'a', 'g', 'g', 'r', 'e', +'g', 'a', 't', 'e', 'V', 'a', 'l', 'u', 'e', '\032', 'J', '\n', '\010', 'N', 'a', 'm', 'e', 'P', 'a', 'r', 't', '\022', '\033', '\n', '\t', +'n', 'a', 'm', 'e', '_', 'p', 'a', 'r', 't', '\030', '\001', ' ', '\002', '(', '\t', 'R', '\010', 'n', 'a', 'm', 'e', 'P', 'a', 'r', 't', +'\022', '!', '\n', '\014', 'i', 's', '_', 'e', 'x', 't', 'e', 'n', 's', 'i', 'o', 'n', '\030', '\002', ' ', '\002', '(', '\010', 'R', '\013', 'i', +'s', 'E', 'x', 't', 'e', 'n', 's', 'i', 'o', 'n', '\"', '\247', '\n', '\n', '\n', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'e', 't', +'\022', '\221', '\001', '\n', '\016', 'f', 'i', 'e', 'l', 'd', '_', 'p', 'r', 'e', 's', 'e', 'n', 'c', 'e', '\030', '\001', ' ', '\001', '(', '\016', +'2', ')', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'e', 'a', 't', 'u', 'r', +'e', 'S', 'e', 't', '.', 'F', 'i', 'e', 'l', 'd', 'P', 'r', 'e', 's', 'e', 'n', 'c', 'e', 'B', '?', '\210', '\001', '\001', '\230', '\001', +'\004', '\230', '\001', '\001', '\242', '\001', '\r', '\022', '\010', 'E', 'X', 'P', 'L', 'I', 'C', 'I', 'T', '\030', '\346', '\007', '\242', '\001', '\r', '\022', '\010', +'I', 'M', 'P', 'L', 'I', 'C', 'I', 'T', '\030', '\347', '\007', '\242', '\001', '\r', '\022', '\010', 'E', 'X', 'P', 'L', 'I', 'C', 'I', 'T', '\030', +'\350', '\007', '\262', '\001', '\003', '\010', '\350', '\007', 'R', '\r', 'f', 'i', 'e', 'l', 'd', 'P', 'r', 'e', 's', 'e', 'n', 'c', 'e', '\022', 'l', +'\n', '\t', 'e', 'n', 'u', 'm', '_', 't', 'y', 'p', 'e', '\030', '\002', ' ', '\001', '(', '\016', '2', '$', '.', 'g', 'o', 'o', 'g', 'l', +'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'e', 't', '.', 'E', 'n', 'u', +'m', 'T', 'y', 'p', 'e', 'B', ')', '\210', '\001', '\001', '\230', '\001', '\006', '\230', '\001', '\001', '\242', '\001', '\013', '\022', '\006', 'C', 'L', 'O', 'S', +'E', 'D', '\030', '\346', '\007', '\242', '\001', '\t', '\022', '\004', 'O', 'P', 'E', 'N', '\030', '\347', '\007', '\262', '\001', '\003', '\010', '\350', '\007', 'R', '\010', +'e', 'n', 'u', 'm', 'T', 'y', 'p', 'e', '\022', '\230', '\001', '\n', '\027', 'r', 'e', 'p', 'e', 'a', 't', 'e', 'd', '_', 'f', 'i', 'e', +'l', 'd', '_', 'e', 'n', 'c', 'o', 'd', 'i', 'n', 'g', '\030', '\003', ' ', '\001', '(', '\016', '2', '1', '.', 'g', 'o', 'o', 'g', 'l', +'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'e', 't', '.', 'R', 'e', 'p', +'e', 'a', 't', 'e', 'd', 'F', 'i', 'e', 'l', 'd', 'E', 'n', 'c', 'o', 'd', 'i', 'n', 'g', 'B', '-', '\210', '\001', '\001', '\230', '\001', +'\004', '\230', '\001', '\001', '\242', '\001', '\r', '\022', '\010', 'E', 'X', 'P', 'A', 'N', 'D', 'E', 'D', '\030', '\346', '\007', '\242', '\001', '\013', '\022', '\006', +'P', 'A', 'C', 'K', 'E', 'D', '\030', '\347', '\007', '\262', '\001', '\003', '\010', '\350', '\007', 'R', '\025', 'r', 'e', 'p', 'e', 'a', 't', 'e', 'd', +'F', 'i', 'e', 'l', 'd', 'E', 'n', 'c', 'o', 'd', 'i', 'n', 'g', '\022', '~', '\n', '\017', 'u', 't', 'f', '8', '_', 'v', 'a', 'l', +'i', 'd', 'a', 't', 'i', 'o', 'n', '\030', '\004', ' ', '\001', '(', '\016', '2', '*', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', +'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'e', 't', '.', 'U', 't', 'f', '8', 'V', 'a', 'l', +'i', 'd', 'a', 't', 'i', 'o', 'n', 'B', ')', '\210', '\001', '\001', '\230', '\001', '\004', '\230', '\001', '\001', '\242', '\001', '\t', '\022', '\004', 'N', 'O', +'N', 'E', '\030', '\346', '\007', '\242', '\001', '\013', '\022', '\006', 'V', 'E', 'R', 'I', 'F', 'Y', '\030', '\347', '\007', '\262', '\001', '\003', '\010', '\350', '\007', +'R', '\016', 'u', 't', 'f', '8', 'V', 'a', 'l', 'i', 'd', 'a', 't', 'i', 'o', 'n', '\022', '~', '\n', '\020', 'm', 'e', 's', 's', 'a', +'g', 'e', '_', 'e', 'n', 'c', 'o', 'd', 'i', 'n', 'g', '\030', '\005', ' ', '\001', '(', '\016', '2', '+', '.', 'g', 'o', 'o', 'g', 'l', +'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'e', 't', '.', 'M', 'e', 's', +'s', 'a', 'g', 'e', 'E', 'n', 'c', 'o', 'd', 'i', 'n', 'g', 'B', '&', '\210', '\001', '\001', '\230', '\001', '\004', '\230', '\001', '\001', '\242', '\001', +'\024', '\022', '\017', 'L', 'E', 'N', 'G', 'T', 'H', '_', 'P', 'R', 'E', 'F', 'I', 'X', 'E', 'D', '\030', '\346', '\007', '\262', '\001', '\003', '\010', +'\350', '\007', 'R', '\017', 'm', 'e', 's', 's', 'a', 'g', 'e', 'E', 'n', 'c', 'o', 'd', 'i', 'n', 'g', '\022', '\202', '\001', '\n', '\013', 'j', +'s', 'o', 'n', '_', 'f', 'o', 'r', 'm', 'a', 't', '\030', '\006', ' ', '\001', '(', '\016', '2', '&', '.', 'g', 'o', 'o', 'g', 'l', 'e', +'.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'e', 't', '.', 'J', 's', 'o', 'n', +'F', 'o', 'r', 'm', 'a', 't', 'B', '9', '\210', '\001', '\001', '\230', '\001', '\003', '\230', '\001', '\006', '\230', '\001', '\001', '\242', '\001', '\027', '\022', '\022', +'L', 'E', 'G', 'A', 'C', 'Y', '_', 'B', 'E', 'S', 'T', '_', 'E', 'F', 'F', 'O', 'R', 'T', '\030', '\346', '\007', '\242', '\001', '\n', '\022', +'\005', 'A', 'L', 'L', 'O', 'W', '\030', '\347', '\007', '\262', '\001', '\003', '\010', '\350', '\007', 'R', '\n', 'j', 's', 'o', 'n', 'F', 'o', 'r', 'm', +'a', 't', '\"', '\\', '\n', '\r', 'F', 'i', 'e', 'l', 'd', 'P', 'r', 'e', 's', 'e', 'n', 'c', 'e', '\022', '\032', '\n', '\026', 'F', 'I', +'E', 'L', 'D', '_', 'P', 'R', 'E', 'S', 'E', 'N', 'C', 'E', '_', 'U', 'N', 'K', 'N', 'O', 'W', 'N', '\020', '\000', '\022', '\014', '\n', +'\010', 'E', 'X', 'P', 'L', 'I', 'C', 'I', 'T', '\020', '\001', '\022', '\014', '\n', '\010', 'I', 'M', 'P', 'L', 'I', 'C', 'I', 'T', '\020', '\002', +'\022', '\023', '\n', '\017', 'L', 'E', 'G', 'A', 'C', 'Y', '_', 'R', 'E', 'Q', 'U', 'I', 'R', 'E', 'D', '\020', '\003', '\"', '7', '\n', '\010', +'E', 'n', 'u', 'm', 'T', 'y', 'p', 'e', '\022', '\025', '\n', '\021', 'E', 'N', 'U', 'M', '_', 'T', 'Y', 'P', 'E', '_', 'U', 'N', 'K', +'N', 'O', 'W', 'N', '\020', '\000', '\022', '\010', '\n', '\004', 'O', 'P', 'E', 'N', '\020', '\001', '\022', '\n', '\n', '\006', 'C', 'L', 'O', 'S', 'E', +'D', '\020', '\002', '\"', 'V', '\n', '\025', 'R', 'e', 'p', 'e', 'a', 't', 'e', 'd', 'F', 'i', 'e', 'l', 'd', 'E', 'n', 'c', 'o', 'd', +'i', 'n', 'g', '\022', '#', '\n', '\037', 'R', 'E', 'P', 'E', 'A', 'T', 'E', 'D', '_', 'F', 'I', 'E', 'L', 'D', '_', 'E', 'N', 'C', +'O', 'D', 'I', 'N', 'G', '_', 'U', 'N', 'K', 'N', 'O', 'W', 'N', '\020', '\000', '\022', '\n', '\n', '\006', 'P', 'A', 'C', 'K', 'E', 'D', +'\020', '\001', '\022', '\014', '\n', '\010', 'E', 'X', 'P', 'A', 'N', 'D', 'E', 'D', '\020', '\002', '\"', 'I', '\n', '\016', 'U', 't', 'f', '8', 'V', +'a', 'l', 'i', 'd', 'a', 't', 'i', 'o', 'n', '\022', '\033', '\n', '\027', 'U', 'T', 'F', '8', '_', 'V', 'A', 'L', 'I', 'D', 'A', 'T', +'I', 'O', 'N', '_', 'U', 'N', 'K', 'N', 'O', 'W', 'N', '\020', '\000', '\022', '\n', '\n', '\006', 'V', 'E', 'R', 'I', 'F', 'Y', '\020', '\002', +'\022', '\010', '\n', '\004', 'N', 'O', 'N', 'E', '\020', '\003', '\"', '\004', '\010', '\001', '\020', '\001', '\"', 'S', '\n', '\017', 'M', 'e', 's', 's', 'a', +'g', 'e', 'E', 'n', 'c', 'o', 'd', 'i', 'n', 'g', '\022', '\034', '\n', '\030', 'M', 'E', 'S', 'S', 'A', 'G', 'E', '_', 'E', 'N', 'C', +'O', 'D', 'I', 'N', 'G', '_', 'U', 'N', 'K', 'N', 'O', 'W', 'N', '\020', '\000', '\022', '\023', '\n', '\017', 'L', 'E', 'N', 'G', 'T', 'H', +'_', 'P', 'R', 'E', 'F', 'I', 'X', 'E', 'D', '\020', '\001', '\022', '\r', '\n', '\t', 'D', 'E', 'L', 'I', 'M', 'I', 'T', 'E', 'D', '\020', +'\002', '\"', 'H', '\n', '\n', 'J', 's', 'o', 'n', 'F', 'o', 'r', 'm', 'a', 't', '\022', '\027', '\n', '\023', 'J', 'S', 'O', 'N', '_', 'F', +'O', 'R', 'M', 'A', 'T', '_', 'U', 'N', 'K', 'N', 'O', 'W', 'N', '\020', '\000', '\022', '\t', '\n', '\005', 'A', 'L', 'L', 'O', 'W', '\020', +'\001', '\022', '\026', '\n', '\022', 'L', 'E', 'G', 'A', 'C', 'Y', '_', 'B', 'E', 'S', 'T', '_', 'E', 'F', 'F', 'O', 'R', 'T', '\020', '\002', +'*', '\006', '\010', '\350', '\007', '\020', '\213', 'N', '*', '\006', '\010', '\213', 'N', '\020', '\220', 'N', '*', '\006', '\010', '\220', 'N', '\020', '\221', 'N', 'J', +'\006', '\010', '\347', '\007', '\020', '\350', '\007', '\"', '\357', '\003', '\n', '\022', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'e', 't', 'D', 'e', 'f', +'a', 'u', 'l', 't', 's', '\022', 'X', '\n', '\010', 'd', 'e', 'f', 'a', 'u', 'l', 't', 's', '\030', '\001', ' ', '\003', '(', '\013', '2', '<', +'.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', +'e', 't', 'D', 'e', 'f', 'a', 'u', 'l', 't', 's', '.', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'e', 't', 'E', 'd', 'i', 't', +'i', 'o', 'n', 'D', 'e', 'f', 'a', 'u', 'l', 't', 'R', '\010', 'd', 'e', 'f', 'a', 'u', 'l', 't', 's', '\022', 'A', '\n', '\017', 'm', +'i', 'n', 'i', 'm', 'u', 'm', '_', 'e', 'd', 'i', 't', 'i', 'o', 'n', '\030', '\004', ' ', '\001', '(', '\016', '2', '\030', '.', 'g', 'o', +'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'E', 'd', 'i', 't', 'i', 'o', 'n', 'R', '\016', 'm', 'i', +'n', 'i', 'm', 'u', 'm', 'E', 'd', 'i', 't', 'i', 'o', 'n', '\022', 'A', '\n', '\017', 'm', 'a', 'x', 'i', 'm', 'u', 'm', '_', 'e', +'d', 'i', 't', 'i', 'o', 'n', '\030', '\005', ' ', '\001', '(', '\016', '2', '\030', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', +'t', 'o', 'b', 'u', 'f', '.', 'E', 'd', 'i', 't', 'i', 'o', 'n', 'R', '\016', 'm', 'a', 'x', 'i', 'm', 'u', 'm', 'E', 'd', 'i', +'t', 'i', 'o', 'n', '\032', '\370', '\001', '\n', '\030', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'e', 't', 'E', 'd', 'i', 't', 'i', 'o', +'n', 'D', 'e', 'f', 'a', 'u', 'l', 't', '\022', '2', '\n', '\007', 'e', 'd', 'i', 't', 'i', 'o', 'n', '\030', '\003', ' ', '\001', '(', '\016', +'2', '\030', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'E', 'd', 'i', 't', 'i', 'o', +'n', 'R', '\007', 'e', 'd', 'i', 't', 'i', 'o', 'n', '\022', 'N', '\n', '\024', 'o', 'v', 'e', 'r', 'r', 'i', 'd', 'a', 'b', 'l', 'e', +'_', 'f', 'e', 'a', 't', 'u', 'r', 'e', 's', '\030', '\004', ' ', '\001', '(', '\013', '2', '\033', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', +'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'e', 't', 'R', '\023', 'o', 'v', 'e', 'r', +'r', 'i', 'd', 'a', 'b', 'l', 'e', 'F', 'e', 'a', 't', 'u', 'r', 'e', 's', '\022', 'B', '\n', '\016', 'f', 'i', 'x', 'e', 'd', '_', +'f', 'e', 'a', 't', 'u', 'r', 'e', 's', '\030', '\005', ' ', '\001', '(', '\013', '2', '\033', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', +'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'F', 'e', 'a', 't', 'u', 'r', 'e', 'S', 'e', 't', 'R', '\r', 'f', 'i', 'x', 'e', 'd', +'F', 'e', 'a', 't', 'u', 'r', 'e', 's', 'J', '\004', '\010', '\001', '\020', '\002', 'J', '\004', '\010', '\002', '\020', '\003', 'R', '\010', 'f', 'e', 'a', +'t', 'u', 'r', 'e', 's', '\"', '\247', '\002', '\n', '\016', 'S', 'o', 'u', 'r', 'c', 'e', 'C', 'o', 'd', 'e', 'I', 'n', 'f', 'o', '\022', +'D', '\n', '\010', 'l', 'o', 'c', 'a', 't', 'i', 'o', 'n', '\030', '\001', ' ', '\003', '(', '\013', '2', '(', '.', 'g', 'o', 'o', 'g', 'l', +'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'S', 'o', 'u', 'r', 'c', 'e', 'C', 'o', 'd', 'e', 'I', 'n', 'f', 'o', +'.', 'L', 'o', 'c', 'a', 't', 'i', 'o', 'n', 'R', '\010', 'l', 'o', 'c', 'a', 't', 'i', 'o', 'n', '\032', '\316', '\001', '\n', '\010', 'L', +'o', 'c', 'a', 't', 'i', 'o', 'n', '\022', '\026', '\n', '\004', 'p', 'a', 't', 'h', '\030', '\001', ' ', '\003', '(', '\005', 'B', '\002', '\020', '\001', +'R', '\004', 'p', 'a', 't', 'h', '\022', '\026', '\n', '\004', 's', 'p', 'a', 'n', '\030', '\002', ' ', '\003', '(', '\005', 'B', '\002', '\020', '\001', 'R', +'\004', 's', 'p', 'a', 'n', '\022', ')', '\n', '\020', 'l', 'e', 'a', 'd', 'i', 'n', 'g', '_', 'c', 'o', 'm', 'm', 'e', 'n', 't', 's', +'\030', '\003', ' ', '\001', '(', '\t', 'R', '\017', 'l', 'e', 'a', 'd', 'i', 'n', 'g', 'C', 'o', 'm', 'm', 'e', 'n', 't', 's', '\022', '+', +'\n', '\021', 't', 'r', 'a', 'i', 'l', 'i', 'n', 'g', '_', 'c', 'o', 'm', 'm', 'e', 'n', 't', 's', '\030', '\004', ' ', '\001', '(', '\t', +'R', '\020', 't', 'r', 'a', 'i', 'l', 'i', 'n', 'g', 'C', 'o', 'm', 'm', 'e', 'n', 't', 's', '\022', ':', '\n', '\031', 'l', 'e', 'a', +'d', 'i', 'n', 'g', '_', 'd', 'e', 't', 'a', 'c', 'h', 'e', 'd', '_', 'c', 'o', 'm', 'm', 'e', 'n', 't', 's', '\030', '\006', ' ', +'\003', '(', '\t', 'R', '\027', 'l', 'e', 'a', 'd', 'i', 'n', 'g', 'D', 'e', 't', 'a', 'c', 'h', 'e', 'd', 'C', 'o', 'm', 'm', 'e', +'n', 't', 's', '\"', '\320', '\002', '\n', '\021', 'G', 'e', 'n', 'e', 'r', 'a', 't', 'e', 'd', 'C', 'o', 'd', 'e', 'I', 'n', 'f', 'o', +'\022', 'M', '\n', '\n', 'a', 'n', 'n', 'o', 't', 'a', 't', 'i', 'o', 'n', '\030', '\001', ' ', '\003', '(', '\013', '2', '-', '.', 'g', 'o', +'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'G', 'e', 'n', 'e', 'r', 'a', 't', 'e', 'd', 'C', 'o', +'d', 'e', 'I', 'n', 'f', 'o', '.', 'A', 'n', 'n', 'o', 't', 'a', 't', 'i', 'o', 'n', 'R', '\n', 'a', 'n', 'n', 'o', 't', 'a', +'t', 'i', 'o', 'n', '\032', '\353', '\001', '\n', '\n', 'A', 'n', 'n', 'o', 't', 'a', 't', 'i', 'o', 'n', '\022', '\026', '\n', '\004', 'p', 'a', +'t', 'h', '\030', '\001', ' ', '\003', '(', '\005', 'B', '\002', '\020', '\001', 'R', '\004', 'p', 'a', 't', 'h', '\022', '\037', '\n', '\013', 's', 'o', 'u', +'r', 'c', 'e', '_', 'f', 'i', 'l', 'e', '\030', '\002', ' ', '\001', '(', '\t', 'R', '\n', 's', 'o', 'u', 'r', 'c', 'e', 'F', 'i', 'l', +'e', '\022', '\024', '\n', '\005', 'b', 'e', 'g', 'i', 'n', '\030', '\003', ' ', '\001', '(', '\005', 'R', '\005', 'b', 'e', 'g', 'i', 'n', '\022', '\020', +'\n', '\003', 'e', 'n', 'd', '\030', '\004', ' ', '\001', '(', '\005', 'R', '\003', 'e', 'n', 'd', '\022', 'R', '\n', '\010', 's', 'e', 'm', 'a', 'n', +'t', 'i', 'c', '\030', '\005', ' ', '\001', '(', '\016', '2', '6', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', +'u', 'f', '.', 'G', 'e', 'n', 'e', 'r', 'a', 't', 'e', 'd', 'C', 'o', 'd', 'e', 'I', 'n', 'f', 'o', '.', 'A', 'n', 'n', 'o', +'t', 'a', 't', 'i', 'o', 'n', '.', 'S', 'e', 'm', 'a', 'n', 't', 'i', 'c', 'R', '\010', 's', 'e', 'm', 'a', 'n', 't', 'i', 'c', +'\"', '(', '\n', '\010', 'S', 'e', 'm', 'a', 'n', 't', 'i', 'c', '\022', '\010', '\n', '\004', 'N', 'O', 'N', 'E', '\020', '\000', '\022', '\007', '\n', +'\003', 'S', 'E', 'T', '\020', '\001', '\022', '\t', '\n', '\005', 'A', 'L', 'I', 'A', 'S', '\020', '\002', '*', '\247', '\002', '\n', '\007', 'E', 'd', 'i', +'t', 'i', 'o', 'n', '\022', '\023', '\n', '\017', 'E', 'D', 'I', 'T', 'I', 'O', 'N', '_', 'U', 'N', 'K', 'N', 'O', 'W', 'N', '\020', '\000', +'\022', '\023', '\n', '\016', 'E', 'D', 'I', 'T', 'I', 'O', 'N', '_', 'L', 'E', 'G', 'A', 'C', 'Y', '\020', '\204', '\007', '\022', '\023', '\n', '\016', +'E', 'D', 'I', 'T', 'I', 'O', 'N', '_', 'P', 'R', 'O', 'T', 'O', '2', '\020', '\346', '\007', '\022', '\023', '\n', '\016', 'E', 'D', 'I', 'T', +'I', 'O', 'N', '_', 'P', 'R', 'O', 'T', 'O', '3', '\020', '\347', '\007', '\022', '\021', '\n', '\014', 'E', 'D', 'I', 'T', 'I', 'O', 'N', '_', +'2', '0', '2', '3', '\020', '\350', '\007', '\022', '\021', '\n', '\014', 'E', 'D', 'I', 'T', 'I', 'O', 'N', '_', '2', '0', '2', '4', '\020', '\351', +'\007', '\022', '\027', '\n', '\023', 'E', 'D', 'I', 'T', 'I', 'O', 'N', '_', '1', '_', 'T', 'E', 'S', 'T', '_', 'O', 'N', 'L', 'Y', '\020', +'\001', '\022', '\027', '\n', '\023', 'E', 'D', 'I', 'T', 'I', 'O', 'N', '_', '2', '_', 'T', 'E', 'S', 'T', '_', 'O', 'N', 'L', 'Y', '\020', +'\002', '\022', '\035', '\n', '\027', 'E', 'D', 'I', 'T', 'I', 'O', 'N', '_', '9', '9', '9', '9', '7', '_', 'T', 'E', 'S', 'T', '_', 'O', +'N', 'L', 'Y', '\020', '\235', '\215', '\006', '\022', '\035', '\n', '\027', 'E', 'D', 'I', 'T', 'I', 'O', 'N', '_', '9', '9', '9', '9', '8', '_', +'T', 'E', 'S', 'T', '_', 'O', 'N', 'L', 'Y', '\020', '\236', '\215', '\006', '\022', '\035', '\n', '\027', 'E', 'D', 'I', 'T', 'I', 'O', 'N', '_', +'9', '9', '9', '9', '9', '_', 'T', 'E', 'S', 'T', '_', 'O', 'N', 'L', 'Y', '\020', '\237', '\215', '\006', '\022', '\023', '\n', '\013', 'E', 'D', +'I', 'T', 'I', 'O', 'N', '_', 'M', 'A', 'X', '\020', '\377', '\377', '\377', '\377', '\007', 'B', '~', '\n', '\023', 'c', 'o', 'm', '.', 'g', 'o', +'o', 'g', 'l', 'e', '.', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', 'B', '\020', 'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', +'P', 'r', 'o', 't', 'o', 's', 'H', '\001', 'Z', '-', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'g', 'o', 'l', 'a', 'n', 'g', '.', 'o', +'r', 'g', '/', 'p', 'r', 'o', 't', 'o', 'b', 'u', 'f', '/', 't', 'y', 'p', 'e', 's', '/', 'd', 'e', 's', 'c', 'r', 'i', 'p', +'t', 'o', 'r', 'p', 'b', '\370', '\001', '\001', '\242', '\002', '\003', 'G', 'P', 'B', '\252', '\002', '\032', 'G', 'o', 'o', 'g', 'l', 'e', '.', 'P', +'r', 'o', 't', 'o', 'b', 'u', 'f', '.', 'R', 'e', 'f', 'l', 'e', 'c', 't', 'i', 'o', 'n', +}; + +static _upb_DefPool_Init *deps[1] = { + NULL +}; + +_upb_DefPool_Init google_protobuf_descriptor_proto_upbdefinit = { + deps, + &google_protobuf_descriptor_proto_upb_file_layout, + "google/protobuf/descriptor.proto", + UPB_STRINGVIEW_INIT(descriptor, 12268) +}; diff --git a/google/protobuf/descriptor.upbdefs.h b/google/protobuf/descriptor.upbdefs.h new file mode 100644 index 0000000..9a488b5 --- /dev/null +++ b/google/protobuf/descriptor.upbdefs.h @@ -0,0 +1,192 @@ +/* This file was generated by upb_generator from the input file: + * + * google/protobuf/descriptor.proto + * + * Do not edit -- your changes will be discarded when the file is + * regenerated. */ + +#ifndef GOOGLE_PROTOBUF_DESCRIPTOR_PROTO_UPBDEFS_H_ +#define GOOGLE_PROTOBUF_DESCRIPTOR_PROTO_UPBDEFS_H_ + +#include "upb/reflection/def.h" +#include "upb/reflection/internal/def_pool.h" + +#include "upb/port/def.inc" // Must be last. +#ifdef __cplusplus +extern "C" { +#endif + +extern _upb_DefPool_Init google_protobuf_descriptor_proto_upbdefinit; + +UPB_INLINE const upb_MessageDef *google_protobuf_FileDescriptorSet_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.FileDescriptorSet"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_FileDescriptorProto_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.FileDescriptorProto"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_DescriptorProto_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.DescriptorProto"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_DescriptorProto_ExtensionRange_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.DescriptorProto.ExtensionRange"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_DescriptorProto_ReservedRange_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.DescriptorProto.ReservedRange"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_ExtensionRangeOptions_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.ExtensionRangeOptions"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_ExtensionRangeOptions_Declaration_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.ExtensionRangeOptions.Declaration"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_FieldDescriptorProto_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.FieldDescriptorProto"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_OneofDescriptorProto_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.OneofDescriptorProto"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_EnumDescriptorProto_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.EnumDescriptorProto"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_EnumDescriptorProto_EnumReservedRange_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.EnumDescriptorProto.EnumReservedRange"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_EnumValueDescriptorProto_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.EnumValueDescriptorProto"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_ServiceDescriptorProto_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.ServiceDescriptorProto"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_MethodDescriptorProto_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.MethodDescriptorProto"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_FileOptions_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.FileOptions"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_MessageOptions_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.MessageOptions"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_FieldOptions_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.FieldOptions"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_FieldOptions_EditionDefault_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.FieldOptions.EditionDefault"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_FieldOptions_FeatureSupport_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.FieldOptions.FeatureSupport"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_OneofOptions_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.OneofOptions"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_EnumOptions_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.EnumOptions"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_EnumValueOptions_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.EnumValueOptions"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_ServiceOptions_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.ServiceOptions"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_MethodOptions_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.MethodOptions"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_UninterpretedOption_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.UninterpretedOption"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_UninterpretedOption_NamePart_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.UninterpretedOption.NamePart"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_FeatureSet_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.FeatureSet"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_FeatureSetDefaults_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.FeatureSetDefaults"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.FeatureSetDefaults.FeatureSetEditionDefault"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_SourceCodeInfo_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.SourceCodeInfo"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_SourceCodeInfo_Location_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.SourceCodeInfo.Location"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_GeneratedCodeInfo_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.GeneratedCodeInfo"); +} + +UPB_INLINE const upb_MessageDef *google_protobuf_GeneratedCodeInfo_Annotation_getmsgdef(upb_DefPool *s) { + _upb_DefPool_LoadDefInit(s, &google_protobuf_descriptor_proto_upbdefinit); + return upb_DefPool_FindMessageByName(s, "google.protobuf.GeneratedCodeInfo.Annotation"); +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#include "upb/port/undef.inc" + +#endif /* GOOGLE_PROTOBUF_DESCRIPTOR_PROTO_UPBDEFS_H_ */ diff --git a/google/protobuf/descriptor_database.py b/google/protobuf/descriptor_database.py new file mode 100644 index 0000000..46a893e --- /dev/null +++ b/google/protobuf/descriptor_database.py @@ -0,0 +1,154 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Provides a container for DescriptorProtos.""" + +__author__ = 'matthewtoia@google.com (Matt Toia)' + +import warnings + + +class Error(Exception): + pass + + +class DescriptorDatabaseConflictingDefinitionError(Error): + """Raised when a proto is added with the same name & different descriptor.""" + + +class DescriptorDatabase(object): + """A container accepting FileDescriptorProtos and maps DescriptorProtos.""" + + def __init__(self): + self._file_desc_protos_by_file = {} + self._file_desc_protos_by_symbol = {} + + def Add(self, file_desc_proto): + """Adds the FileDescriptorProto and its types to this database. + + Args: + file_desc_proto: The FileDescriptorProto to add. + Raises: + DescriptorDatabaseConflictingDefinitionError: if an attempt is made to + add a proto with the same name but different definition than an + existing proto in the database. + """ + proto_name = file_desc_proto.name + if proto_name not in self._file_desc_protos_by_file: + self._file_desc_protos_by_file[proto_name] = file_desc_proto + elif self._file_desc_protos_by_file[proto_name] != file_desc_proto: + raise DescriptorDatabaseConflictingDefinitionError( + '%s already added, but with different descriptor.' % proto_name) + else: + return + + # Add all the top-level descriptors to the index. + package = file_desc_proto.package + for message in file_desc_proto.message_type: + for name in _ExtractSymbols(message, package): + self._AddSymbol(name, file_desc_proto) + for enum in file_desc_proto.enum_type: + self._AddSymbol(('.'.join((package, enum.name))), file_desc_proto) + for enum_value in enum.value: + self._file_desc_protos_by_symbol[ + '.'.join((package, enum_value.name))] = file_desc_proto + for extension in file_desc_proto.extension: + self._AddSymbol(('.'.join((package, extension.name))), file_desc_proto) + for service in file_desc_proto.service: + self._AddSymbol(('.'.join((package, service.name))), file_desc_proto) + + def FindFileByName(self, name): + """Finds the file descriptor proto by file name. + + Typically the file name is a relative path ending to a .proto file. The + proto with the given name will have to have been added to this database + using the Add method or else an error will be raised. + + Args: + name: The file name to find. + + Returns: + The file descriptor proto matching the name. + + Raises: + KeyError if no file by the given name was added. + """ + + return self._file_desc_protos_by_file[name] + + def FindFileContainingSymbol(self, symbol): + """Finds the file descriptor proto containing the specified symbol. + + The symbol should be a fully qualified name including the file descriptor's + package and any containing messages. Some examples: + + 'some.package.name.Message' + 'some.package.name.Message.NestedEnum' + 'some.package.name.Message.some_field' + + The file descriptor proto containing the specified symbol must be added to + this database using the Add method or else an error will be raised. + + Args: + symbol: The fully qualified symbol name. + + Returns: + The file descriptor proto containing the symbol. + + Raises: + KeyError if no file contains the specified symbol. + """ + try: + return self._file_desc_protos_by_symbol[symbol] + except KeyError: + # Fields, enum values, and nested extensions are not in + # _file_desc_protos_by_symbol. Try to find the top level + # descriptor. Non-existent nested symbol under a valid top level + # descriptor can also be found. The behavior is the same with + # protobuf C++. + top_level, _, _ = symbol.rpartition('.') + try: + return self._file_desc_protos_by_symbol[top_level] + except KeyError: + # Raise the original symbol as a KeyError for better diagnostics. + raise KeyError(symbol) + + def FindFileContainingExtension(self, extendee_name, extension_number): + # TODO: implement this API. + return None + + def FindAllExtensionNumbers(self, extendee_name): + # TODO: implement this API. + return [] + + def _AddSymbol(self, name, file_desc_proto): + if name in self._file_desc_protos_by_symbol: + warn_msg = ('Conflict register for file "' + file_desc_proto.name + + '": ' + name + + ' is already defined in file "' + + self._file_desc_protos_by_symbol[name].name + '"') + warnings.warn(warn_msg, RuntimeWarning) + self._file_desc_protos_by_symbol[name] = file_desc_proto + + +def _ExtractSymbols(desc_proto, package): + """Pulls out all the symbols from a descriptor proto. + + Args: + desc_proto: The proto to extract symbols from. + package: The package containing the descriptor type. + + Yields: + The fully qualified name found in the descriptor. + """ + message_name = package + '.' + desc_proto.name if package else desc_proto.name + yield message_name + for nested_type in desc_proto.nested_type: + for symbol in _ExtractSymbols(nested_type, message_name): + yield symbol + for enum_type in desc_proto.enum_type: + yield '.'.join((message_name, enum_type.name)) diff --git a/google/protobuf/descriptor_pb2.py b/google/protobuf/descriptor_pb2.py new file mode 100644 index 0000000..ad5a206 --- /dev/null +++ b/google/protobuf/descriptor_pb2.py @@ -0,0 +1,3169 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: google/protobuf/descriptor.proto +# Protobuf Python Version: 5.27.3 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 3, + '', + 'google/protobuf/descriptor.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR = _descriptor.FileDescriptor( + name='google/protobuf/descriptor.proto', + package='google.protobuf', + syntax='proto2', + edition='EDITION_PROTO2', + serialized_options=b'\n\023com.google.protobufB\020DescriptorProtosH\001Z-google.golang.org/protobuf/types/descriptorpb\370\001\001\242\002\003GPB\252\002\032Google.Protobuf.Reflection', + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n google/protobuf/descriptor.proto\x12\x0fgoogle.protobuf\"M\n\x11\x46ileDescriptorSet\x12\x38\n\x04\x66ile\x18\x01 \x03(\x0b\x32$.google.protobuf.FileDescriptorProtoR\x04\x66ile\"\x98\x05\n\x13\x46ileDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n\x07package\x18\x02 \x01(\tR\x07package\x12\x1e\n\ndependency\x18\x03 \x03(\tR\ndependency\x12+\n\x11public_dependency\x18\n \x03(\x05R\x10publicDependency\x12\'\n\x0fweak_dependency\x18\x0b \x03(\x05R\x0eweakDependency\x12\x43\n\x0cmessage_type\x18\x04 \x03(\x0b\x32 .google.protobuf.DescriptorProtoR\x0bmessageType\x12\x41\n\tenum_type\x18\x05 \x03(\x0b\x32$.google.protobuf.EnumDescriptorProtoR\x08\x65numType\x12\x41\n\x07service\x18\x06 \x03(\x0b\x32\'.google.protobuf.ServiceDescriptorProtoR\x07service\x12\x43\n\textension\x18\x07 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\textension\x12\x36\n\x07options\x18\x08 \x01(\x0b\x32\x1c.google.protobuf.FileOptionsR\x07options\x12I\n\x10source_code_info\x18\t \x01(\x0b\x32\x1f.google.protobuf.SourceCodeInfoR\x0esourceCodeInfo\x12\x16\n\x06syntax\x18\x0c \x01(\tR\x06syntax\x12\x32\n\x07\x65\x64ition\x18\x0e \x01(\x0e\x32\x18.google.protobuf.EditionR\x07\x65\x64ition\"\xb9\x06\n\x0f\x44\x65scriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12;\n\x05\x66ield\x18\x02 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\x05\x66ield\x12\x43\n\textension\x18\x06 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\textension\x12\x41\n\x0bnested_type\x18\x03 \x03(\x0b\x32 .google.protobuf.DescriptorProtoR\nnestedType\x12\x41\n\tenum_type\x18\x04 \x03(\x0b\x32$.google.protobuf.EnumDescriptorProtoR\x08\x65numType\x12X\n\x0f\x65xtension_range\x18\x05 \x03(\x0b\x32/.google.protobuf.DescriptorProto.ExtensionRangeR\x0e\x65xtensionRange\x12\x44\n\noneof_decl\x18\x08 \x03(\x0b\x32%.google.protobuf.OneofDescriptorProtoR\toneofDecl\x12\x39\n\x07options\x18\x07 \x01(\x0b\x32\x1f.google.protobuf.MessageOptionsR\x07options\x12U\n\x0ereserved_range\x18\t \x03(\x0b\x32..google.protobuf.DescriptorProto.ReservedRangeR\rreservedRange\x12#\n\rreserved_name\x18\n \x03(\tR\x0creservedName\x1az\n\x0e\x45xtensionRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\x12@\n\x07options\x18\x03 \x01(\x0b\x32&.google.protobuf.ExtensionRangeOptionsR\x07options\x1a\x37\n\rReservedRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\"\xcc\x04\n\x15\x45xtensionRangeOptions\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\x12Y\n\x0b\x64\x65\x63laration\x18\x02 \x03(\x0b\x32\x32.google.protobuf.ExtensionRangeOptions.DeclarationB\x03\x88\x01\x02R\x0b\x64\x65\x63laration\x12\x37\n\x08\x66\x65\x61tures\x18\x32 \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12m\n\x0cverification\x18\x03 \x01(\x0e\x32\x38.google.protobuf.ExtensionRangeOptions.VerificationState:\nUNVERIFIEDB\x03\x88\x01\x02R\x0cverification\x1a\x94\x01\n\x0b\x44\x65\x63laration\x12\x16\n\x06number\x18\x01 \x01(\x05R\x06number\x12\x1b\n\tfull_name\x18\x02 \x01(\tR\x08\x66ullName\x12\x12\n\x04type\x18\x03 \x01(\tR\x04type\x12\x1a\n\x08reserved\x18\x05 \x01(\x08R\x08reserved\x12\x1a\n\x08repeated\x18\x06 \x01(\x08R\x08repeatedJ\x04\x08\x04\x10\x05\"4\n\x11VerificationState\x12\x0f\n\x0b\x44\x45\x43LARATION\x10\x00\x12\x0e\n\nUNVERIFIED\x10\x01*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xc1\x06\n\x14\x46ieldDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n\x06number\x18\x03 \x01(\x05R\x06number\x12\x41\n\x05label\x18\x04 \x01(\x0e\x32+.google.protobuf.FieldDescriptorProto.LabelR\x05label\x12>\n\x04type\x18\x05 \x01(\x0e\x32*.google.protobuf.FieldDescriptorProto.TypeR\x04type\x12\x1b\n\ttype_name\x18\x06 \x01(\tR\x08typeName\x12\x1a\n\x08\x65xtendee\x18\x02 \x01(\tR\x08\x65xtendee\x12#\n\rdefault_value\x18\x07 \x01(\tR\x0c\x64\x65\x66\x61ultValue\x12\x1f\n\x0boneof_index\x18\t \x01(\x05R\noneofIndex\x12\x1b\n\tjson_name\x18\n \x01(\tR\x08jsonName\x12\x37\n\x07options\x18\x08 \x01(\x0b\x32\x1d.google.protobuf.FieldOptionsR\x07options\x12\'\n\x0fproto3_optional\x18\x11 \x01(\x08R\x0eproto3Optional\"\xb6\x02\n\x04Type\x12\x0f\n\x0bTYPE_DOUBLE\x10\x01\x12\x0e\n\nTYPE_FLOAT\x10\x02\x12\x0e\n\nTYPE_INT64\x10\x03\x12\x0f\n\x0bTYPE_UINT64\x10\x04\x12\x0e\n\nTYPE_INT32\x10\x05\x12\x10\n\x0cTYPE_FIXED64\x10\x06\x12\x10\n\x0cTYPE_FIXED32\x10\x07\x12\r\n\tTYPE_BOOL\x10\x08\x12\x0f\n\x0bTYPE_STRING\x10\t\x12\x0e\n\nTYPE_GROUP\x10\n\x12\x10\n\x0cTYPE_MESSAGE\x10\x0b\x12\x0e\n\nTYPE_BYTES\x10\x0c\x12\x0f\n\x0bTYPE_UINT32\x10\r\x12\r\n\tTYPE_ENUM\x10\x0e\x12\x11\n\rTYPE_SFIXED32\x10\x0f\x12\x11\n\rTYPE_SFIXED64\x10\x10\x12\x0f\n\x0bTYPE_SINT32\x10\x11\x12\x0f\n\x0bTYPE_SINT64\x10\x12\"C\n\x05Label\x12\x12\n\x0eLABEL_OPTIONAL\x10\x01\x12\x12\n\x0eLABEL_REPEATED\x10\x03\x12\x12\n\x0eLABEL_REQUIRED\x10\x02\"c\n\x14OneofDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x37\n\x07options\x18\x02 \x01(\x0b\x32\x1d.google.protobuf.OneofOptionsR\x07options\"\xe3\x02\n\x13\x45numDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12?\n\x05value\x18\x02 \x03(\x0b\x32).google.protobuf.EnumValueDescriptorProtoR\x05value\x12\x36\n\x07options\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.EnumOptionsR\x07options\x12]\n\x0ereserved_range\x18\x04 \x03(\x0b\x32\x36.google.protobuf.EnumDescriptorProto.EnumReservedRangeR\rreservedRange\x12#\n\rreserved_name\x18\x05 \x03(\tR\x0creservedName\x1a;\n\x11\x45numReservedRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\"\x83\x01\n\x18\x45numValueDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n\x06number\x18\x02 \x01(\x05R\x06number\x12;\n\x07options\x18\x03 \x01(\x0b\x32!.google.protobuf.EnumValueOptionsR\x07options\"\xa7\x01\n\x16ServiceDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12>\n\x06method\x18\x02 \x03(\x0b\x32&.google.protobuf.MethodDescriptorProtoR\x06method\x12\x39\n\x07options\x18\x03 \x01(\x0b\x32\x1f.google.protobuf.ServiceOptionsR\x07options\"\x89\x02\n\x15MethodDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x1d\n\ninput_type\x18\x02 \x01(\tR\tinputType\x12\x1f\n\x0boutput_type\x18\x03 \x01(\tR\noutputType\x12\x38\n\x07options\x18\x04 \x01(\x0b\x32\x1e.google.protobuf.MethodOptionsR\x07options\x12\x30\n\x10\x63lient_streaming\x18\x05 \x01(\x08:\x05\x66\x61lseR\x0f\x63lientStreaming\x12\x30\n\x10server_streaming\x18\x06 \x01(\x08:\x05\x66\x61lseR\x0fserverStreaming\"\xad\t\n\x0b\x46ileOptions\x12!\n\x0cjava_package\x18\x01 \x01(\tR\x0bjavaPackage\x12\x30\n\x14java_outer_classname\x18\x08 \x01(\tR\x12javaOuterClassname\x12\x35\n\x13java_multiple_files\x18\n \x01(\x08:\x05\x66\x61lseR\x11javaMultipleFiles\x12\x44\n\x1djava_generate_equals_and_hash\x18\x14 \x01(\x08\x42\x02\x18\x01R\x19javaGenerateEqualsAndHash\x12:\n\x16java_string_check_utf8\x18\x1b \x01(\x08:\x05\x66\x61lseR\x13javaStringCheckUtf8\x12S\n\x0coptimize_for\x18\t \x01(\x0e\x32).google.protobuf.FileOptions.OptimizeMode:\x05SPEEDR\x0boptimizeFor\x12\x1d\n\ngo_package\x18\x0b \x01(\tR\tgoPackage\x12\x35\n\x13\x63\x63_generic_services\x18\x10 \x01(\x08:\x05\x66\x61lseR\x11\x63\x63GenericServices\x12\x39\n\x15java_generic_services\x18\x11 \x01(\x08:\x05\x66\x61lseR\x13javaGenericServices\x12\x35\n\x13py_generic_services\x18\x12 \x01(\x08:\x05\x66\x61lseR\x11pyGenericServices\x12%\n\ndeprecated\x18\x17 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12.\n\x10\x63\x63_enable_arenas\x18\x1f \x01(\x08:\x04trueR\x0e\x63\x63\x45nableArenas\x12*\n\x11objc_class_prefix\x18$ \x01(\tR\x0fobjcClassPrefix\x12)\n\x10\x63sharp_namespace\x18% \x01(\tR\x0f\x63sharpNamespace\x12!\n\x0cswift_prefix\x18\' \x01(\tR\x0bswiftPrefix\x12(\n\x10php_class_prefix\x18( \x01(\tR\x0ephpClassPrefix\x12#\n\rphp_namespace\x18) \x01(\tR\x0cphpNamespace\x12\x34\n\x16php_metadata_namespace\x18, \x01(\tR\x14phpMetadataNamespace\x12!\n\x0cruby_package\x18- \x01(\tR\x0brubyPackage\x12\x37\n\x08\x66\x65\x61tures\x18\x32 \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\":\n\x0cOptimizeMode\x12\t\n\x05SPEED\x10\x01\x12\r\n\tCODE_SIZE\x10\x02\x12\x10\n\x0cLITE_RUNTIME\x10\x03*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08*\x10+J\x04\x08&\x10\'R\x14php_generic_services\"\xf4\x03\n\x0eMessageOptions\x12<\n\x17message_set_wire_format\x18\x01 \x01(\x08:\x05\x66\x61lseR\x14messageSetWireFormat\x12L\n\x1fno_standard_descriptor_accessor\x18\x02 \x01(\x08:\x05\x66\x61lseR\x1cnoStandardDescriptorAccessor\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12\x1b\n\tmap_entry\x18\x07 \x01(\x08R\x08mapEntry\x12V\n&deprecated_legacy_json_field_conflicts\x18\x0b \x01(\x08\x42\x02\x18\x01R\"deprecatedLegacyJsonFieldConflicts\x12\x37\n\x08\x66\x65\x61tures\x18\x0c \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x04\x10\x05J\x04\x08\x05\x10\x06J\x04\x08\x06\x10\x07J\x04\x08\x08\x10\tJ\x04\x08\t\x10\n\"\x9d\r\n\x0c\x46ieldOptions\x12\x41\n\x05\x63type\x18\x01 \x01(\x0e\x32#.google.protobuf.FieldOptions.CType:\x06STRINGR\x05\x63type\x12\x16\n\x06packed\x18\x02 \x01(\x08R\x06packed\x12G\n\x06jstype\x18\x06 \x01(\x0e\x32$.google.protobuf.FieldOptions.JSType:\tJS_NORMALR\x06jstype\x12\x19\n\x04lazy\x18\x05 \x01(\x08:\x05\x66\x61lseR\x04lazy\x12.\n\x0funverified_lazy\x18\x0f \x01(\x08:\x05\x66\x61lseR\x0eunverifiedLazy\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12\x19\n\x04weak\x18\n \x01(\x08:\x05\x66\x61lseR\x04weak\x12(\n\x0c\x64\x65\x62ug_redact\x18\x10 \x01(\x08:\x05\x66\x61lseR\x0b\x64\x65\x62ugRedact\x12K\n\tretention\x18\x11 \x01(\x0e\x32-.google.protobuf.FieldOptions.OptionRetentionR\tretention\x12H\n\x07targets\x18\x13 \x03(\x0e\x32..google.protobuf.FieldOptions.OptionTargetTypeR\x07targets\x12W\n\x10\x65\x64ition_defaults\x18\x14 \x03(\x0b\x32,.google.protobuf.FieldOptions.EditionDefaultR\x0f\x65\x64itionDefaults\x12\x37\n\x08\x66\x65\x61tures\x18\x15 \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12U\n\x0f\x66\x65\x61ture_support\x18\x16 \x01(\x0b\x32,.google.protobuf.FieldOptions.FeatureSupportR\x0e\x66\x65\x61tureSupport\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\x1aZ\n\x0e\x45\x64itionDefault\x12\x32\n\x07\x65\x64ition\x18\x03 \x01(\x0e\x32\x18.google.protobuf.EditionR\x07\x65\x64ition\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value\x1a\x96\x02\n\x0e\x46\x65\x61tureSupport\x12G\n\x12\x65\x64ition_introduced\x18\x01 \x01(\x0e\x32\x18.google.protobuf.EditionR\x11\x65\x64itionIntroduced\x12G\n\x12\x65\x64ition_deprecated\x18\x02 \x01(\x0e\x32\x18.google.protobuf.EditionR\x11\x65\x64itionDeprecated\x12/\n\x13\x64\x65precation_warning\x18\x03 \x01(\tR\x12\x64\x65precationWarning\x12\x41\n\x0f\x65\x64ition_removed\x18\x04 \x01(\x0e\x32\x18.google.protobuf.EditionR\x0e\x65\x64itionRemoved\"/\n\x05\x43Type\x12\n\n\x06STRING\x10\x00\x12\x08\n\x04\x43ORD\x10\x01\x12\x10\n\x0cSTRING_PIECE\x10\x02\"5\n\x06JSType\x12\r\n\tJS_NORMAL\x10\x00\x12\r\n\tJS_STRING\x10\x01\x12\r\n\tJS_NUMBER\x10\x02\"U\n\x0fOptionRetention\x12\x15\n\x11RETENTION_UNKNOWN\x10\x00\x12\x15\n\x11RETENTION_RUNTIME\x10\x01\x12\x14\n\x10RETENTION_SOURCE\x10\x02\"\x8c\x02\n\x10OptionTargetType\x12\x17\n\x13TARGET_TYPE_UNKNOWN\x10\x00\x12\x14\n\x10TARGET_TYPE_FILE\x10\x01\x12\x1f\n\x1bTARGET_TYPE_EXTENSION_RANGE\x10\x02\x12\x17\n\x13TARGET_TYPE_MESSAGE\x10\x03\x12\x15\n\x11TARGET_TYPE_FIELD\x10\x04\x12\x15\n\x11TARGET_TYPE_ONEOF\x10\x05\x12\x14\n\x10TARGET_TYPE_ENUM\x10\x06\x12\x1a\n\x16TARGET_TYPE_ENUM_ENTRY\x10\x07\x12\x17\n\x13TARGET_TYPE_SERVICE\x10\x08\x12\x16\n\x12TARGET_TYPE_METHOD\x10\t*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x04\x10\x05J\x04\x08\x12\x10\x13\"\xac\x01\n\x0cOneofOptions\x12\x37\n\x08\x66\x65\x61tures\x18\x01 \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xd1\x02\n\x0b\x45numOptions\x12\x1f\n\x0b\x61llow_alias\x18\x02 \x01(\x08R\nallowAlias\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12V\n&deprecated_legacy_json_field_conflicts\x18\x06 \x01(\x08\x42\x02\x18\x01R\"deprecatedLegacyJsonFieldConflicts\x12\x37\n\x08\x66\x65\x61tures\x18\x07 \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x05\x10\x06\"\xd8\x02\n\x10\x45numValueOptions\x12%\n\ndeprecated\x18\x01 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12\x37\n\x08\x66\x65\x61tures\x18\x02 \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12(\n\x0c\x64\x65\x62ug_redact\x18\x03 \x01(\x08:\x05\x66\x61lseR\x0b\x64\x65\x62ugRedact\x12U\n\x0f\x66\x65\x61ture_support\x18\x04 \x01(\x0b\x32,.google.protobuf.FieldOptions.FeatureSupportR\x0e\x66\x65\x61tureSupport\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xd5\x01\n\x0eServiceOptions\x12\x37\n\x08\x66\x65\x61tures\x18\" \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12%\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x99\x03\n\rMethodOptions\x12%\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12q\n\x11idempotency_level\x18\" \x01(\x0e\x32/.google.protobuf.MethodOptions.IdempotencyLevel:\x13IDEMPOTENCY_UNKNOWNR\x10idempotencyLevel\x12\x37\n\x08\x66\x65\x61tures\x18# \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\"P\n\x10IdempotencyLevel\x12\x17\n\x13IDEMPOTENCY_UNKNOWN\x10\x00\x12\x13\n\x0fNO_SIDE_EFFECTS\x10\x01\x12\x0e\n\nIDEMPOTENT\x10\x02*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x9a\x03\n\x13UninterpretedOption\x12\x41\n\x04name\x18\x02 \x03(\x0b\x32-.google.protobuf.UninterpretedOption.NamePartR\x04name\x12)\n\x10identifier_value\x18\x03 \x01(\tR\x0fidentifierValue\x12,\n\x12positive_int_value\x18\x04 \x01(\x04R\x10positiveIntValue\x12,\n\x12negative_int_value\x18\x05 \x01(\x03R\x10negativeIntValue\x12!\n\x0c\x64ouble_value\x18\x06 \x01(\x01R\x0b\x64oubleValue\x12!\n\x0cstring_value\x18\x07 \x01(\x0cR\x0bstringValue\x12\'\n\x0f\x61ggregate_value\x18\x08 \x01(\tR\x0e\x61ggregateValue\x1aJ\n\x08NamePart\x12\x1b\n\tname_part\x18\x01 \x02(\tR\x08namePart\x12!\n\x0cis_extension\x18\x02 \x02(\x08R\x0bisExtension\"\xa7\n\n\nFeatureSet\x12\x91\x01\n\x0e\x66ield_presence\x18\x01 \x01(\x0e\x32).google.protobuf.FeatureSet.FieldPresenceB?\x88\x01\x01\x98\x01\x04\x98\x01\x01\xa2\x01\r\x12\x08\x45XPLICIT\x18\xe6\x07\xa2\x01\r\x12\x08IMPLICIT\x18\xe7\x07\xa2\x01\r\x12\x08\x45XPLICIT\x18\xe8\x07\xb2\x01\x03\x08\xe8\x07R\rfieldPresence\x12l\n\tenum_type\x18\x02 \x01(\x0e\x32$.google.protobuf.FeatureSet.EnumTypeB)\x88\x01\x01\x98\x01\x06\x98\x01\x01\xa2\x01\x0b\x12\x06\x43LOSED\x18\xe6\x07\xa2\x01\t\x12\x04OPEN\x18\xe7\x07\xb2\x01\x03\x08\xe8\x07R\x08\x65numType\x12\x98\x01\n\x17repeated_field_encoding\x18\x03 \x01(\x0e\x32\x31.google.protobuf.FeatureSet.RepeatedFieldEncodingB-\x88\x01\x01\x98\x01\x04\x98\x01\x01\xa2\x01\r\x12\x08\x45XPANDED\x18\xe6\x07\xa2\x01\x0b\x12\x06PACKED\x18\xe7\x07\xb2\x01\x03\x08\xe8\x07R\x15repeatedFieldEncoding\x12~\n\x0futf8_validation\x18\x04 \x01(\x0e\x32*.google.protobuf.FeatureSet.Utf8ValidationB)\x88\x01\x01\x98\x01\x04\x98\x01\x01\xa2\x01\t\x12\x04NONE\x18\xe6\x07\xa2\x01\x0b\x12\x06VERIFY\x18\xe7\x07\xb2\x01\x03\x08\xe8\x07R\x0eutf8Validation\x12~\n\x10message_encoding\x18\x05 \x01(\x0e\x32+.google.protobuf.FeatureSet.MessageEncodingB&\x88\x01\x01\x98\x01\x04\x98\x01\x01\xa2\x01\x14\x12\x0fLENGTH_PREFIXED\x18\xe6\x07\xb2\x01\x03\x08\xe8\x07R\x0fmessageEncoding\x12\x82\x01\n\x0bjson_format\x18\x06 \x01(\x0e\x32&.google.protobuf.FeatureSet.JsonFormatB9\x88\x01\x01\x98\x01\x03\x98\x01\x06\x98\x01\x01\xa2\x01\x17\x12\x12LEGACY_BEST_EFFORT\x18\xe6\x07\xa2\x01\n\x12\x05\x41LLOW\x18\xe7\x07\xb2\x01\x03\x08\xe8\x07R\njsonFormat\"\\\n\rFieldPresence\x12\x1a\n\x16\x46IELD_PRESENCE_UNKNOWN\x10\x00\x12\x0c\n\x08\x45XPLICIT\x10\x01\x12\x0c\n\x08IMPLICIT\x10\x02\x12\x13\n\x0fLEGACY_REQUIRED\x10\x03\"7\n\x08\x45numType\x12\x15\n\x11\x45NUM_TYPE_UNKNOWN\x10\x00\x12\x08\n\x04OPEN\x10\x01\x12\n\n\x06\x43LOSED\x10\x02\"V\n\x15RepeatedFieldEncoding\x12#\n\x1fREPEATED_FIELD_ENCODING_UNKNOWN\x10\x00\x12\n\n\x06PACKED\x10\x01\x12\x0c\n\x08\x45XPANDED\x10\x02\"I\n\x0eUtf8Validation\x12\x1b\n\x17UTF8_VALIDATION_UNKNOWN\x10\x00\x12\n\n\x06VERIFY\x10\x02\x12\x08\n\x04NONE\x10\x03\"\x04\x08\x01\x10\x01\"S\n\x0fMessageEncoding\x12\x1c\n\x18MESSAGE_ENCODING_UNKNOWN\x10\x00\x12\x13\n\x0fLENGTH_PREFIXED\x10\x01\x12\r\n\tDELIMITED\x10\x02\"H\n\nJsonFormat\x12\x17\n\x13JSON_FORMAT_UNKNOWN\x10\x00\x12\t\n\x05\x41LLOW\x10\x01\x12\x16\n\x12LEGACY_BEST_EFFORT\x10\x02*\x06\x08\xe8\x07\x10\x8bN*\x06\x08\x8bN\x10\x90N*\x06\x08\x90N\x10\x91NJ\x06\x08\xe7\x07\x10\xe8\x07\"\xef\x03\n\x12\x46\x65\x61tureSetDefaults\x12X\n\x08\x64\x65\x66\x61ults\x18\x01 \x03(\x0b\x32<.google.protobuf.FeatureSetDefaults.FeatureSetEditionDefaultR\x08\x64\x65\x66\x61ults\x12\x41\n\x0fminimum_edition\x18\x04 \x01(\x0e\x32\x18.google.protobuf.EditionR\x0eminimumEdition\x12\x41\n\x0fmaximum_edition\x18\x05 \x01(\x0e\x32\x18.google.protobuf.EditionR\x0emaximumEdition\x1a\xf8\x01\n\x18\x46\x65\x61tureSetEditionDefault\x12\x32\n\x07\x65\x64ition\x18\x03 \x01(\x0e\x32\x18.google.protobuf.EditionR\x07\x65\x64ition\x12N\n\x14overridable_features\x18\x04 \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x13overridableFeatures\x12\x42\n\x0e\x66ixed_features\x18\x05 \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\rfixedFeaturesJ\x04\x08\x01\x10\x02J\x04\x08\x02\x10\x03R\x08\x66\x65\x61tures\"\xa7\x02\n\x0eSourceCodeInfo\x12\x44\n\x08location\x18\x01 \x03(\x0b\x32(.google.protobuf.SourceCodeInfo.LocationR\x08location\x1a\xce\x01\n\x08Location\x12\x16\n\x04path\x18\x01 \x03(\x05\x42\x02\x10\x01R\x04path\x12\x16\n\x04span\x18\x02 \x03(\x05\x42\x02\x10\x01R\x04span\x12)\n\x10leading_comments\x18\x03 \x01(\tR\x0fleadingComments\x12+\n\x11trailing_comments\x18\x04 \x01(\tR\x10trailingComments\x12:\n\x19leading_detached_comments\x18\x06 \x03(\tR\x17leadingDetachedComments\"\xd0\x02\n\x11GeneratedCodeInfo\x12M\n\nannotation\x18\x01 \x03(\x0b\x32-.google.protobuf.GeneratedCodeInfo.AnnotationR\nannotation\x1a\xeb\x01\n\nAnnotation\x12\x16\n\x04path\x18\x01 \x03(\x05\x42\x02\x10\x01R\x04path\x12\x1f\n\x0bsource_file\x18\x02 \x01(\tR\nsourceFile\x12\x14\n\x05\x62\x65gin\x18\x03 \x01(\x05R\x05\x62\x65gin\x12\x10\n\x03\x65nd\x18\x04 \x01(\x05R\x03\x65nd\x12R\n\x08semantic\x18\x05 \x01(\x0e\x32\x36.google.protobuf.GeneratedCodeInfo.Annotation.SemanticR\x08semantic\"(\n\x08Semantic\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03SET\x10\x01\x12\t\n\x05\x41LIAS\x10\x02*\xa7\x02\n\x07\x45\x64ition\x12\x13\n\x0f\x45\x44ITION_UNKNOWN\x10\x00\x12\x13\n\x0e\x45\x44ITION_LEGACY\x10\x84\x07\x12\x13\n\x0e\x45\x44ITION_PROTO2\x10\xe6\x07\x12\x13\n\x0e\x45\x44ITION_PROTO3\x10\xe7\x07\x12\x11\n\x0c\x45\x44ITION_2023\x10\xe8\x07\x12\x11\n\x0c\x45\x44ITION_2024\x10\xe9\x07\x12\x17\n\x13\x45\x44ITION_1_TEST_ONLY\x10\x01\x12\x17\n\x13\x45\x44ITION_2_TEST_ONLY\x10\x02\x12\x1d\n\x17\x45\x44ITION_99997_TEST_ONLY\x10\x9d\x8d\x06\x12\x1d\n\x17\x45\x44ITION_99998_TEST_ONLY\x10\x9e\x8d\x06\x12\x1d\n\x17\x45\x44ITION_99999_TEST_ONLY\x10\x9f\x8d\x06\x12\x13\n\x0b\x45\x44ITION_MAX\x10\xff\xff\xff\xff\x07\x42~\n\x13\x63om.google.protobufB\x10\x44\x65scriptorProtosH\x01Z-google.golang.org/protobuf/types/descriptorpb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1aGoogle.Protobuf.Reflection' + ) +else: + DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n google/protobuf/descriptor.proto\x12\x0fgoogle.protobuf\"M\n\x11\x46ileDescriptorSet\x12\x38\n\x04\x66ile\x18\x01 \x03(\x0b\x32$.google.protobuf.FileDescriptorProtoR\x04\x66ile\"\x98\x05\n\x13\x46ileDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n\x07package\x18\x02 \x01(\tR\x07package\x12\x1e\n\ndependency\x18\x03 \x03(\tR\ndependency\x12+\n\x11public_dependency\x18\n \x03(\x05R\x10publicDependency\x12\'\n\x0fweak_dependency\x18\x0b \x03(\x05R\x0eweakDependency\x12\x43\n\x0cmessage_type\x18\x04 \x03(\x0b\x32 .google.protobuf.DescriptorProtoR\x0bmessageType\x12\x41\n\tenum_type\x18\x05 \x03(\x0b\x32$.google.protobuf.EnumDescriptorProtoR\x08\x65numType\x12\x41\n\x07service\x18\x06 \x03(\x0b\x32\'.google.protobuf.ServiceDescriptorProtoR\x07service\x12\x43\n\textension\x18\x07 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\textension\x12\x36\n\x07options\x18\x08 \x01(\x0b\x32\x1c.google.protobuf.FileOptionsR\x07options\x12I\n\x10source_code_info\x18\t \x01(\x0b\x32\x1f.google.protobuf.SourceCodeInfoR\x0esourceCodeInfo\x12\x16\n\x06syntax\x18\x0c \x01(\tR\x06syntax\x12\x32\n\x07\x65\x64ition\x18\x0e \x01(\x0e\x32\x18.google.protobuf.EditionR\x07\x65\x64ition\"\xb9\x06\n\x0f\x44\x65scriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12;\n\x05\x66ield\x18\x02 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\x05\x66ield\x12\x43\n\textension\x18\x06 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\textension\x12\x41\n\x0bnested_type\x18\x03 \x03(\x0b\x32 .google.protobuf.DescriptorProtoR\nnestedType\x12\x41\n\tenum_type\x18\x04 \x03(\x0b\x32$.google.protobuf.EnumDescriptorProtoR\x08\x65numType\x12X\n\x0f\x65xtension_range\x18\x05 \x03(\x0b\x32/.google.protobuf.DescriptorProto.ExtensionRangeR\x0e\x65xtensionRange\x12\x44\n\noneof_decl\x18\x08 \x03(\x0b\x32%.google.protobuf.OneofDescriptorProtoR\toneofDecl\x12\x39\n\x07options\x18\x07 \x01(\x0b\x32\x1f.google.protobuf.MessageOptionsR\x07options\x12U\n\x0ereserved_range\x18\t \x03(\x0b\x32..google.protobuf.DescriptorProto.ReservedRangeR\rreservedRange\x12#\n\rreserved_name\x18\n \x03(\tR\x0creservedName\x1az\n\x0e\x45xtensionRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\x12@\n\x07options\x18\x03 \x01(\x0b\x32&.google.protobuf.ExtensionRangeOptionsR\x07options\x1a\x37\n\rReservedRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\"\xcc\x04\n\x15\x45xtensionRangeOptions\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\x12Y\n\x0b\x64\x65\x63laration\x18\x02 \x03(\x0b\x32\x32.google.protobuf.ExtensionRangeOptions.DeclarationB\x03\x88\x01\x02R\x0b\x64\x65\x63laration\x12\x37\n\x08\x66\x65\x61tures\x18\x32 \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12m\n\x0cverification\x18\x03 \x01(\x0e\x32\x38.google.protobuf.ExtensionRangeOptions.VerificationState:\nUNVERIFIEDB\x03\x88\x01\x02R\x0cverification\x1a\x94\x01\n\x0b\x44\x65\x63laration\x12\x16\n\x06number\x18\x01 \x01(\x05R\x06number\x12\x1b\n\tfull_name\x18\x02 \x01(\tR\x08\x66ullName\x12\x12\n\x04type\x18\x03 \x01(\tR\x04type\x12\x1a\n\x08reserved\x18\x05 \x01(\x08R\x08reserved\x12\x1a\n\x08repeated\x18\x06 \x01(\x08R\x08repeatedJ\x04\x08\x04\x10\x05\"4\n\x11VerificationState\x12\x0f\n\x0b\x44\x45\x43LARATION\x10\x00\x12\x0e\n\nUNVERIFIED\x10\x01*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xc1\x06\n\x14\x46ieldDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n\x06number\x18\x03 \x01(\x05R\x06number\x12\x41\n\x05label\x18\x04 \x01(\x0e\x32+.google.protobuf.FieldDescriptorProto.LabelR\x05label\x12>\n\x04type\x18\x05 \x01(\x0e\x32*.google.protobuf.FieldDescriptorProto.TypeR\x04type\x12\x1b\n\ttype_name\x18\x06 \x01(\tR\x08typeName\x12\x1a\n\x08\x65xtendee\x18\x02 \x01(\tR\x08\x65xtendee\x12#\n\rdefault_value\x18\x07 \x01(\tR\x0c\x64\x65\x66\x61ultValue\x12\x1f\n\x0boneof_index\x18\t \x01(\x05R\noneofIndex\x12\x1b\n\tjson_name\x18\n \x01(\tR\x08jsonName\x12\x37\n\x07options\x18\x08 \x01(\x0b\x32\x1d.google.protobuf.FieldOptionsR\x07options\x12\'\n\x0fproto3_optional\x18\x11 \x01(\x08R\x0eproto3Optional\"\xb6\x02\n\x04Type\x12\x0f\n\x0bTYPE_DOUBLE\x10\x01\x12\x0e\n\nTYPE_FLOAT\x10\x02\x12\x0e\n\nTYPE_INT64\x10\x03\x12\x0f\n\x0bTYPE_UINT64\x10\x04\x12\x0e\n\nTYPE_INT32\x10\x05\x12\x10\n\x0cTYPE_FIXED64\x10\x06\x12\x10\n\x0cTYPE_FIXED32\x10\x07\x12\r\n\tTYPE_BOOL\x10\x08\x12\x0f\n\x0bTYPE_STRING\x10\t\x12\x0e\n\nTYPE_GROUP\x10\n\x12\x10\n\x0cTYPE_MESSAGE\x10\x0b\x12\x0e\n\nTYPE_BYTES\x10\x0c\x12\x0f\n\x0bTYPE_UINT32\x10\r\x12\r\n\tTYPE_ENUM\x10\x0e\x12\x11\n\rTYPE_SFIXED32\x10\x0f\x12\x11\n\rTYPE_SFIXED64\x10\x10\x12\x0f\n\x0bTYPE_SINT32\x10\x11\x12\x0f\n\x0bTYPE_SINT64\x10\x12\"C\n\x05Label\x12\x12\n\x0eLABEL_OPTIONAL\x10\x01\x12\x12\n\x0eLABEL_REPEATED\x10\x03\x12\x12\n\x0eLABEL_REQUIRED\x10\x02\"c\n\x14OneofDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x37\n\x07options\x18\x02 \x01(\x0b\x32\x1d.google.protobuf.OneofOptionsR\x07options\"\xe3\x02\n\x13\x45numDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12?\n\x05value\x18\x02 \x03(\x0b\x32).google.protobuf.EnumValueDescriptorProtoR\x05value\x12\x36\n\x07options\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.EnumOptionsR\x07options\x12]\n\x0ereserved_range\x18\x04 \x03(\x0b\x32\x36.google.protobuf.EnumDescriptorProto.EnumReservedRangeR\rreservedRange\x12#\n\rreserved_name\x18\x05 \x03(\tR\x0creservedName\x1a;\n\x11\x45numReservedRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\"\x83\x01\n\x18\x45numValueDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n\x06number\x18\x02 \x01(\x05R\x06number\x12;\n\x07options\x18\x03 \x01(\x0b\x32!.google.protobuf.EnumValueOptionsR\x07options\"\xa7\x01\n\x16ServiceDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12>\n\x06method\x18\x02 \x03(\x0b\x32&.google.protobuf.MethodDescriptorProtoR\x06method\x12\x39\n\x07options\x18\x03 \x01(\x0b\x32\x1f.google.protobuf.ServiceOptionsR\x07options\"\x89\x02\n\x15MethodDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x1d\n\ninput_type\x18\x02 \x01(\tR\tinputType\x12\x1f\n\x0boutput_type\x18\x03 \x01(\tR\noutputType\x12\x38\n\x07options\x18\x04 \x01(\x0b\x32\x1e.google.protobuf.MethodOptionsR\x07options\x12\x30\n\x10\x63lient_streaming\x18\x05 \x01(\x08:\x05\x66\x61lseR\x0f\x63lientStreaming\x12\x30\n\x10server_streaming\x18\x06 \x01(\x08:\x05\x66\x61lseR\x0fserverStreaming\"\xad\t\n\x0b\x46ileOptions\x12!\n\x0cjava_package\x18\x01 \x01(\tR\x0bjavaPackage\x12\x30\n\x14java_outer_classname\x18\x08 \x01(\tR\x12javaOuterClassname\x12\x35\n\x13java_multiple_files\x18\n \x01(\x08:\x05\x66\x61lseR\x11javaMultipleFiles\x12\x44\n\x1djava_generate_equals_and_hash\x18\x14 \x01(\x08\x42\x02\x18\x01R\x19javaGenerateEqualsAndHash\x12:\n\x16java_string_check_utf8\x18\x1b \x01(\x08:\x05\x66\x61lseR\x13javaStringCheckUtf8\x12S\n\x0coptimize_for\x18\t \x01(\x0e\x32).google.protobuf.FileOptions.OptimizeMode:\x05SPEEDR\x0boptimizeFor\x12\x1d\n\ngo_package\x18\x0b \x01(\tR\tgoPackage\x12\x35\n\x13\x63\x63_generic_services\x18\x10 \x01(\x08:\x05\x66\x61lseR\x11\x63\x63GenericServices\x12\x39\n\x15java_generic_services\x18\x11 \x01(\x08:\x05\x66\x61lseR\x13javaGenericServices\x12\x35\n\x13py_generic_services\x18\x12 \x01(\x08:\x05\x66\x61lseR\x11pyGenericServices\x12%\n\ndeprecated\x18\x17 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12.\n\x10\x63\x63_enable_arenas\x18\x1f \x01(\x08:\x04trueR\x0e\x63\x63\x45nableArenas\x12*\n\x11objc_class_prefix\x18$ \x01(\tR\x0fobjcClassPrefix\x12)\n\x10\x63sharp_namespace\x18% \x01(\tR\x0f\x63sharpNamespace\x12!\n\x0cswift_prefix\x18\' \x01(\tR\x0bswiftPrefix\x12(\n\x10php_class_prefix\x18( \x01(\tR\x0ephpClassPrefix\x12#\n\rphp_namespace\x18) \x01(\tR\x0cphpNamespace\x12\x34\n\x16php_metadata_namespace\x18, \x01(\tR\x14phpMetadataNamespace\x12!\n\x0cruby_package\x18- \x01(\tR\x0brubyPackage\x12\x37\n\x08\x66\x65\x61tures\x18\x32 \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\":\n\x0cOptimizeMode\x12\t\n\x05SPEED\x10\x01\x12\r\n\tCODE_SIZE\x10\x02\x12\x10\n\x0cLITE_RUNTIME\x10\x03*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08*\x10+J\x04\x08&\x10\'R\x14php_generic_services\"\xf4\x03\n\x0eMessageOptions\x12<\n\x17message_set_wire_format\x18\x01 \x01(\x08:\x05\x66\x61lseR\x14messageSetWireFormat\x12L\n\x1fno_standard_descriptor_accessor\x18\x02 \x01(\x08:\x05\x66\x61lseR\x1cnoStandardDescriptorAccessor\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12\x1b\n\tmap_entry\x18\x07 \x01(\x08R\x08mapEntry\x12V\n&deprecated_legacy_json_field_conflicts\x18\x0b \x01(\x08\x42\x02\x18\x01R\"deprecatedLegacyJsonFieldConflicts\x12\x37\n\x08\x66\x65\x61tures\x18\x0c \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x04\x10\x05J\x04\x08\x05\x10\x06J\x04\x08\x06\x10\x07J\x04\x08\x08\x10\tJ\x04\x08\t\x10\n\"\x9d\r\n\x0c\x46ieldOptions\x12\x41\n\x05\x63type\x18\x01 \x01(\x0e\x32#.google.protobuf.FieldOptions.CType:\x06STRINGR\x05\x63type\x12\x16\n\x06packed\x18\x02 \x01(\x08R\x06packed\x12G\n\x06jstype\x18\x06 \x01(\x0e\x32$.google.protobuf.FieldOptions.JSType:\tJS_NORMALR\x06jstype\x12\x19\n\x04lazy\x18\x05 \x01(\x08:\x05\x66\x61lseR\x04lazy\x12.\n\x0funverified_lazy\x18\x0f \x01(\x08:\x05\x66\x61lseR\x0eunverifiedLazy\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12\x19\n\x04weak\x18\n \x01(\x08:\x05\x66\x61lseR\x04weak\x12(\n\x0c\x64\x65\x62ug_redact\x18\x10 \x01(\x08:\x05\x66\x61lseR\x0b\x64\x65\x62ugRedact\x12K\n\tretention\x18\x11 \x01(\x0e\x32-.google.protobuf.FieldOptions.OptionRetentionR\tretention\x12H\n\x07targets\x18\x13 \x03(\x0e\x32..google.protobuf.FieldOptions.OptionTargetTypeR\x07targets\x12W\n\x10\x65\x64ition_defaults\x18\x14 \x03(\x0b\x32,.google.protobuf.FieldOptions.EditionDefaultR\x0f\x65\x64itionDefaults\x12\x37\n\x08\x66\x65\x61tures\x18\x15 \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12U\n\x0f\x66\x65\x61ture_support\x18\x16 \x01(\x0b\x32,.google.protobuf.FieldOptions.FeatureSupportR\x0e\x66\x65\x61tureSupport\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\x1aZ\n\x0e\x45\x64itionDefault\x12\x32\n\x07\x65\x64ition\x18\x03 \x01(\x0e\x32\x18.google.protobuf.EditionR\x07\x65\x64ition\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value\x1a\x96\x02\n\x0e\x46\x65\x61tureSupport\x12G\n\x12\x65\x64ition_introduced\x18\x01 \x01(\x0e\x32\x18.google.protobuf.EditionR\x11\x65\x64itionIntroduced\x12G\n\x12\x65\x64ition_deprecated\x18\x02 \x01(\x0e\x32\x18.google.protobuf.EditionR\x11\x65\x64itionDeprecated\x12/\n\x13\x64\x65precation_warning\x18\x03 \x01(\tR\x12\x64\x65precationWarning\x12\x41\n\x0f\x65\x64ition_removed\x18\x04 \x01(\x0e\x32\x18.google.protobuf.EditionR\x0e\x65\x64itionRemoved\"/\n\x05\x43Type\x12\n\n\x06STRING\x10\x00\x12\x08\n\x04\x43ORD\x10\x01\x12\x10\n\x0cSTRING_PIECE\x10\x02\"5\n\x06JSType\x12\r\n\tJS_NORMAL\x10\x00\x12\r\n\tJS_STRING\x10\x01\x12\r\n\tJS_NUMBER\x10\x02\"U\n\x0fOptionRetention\x12\x15\n\x11RETENTION_UNKNOWN\x10\x00\x12\x15\n\x11RETENTION_RUNTIME\x10\x01\x12\x14\n\x10RETENTION_SOURCE\x10\x02\"\x8c\x02\n\x10OptionTargetType\x12\x17\n\x13TARGET_TYPE_UNKNOWN\x10\x00\x12\x14\n\x10TARGET_TYPE_FILE\x10\x01\x12\x1f\n\x1bTARGET_TYPE_EXTENSION_RANGE\x10\x02\x12\x17\n\x13TARGET_TYPE_MESSAGE\x10\x03\x12\x15\n\x11TARGET_TYPE_FIELD\x10\x04\x12\x15\n\x11TARGET_TYPE_ONEOF\x10\x05\x12\x14\n\x10TARGET_TYPE_ENUM\x10\x06\x12\x1a\n\x16TARGET_TYPE_ENUM_ENTRY\x10\x07\x12\x17\n\x13TARGET_TYPE_SERVICE\x10\x08\x12\x16\n\x12TARGET_TYPE_METHOD\x10\t*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x04\x10\x05J\x04\x08\x12\x10\x13\"\xac\x01\n\x0cOneofOptions\x12\x37\n\x08\x66\x65\x61tures\x18\x01 \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xd1\x02\n\x0b\x45numOptions\x12\x1f\n\x0b\x61llow_alias\x18\x02 \x01(\x08R\nallowAlias\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12V\n&deprecated_legacy_json_field_conflicts\x18\x06 \x01(\x08\x42\x02\x18\x01R\"deprecatedLegacyJsonFieldConflicts\x12\x37\n\x08\x66\x65\x61tures\x18\x07 \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x05\x10\x06\"\xd8\x02\n\x10\x45numValueOptions\x12%\n\ndeprecated\x18\x01 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12\x37\n\x08\x66\x65\x61tures\x18\x02 \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12(\n\x0c\x64\x65\x62ug_redact\x18\x03 \x01(\x08:\x05\x66\x61lseR\x0b\x64\x65\x62ugRedact\x12U\n\x0f\x66\x65\x61ture_support\x18\x04 \x01(\x0b\x32,.google.protobuf.FieldOptions.FeatureSupportR\x0e\x66\x65\x61tureSupport\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xd5\x01\n\x0eServiceOptions\x12\x37\n\x08\x66\x65\x61tures\x18\" \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12%\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x99\x03\n\rMethodOptions\x12%\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12q\n\x11idempotency_level\x18\" \x01(\x0e\x32/.google.protobuf.MethodOptions.IdempotencyLevel:\x13IDEMPOTENCY_UNKNOWNR\x10idempotencyLevel\x12\x37\n\x08\x66\x65\x61tures\x18# \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x08\x66\x65\x61tures\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\"P\n\x10IdempotencyLevel\x12\x17\n\x13IDEMPOTENCY_UNKNOWN\x10\x00\x12\x13\n\x0fNO_SIDE_EFFECTS\x10\x01\x12\x0e\n\nIDEMPOTENT\x10\x02*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x9a\x03\n\x13UninterpretedOption\x12\x41\n\x04name\x18\x02 \x03(\x0b\x32-.google.protobuf.UninterpretedOption.NamePartR\x04name\x12)\n\x10identifier_value\x18\x03 \x01(\tR\x0fidentifierValue\x12,\n\x12positive_int_value\x18\x04 \x01(\x04R\x10positiveIntValue\x12,\n\x12negative_int_value\x18\x05 \x01(\x03R\x10negativeIntValue\x12!\n\x0c\x64ouble_value\x18\x06 \x01(\x01R\x0b\x64oubleValue\x12!\n\x0cstring_value\x18\x07 \x01(\x0cR\x0bstringValue\x12\'\n\x0f\x61ggregate_value\x18\x08 \x01(\tR\x0e\x61ggregateValue\x1aJ\n\x08NamePart\x12\x1b\n\tname_part\x18\x01 \x02(\tR\x08namePart\x12!\n\x0cis_extension\x18\x02 \x02(\x08R\x0bisExtension\"\xa7\n\n\nFeatureSet\x12\x91\x01\n\x0e\x66ield_presence\x18\x01 \x01(\x0e\x32).google.protobuf.FeatureSet.FieldPresenceB?\x88\x01\x01\x98\x01\x04\x98\x01\x01\xa2\x01\r\x12\x08\x45XPLICIT\x18\xe6\x07\xa2\x01\r\x12\x08IMPLICIT\x18\xe7\x07\xa2\x01\r\x12\x08\x45XPLICIT\x18\xe8\x07\xb2\x01\x03\x08\xe8\x07R\rfieldPresence\x12l\n\tenum_type\x18\x02 \x01(\x0e\x32$.google.protobuf.FeatureSet.EnumTypeB)\x88\x01\x01\x98\x01\x06\x98\x01\x01\xa2\x01\x0b\x12\x06\x43LOSED\x18\xe6\x07\xa2\x01\t\x12\x04OPEN\x18\xe7\x07\xb2\x01\x03\x08\xe8\x07R\x08\x65numType\x12\x98\x01\n\x17repeated_field_encoding\x18\x03 \x01(\x0e\x32\x31.google.protobuf.FeatureSet.RepeatedFieldEncodingB-\x88\x01\x01\x98\x01\x04\x98\x01\x01\xa2\x01\r\x12\x08\x45XPANDED\x18\xe6\x07\xa2\x01\x0b\x12\x06PACKED\x18\xe7\x07\xb2\x01\x03\x08\xe8\x07R\x15repeatedFieldEncoding\x12~\n\x0futf8_validation\x18\x04 \x01(\x0e\x32*.google.protobuf.FeatureSet.Utf8ValidationB)\x88\x01\x01\x98\x01\x04\x98\x01\x01\xa2\x01\t\x12\x04NONE\x18\xe6\x07\xa2\x01\x0b\x12\x06VERIFY\x18\xe7\x07\xb2\x01\x03\x08\xe8\x07R\x0eutf8Validation\x12~\n\x10message_encoding\x18\x05 \x01(\x0e\x32+.google.protobuf.FeatureSet.MessageEncodingB&\x88\x01\x01\x98\x01\x04\x98\x01\x01\xa2\x01\x14\x12\x0fLENGTH_PREFIXED\x18\xe6\x07\xb2\x01\x03\x08\xe8\x07R\x0fmessageEncoding\x12\x82\x01\n\x0bjson_format\x18\x06 \x01(\x0e\x32&.google.protobuf.FeatureSet.JsonFormatB9\x88\x01\x01\x98\x01\x03\x98\x01\x06\x98\x01\x01\xa2\x01\x17\x12\x12LEGACY_BEST_EFFORT\x18\xe6\x07\xa2\x01\n\x12\x05\x41LLOW\x18\xe7\x07\xb2\x01\x03\x08\xe8\x07R\njsonFormat\"\\\n\rFieldPresence\x12\x1a\n\x16\x46IELD_PRESENCE_UNKNOWN\x10\x00\x12\x0c\n\x08\x45XPLICIT\x10\x01\x12\x0c\n\x08IMPLICIT\x10\x02\x12\x13\n\x0fLEGACY_REQUIRED\x10\x03\"7\n\x08\x45numType\x12\x15\n\x11\x45NUM_TYPE_UNKNOWN\x10\x00\x12\x08\n\x04OPEN\x10\x01\x12\n\n\x06\x43LOSED\x10\x02\"V\n\x15RepeatedFieldEncoding\x12#\n\x1fREPEATED_FIELD_ENCODING_UNKNOWN\x10\x00\x12\n\n\x06PACKED\x10\x01\x12\x0c\n\x08\x45XPANDED\x10\x02\"I\n\x0eUtf8Validation\x12\x1b\n\x17UTF8_VALIDATION_UNKNOWN\x10\x00\x12\n\n\x06VERIFY\x10\x02\x12\x08\n\x04NONE\x10\x03\"\x04\x08\x01\x10\x01\"S\n\x0fMessageEncoding\x12\x1c\n\x18MESSAGE_ENCODING_UNKNOWN\x10\x00\x12\x13\n\x0fLENGTH_PREFIXED\x10\x01\x12\r\n\tDELIMITED\x10\x02\"H\n\nJsonFormat\x12\x17\n\x13JSON_FORMAT_UNKNOWN\x10\x00\x12\t\n\x05\x41LLOW\x10\x01\x12\x16\n\x12LEGACY_BEST_EFFORT\x10\x02*\x06\x08\xe8\x07\x10\x8bN*\x06\x08\x8bN\x10\x90N*\x06\x08\x90N\x10\x91NJ\x06\x08\xe7\x07\x10\xe8\x07\"\xef\x03\n\x12\x46\x65\x61tureSetDefaults\x12X\n\x08\x64\x65\x66\x61ults\x18\x01 \x03(\x0b\x32<.google.protobuf.FeatureSetDefaults.FeatureSetEditionDefaultR\x08\x64\x65\x66\x61ults\x12\x41\n\x0fminimum_edition\x18\x04 \x01(\x0e\x32\x18.google.protobuf.EditionR\x0eminimumEdition\x12\x41\n\x0fmaximum_edition\x18\x05 \x01(\x0e\x32\x18.google.protobuf.EditionR\x0emaximumEdition\x1a\xf8\x01\n\x18\x46\x65\x61tureSetEditionDefault\x12\x32\n\x07\x65\x64ition\x18\x03 \x01(\x0e\x32\x18.google.protobuf.EditionR\x07\x65\x64ition\x12N\n\x14overridable_features\x18\x04 \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\x13overridableFeatures\x12\x42\n\x0e\x66ixed_features\x18\x05 \x01(\x0b\x32\x1b.google.protobuf.FeatureSetR\rfixedFeaturesJ\x04\x08\x01\x10\x02J\x04\x08\x02\x10\x03R\x08\x66\x65\x61tures\"\xa7\x02\n\x0eSourceCodeInfo\x12\x44\n\x08location\x18\x01 \x03(\x0b\x32(.google.protobuf.SourceCodeInfo.LocationR\x08location\x1a\xce\x01\n\x08Location\x12\x16\n\x04path\x18\x01 \x03(\x05\x42\x02\x10\x01R\x04path\x12\x16\n\x04span\x18\x02 \x03(\x05\x42\x02\x10\x01R\x04span\x12)\n\x10leading_comments\x18\x03 \x01(\tR\x0fleadingComments\x12+\n\x11trailing_comments\x18\x04 \x01(\tR\x10trailingComments\x12:\n\x19leading_detached_comments\x18\x06 \x03(\tR\x17leadingDetachedComments\"\xd0\x02\n\x11GeneratedCodeInfo\x12M\n\nannotation\x18\x01 \x03(\x0b\x32-.google.protobuf.GeneratedCodeInfo.AnnotationR\nannotation\x1a\xeb\x01\n\nAnnotation\x12\x16\n\x04path\x18\x01 \x03(\x05\x42\x02\x10\x01R\x04path\x12\x1f\n\x0bsource_file\x18\x02 \x01(\tR\nsourceFile\x12\x14\n\x05\x62\x65gin\x18\x03 \x01(\x05R\x05\x62\x65gin\x12\x10\n\x03\x65nd\x18\x04 \x01(\x05R\x03\x65nd\x12R\n\x08semantic\x18\x05 \x01(\x0e\x32\x36.google.protobuf.GeneratedCodeInfo.Annotation.SemanticR\x08semantic\"(\n\x08Semantic\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03SET\x10\x01\x12\t\n\x05\x41LIAS\x10\x02*\xa7\x02\n\x07\x45\x64ition\x12\x13\n\x0f\x45\x44ITION_UNKNOWN\x10\x00\x12\x13\n\x0e\x45\x44ITION_LEGACY\x10\x84\x07\x12\x13\n\x0e\x45\x44ITION_PROTO2\x10\xe6\x07\x12\x13\n\x0e\x45\x44ITION_PROTO3\x10\xe7\x07\x12\x11\n\x0c\x45\x44ITION_2023\x10\xe8\x07\x12\x11\n\x0c\x45\x44ITION_2024\x10\xe9\x07\x12\x17\n\x13\x45\x44ITION_1_TEST_ONLY\x10\x01\x12\x17\n\x13\x45\x44ITION_2_TEST_ONLY\x10\x02\x12\x1d\n\x17\x45\x44ITION_99997_TEST_ONLY\x10\x9d\x8d\x06\x12\x1d\n\x17\x45\x44ITION_99998_TEST_ONLY\x10\x9e\x8d\x06\x12\x1d\n\x17\x45\x44ITION_99999_TEST_ONLY\x10\x9f\x8d\x06\x12\x13\n\x0b\x45\x44ITION_MAX\x10\xff\xff\xff\xff\x07\x42~\n\x13\x63om.google.protobufB\x10\x44\x65scriptorProtosH\x01Z-google.golang.org/protobuf/types/descriptorpb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1aGoogle.Protobuf.Reflection') + +_globals = globals() +if not _descriptor._USE_C_DESCRIPTORS: + _EDITION = _descriptor.EnumDescriptor( + name='Edition', + full_name='google.protobuf.Edition', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='EDITION_UNKNOWN', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='EDITION_LEGACY', index=1, number=900, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='EDITION_PROTO2', index=2, number=998, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='EDITION_PROTO3', index=3, number=999, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='EDITION_2023', index=4, number=1000, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='EDITION_2024', index=5, number=1001, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='EDITION_1_TEST_ONLY', index=6, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='EDITION_2_TEST_ONLY', index=7, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='EDITION_99997_TEST_ONLY', index=8, number=99997, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='EDITION_99998_TEST_ONLY', index=9, number=99998, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='EDITION_99999_TEST_ONLY', index=10, number=99999, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='EDITION_MAX', index=11, number=2147483647, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_EDITION) + + _EXTENSIONRANGEOPTIONS_VERIFICATIONSTATE = _descriptor.EnumDescriptor( + name='VerificationState', + full_name='google.protobuf.ExtensionRangeOptions.VerificationState', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='DECLARATION', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='UNVERIFIED', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_EXTENSIONRANGEOPTIONS_VERIFICATIONSTATE) + + _FIELDDESCRIPTORPROTO_TYPE = _descriptor.EnumDescriptor( + name='Type', + full_name='google.protobuf.FieldDescriptorProto.Type', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='TYPE_DOUBLE', index=0, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_FLOAT', index=1, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_INT64', index=2, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_UINT64', index=3, number=4, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_INT32', index=4, number=5, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_FIXED64', index=5, number=6, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_FIXED32', index=6, number=7, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_BOOL', index=7, number=8, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_STRING', index=8, number=9, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_GROUP', index=9, number=10, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_MESSAGE', index=10, number=11, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_BYTES', index=11, number=12, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_UINT32', index=12, number=13, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_ENUM', index=13, number=14, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_SFIXED32', index=14, number=15, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_SFIXED64', index=15, number=16, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_SINT32', index=16, number=17, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TYPE_SINT64', index=17, number=18, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_FIELDDESCRIPTORPROTO_TYPE) + + _FIELDDESCRIPTORPROTO_LABEL = _descriptor.EnumDescriptor( + name='Label', + full_name='google.protobuf.FieldDescriptorProto.Label', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='LABEL_OPTIONAL', index=0, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='LABEL_REPEATED', index=1, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='LABEL_REQUIRED', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_FIELDDESCRIPTORPROTO_LABEL) + + _FILEOPTIONS_OPTIMIZEMODE = _descriptor.EnumDescriptor( + name='OptimizeMode', + full_name='google.protobuf.FileOptions.OptimizeMode', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='SPEED', index=0, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='CODE_SIZE', index=1, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='LITE_RUNTIME', index=2, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_FILEOPTIONS_OPTIMIZEMODE) + + _FIELDOPTIONS_CTYPE = _descriptor.EnumDescriptor( + name='CType', + full_name='google.protobuf.FieldOptions.CType', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='STRING', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='CORD', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='STRING_PIECE', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_FIELDOPTIONS_CTYPE) + + _FIELDOPTIONS_JSTYPE = _descriptor.EnumDescriptor( + name='JSType', + full_name='google.protobuf.FieldOptions.JSType', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='JS_NORMAL', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='JS_STRING', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='JS_NUMBER', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_FIELDOPTIONS_JSTYPE) + + _FIELDOPTIONS_OPTIONRETENTION = _descriptor.EnumDescriptor( + name='OptionRetention', + full_name='google.protobuf.FieldOptions.OptionRetention', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='RETENTION_UNKNOWN', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='RETENTION_RUNTIME', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='RETENTION_SOURCE', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_FIELDOPTIONS_OPTIONRETENTION) + + _FIELDOPTIONS_OPTIONTARGETTYPE = _descriptor.EnumDescriptor( + name='OptionTargetType', + full_name='google.protobuf.FieldOptions.OptionTargetType', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_UNKNOWN', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_FILE', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_EXTENSION_RANGE', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_MESSAGE', index=3, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_FIELD', index=4, number=4, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_ONEOF', index=5, number=5, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_ENUM', index=6, number=6, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_ENUM_ENTRY', index=7, number=7, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_SERVICE', index=8, number=8, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_METHOD', index=9, number=9, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_FIELDOPTIONS_OPTIONTARGETTYPE) + + _METHODOPTIONS_IDEMPOTENCYLEVEL = _descriptor.EnumDescriptor( + name='IdempotencyLevel', + full_name='google.protobuf.MethodOptions.IdempotencyLevel', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='IDEMPOTENCY_UNKNOWN', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='NO_SIDE_EFFECTS', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='IDEMPOTENT', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_METHODOPTIONS_IDEMPOTENCYLEVEL) + + _FEATURESET_FIELDPRESENCE = _descriptor.EnumDescriptor( + name='FieldPresence', + full_name='google.protobuf.FeatureSet.FieldPresence', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='FIELD_PRESENCE_UNKNOWN', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='EXPLICIT', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='IMPLICIT', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='LEGACY_REQUIRED', index=3, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_FEATURESET_FIELDPRESENCE) + + _FEATURESET_ENUMTYPE = _descriptor.EnumDescriptor( + name='EnumType', + full_name='google.protobuf.FeatureSet.EnumType', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='ENUM_TYPE_UNKNOWN', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='OPEN', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='CLOSED', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_FEATURESET_ENUMTYPE) + + _FEATURESET_REPEATEDFIELDENCODING = _descriptor.EnumDescriptor( + name='RepeatedFieldEncoding', + full_name='google.protobuf.FeatureSet.RepeatedFieldEncoding', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='REPEATED_FIELD_ENCODING_UNKNOWN', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='PACKED', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='EXPANDED', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_FEATURESET_REPEATEDFIELDENCODING) + + _FEATURESET_UTF8VALIDATION = _descriptor.EnumDescriptor( + name='Utf8Validation', + full_name='google.protobuf.FeatureSet.Utf8Validation', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='UTF8_VALIDATION_UNKNOWN', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='VERIFY', index=1, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='NONE', index=2, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_FEATURESET_UTF8VALIDATION) + + _FEATURESET_MESSAGEENCODING = _descriptor.EnumDescriptor( + name='MessageEncoding', + full_name='google.protobuf.FeatureSet.MessageEncoding', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='MESSAGE_ENCODING_UNKNOWN', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='LENGTH_PREFIXED', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='DELIMITED', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_FEATURESET_MESSAGEENCODING) + + _FEATURESET_JSONFORMAT = _descriptor.EnumDescriptor( + name='JsonFormat', + full_name='google.protobuf.FeatureSet.JsonFormat', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='JSON_FORMAT_UNKNOWN', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ALLOW', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='LEGACY_BEST_EFFORT', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_FEATURESET_JSONFORMAT) + + _GENERATEDCODEINFO_ANNOTATION_SEMANTIC = _descriptor.EnumDescriptor( + name='Semantic', + full_name='google.protobuf.GeneratedCodeInfo.Annotation.Semantic', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='NONE', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='SET', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ALIAS', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_GENERATEDCODEINFO_ANNOTATION_SEMANTIC) + + + _FILEDESCRIPTORSET = _descriptor.Descriptor( + name='FileDescriptorSet', + full_name='google.protobuf.FileDescriptorSet', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='file', full_name='google.protobuf.FileDescriptorSet.file', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='file', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + + _FILEDESCRIPTORPROTO = _descriptor.Descriptor( + name='FileDescriptorProto', + full_name='google.protobuf.FileDescriptorProto', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='google.protobuf.FileDescriptorProto.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='name', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='package', full_name='google.protobuf.FileDescriptorProto.package', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='package', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='dependency', full_name='google.protobuf.FileDescriptorProto.dependency', index=2, + number=3, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='dependency', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='public_dependency', full_name='google.protobuf.FileDescriptorProto.public_dependency', index=3, + number=10, type=5, cpp_type=1, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='publicDependency', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='weak_dependency', full_name='google.protobuf.FileDescriptorProto.weak_dependency', index=4, + number=11, type=5, cpp_type=1, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='weakDependency', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='message_type', full_name='google.protobuf.FileDescriptorProto.message_type', index=5, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='messageType', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='enum_type', full_name='google.protobuf.FileDescriptorProto.enum_type', index=6, + number=5, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='enumType', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='service', full_name='google.protobuf.FileDescriptorProto.service', index=7, + number=6, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='service', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='extension', full_name='google.protobuf.FileDescriptorProto.extension', index=8, + number=7, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='extension', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='options', full_name='google.protobuf.FileDescriptorProto.options', index=9, + number=8, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='options', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='source_code_info', full_name='google.protobuf.FileDescriptorProto.source_code_info', index=10, + number=9, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='sourceCodeInfo', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='syntax', full_name='google.protobuf.FileDescriptorProto.syntax', index=11, + number=12, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='syntax', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='edition', full_name='google.protobuf.FileDescriptorProto.edition', index=12, + number=14, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='edition', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + + _DESCRIPTORPROTO_EXTENSIONRANGE = _descriptor.Descriptor( + name='ExtensionRange', + full_name='google.protobuf.DescriptorProto.ExtensionRange', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='start', full_name='google.protobuf.DescriptorProto.ExtensionRange.start', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='start', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='end', full_name='google.protobuf.DescriptorProto.ExtensionRange.end', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='end', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='options', full_name='google.protobuf.DescriptorProto.ExtensionRange.options', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='options', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + _DESCRIPTORPROTO_RESERVEDRANGE = _descriptor.Descriptor( + name='ReservedRange', + full_name='google.protobuf.DescriptorProto.ReservedRange', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='start', full_name='google.protobuf.DescriptorProto.ReservedRange.start', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='start', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='end', full_name='google.protobuf.DescriptorProto.ReservedRange.end', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='end', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + _DESCRIPTORPROTO = _descriptor.Descriptor( + name='DescriptorProto', + full_name='google.protobuf.DescriptorProto', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='google.protobuf.DescriptorProto.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='name', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='field', full_name='google.protobuf.DescriptorProto.field', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='field', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='extension', full_name='google.protobuf.DescriptorProto.extension', index=2, + number=6, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='extension', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='nested_type', full_name='google.protobuf.DescriptorProto.nested_type', index=3, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='nestedType', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='enum_type', full_name='google.protobuf.DescriptorProto.enum_type', index=4, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='enumType', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='extension_range', full_name='google.protobuf.DescriptorProto.extension_range', index=5, + number=5, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='extensionRange', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='oneof_decl', full_name='google.protobuf.DescriptorProto.oneof_decl', index=6, + number=8, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='oneofDecl', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='options', full_name='google.protobuf.DescriptorProto.options', index=7, + number=7, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='options', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='reserved_range', full_name='google.protobuf.DescriptorProto.reserved_range', index=8, + number=9, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='reservedRange', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='reserved_name', full_name='google.protobuf.DescriptorProto.reserved_name', index=9, + number=10, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='reservedName', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_DESCRIPTORPROTO_EXTENSIONRANGE, _DESCRIPTORPROTO_RESERVEDRANGE, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + + _EXTENSIONRANGEOPTIONS_DECLARATION = _descriptor.Descriptor( + name='Declaration', + full_name='google.protobuf.ExtensionRangeOptions.Declaration', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='number', full_name='google.protobuf.ExtensionRangeOptions.Declaration.number', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='number', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='full_name', full_name='google.protobuf.ExtensionRangeOptions.Declaration.full_name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='fullName', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='type', full_name='google.protobuf.ExtensionRangeOptions.Declaration.type', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='type', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='reserved', full_name='google.protobuf.ExtensionRangeOptions.Declaration.reserved', index=3, + number=5, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='reserved', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='repeated', full_name='google.protobuf.ExtensionRangeOptions.Declaration.repeated', index=4, + number=6, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='repeated', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + _EXTENSIONRANGEOPTIONS = _descriptor.Descriptor( + name='ExtensionRangeOptions', + full_name='google.protobuf.ExtensionRangeOptions', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='uninterpreted_option', full_name='google.protobuf.ExtensionRangeOptions.uninterpreted_option', index=0, + number=999, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='uninterpretedOption', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='declaration', full_name='google.protobuf.ExtensionRangeOptions.declaration', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=b'\210\001\002', json_name='declaration', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='features', full_name='google.protobuf.ExtensionRangeOptions.features', index=2, + number=50, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='features', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='verification', full_name='google.protobuf.ExtensionRangeOptions.verification', index=3, + number=3, type=14, cpp_type=8, label=1, + has_default_value=True, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=b'\210\001\002', json_name='verification', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_EXTENSIONRANGEOPTIONS_DECLARATION, ], + enum_types=[ + _EXTENSIONRANGEOPTIONS_VERIFICATIONSTATE, + ], + serialized_options=None, + is_extendable=True, + extension_ranges=[(1000, 536870912), ], + oneofs=[ + ], + ) + + + _FIELDDESCRIPTORPROTO = _descriptor.Descriptor( + name='FieldDescriptorProto', + full_name='google.protobuf.FieldDescriptorProto', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='google.protobuf.FieldDescriptorProto.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='name', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='number', full_name='google.protobuf.FieldDescriptorProto.number', index=1, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='number', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='label', full_name='google.protobuf.FieldDescriptorProto.label', index=2, + number=4, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='label', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='type', full_name='google.protobuf.FieldDescriptorProto.type', index=3, + number=5, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='type', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='type_name', full_name='google.protobuf.FieldDescriptorProto.type_name', index=4, + number=6, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='typeName', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='extendee', full_name='google.protobuf.FieldDescriptorProto.extendee', index=5, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='extendee', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='default_value', full_name='google.protobuf.FieldDescriptorProto.default_value', index=6, + number=7, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='defaultValue', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='oneof_index', full_name='google.protobuf.FieldDescriptorProto.oneof_index', index=7, + number=9, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='oneofIndex', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='json_name', full_name='google.protobuf.FieldDescriptorProto.json_name', index=8, + number=10, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='jsonName', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='options', full_name='google.protobuf.FieldDescriptorProto.options', index=9, + number=8, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='options', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='proto3_optional', full_name='google.protobuf.FieldDescriptorProto.proto3_optional', index=10, + number=17, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='proto3Optional', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _FIELDDESCRIPTORPROTO_TYPE, + _FIELDDESCRIPTORPROTO_LABEL, + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + + _ONEOFDESCRIPTORPROTO = _descriptor.Descriptor( + name='OneofDescriptorProto', + full_name='google.protobuf.OneofDescriptorProto', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='google.protobuf.OneofDescriptorProto.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='name', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='options', full_name='google.protobuf.OneofDescriptorProto.options', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='options', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + + _ENUMDESCRIPTORPROTO_ENUMRESERVEDRANGE = _descriptor.Descriptor( + name='EnumReservedRange', + full_name='google.protobuf.EnumDescriptorProto.EnumReservedRange', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='start', full_name='google.protobuf.EnumDescriptorProto.EnumReservedRange.start', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='start', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='end', full_name='google.protobuf.EnumDescriptorProto.EnumReservedRange.end', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='end', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + _ENUMDESCRIPTORPROTO = _descriptor.Descriptor( + name='EnumDescriptorProto', + full_name='google.protobuf.EnumDescriptorProto', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='google.protobuf.EnumDescriptorProto.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='name', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='value', full_name='google.protobuf.EnumDescriptorProto.value', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='value', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='options', full_name='google.protobuf.EnumDescriptorProto.options', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='options', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='reserved_range', full_name='google.protobuf.EnumDescriptorProto.reserved_range', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='reservedRange', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='reserved_name', full_name='google.protobuf.EnumDescriptorProto.reserved_name', index=4, + number=5, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='reservedName', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_ENUMDESCRIPTORPROTO_ENUMRESERVEDRANGE, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + + _ENUMVALUEDESCRIPTORPROTO = _descriptor.Descriptor( + name='EnumValueDescriptorProto', + full_name='google.protobuf.EnumValueDescriptorProto', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='google.protobuf.EnumValueDescriptorProto.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='name', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='number', full_name='google.protobuf.EnumValueDescriptorProto.number', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='number', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='options', full_name='google.protobuf.EnumValueDescriptorProto.options', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='options', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + + _SERVICEDESCRIPTORPROTO = _descriptor.Descriptor( + name='ServiceDescriptorProto', + full_name='google.protobuf.ServiceDescriptorProto', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='google.protobuf.ServiceDescriptorProto.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='name', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='method', full_name='google.protobuf.ServiceDescriptorProto.method', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='method', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='options', full_name='google.protobuf.ServiceDescriptorProto.options', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='options', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + + _METHODDESCRIPTORPROTO = _descriptor.Descriptor( + name='MethodDescriptorProto', + full_name='google.protobuf.MethodDescriptorProto', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='google.protobuf.MethodDescriptorProto.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='name', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='input_type', full_name='google.protobuf.MethodDescriptorProto.input_type', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='inputType', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='output_type', full_name='google.protobuf.MethodDescriptorProto.output_type', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='outputType', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='options', full_name='google.protobuf.MethodDescriptorProto.options', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='options', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='client_streaming', full_name='google.protobuf.MethodDescriptorProto.client_streaming', index=4, + number=5, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='clientStreaming', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='server_streaming', full_name='google.protobuf.MethodDescriptorProto.server_streaming', index=5, + number=6, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='serverStreaming', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + + _FILEOPTIONS = _descriptor.Descriptor( + name='FileOptions', + full_name='google.protobuf.FileOptions', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='java_package', full_name='google.protobuf.FileOptions.java_package', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='javaPackage', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='java_outer_classname', full_name='google.protobuf.FileOptions.java_outer_classname', index=1, + number=8, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='javaOuterClassname', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='java_multiple_files', full_name='google.protobuf.FileOptions.java_multiple_files', index=2, + number=10, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='javaMultipleFiles', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='java_generate_equals_and_hash', full_name='google.protobuf.FileOptions.java_generate_equals_and_hash', index=3, + number=20, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=b'\030\001', json_name='javaGenerateEqualsAndHash', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='java_string_check_utf8', full_name='google.protobuf.FileOptions.java_string_check_utf8', index=4, + number=27, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='javaStringCheckUtf8', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='optimize_for', full_name='google.protobuf.FileOptions.optimize_for', index=5, + number=9, type=14, cpp_type=8, label=1, + has_default_value=True, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='optimizeFor', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='go_package', full_name='google.protobuf.FileOptions.go_package', index=6, + number=11, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='goPackage', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='cc_generic_services', full_name='google.protobuf.FileOptions.cc_generic_services', index=7, + number=16, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='ccGenericServices', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='java_generic_services', full_name='google.protobuf.FileOptions.java_generic_services', index=8, + number=17, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='javaGenericServices', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='py_generic_services', full_name='google.protobuf.FileOptions.py_generic_services', index=9, + number=18, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='pyGenericServices', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='deprecated', full_name='google.protobuf.FileOptions.deprecated', index=10, + number=23, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='deprecated', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='cc_enable_arenas', full_name='google.protobuf.FileOptions.cc_enable_arenas', index=11, + number=31, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=True, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='ccEnableArenas', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='objc_class_prefix', full_name='google.protobuf.FileOptions.objc_class_prefix', index=12, + number=36, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='objcClassPrefix', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='csharp_namespace', full_name='google.protobuf.FileOptions.csharp_namespace', index=13, + number=37, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='csharpNamespace', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='swift_prefix', full_name='google.protobuf.FileOptions.swift_prefix', index=14, + number=39, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='swiftPrefix', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='php_class_prefix', full_name='google.protobuf.FileOptions.php_class_prefix', index=15, + number=40, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='phpClassPrefix', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='php_namespace', full_name='google.protobuf.FileOptions.php_namespace', index=16, + number=41, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='phpNamespace', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='php_metadata_namespace', full_name='google.protobuf.FileOptions.php_metadata_namespace', index=17, + number=44, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='phpMetadataNamespace', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='ruby_package', full_name='google.protobuf.FileOptions.ruby_package', index=18, + number=45, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='rubyPackage', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='features', full_name='google.protobuf.FileOptions.features', index=19, + number=50, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='features', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='uninterpreted_option', full_name='google.protobuf.FileOptions.uninterpreted_option', index=20, + number=999, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='uninterpretedOption', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _FILEOPTIONS_OPTIMIZEMODE, + ], + serialized_options=None, + is_extendable=True, + extension_ranges=[(1000, 536870912), ], + oneofs=[ + ], + ) + + + _MESSAGEOPTIONS = _descriptor.Descriptor( + name='MessageOptions', + full_name='google.protobuf.MessageOptions', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='message_set_wire_format', full_name='google.protobuf.MessageOptions.message_set_wire_format', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='messageSetWireFormat', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='no_standard_descriptor_accessor', full_name='google.protobuf.MessageOptions.no_standard_descriptor_accessor', index=1, + number=2, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='noStandardDescriptorAccessor', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='deprecated', full_name='google.protobuf.MessageOptions.deprecated', index=2, + number=3, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='deprecated', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='map_entry', full_name='google.protobuf.MessageOptions.map_entry', index=3, + number=7, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='mapEntry', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='deprecated_legacy_json_field_conflicts', full_name='google.protobuf.MessageOptions.deprecated_legacy_json_field_conflicts', index=4, + number=11, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=b'\030\001', json_name='deprecatedLegacyJsonFieldConflicts', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='features', full_name='google.protobuf.MessageOptions.features', index=5, + number=12, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='features', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='uninterpreted_option', full_name='google.protobuf.MessageOptions.uninterpreted_option', index=6, + number=999, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='uninterpretedOption', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=True, + extension_ranges=[(1000, 536870912), ], + oneofs=[ + ], + ) + + + _FIELDOPTIONS_EDITIONDEFAULT = _descriptor.Descriptor( + name='EditionDefault', + full_name='google.protobuf.FieldOptions.EditionDefault', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='edition', full_name='google.protobuf.FieldOptions.EditionDefault.edition', index=0, + number=3, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='edition', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='value', full_name='google.protobuf.FieldOptions.EditionDefault.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='value', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + _FIELDOPTIONS_FEATURESUPPORT = _descriptor.Descriptor( + name='FeatureSupport', + full_name='google.protobuf.FieldOptions.FeatureSupport', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='edition_introduced', full_name='google.protobuf.FieldOptions.FeatureSupport.edition_introduced', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='editionIntroduced', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='edition_deprecated', full_name='google.protobuf.FieldOptions.FeatureSupport.edition_deprecated', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='editionDeprecated', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='deprecation_warning', full_name='google.protobuf.FieldOptions.FeatureSupport.deprecation_warning', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='deprecationWarning', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='edition_removed', full_name='google.protobuf.FieldOptions.FeatureSupport.edition_removed', index=3, + number=4, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='editionRemoved', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + _FIELDOPTIONS = _descriptor.Descriptor( + name='FieldOptions', + full_name='google.protobuf.FieldOptions', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='ctype', full_name='google.protobuf.FieldOptions.ctype', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=True, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='ctype', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='packed', full_name='google.protobuf.FieldOptions.packed', index=1, + number=2, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='packed', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='jstype', full_name='google.protobuf.FieldOptions.jstype', index=2, + number=6, type=14, cpp_type=8, label=1, + has_default_value=True, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='jstype', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='lazy', full_name='google.protobuf.FieldOptions.lazy', index=3, + number=5, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='lazy', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='unverified_lazy', full_name='google.protobuf.FieldOptions.unverified_lazy', index=4, + number=15, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='unverifiedLazy', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='deprecated', full_name='google.protobuf.FieldOptions.deprecated', index=5, + number=3, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='deprecated', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='weak', full_name='google.protobuf.FieldOptions.weak', index=6, + number=10, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='weak', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='debug_redact', full_name='google.protobuf.FieldOptions.debug_redact', index=7, + number=16, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='debugRedact', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='retention', full_name='google.protobuf.FieldOptions.retention', index=8, + number=17, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='retention', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='targets', full_name='google.protobuf.FieldOptions.targets', index=9, + number=19, type=14, cpp_type=8, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='targets', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='edition_defaults', full_name='google.protobuf.FieldOptions.edition_defaults', index=10, + number=20, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='editionDefaults', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='features', full_name='google.protobuf.FieldOptions.features', index=11, + number=21, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='features', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='feature_support', full_name='google.protobuf.FieldOptions.feature_support', index=12, + number=22, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='featureSupport', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='uninterpreted_option', full_name='google.protobuf.FieldOptions.uninterpreted_option', index=13, + number=999, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='uninterpretedOption', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_FIELDOPTIONS_EDITIONDEFAULT, _FIELDOPTIONS_FEATURESUPPORT, ], + enum_types=[ + _FIELDOPTIONS_CTYPE, + _FIELDOPTIONS_JSTYPE, + _FIELDOPTIONS_OPTIONRETENTION, + _FIELDOPTIONS_OPTIONTARGETTYPE, + ], + serialized_options=None, + is_extendable=True, + extension_ranges=[(1000, 536870912), ], + oneofs=[ + ], + ) + + + _ONEOFOPTIONS = _descriptor.Descriptor( + name='OneofOptions', + full_name='google.protobuf.OneofOptions', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='features', full_name='google.protobuf.OneofOptions.features', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='features', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='uninterpreted_option', full_name='google.protobuf.OneofOptions.uninterpreted_option', index=1, + number=999, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='uninterpretedOption', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=True, + extension_ranges=[(1000, 536870912), ], + oneofs=[ + ], + ) + + + _ENUMOPTIONS = _descriptor.Descriptor( + name='EnumOptions', + full_name='google.protobuf.EnumOptions', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='allow_alias', full_name='google.protobuf.EnumOptions.allow_alias', index=0, + number=2, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='allowAlias', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='deprecated', full_name='google.protobuf.EnumOptions.deprecated', index=1, + number=3, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='deprecated', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='deprecated_legacy_json_field_conflicts', full_name='google.protobuf.EnumOptions.deprecated_legacy_json_field_conflicts', index=2, + number=6, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=b'\030\001', json_name='deprecatedLegacyJsonFieldConflicts', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='features', full_name='google.protobuf.EnumOptions.features', index=3, + number=7, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='features', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='uninterpreted_option', full_name='google.protobuf.EnumOptions.uninterpreted_option', index=4, + number=999, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='uninterpretedOption', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=True, + extension_ranges=[(1000, 536870912), ], + oneofs=[ + ], + ) + + + _ENUMVALUEOPTIONS = _descriptor.Descriptor( + name='EnumValueOptions', + full_name='google.protobuf.EnumValueOptions', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='deprecated', full_name='google.protobuf.EnumValueOptions.deprecated', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='deprecated', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='features', full_name='google.protobuf.EnumValueOptions.features', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='features', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='debug_redact', full_name='google.protobuf.EnumValueOptions.debug_redact', index=2, + number=3, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='debugRedact', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='feature_support', full_name='google.protobuf.EnumValueOptions.feature_support', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='featureSupport', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='uninterpreted_option', full_name='google.protobuf.EnumValueOptions.uninterpreted_option', index=4, + number=999, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='uninterpretedOption', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=True, + extension_ranges=[(1000, 536870912), ], + oneofs=[ + ], + ) + + + _SERVICEOPTIONS = _descriptor.Descriptor( + name='ServiceOptions', + full_name='google.protobuf.ServiceOptions', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='features', full_name='google.protobuf.ServiceOptions.features', index=0, + number=34, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='features', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='deprecated', full_name='google.protobuf.ServiceOptions.deprecated', index=1, + number=33, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='deprecated', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='uninterpreted_option', full_name='google.protobuf.ServiceOptions.uninterpreted_option', index=2, + number=999, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='uninterpretedOption', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=True, + extension_ranges=[(1000, 536870912), ], + oneofs=[ + ], + ) + + + _METHODOPTIONS = _descriptor.Descriptor( + name='MethodOptions', + full_name='google.protobuf.MethodOptions', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='deprecated', full_name='google.protobuf.MethodOptions.deprecated', index=0, + number=33, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='deprecated', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='idempotency_level', full_name='google.protobuf.MethodOptions.idempotency_level', index=1, + number=34, type=14, cpp_type=8, label=1, + has_default_value=True, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='idempotencyLevel', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='features', full_name='google.protobuf.MethodOptions.features', index=2, + number=35, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='features', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='uninterpreted_option', full_name='google.protobuf.MethodOptions.uninterpreted_option', index=3, + number=999, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='uninterpretedOption', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _METHODOPTIONS_IDEMPOTENCYLEVEL, + ], + serialized_options=None, + is_extendable=True, + extension_ranges=[(1000, 536870912), ], + oneofs=[ + ], + ) + + + _UNINTERPRETEDOPTION_NAMEPART = _descriptor.Descriptor( + name='NamePart', + full_name='google.protobuf.UninterpretedOption.NamePart', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='name_part', full_name='google.protobuf.UninterpretedOption.NamePart.name_part', index=0, + number=1, type=9, cpp_type=9, label=2, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='namePart', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='is_extension', full_name='google.protobuf.UninterpretedOption.NamePart.is_extension', index=1, + number=2, type=8, cpp_type=7, label=2, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='isExtension', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + _UNINTERPRETEDOPTION = _descriptor.Descriptor( + name='UninterpretedOption', + full_name='google.protobuf.UninterpretedOption', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='google.protobuf.UninterpretedOption.name', index=0, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='name', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='identifier_value', full_name='google.protobuf.UninterpretedOption.identifier_value', index=1, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='identifierValue', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='positive_int_value', full_name='google.protobuf.UninterpretedOption.positive_int_value', index=2, + number=4, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='positiveIntValue', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='negative_int_value', full_name='google.protobuf.UninterpretedOption.negative_int_value', index=3, + number=5, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='negativeIntValue', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='double_value', full_name='google.protobuf.UninterpretedOption.double_value', index=4, + number=6, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='doubleValue', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='string_value', full_name='google.protobuf.UninterpretedOption.string_value', index=5, + number=7, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='stringValue', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='aggregate_value', full_name='google.protobuf.UninterpretedOption.aggregate_value', index=6, + number=8, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='aggregateValue', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_UNINTERPRETEDOPTION_NAMEPART, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + + _FEATURESET = _descriptor.Descriptor( + name='FeatureSet', + full_name='google.protobuf.FeatureSet', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='field_presence', full_name='google.protobuf.FeatureSet.field_presence', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=b'\210\001\001\230\001\004\230\001\001\242\001\r\022\010EXPLICIT\030\346\007\242\001\r\022\010IMPLICIT\030\347\007\242\001\r\022\010EXPLICIT\030\350\007\262\001\003\010\350\007', json_name='fieldPresence', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='enum_type', full_name='google.protobuf.FeatureSet.enum_type', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=b'\210\001\001\230\001\006\230\001\001\242\001\013\022\006CLOSED\030\346\007\242\001\t\022\004OPEN\030\347\007\262\001\003\010\350\007', json_name='enumType', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='repeated_field_encoding', full_name='google.protobuf.FeatureSet.repeated_field_encoding', index=2, + number=3, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=b'\210\001\001\230\001\004\230\001\001\242\001\r\022\010EXPANDED\030\346\007\242\001\013\022\006PACKED\030\347\007\262\001\003\010\350\007', json_name='repeatedFieldEncoding', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='utf8_validation', full_name='google.protobuf.FeatureSet.utf8_validation', index=3, + number=4, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=b'\210\001\001\230\001\004\230\001\001\242\001\t\022\004NONE\030\346\007\242\001\013\022\006VERIFY\030\347\007\262\001\003\010\350\007', json_name='utf8Validation', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='message_encoding', full_name='google.protobuf.FeatureSet.message_encoding', index=4, + number=5, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=b'\210\001\001\230\001\004\230\001\001\242\001\024\022\017LENGTH_PREFIXED\030\346\007\262\001\003\010\350\007', json_name='messageEncoding', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='json_format', full_name='google.protobuf.FeatureSet.json_format', index=5, + number=6, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=b'\210\001\001\230\001\003\230\001\006\230\001\001\242\001\027\022\022LEGACY_BEST_EFFORT\030\346\007\242\001\n\022\005ALLOW\030\347\007\262\001\003\010\350\007', json_name='jsonFormat', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _FEATURESET_FIELDPRESENCE, + _FEATURESET_ENUMTYPE, + _FEATURESET_REPEATEDFIELDENCODING, + _FEATURESET_UTF8VALIDATION, + _FEATURESET_MESSAGEENCODING, + _FEATURESET_JSONFORMAT, + ], + serialized_options=None, + is_extendable=True, + extension_ranges=[(1000, 9995), (9995, 10000), (10000, 10001), ], + oneofs=[ + ], + ) + + + _FEATURESETDEFAULTS_FEATURESETEDITIONDEFAULT = _descriptor.Descriptor( + name='FeatureSetEditionDefault', + full_name='google.protobuf.FeatureSetDefaults.FeatureSetEditionDefault', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='edition', full_name='google.protobuf.FeatureSetDefaults.FeatureSetEditionDefault.edition', index=0, + number=3, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='edition', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='overridable_features', full_name='google.protobuf.FeatureSetDefaults.FeatureSetEditionDefault.overridable_features', index=1, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='overridableFeatures', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='fixed_features', full_name='google.protobuf.FeatureSetDefaults.FeatureSetEditionDefault.fixed_features', index=2, + number=5, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='fixedFeatures', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + _FEATURESETDEFAULTS = _descriptor.Descriptor( + name='FeatureSetDefaults', + full_name='google.protobuf.FeatureSetDefaults', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='defaults', full_name='google.protobuf.FeatureSetDefaults.defaults', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='defaults', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='minimum_edition', full_name='google.protobuf.FeatureSetDefaults.minimum_edition', index=1, + number=4, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='minimumEdition', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='maximum_edition', full_name='google.protobuf.FeatureSetDefaults.maximum_edition', index=2, + number=5, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='maximumEdition', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_FEATURESETDEFAULTS_FEATURESETEDITIONDEFAULT, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + + _SOURCECODEINFO_LOCATION = _descriptor.Descriptor( + name='Location', + full_name='google.protobuf.SourceCodeInfo.Location', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='path', full_name='google.protobuf.SourceCodeInfo.Location.path', index=0, + number=1, type=5, cpp_type=1, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=b'\020\001', json_name='path', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='span', full_name='google.protobuf.SourceCodeInfo.Location.span', index=1, + number=2, type=5, cpp_type=1, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=b'\020\001', json_name='span', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='leading_comments', full_name='google.protobuf.SourceCodeInfo.Location.leading_comments', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='leadingComments', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='trailing_comments', full_name='google.protobuf.SourceCodeInfo.Location.trailing_comments', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='trailingComments', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='leading_detached_comments', full_name='google.protobuf.SourceCodeInfo.Location.leading_detached_comments', index=4, + number=6, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='leadingDetachedComments', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + _SOURCECODEINFO = _descriptor.Descriptor( + name='SourceCodeInfo', + full_name='google.protobuf.SourceCodeInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='location', full_name='google.protobuf.SourceCodeInfo.location', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='location', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_SOURCECODEINFO_LOCATION, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + + _GENERATEDCODEINFO_ANNOTATION = _descriptor.Descriptor( + name='Annotation', + full_name='google.protobuf.GeneratedCodeInfo.Annotation', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='path', full_name='google.protobuf.GeneratedCodeInfo.Annotation.path', index=0, + number=1, type=5, cpp_type=1, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=b'\020\001', json_name='path', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='source_file', full_name='google.protobuf.GeneratedCodeInfo.Annotation.source_file', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='sourceFile', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='begin', full_name='google.protobuf.GeneratedCodeInfo.Annotation.begin', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='begin', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='end', full_name='google.protobuf.GeneratedCodeInfo.Annotation.end', index=3, + number=4, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='end', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='semantic', full_name='google.protobuf.GeneratedCodeInfo.Annotation.semantic', index=4, + number=5, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='semantic', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _GENERATEDCODEINFO_ANNOTATION_SEMANTIC, + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + _GENERATEDCODEINFO = _descriptor.Descriptor( + name='GeneratedCodeInfo', + full_name='google.protobuf.GeneratedCodeInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='annotation', full_name='google.protobuf.GeneratedCodeInfo.annotation', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='annotation', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_GENERATEDCODEINFO_ANNOTATION, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + ) + + _FILEDESCRIPTORSET.fields_by_name['file'].message_type = _FILEDESCRIPTORPROTO + _FILEDESCRIPTORPROTO.fields_by_name['message_type'].message_type = _DESCRIPTORPROTO + _FILEDESCRIPTORPROTO.fields_by_name['enum_type'].message_type = _ENUMDESCRIPTORPROTO + _FILEDESCRIPTORPROTO.fields_by_name['service'].message_type = _SERVICEDESCRIPTORPROTO + _FILEDESCRIPTORPROTO.fields_by_name['extension'].message_type = _FIELDDESCRIPTORPROTO + _FILEDESCRIPTORPROTO.fields_by_name['options'].message_type = _FILEOPTIONS + _FILEDESCRIPTORPROTO.fields_by_name['source_code_info'].message_type = _SOURCECODEINFO + _FILEDESCRIPTORPROTO.fields_by_name['edition'].enum_type = _EDITION + _DESCRIPTORPROTO_EXTENSIONRANGE.fields_by_name['options'].message_type = _EXTENSIONRANGEOPTIONS + _DESCRIPTORPROTO_EXTENSIONRANGE.containing_type = _DESCRIPTORPROTO + _DESCRIPTORPROTO_RESERVEDRANGE.containing_type = _DESCRIPTORPROTO + _DESCRIPTORPROTO.fields_by_name['field'].message_type = _FIELDDESCRIPTORPROTO + _DESCRIPTORPROTO.fields_by_name['extension'].message_type = _FIELDDESCRIPTORPROTO + _DESCRIPTORPROTO.fields_by_name['nested_type'].message_type = _DESCRIPTORPROTO + _DESCRIPTORPROTO.fields_by_name['enum_type'].message_type = _ENUMDESCRIPTORPROTO + _DESCRIPTORPROTO.fields_by_name['extension_range'].message_type = _DESCRIPTORPROTO_EXTENSIONRANGE + _DESCRIPTORPROTO.fields_by_name['oneof_decl'].message_type = _ONEOFDESCRIPTORPROTO + _DESCRIPTORPROTO.fields_by_name['options'].message_type = _MESSAGEOPTIONS + _DESCRIPTORPROTO.fields_by_name['reserved_range'].message_type = _DESCRIPTORPROTO_RESERVEDRANGE + _EXTENSIONRANGEOPTIONS_DECLARATION.containing_type = _EXTENSIONRANGEOPTIONS + _EXTENSIONRANGEOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION + _EXTENSIONRANGEOPTIONS.fields_by_name['declaration'].message_type = _EXTENSIONRANGEOPTIONS_DECLARATION + _EXTENSIONRANGEOPTIONS.fields_by_name['features'].message_type = _FEATURESET + _EXTENSIONRANGEOPTIONS.fields_by_name['verification'].enum_type = _EXTENSIONRANGEOPTIONS_VERIFICATIONSTATE + _EXTENSIONRANGEOPTIONS_VERIFICATIONSTATE.containing_type = _EXTENSIONRANGEOPTIONS + _FIELDDESCRIPTORPROTO.fields_by_name['label'].enum_type = _FIELDDESCRIPTORPROTO_LABEL + _FIELDDESCRIPTORPROTO.fields_by_name['type'].enum_type = _FIELDDESCRIPTORPROTO_TYPE + _FIELDDESCRIPTORPROTO.fields_by_name['options'].message_type = _FIELDOPTIONS + _FIELDDESCRIPTORPROTO_TYPE.containing_type = _FIELDDESCRIPTORPROTO + _FIELDDESCRIPTORPROTO_LABEL.containing_type = _FIELDDESCRIPTORPROTO + _ONEOFDESCRIPTORPROTO.fields_by_name['options'].message_type = _ONEOFOPTIONS + _ENUMDESCRIPTORPROTO_ENUMRESERVEDRANGE.containing_type = _ENUMDESCRIPTORPROTO + _ENUMDESCRIPTORPROTO.fields_by_name['value'].message_type = _ENUMVALUEDESCRIPTORPROTO + _ENUMDESCRIPTORPROTO.fields_by_name['options'].message_type = _ENUMOPTIONS + _ENUMDESCRIPTORPROTO.fields_by_name['reserved_range'].message_type = _ENUMDESCRIPTORPROTO_ENUMRESERVEDRANGE + _ENUMVALUEDESCRIPTORPROTO.fields_by_name['options'].message_type = _ENUMVALUEOPTIONS + _SERVICEDESCRIPTORPROTO.fields_by_name['method'].message_type = _METHODDESCRIPTORPROTO + _SERVICEDESCRIPTORPROTO.fields_by_name['options'].message_type = _SERVICEOPTIONS + _METHODDESCRIPTORPROTO.fields_by_name['options'].message_type = _METHODOPTIONS + _FILEOPTIONS.fields_by_name['optimize_for'].enum_type = _FILEOPTIONS_OPTIMIZEMODE + _FILEOPTIONS.fields_by_name['features'].message_type = _FEATURESET + _FILEOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION + _FILEOPTIONS_OPTIMIZEMODE.containing_type = _FILEOPTIONS + _MESSAGEOPTIONS.fields_by_name['features'].message_type = _FEATURESET + _MESSAGEOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION + _FIELDOPTIONS_EDITIONDEFAULT.fields_by_name['edition'].enum_type = _EDITION + _FIELDOPTIONS_EDITIONDEFAULT.containing_type = _FIELDOPTIONS + _FIELDOPTIONS_FEATURESUPPORT.fields_by_name['edition_introduced'].enum_type = _EDITION + _FIELDOPTIONS_FEATURESUPPORT.fields_by_name['edition_deprecated'].enum_type = _EDITION + _FIELDOPTIONS_FEATURESUPPORT.fields_by_name['edition_removed'].enum_type = _EDITION + _FIELDOPTIONS_FEATURESUPPORT.containing_type = _FIELDOPTIONS + _FIELDOPTIONS.fields_by_name['ctype'].enum_type = _FIELDOPTIONS_CTYPE + _FIELDOPTIONS.fields_by_name['jstype'].enum_type = _FIELDOPTIONS_JSTYPE + _FIELDOPTIONS.fields_by_name['retention'].enum_type = _FIELDOPTIONS_OPTIONRETENTION + _FIELDOPTIONS.fields_by_name['targets'].enum_type = _FIELDOPTIONS_OPTIONTARGETTYPE + _FIELDOPTIONS.fields_by_name['edition_defaults'].message_type = _FIELDOPTIONS_EDITIONDEFAULT + _FIELDOPTIONS.fields_by_name['features'].message_type = _FEATURESET + _FIELDOPTIONS.fields_by_name['feature_support'].message_type = _FIELDOPTIONS_FEATURESUPPORT + _FIELDOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION + _FIELDOPTIONS_CTYPE.containing_type = _FIELDOPTIONS + _FIELDOPTIONS_JSTYPE.containing_type = _FIELDOPTIONS + _FIELDOPTIONS_OPTIONRETENTION.containing_type = _FIELDOPTIONS + _FIELDOPTIONS_OPTIONTARGETTYPE.containing_type = _FIELDOPTIONS + _ONEOFOPTIONS.fields_by_name['features'].message_type = _FEATURESET + _ONEOFOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION + _ENUMOPTIONS.fields_by_name['features'].message_type = _FEATURESET + _ENUMOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION + _ENUMVALUEOPTIONS.fields_by_name['features'].message_type = _FEATURESET + _ENUMVALUEOPTIONS.fields_by_name['feature_support'].message_type = _FIELDOPTIONS_FEATURESUPPORT + _ENUMVALUEOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION + _SERVICEOPTIONS.fields_by_name['features'].message_type = _FEATURESET + _SERVICEOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION + _METHODOPTIONS.fields_by_name['idempotency_level'].enum_type = _METHODOPTIONS_IDEMPOTENCYLEVEL + _METHODOPTIONS.fields_by_name['features'].message_type = _FEATURESET + _METHODOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION + _METHODOPTIONS_IDEMPOTENCYLEVEL.containing_type = _METHODOPTIONS + _UNINTERPRETEDOPTION_NAMEPART.containing_type = _UNINTERPRETEDOPTION + _UNINTERPRETEDOPTION.fields_by_name['name'].message_type = _UNINTERPRETEDOPTION_NAMEPART + _FEATURESET.fields_by_name['field_presence'].enum_type = _FEATURESET_FIELDPRESENCE + _FEATURESET.fields_by_name['enum_type'].enum_type = _FEATURESET_ENUMTYPE + _FEATURESET.fields_by_name['repeated_field_encoding'].enum_type = _FEATURESET_REPEATEDFIELDENCODING + _FEATURESET.fields_by_name['utf8_validation'].enum_type = _FEATURESET_UTF8VALIDATION + _FEATURESET.fields_by_name['message_encoding'].enum_type = _FEATURESET_MESSAGEENCODING + _FEATURESET.fields_by_name['json_format'].enum_type = _FEATURESET_JSONFORMAT + _FEATURESET_FIELDPRESENCE.containing_type = _FEATURESET + _FEATURESET_ENUMTYPE.containing_type = _FEATURESET + _FEATURESET_REPEATEDFIELDENCODING.containing_type = _FEATURESET + _FEATURESET_UTF8VALIDATION.containing_type = _FEATURESET + _FEATURESET_MESSAGEENCODING.containing_type = _FEATURESET + _FEATURESET_JSONFORMAT.containing_type = _FEATURESET + _FEATURESETDEFAULTS_FEATURESETEDITIONDEFAULT.fields_by_name['edition'].enum_type = _EDITION + _FEATURESETDEFAULTS_FEATURESETEDITIONDEFAULT.fields_by_name['overridable_features'].message_type = _FEATURESET + _FEATURESETDEFAULTS_FEATURESETEDITIONDEFAULT.fields_by_name['fixed_features'].message_type = _FEATURESET + _FEATURESETDEFAULTS_FEATURESETEDITIONDEFAULT.containing_type = _FEATURESETDEFAULTS + _FEATURESETDEFAULTS.fields_by_name['defaults'].message_type = _FEATURESETDEFAULTS_FEATURESETEDITIONDEFAULT + _FEATURESETDEFAULTS.fields_by_name['minimum_edition'].enum_type = _EDITION + _FEATURESETDEFAULTS.fields_by_name['maximum_edition'].enum_type = _EDITION + _SOURCECODEINFO_LOCATION.containing_type = _SOURCECODEINFO + _SOURCECODEINFO.fields_by_name['location'].message_type = _SOURCECODEINFO_LOCATION + _GENERATEDCODEINFO_ANNOTATION.fields_by_name['semantic'].enum_type = _GENERATEDCODEINFO_ANNOTATION_SEMANTIC + _GENERATEDCODEINFO_ANNOTATION.containing_type = _GENERATEDCODEINFO + _GENERATEDCODEINFO_ANNOTATION_SEMANTIC.containing_type = _GENERATEDCODEINFO_ANNOTATION + _GENERATEDCODEINFO.fields_by_name['annotation'].message_type = _GENERATEDCODEINFO_ANNOTATION + DESCRIPTOR.message_types_by_name['FileDescriptorSet'] = _FILEDESCRIPTORSET + DESCRIPTOR.message_types_by_name['FileDescriptorProto'] = _FILEDESCRIPTORPROTO + DESCRIPTOR.message_types_by_name['DescriptorProto'] = _DESCRIPTORPROTO + DESCRIPTOR.message_types_by_name['ExtensionRangeOptions'] = _EXTENSIONRANGEOPTIONS + DESCRIPTOR.message_types_by_name['FieldDescriptorProto'] = _FIELDDESCRIPTORPROTO + DESCRIPTOR.message_types_by_name['OneofDescriptorProto'] = _ONEOFDESCRIPTORPROTO + DESCRIPTOR.message_types_by_name['EnumDescriptorProto'] = _ENUMDESCRIPTORPROTO + DESCRIPTOR.message_types_by_name['EnumValueDescriptorProto'] = _ENUMVALUEDESCRIPTORPROTO + DESCRIPTOR.message_types_by_name['ServiceDescriptorProto'] = _SERVICEDESCRIPTORPROTO + DESCRIPTOR.message_types_by_name['MethodDescriptorProto'] = _METHODDESCRIPTORPROTO + DESCRIPTOR.message_types_by_name['FileOptions'] = _FILEOPTIONS + DESCRIPTOR.message_types_by_name['MessageOptions'] = _MESSAGEOPTIONS + DESCRIPTOR.message_types_by_name['FieldOptions'] = _FIELDOPTIONS + DESCRIPTOR.message_types_by_name['OneofOptions'] = _ONEOFOPTIONS + DESCRIPTOR.message_types_by_name['EnumOptions'] = _ENUMOPTIONS + DESCRIPTOR.message_types_by_name['EnumValueOptions'] = _ENUMVALUEOPTIONS + DESCRIPTOR.message_types_by_name['ServiceOptions'] = _SERVICEOPTIONS + DESCRIPTOR.message_types_by_name['MethodOptions'] = _METHODOPTIONS + DESCRIPTOR.message_types_by_name['UninterpretedOption'] = _UNINTERPRETEDOPTION + DESCRIPTOR.message_types_by_name['FeatureSet'] = _FEATURESET + DESCRIPTOR.message_types_by_name['FeatureSetDefaults'] = _FEATURESETDEFAULTS + DESCRIPTOR.message_types_by_name['SourceCodeInfo'] = _SOURCECODEINFO + DESCRIPTOR.message_types_by_name['GeneratedCodeInfo'] = _GENERATEDCODEINFO + DESCRIPTOR.enum_types_by_name['Edition'] = _EDITION + _sym_db.RegisterFileDescriptor(DESCRIPTOR) + + class _ResolvedFeatures: + def __init__(self, features = None, **kwargs): + if features: + for k, v in features.FIELDS.items(): + setattr(self, k, getattr(features, k)) + else: + for k, v in kwargs.items(): + setattr(self, k, v) + DESCRIPTOR._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEDESCRIPTORSET._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEDESCRIPTORSET.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEDESCRIPTORPROTO._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEDESCRIPTORPROTO.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEDESCRIPTORPROTO.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEDESCRIPTORPROTO.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEDESCRIPTORPROTO.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEDESCRIPTORPROTO.fields[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEDESCRIPTORPROTO.fields[5]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEDESCRIPTORPROTO.fields[6]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEDESCRIPTORPROTO.fields[7]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEDESCRIPTORPROTO.fields[8]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEDESCRIPTORPROTO.fields[9]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEDESCRIPTORPROTO.fields[10]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEDESCRIPTORPROTO.fields[11]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEDESCRIPTORPROTO.fields[12]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO.fields[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO.fields[5]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO.fields[6]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO.fields[7]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO.fields[8]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO.fields[9]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO_EXTENSIONRANGE._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO_EXTENSIONRANGE.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO_EXTENSIONRANGE.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO_EXTENSIONRANGE.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO_RESERVEDRANGE._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO_RESERVEDRANGE.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _DESCRIPTORPROTO_RESERVEDRANGE.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EXTENSIONRANGEOPTIONS._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EXTENSIONRANGEOPTIONS.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EXTENSIONRANGEOPTIONS.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EXTENSIONRANGEOPTIONS.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EXTENSIONRANGEOPTIONS.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EXTENSIONRANGEOPTIONS_DECLARATION._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EXTENSIONRANGEOPTIONS_DECLARATION.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EXTENSIONRANGEOPTIONS_DECLARATION.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EXTENSIONRANGEOPTIONS_DECLARATION.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EXTENSIONRANGEOPTIONS_DECLARATION.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EXTENSIONRANGEOPTIONS_DECLARATION.fields[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO.fields[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO.fields[5]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO.fields[6]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO.fields[7]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO.fields[8]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO.fields[9]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO.fields[10]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ONEOFDESCRIPTORPROTO._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ONEOFDESCRIPTORPROTO.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ONEOFDESCRIPTORPROTO.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMDESCRIPTORPROTO._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMDESCRIPTORPROTO.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMDESCRIPTORPROTO.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMDESCRIPTORPROTO.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMDESCRIPTORPROTO.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMDESCRIPTORPROTO.fields[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMDESCRIPTORPROTO_ENUMRESERVEDRANGE._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMDESCRIPTORPROTO_ENUMRESERVEDRANGE.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMDESCRIPTORPROTO_ENUMRESERVEDRANGE.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMVALUEDESCRIPTORPROTO._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMVALUEDESCRIPTORPROTO.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMVALUEDESCRIPTORPROTO.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMVALUEDESCRIPTORPROTO.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _SERVICEDESCRIPTORPROTO._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _SERVICEDESCRIPTORPROTO.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _SERVICEDESCRIPTORPROTO.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _SERVICEDESCRIPTORPROTO.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _METHODDESCRIPTORPROTO._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _METHODDESCRIPTORPROTO.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _METHODDESCRIPTORPROTO.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _METHODDESCRIPTORPROTO.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _METHODDESCRIPTORPROTO.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _METHODDESCRIPTORPROTO.fields[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _METHODDESCRIPTORPROTO.fields[5]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[5]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[6]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[7]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[8]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[9]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[10]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[11]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[12]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[13]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[14]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[15]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[16]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[17]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[18]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[19]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS.fields[20]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _MESSAGEOPTIONS._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _MESSAGEOPTIONS.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _MESSAGEOPTIONS.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _MESSAGEOPTIONS.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _MESSAGEOPTIONS.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _MESSAGEOPTIONS.fields[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _MESSAGEOPTIONS.fields[5]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _MESSAGEOPTIONS.fields[6]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS.fields[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS.fields[5]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS.fields[6]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS.fields[7]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS.fields[8]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS.fields[9]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS.fields[10]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS.fields[11]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS.fields[12]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS.fields[13]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_EDITIONDEFAULT._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_EDITIONDEFAULT.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_EDITIONDEFAULT.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_FEATURESUPPORT._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_FEATURESUPPORT.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_FEATURESUPPORT.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_FEATURESUPPORT.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_FEATURESUPPORT.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ONEOFOPTIONS._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ONEOFOPTIONS.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ONEOFOPTIONS.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMOPTIONS._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMOPTIONS.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMOPTIONS.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMOPTIONS.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMOPTIONS.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMOPTIONS.fields[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMVALUEOPTIONS._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMVALUEOPTIONS.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMVALUEOPTIONS.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMVALUEOPTIONS.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMVALUEOPTIONS.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _ENUMVALUEOPTIONS.fields[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _SERVICEOPTIONS._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _SERVICEOPTIONS.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _SERVICEOPTIONS.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _SERVICEOPTIONS.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _METHODOPTIONS._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _METHODOPTIONS.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _METHODOPTIONS.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _METHODOPTIONS.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _METHODOPTIONS.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _UNINTERPRETEDOPTION._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _UNINTERPRETEDOPTION.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _UNINTERPRETEDOPTION.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _UNINTERPRETEDOPTION.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _UNINTERPRETEDOPTION.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _UNINTERPRETEDOPTION.fields[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _UNINTERPRETEDOPTION.fields[5]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _UNINTERPRETEDOPTION.fields[6]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _UNINTERPRETEDOPTION_NAMEPART._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _UNINTERPRETEDOPTION_NAMEPART.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["LEGACY_REQUIRED"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _UNINTERPRETEDOPTION_NAMEPART.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["LEGACY_REQUIRED"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET.fields[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET.fields[5]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESETDEFAULTS._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESETDEFAULTS.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESETDEFAULTS.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESETDEFAULTS.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESETDEFAULTS_FEATURESETEDITIONDEFAULT._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESETDEFAULTS_FEATURESETEDITIONDEFAULT.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESETDEFAULTS_FEATURESETEDITIONDEFAULT.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESETDEFAULTS_FEATURESETEDITIONDEFAULT.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _SOURCECODEINFO._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _SOURCECODEINFO.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _SOURCECODEINFO_LOCATION._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _SOURCECODEINFO_LOCATION.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["PACKED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _SOURCECODEINFO_LOCATION.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["PACKED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _SOURCECODEINFO_LOCATION.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _SOURCECODEINFO_LOCATION.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _SOURCECODEINFO_LOCATION.fields[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _GENERATEDCODEINFO._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _GENERATEDCODEINFO.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _GENERATEDCODEINFO_ANNOTATION._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _GENERATEDCODEINFO_ANNOTATION.fields[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["PACKED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _GENERATEDCODEINFO_ANNOTATION.fields[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _GENERATEDCODEINFO_ANNOTATION.fields[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _GENERATEDCODEINFO_ANNOTATION.fields[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _GENERATEDCODEINFO_ANNOTATION.fields[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EXTENSIONRANGEOPTIONS_VERIFICATIONSTATE._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EXTENSIONRANGEOPTIONS_VERIFICATIONSTATE.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EXTENSIONRANGEOPTIONS_VERIFICATIONSTATE.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[5]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[6]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[7]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[8]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[9]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[10]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[11]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[12]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[13]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[14]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[15]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[16]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_TYPE.values[17]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_LABEL._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_LABEL.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_LABEL.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDDESCRIPTORPROTO_LABEL.values[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS_OPTIMIZEMODE._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS_OPTIMIZEMODE.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS_OPTIMIZEMODE.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FILEOPTIONS_OPTIMIZEMODE.values[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_CTYPE._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_CTYPE.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_CTYPE.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_CTYPE.values[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_JSTYPE._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_JSTYPE.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_JSTYPE.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_JSTYPE.values[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_OPTIONRETENTION._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_OPTIONRETENTION.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_OPTIONRETENTION.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_OPTIONRETENTION.values[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_OPTIONTARGETTYPE._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_OPTIONTARGETTYPE.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_OPTIONTARGETTYPE.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_OPTIONTARGETTYPE.values[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_OPTIONTARGETTYPE.values[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_OPTIONTARGETTYPE.values[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_OPTIONTARGETTYPE.values[5]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_OPTIONTARGETTYPE.values[6]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_OPTIONTARGETTYPE.values[7]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_OPTIONTARGETTYPE.values[8]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FIELDOPTIONS_OPTIONTARGETTYPE.values[9]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _METHODOPTIONS_IDEMPOTENCYLEVEL._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _METHODOPTIONS_IDEMPOTENCYLEVEL.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _METHODOPTIONS_IDEMPOTENCYLEVEL.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _METHODOPTIONS_IDEMPOTENCYLEVEL.values[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_FIELDPRESENCE._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_FIELDPRESENCE.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_FIELDPRESENCE.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_FIELDPRESENCE.values[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_FIELDPRESENCE.values[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_ENUMTYPE._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_ENUMTYPE.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_ENUMTYPE.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_ENUMTYPE.values[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_REPEATEDFIELDENCODING._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_REPEATEDFIELDENCODING.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_REPEATEDFIELDENCODING.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_REPEATEDFIELDENCODING.values[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_UTF8VALIDATION._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_UTF8VALIDATION.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_UTF8VALIDATION.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_UTF8VALIDATION.values[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_MESSAGEENCODING._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_MESSAGEENCODING.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_MESSAGEENCODING.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_MESSAGEENCODING.values[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_JSONFORMAT._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_JSONFORMAT.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_JSONFORMAT.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _FEATURESET_JSONFORMAT.values[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _GENERATEDCODEINFO_ANNOTATION_SEMANTIC._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _GENERATEDCODEINFO_ANNOTATION_SEMANTIC.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _GENERATEDCODEINFO_ANNOTATION_SEMANTIC.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _GENERATEDCODEINFO_ANNOTATION_SEMANTIC.values[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EDITION._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EDITION.values[0]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EDITION.values[1]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EDITION.values[2]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EDITION.values[3]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EDITION.values[4]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EDITION.values[5]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EDITION.values[6]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EDITION.values[7]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EDITION.values[8]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EDITION.values[9]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EDITION.values[10]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) + _EDITION.values[11]._features = _ResolvedFeatures(field_presence=_FEATURESET_FIELDPRESENCE.values_by_name["EXPLICIT"].number,enum_type=_FEATURESET_ENUMTYPE.values_by_name["CLOSED"].number,repeated_field_encoding=_FEATURESET_REPEATEDFIELDENCODING.values_by_name["EXPANDED"].number,utf8_validation=_FEATURESET_UTF8VALIDATION.values_by_name["NONE"].number,message_encoding=_FEATURESET_MESSAGEENCODING.values_by_name["LENGTH_PREFIXED"].number,json_format=_FEATURESET_JSONFORMAT.values_by_name["LEGACY_BEST_EFFORT"].number) +else: + _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.descriptor_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\023com.google.protobufB\020DescriptorProtosH\001Z-google.golang.org/protobuf/types/descriptorpb\370\001\001\242\002\003GPB\252\002\032Google.Protobuf.Reflection' + _globals['_EXTENSIONRANGEOPTIONS'].fields_by_name['declaration']._loaded_options = None + _globals['_EXTENSIONRANGEOPTIONS'].fields_by_name['declaration']._serialized_options = b'\210\001\002' + _globals['_EXTENSIONRANGEOPTIONS'].fields_by_name['verification']._loaded_options = None + _globals['_EXTENSIONRANGEOPTIONS'].fields_by_name['verification']._serialized_options = b'\210\001\002' + _globals['_FILEOPTIONS'].fields_by_name['java_generate_equals_and_hash']._loaded_options = None + _globals['_FILEOPTIONS'].fields_by_name['java_generate_equals_and_hash']._serialized_options = b'\030\001' + _globals['_MESSAGEOPTIONS'].fields_by_name['deprecated_legacy_json_field_conflicts']._loaded_options = None + _globals['_MESSAGEOPTIONS'].fields_by_name['deprecated_legacy_json_field_conflicts']._serialized_options = b'\030\001' + _globals['_ENUMOPTIONS'].fields_by_name['deprecated_legacy_json_field_conflicts']._loaded_options = None + _globals['_ENUMOPTIONS'].fields_by_name['deprecated_legacy_json_field_conflicts']._serialized_options = b'\030\001' + _globals['_FEATURESET'].fields_by_name['field_presence']._loaded_options = None + _globals['_FEATURESET'].fields_by_name['field_presence']._serialized_options = b'\210\001\001\230\001\004\230\001\001\242\001\r\022\010EXPLICIT\030\346\007\242\001\r\022\010IMPLICIT\030\347\007\242\001\r\022\010EXPLICIT\030\350\007\262\001\003\010\350\007' + _globals['_FEATURESET'].fields_by_name['enum_type']._loaded_options = None + _globals['_FEATURESET'].fields_by_name['enum_type']._serialized_options = b'\210\001\001\230\001\006\230\001\001\242\001\013\022\006CLOSED\030\346\007\242\001\t\022\004OPEN\030\347\007\262\001\003\010\350\007' + _globals['_FEATURESET'].fields_by_name['repeated_field_encoding']._loaded_options = None + _globals['_FEATURESET'].fields_by_name['repeated_field_encoding']._serialized_options = b'\210\001\001\230\001\004\230\001\001\242\001\r\022\010EXPANDED\030\346\007\242\001\013\022\006PACKED\030\347\007\262\001\003\010\350\007' + _globals['_FEATURESET'].fields_by_name['utf8_validation']._loaded_options = None + _globals['_FEATURESET'].fields_by_name['utf8_validation']._serialized_options = b'\210\001\001\230\001\004\230\001\001\242\001\t\022\004NONE\030\346\007\242\001\013\022\006VERIFY\030\347\007\262\001\003\010\350\007' + _globals['_FEATURESET'].fields_by_name['message_encoding']._loaded_options = None + _globals['_FEATURESET'].fields_by_name['message_encoding']._serialized_options = b'\210\001\001\230\001\004\230\001\001\242\001\024\022\017LENGTH_PREFIXED\030\346\007\262\001\003\010\350\007' + _globals['_FEATURESET'].fields_by_name['json_format']._loaded_options = None + _globals['_FEATURESET'].fields_by_name['json_format']._serialized_options = b'\210\001\001\230\001\003\230\001\006\230\001\001\242\001\027\022\022LEGACY_BEST_EFFORT\030\346\007\242\001\n\022\005ALLOW\030\347\007\262\001\003\010\350\007' + _globals['_SOURCECODEINFO_LOCATION'].fields_by_name['path']._loaded_options = None + _globals['_SOURCECODEINFO_LOCATION'].fields_by_name['path']._serialized_options = b'\020\001' + _globals['_SOURCECODEINFO_LOCATION'].fields_by_name['span']._loaded_options = None + _globals['_SOURCECODEINFO_LOCATION'].fields_by_name['span']._serialized_options = b'\020\001' + _globals['_GENERATEDCODEINFO_ANNOTATION'].fields_by_name['path']._loaded_options = None + _globals['_GENERATEDCODEINFO_ANNOTATION'].fields_by_name['path']._serialized_options = b'\020\001' + _globals['_EDITION']._serialized_start=11845 + _globals['_EDITION']._serialized_end=12140 + _globals['_FILEDESCRIPTORSET']._serialized_start=53 + _globals['_FILEDESCRIPTORSET']._serialized_end=130 + _globals['_FILEDESCRIPTORPROTO']._serialized_start=133 + _globals['_FILEDESCRIPTORPROTO']._serialized_end=797 + _globals['_DESCRIPTORPROTO']._serialized_start=800 + _globals['_DESCRIPTORPROTO']._serialized_end=1625 + _globals['_DESCRIPTORPROTO_EXTENSIONRANGE']._serialized_start=1446 + _globals['_DESCRIPTORPROTO_EXTENSIONRANGE']._serialized_end=1568 + _globals['_DESCRIPTORPROTO_RESERVEDRANGE']._serialized_start=1570 + _globals['_DESCRIPTORPROTO_RESERVEDRANGE']._serialized_end=1625 + _globals['_EXTENSIONRANGEOPTIONS']._serialized_start=1628 + _globals['_EXTENSIONRANGEOPTIONS']._serialized_end=2216 + _globals['_EXTENSIONRANGEOPTIONS_DECLARATION']._serialized_start=2003 + _globals['_EXTENSIONRANGEOPTIONS_DECLARATION']._serialized_end=2151 + _globals['_EXTENSIONRANGEOPTIONS_VERIFICATIONSTATE']._serialized_start=2153 + _globals['_EXTENSIONRANGEOPTIONS_VERIFICATIONSTATE']._serialized_end=2205 + _globals['_FIELDDESCRIPTORPROTO']._serialized_start=2219 + _globals['_FIELDDESCRIPTORPROTO']._serialized_end=3052 + _globals['_FIELDDESCRIPTORPROTO_TYPE']._serialized_start=2673 + _globals['_FIELDDESCRIPTORPROTO_TYPE']._serialized_end=2983 + _globals['_FIELDDESCRIPTORPROTO_LABEL']._serialized_start=2985 + _globals['_FIELDDESCRIPTORPROTO_LABEL']._serialized_end=3052 + _globals['_ONEOFDESCRIPTORPROTO']._serialized_start=3054 + _globals['_ONEOFDESCRIPTORPROTO']._serialized_end=3153 + _globals['_ENUMDESCRIPTORPROTO']._serialized_start=3156 + _globals['_ENUMDESCRIPTORPROTO']._serialized_end=3511 + _globals['_ENUMDESCRIPTORPROTO_ENUMRESERVEDRANGE']._serialized_start=3452 + _globals['_ENUMDESCRIPTORPROTO_ENUMRESERVEDRANGE']._serialized_end=3511 + _globals['_ENUMVALUEDESCRIPTORPROTO']._serialized_start=3514 + _globals['_ENUMVALUEDESCRIPTORPROTO']._serialized_end=3645 + _globals['_SERVICEDESCRIPTORPROTO']._serialized_start=3648 + _globals['_SERVICEDESCRIPTORPROTO']._serialized_end=3815 + _globals['_METHODDESCRIPTORPROTO']._serialized_start=3818 + _globals['_METHODDESCRIPTORPROTO']._serialized_end=4083 + _globals['_FILEOPTIONS']._serialized_start=4086 + _globals['_FILEOPTIONS']._serialized_end=5283 + _globals['_FILEOPTIONS_OPTIMIZEMODE']._serialized_start=5180 + _globals['_FILEOPTIONS_OPTIMIZEMODE']._serialized_end=5238 + _globals['_MESSAGEOPTIONS']._serialized_start=5286 + _globals['_MESSAGEOPTIONS']._serialized_end=5786 + _globals['_FIELDOPTIONS']._serialized_start=5789 + _globals['_FIELDOPTIONS']._serialized_end=7482 + _globals['_FIELDOPTIONS_EDITIONDEFAULT']._serialized_start=6626 + _globals['_FIELDOPTIONS_EDITIONDEFAULT']._serialized_end=6716 + _globals['_FIELDOPTIONS_FEATURESUPPORT']._serialized_start=6719 + _globals['_FIELDOPTIONS_FEATURESUPPORT']._serialized_end=6997 + _globals['_FIELDOPTIONS_CTYPE']._serialized_start=6999 + _globals['_FIELDOPTIONS_CTYPE']._serialized_end=7046 + _globals['_FIELDOPTIONS_JSTYPE']._serialized_start=7048 + _globals['_FIELDOPTIONS_JSTYPE']._serialized_end=7101 + _globals['_FIELDOPTIONS_OPTIONRETENTION']._serialized_start=7103 + _globals['_FIELDOPTIONS_OPTIONRETENTION']._serialized_end=7188 + _globals['_FIELDOPTIONS_OPTIONTARGETTYPE']._serialized_start=7191 + _globals['_FIELDOPTIONS_OPTIONTARGETTYPE']._serialized_end=7459 + _globals['_ONEOFOPTIONS']._serialized_start=7485 + _globals['_ONEOFOPTIONS']._serialized_end=7657 + _globals['_ENUMOPTIONS']._serialized_start=7660 + _globals['_ENUMOPTIONS']._serialized_end=7997 + _globals['_ENUMVALUEOPTIONS']._serialized_start=8000 + _globals['_ENUMVALUEOPTIONS']._serialized_end=8344 + _globals['_SERVICEOPTIONS']._serialized_start=8347 + _globals['_SERVICEOPTIONS']._serialized_end=8560 + _globals['_METHODOPTIONS']._serialized_start=8563 + _globals['_METHODOPTIONS']._serialized_end=8972 + _globals['_METHODOPTIONS_IDEMPOTENCYLEVEL']._serialized_start=8881 + _globals['_METHODOPTIONS_IDEMPOTENCYLEVEL']._serialized_end=8961 + _globals['_UNINTERPRETEDOPTION']._serialized_start=8975 + _globals['_UNINTERPRETEDOPTION']._serialized_end=9385 + _globals['_UNINTERPRETEDOPTION_NAMEPART']._serialized_start=9311 + _globals['_UNINTERPRETEDOPTION_NAMEPART']._serialized_end=9385 + _globals['_FEATURESET']._serialized_start=9388 + _globals['_FEATURESET']._serialized_end=10707 + _globals['_FEATURESET_FIELDPRESENCE']._serialized_start=10204 + _globals['_FEATURESET_FIELDPRESENCE']._serialized_end=10296 + _globals['_FEATURESET_ENUMTYPE']._serialized_start=10298 + _globals['_FEATURESET_ENUMTYPE']._serialized_end=10353 + _globals['_FEATURESET_REPEATEDFIELDENCODING']._serialized_start=10355 + _globals['_FEATURESET_REPEATEDFIELDENCODING']._serialized_end=10441 + _globals['_FEATURESET_UTF8VALIDATION']._serialized_start=10443 + _globals['_FEATURESET_UTF8VALIDATION']._serialized_end=10516 + _globals['_FEATURESET_MESSAGEENCODING']._serialized_start=10518 + _globals['_FEATURESET_MESSAGEENCODING']._serialized_end=10601 + _globals['_FEATURESET_JSONFORMAT']._serialized_start=10603 + _globals['_FEATURESET_JSONFORMAT']._serialized_end=10675 + _globals['_FEATURESETDEFAULTS']._serialized_start=10710 + _globals['_FEATURESETDEFAULTS']._serialized_end=11205 + _globals['_FEATURESETDEFAULTS_FEATURESETEDITIONDEFAULT']._serialized_start=10957 + _globals['_FEATURESETDEFAULTS_FEATURESETEDITIONDEFAULT']._serialized_end=11205 + _globals['_SOURCECODEINFO']._serialized_start=11208 + _globals['_SOURCECODEINFO']._serialized_end=11503 + _globals['_SOURCECODEINFO_LOCATION']._serialized_start=11297 + _globals['_SOURCECODEINFO_LOCATION']._serialized_end=11503 + _globals['_GENERATEDCODEINFO']._serialized_start=11506 + _globals['_GENERATEDCODEINFO']._serialized_end=11842 + _globals['_GENERATEDCODEINFO_ANNOTATION']._serialized_start=11607 + _globals['_GENERATEDCODEINFO_ANNOTATION']._serialized_end=11842 + _globals['_GENERATEDCODEINFO_ANNOTATION_SEMANTIC']._serialized_start=11802 + _globals['_GENERATEDCODEINFO_ANNOTATION_SEMANTIC']._serialized_end=11842 +# @@protoc_insertion_point(module_scope) diff --git a/google/protobuf/descriptor_pool.py b/google/protobuf/descriptor_pool.py new file mode 100644 index 0000000..5545aed --- /dev/null +++ b/google/protobuf/descriptor_pool.py @@ -0,0 +1,1355 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Provides DescriptorPool to use as a container for proto2 descriptors. + +The DescriptorPool is used in conjection with a DescriptorDatabase to maintain +a collection of protocol buffer descriptors for use when dynamically creating +message types at runtime. + +For most applications protocol buffers should be used via modules generated by +the protocol buffer compiler tool. This should only be used when the type of +protocol buffers used in an application or library cannot be predetermined. + +Below is a straightforward example on how to use this class:: + + pool = DescriptorPool() + file_descriptor_protos = [ ... ] + for file_descriptor_proto in file_descriptor_protos: + pool.Add(file_descriptor_proto) + my_message_descriptor = pool.FindMessageTypeByName('some.package.MessageType') + +The message descriptor can be used in conjunction with the message_factory +module in order to create a protocol buffer class that can be encoded and +decoded. + +If you want to get a Python class for the specified proto, use the +helper functions inside google.protobuf.message_factory +directly instead of this class. +""" + +__author__ = 'matthewtoia@google.com (Matt Toia)' + +import collections +import threading +import warnings + +from google.protobuf import descriptor +from google.protobuf import descriptor_database +from google.protobuf import text_encoding +from google.protobuf.internal import python_edition_defaults +from google.protobuf.internal import python_message + +_USE_C_DESCRIPTORS = descriptor._USE_C_DESCRIPTORS # pylint: disable=protected-access + + +def _NormalizeFullyQualifiedName(name): + """Remove leading period from fully-qualified type name. + + Due to b/13860351 in descriptor_database.py, types in the root namespace are + generated with a leading period. This function removes that prefix. + + Args: + name (str): The fully-qualified symbol name. + + Returns: + str: The normalized fully-qualified symbol name. + """ + return name.lstrip('.') + + +def _OptionsOrNone(descriptor_proto): + """Returns the value of the field `options`, or None if it is not set.""" + if descriptor_proto.HasField('options'): + return descriptor_proto.options + else: + return None + + +def _IsMessageSetExtension(field): + return (field.is_extension and + field.containing_type.has_options and + field.containing_type.GetOptions().message_set_wire_format and + field.type == descriptor.FieldDescriptor.TYPE_MESSAGE and + field.label == descriptor.FieldDescriptor.LABEL_OPTIONAL) + +_edition_defaults_lock = threading.Lock() + + +class DescriptorPool(object): + """A collection of protobufs dynamically constructed by descriptor protos.""" + + if _USE_C_DESCRIPTORS: + + def __new__(cls, descriptor_db=None): + # pylint: disable=protected-access + return descriptor._message.DescriptorPool(descriptor_db) + + def __init__( + self, descriptor_db=None, use_deprecated_legacy_json_field_conflicts=False + ): + """Initializes a Pool of proto buffs. + + The descriptor_db argument to the constructor is provided to allow + specialized file descriptor proto lookup code to be triggered on demand. An + example would be an implementation which will read and compile a file + specified in a call to FindFileByName() and not require the call to Add() + at all. Results from this database will be cached internally here as well. + + Args: + descriptor_db: A secondary source of file descriptors. + use_deprecated_legacy_json_field_conflicts: Unused, for compatibility with + C++. + """ + + self._internal_db = descriptor_database.DescriptorDatabase() + self._descriptor_db = descriptor_db + self._descriptors = {} + self._enum_descriptors = {} + self._service_descriptors = {} + self._file_descriptors = {} + self._toplevel_extensions = {} + self._top_enum_values = {} + # We store extensions in two two-level mappings: The first key is the + # descriptor of the message being extended, the second key is the extension + # full name or its tag number. + self._extensions_by_name = collections.defaultdict(dict) + self._extensions_by_number = collections.defaultdict(dict) + self._serialized_edition_defaults = ( + python_edition_defaults._PROTOBUF_INTERNAL_PYTHON_EDITION_DEFAULTS + ) + self._edition_defaults = None + self._feature_cache = dict() + + def _CheckConflictRegister(self, desc, desc_name, file_name): + """Check if the descriptor name conflicts with another of the same name. + + Args: + desc: Descriptor of a message, enum, service, extension or enum value. + desc_name (str): the full name of desc. + file_name (str): The file name of descriptor. + """ + for register, descriptor_type in [ + (self._descriptors, descriptor.Descriptor), + (self._enum_descriptors, descriptor.EnumDescriptor), + (self._service_descriptors, descriptor.ServiceDescriptor), + (self._toplevel_extensions, descriptor.FieldDescriptor), + (self._top_enum_values, descriptor.EnumValueDescriptor)]: + if desc_name in register: + old_desc = register[desc_name] + if isinstance(old_desc, descriptor.EnumValueDescriptor): + old_file = old_desc.type.file.name + else: + old_file = old_desc.file.name + + if not isinstance(desc, descriptor_type) or ( + old_file != file_name): + error_msg = ('Conflict register for file "' + file_name + + '": ' + desc_name + + ' is already defined in file "' + + old_file + '". Please fix the conflict by adding ' + 'package name on the proto file, or use different ' + 'name for the duplication.') + if isinstance(desc, descriptor.EnumValueDescriptor): + error_msg += ('\nNote: enum values appear as ' + 'siblings of the enum type instead of ' + 'children of it.') + + raise TypeError(error_msg) + + return + + def Add(self, file_desc_proto): + """Adds the FileDescriptorProto and its types to this pool. + + Args: + file_desc_proto (FileDescriptorProto): The file descriptor to add. + """ + + self._internal_db.Add(file_desc_proto) + + def AddSerializedFile(self, serialized_file_desc_proto): + """Adds the FileDescriptorProto and its types to this pool. + + Args: + serialized_file_desc_proto (bytes): A bytes string, serialization of the + :class:`FileDescriptorProto` to add. + + Returns: + FileDescriptor: Descriptor for the added file. + """ + + # pylint: disable=g-import-not-at-top + from google.protobuf import descriptor_pb2 + file_desc_proto = descriptor_pb2.FileDescriptorProto.FromString( + serialized_file_desc_proto) + file_desc = self._ConvertFileProtoToFileDescriptor(file_desc_proto) + file_desc.serialized_pb = serialized_file_desc_proto + return file_desc + + # Never call this method. It is for internal usage only. + def _AddDescriptor(self, desc): + """Adds a Descriptor to the pool, non-recursively. + + If the Descriptor contains nested messages or enums, the caller must + explicitly register them. This method also registers the FileDescriptor + associated with the message. + + Args: + desc: A Descriptor. + """ + if not isinstance(desc, descriptor.Descriptor): + raise TypeError('Expected instance of descriptor.Descriptor.') + + self._CheckConflictRegister(desc, desc.full_name, desc.file.name) + + self._descriptors[desc.full_name] = desc + self._AddFileDescriptor(desc.file) + + # Never call this method. It is for internal usage only. + def _AddEnumDescriptor(self, enum_desc): + """Adds an EnumDescriptor to the pool. + + This method also registers the FileDescriptor associated with the enum. + + Args: + enum_desc: An EnumDescriptor. + """ + + if not isinstance(enum_desc, descriptor.EnumDescriptor): + raise TypeError('Expected instance of descriptor.EnumDescriptor.') + + file_name = enum_desc.file.name + self._CheckConflictRegister(enum_desc, enum_desc.full_name, file_name) + self._enum_descriptors[enum_desc.full_name] = enum_desc + + # Top enum values need to be indexed. + # Count the number of dots to see whether the enum is toplevel or nested + # in a message. We cannot use enum_desc.containing_type at this stage. + if enum_desc.file.package: + top_level = (enum_desc.full_name.count('.') + - enum_desc.file.package.count('.') == 1) + else: + top_level = enum_desc.full_name.count('.') == 0 + if top_level: + file_name = enum_desc.file.name + package = enum_desc.file.package + for enum_value in enum_desc.values: + full_name = _NormalizeFullyQualifiedName( + '.'.join((package, enum_value.name))) + self._CheckConflictRegister(enum_value, full_name, file_name) + self._top_enum_values[full_name] = enum_value + self._AddFileDescriptor(enum_desc.file) + + # Never call this method. It is for internal usage only. + def _AddServiceDescriptor(self, service_desc): + """Adds a ServiceDescriptor to the pool. + + Args: + service_desc: A ServiceDescriptor. + """ + + if not isinstance(service_desc, descriptor.ServiceDescriptor): + raise TypeError('Expected instance of descriptor.ServiceDescriptor.') + + self._CheckConflictRegister(service_desc, service_desc.full_name, + service_desc.file.name) + self._service_descriptors[service_desc.full_name] = service_desc + + # Never call this method. It is for internal usage only. + def _AddExtensionDescriptor(self, extension): + """Adds a FieldDescriptor describing an extension to the pool. + + Args: + extension: A FieldDescriptor. + + Raises: + AssertionError: when another extension with the same number extends the + same message. + TypeError: when the specified extension is not a + descriptor.FieldDescriptor. + """ + if not (isinstance(extension, descriptor.FieldDescriptor) and + extension.is_extension): + raise TypeError('Expected an extension descriptor.') + + if extension.extension_scope is None: + self._CheckConflictRegister( + extension, extension.full_name, extension.file.name) + self._toplevel_extensions[extension.full_name] = extension + + try: + existing_desc = self._extensions_by_number[ + extension.containing_type][extension.number] + except KeyError: + pass + else: + if extension is not existing_desc: + raise AssertionError( + 'Extensions "%s" and "%s" both try to extend message type "%s" ' + 'with field number %d.' % + (extension.full_name, existing_desc.full_name, + extension.containing_type.full_name, extension.number)) + + self._extensions_by_number[extension.containing_type][ + extension.number] = extension + self._extensions_by_name[extension.containing_type][ + extension.full_name] = extension + + # Also register MessageSet extensions with the type name. + if _IsMessageSetExtension(extension): + self._extensions_by_name[extension.containing_type][ + extension.message_type.full_name] = extension + + if hasattr(extension.containing_type, '_concrete_class'): + python_message._AttachFieldHelpers( + extension.containing_type._concrete_class, extension) + + # Never call this method. It is for internal usage only. + def _InternalAddFileDescriptor(self, file_desc): + """Adds a FileDescriptor to the pool, non-recursively. + + If the FileDescriptor contains messages or enums, the caller must explicitly + register them. + + Args: + file_desc: A FileDescriptor. + """ + + self._AddFileDescriptor(file_desc) + + def _AddFileDescriptor(self, file_desc): + """Adds a FileDescriptor to the pool, non-recursively. + + If the FileDescriptor contains messages or enums, the caller must explicitly + register them. + + Args: + file_desc: A FileDescriptor. + """ + + if not isinstance(file_desc, descriptor.FileDescriptor): + raise TypeError('Expected instance of descriptor.FileDescriptor.') + self._file_descriptors[file_desc.name] = file_desc + + def FindFileByName(self, file_name): + """Gets a FileDescriptor by file name. + + Args: + file_name (str): The path to the file to get a descriptor for. + + Returns: + FileDescriptor: The descriptor for the named file. + + Raises: + KeyError: if the file cannot be found in the pool. + """ + + try: + return self._file_descriptors[file_name] + except KeyError: + pass + + try: + file_proto = self._internal_db.FindFileByName(file_name) + except KeyError as error: + if self._descriptor_db: + file_proto = self._descriptor_db.FindFileByName(file_name) + else: + raise error + if not file_proto: + raise KeyError('Cannot find a file named %s' % file_name) + return self._ConvertFileProtoToFileDescriptor(file_proto) + + def FindFileContainingSymbol(self, symbol): + """Gets the FileDescriptor for the file containing the specified symbol. + + Args: + symbol (str): The name of the symbol to search for. + + Returns: + FileDescriptor: Descriptor for the file that contains the specified + symbol. + + Raises: + KeyError: if the file cannot be found in the pool. + """ + + symbol = _NormalizeFullyQualifiedName(symbol) + try: + return self._InternalFindFileContainingSymbol(symbol) + except KeyError: + pass + + try: + # Try fallback database. Build and find again if possible. + self._FindFileContainingSymbolInDb(symbol) + return self._InternalFindFileContainingSymbol(symbol) + except KeyError: + raise KeyError('Cannot find a file containing %s' % symbol) + + def _InternalFindFileContainingSymbol(self, symbol): + """Gets the already built FileDescriptor containing the specified symbol. + + Args: + symbol (str): The name of the symbol to search for. + + Returns: + FileDescriptor: Descriptor for the file that contains the specified + symbol. + + Raises: + KeyError: if the file cannot be found in the pool. + """ + try: + return self._descriptors[symbol].file + except KeyError: + pass + + try: + return self._enum_descriptors[symbol].file + except KeyError: + pass + + try: + return self._service_descriptors[symbol].file + except KeyError: + pass + + try: + return self._top_enum_values[symbol].type.file + except KeyError: + pass + + try: + return self._toplevel_extensions[symbol].file + except KeyError: + pass + + # Try fields, enum values and nested extensions inside a message. + top_name, _, sub_name = symbol.rpartition('.') + try: + message = self.FindMessageTypeByName(top_name) + assert (sub_name in message.extensions_by_name or + sub_name in message.fields_by_name or + sub_name in message.enum_values_by_name) + return message.file + except (KeyError, AssertionError): + raise KeyError('Cannot find a file containing %s' % symbol) + + def FindMessageTypeByName(self, full_name): + """Loads the named descriptor from the pool. + + Args: + full_name (str): The full name of the descriptor to load. + + Returns: + Descriptor: The descriptor for the named type. + + Raises: + KeyError: if the message cannot be found in the pool. + """ + + full_name = _NormalizeFullyQualifiedName(full_name) + if full_name not in self._descriptors: + self._FindFileContainingSymbolInDb(full_name) + return self._descriptors[full_name] + + def FindEnumTypeByName(self, full_name): + """Loads the named enum descriptor from the pool. + + Args: + full_name (str): The full name of the enum descriptor to load. + + Returns: + EnumDescriptor: The enum descriptor for the named type. + + Raises: + KeyError: if the enum cannot be found in the pool. + """ + + full_name = _NormalizeFullyQualifiedName(full_name) + if full_name not in self._enum_descriptors: + self._FindFileContainingSymbolInDb(full_name) + return self._enum_descriptors[full_name] + + def FindFieldByName(self, full_name): + """Loads the named field descriptor from the pool. + + Args: + full_name (str): The full name of the field descriptor to load. + + Returns: + FieldDescriptor: The field descriptor for the named field. + + Raises: + KeyError: if the field cannot be found in the pool. + """ + full_name = _NormalizeFullyQualifiedName(full_name) + message_name, _, field_name = full_name.rpartition('.') + message_descriptor = self.FindMessageTypeByName(message_name) + return message_descriptor.fields_by_name[field_name] + + def FindOneofByName(self, full_name): + """Loads the named oneof descriptor from the pool. + + Args: + full_name (str): The full name of the oneof descriptor to load. + + Returns: + OneofDescriptor: The oneof descriptor for the named oneof. + + Raises: + KeyError: if the oneof cannot be found in the pool. + """ + full_name = _NormalizeFullyQualifiedName(full_name) + message_name, _, oneof_name = full_name.rpartition('.') + message_descriptor = self.FindMessageTypeByName(message_name) + return message_descriptor.oneofs_by_name[oneof_name] + + def FindExtensionByName(self, full_name): + """Loads the named extension descriptor from the pool. + + Args: + full_name (str): The full name of the extension descriptor to load. + + Returns: + FieldDescriptor: The field descriptor for the named extension. + + Raises: + KeyError: if the extension cannot be found in the pool. + """ + full_name = _NormalizeFullyQualifiedName(full_name) + try: + # The proto compiler does not give any link between the FileDescriptor + # and top-level extensions unless the FileDescriptorProto is added to + # the DescriptorDatabase, but this can impact memory usage. + # So we registered these extensions by name explicitly. + return self._toplevel_extensions[full_name] + except KeyError: + pass + message_name, _, extension_name = full_name.rpartition('.') + try: + # Most extensions are nested inside a message. + scope = self.FindMessageTypeByName(message_name) + except KeyError: + # Some extensions are defined at file scope. + scope = self._FindFileContainingSymbolInDb(full_name) + return scope.extensions_by_name[extension_name] + + def FindExtensionByNumber(self, message_descriptor, number): + """Gets the extension of the specified message with the specified number. + + Extensions have to be registered to this pool by calling :func:`Add` or + :func:`AddExtensionDescriptor`. + + Args: + message_descriptor (Descriptor): descriptor of the extended message. + number (int): Number of the extension field. + + Returns: + FieldDescriptor: The descriptor for the extension. + + Raises: + KeyError: when no extension with the given number is known for the + specified message. + """ + try: + return self._extensions_by_number[message_descriptor][number] + except KeyError: + self._TryLoadExtensionFromDB(message_descriptor, number) + return self._extensions_by_number[message_descriptor][number] + + def FindAllExtensions(self, message_descriptor): + """Gets all the known extensions of a given message. + + Extensions have to be registered to this pool by build related + :func:`Add` or :func:`AddExtensionDescriptor`. + + Args: + message_descriptor (Descriptor): Descriptor of the extended message. + + Returns: + list[FieldDescriptor]: Field descriptors describing the extensions. + """ + # Fallback to descriptor db if FindAllExtensionNumbers is provided. + if self._descriptor_db and hasattr( + self._descriptor_db, 'FindAllExtensionNumbers'): + full_name = message_descriptor.full_name + all_numbers = self._descriptor_db.FindAllExtensionNumbers(full_name) + for number in all_numbers: + if number in self._extensions_by_number[message_descriptor]: + continue + self._TryLoadExtensionFromDB(message_descriptor, number) + + return list(self._extensions_by_number[message_descriptor].values()) + + def _TryLoadExtensionFromDB(self, message_descriptor, number): + """Try to Load extensions from descriptor db. + + Args: + message_descriptor: descriptor of the extended message. + number: the extension number that needs to be loaded. + """ + if not self._descriptor_db: + return + # Only supported when FindFileContainingExtension is provided. + if not hasattr( + self._descriptor_db, 'FindFileContainingExtension'): + return + + full_name = message_descriptor.full_name + file_proto = self._descriptor_db.FindFileContainingExtension( + full_name, number) + + if file_proto is None: + return + + try: + self._ConvertFileProtoToFileDescriptor(file_proto) + except: + warn_msg = ('Unable to load proto file %s for extension number %d.' % + (file_proto.name, number)) + warnings.warn(warn_msg, RuntimeWarning) + + def FindServiceByName(self, full_name): + """Loads the named service descriptor from the pool. + + Args: + full_name (str): The full name of the service descriptor to load. + + Returns: + ServiceDescriptor: The service descriptor for the named service. + + Raises: + KeyError: if the service cannot be found in the pool. + """ + full_name = _NormalizeFullyQualifiedName(full_name) + if full_name not in self._service_descriptors: + self._FindFileContainingSymbolInDb(full_name) + return self._service_descriptors[full_name] + + def FindMethodByName(self, full_name): + """Loads the named service method descriptor from the pool. + + Args: + full_name (str): The full name of the method descriptor to load. + + Returns: + MethodDescriptor: The method descriptor for the service method. + + Raises: + KeyError: if the method cannot be found in the pool. + """ + full_name = _NormalizeFullyQualifiedName(full_name) + service_name, _, method_name = full_name.rpartition('.') + service_descriptor = self.FindServiceByName(service_name) + return service_descriptor.methods_by_name[method_name] + + def SetFeatureSetDefaults(self, defaults): + """Sets the default feature mappings used during the build. + + Args: + defaults: a FeatureSetDefaults message containing the new mappings. + """ + if self._edition_defaults is not None: + raise ValueError( + "Feature set defaults can't be changed once the pool has started" + ' building!' + ) + + # pylint: disable=g-import-not-at-top + from google.protobuf import descriptor_pb2 + + if not isinstance(defaults, descriptor_pb2.FeatureSetDefaults): + raise TypeError('SetFeatureSetDefaults called with invalid type') + + + if defaults.minimum_edition > defaults.maximum_edition: + raise ValueError( + 'Invalid edition range %s to %s' + % ( + descriptor_pb2.Edition.Name(defaults.minimum_edition), + descriptor_pb2.Edition.Name(defaults.maximum_edition), + ) + ) + + prev_edition = descriptor_pb2.Edition.EDITION_UNKNOWN + for d in defaults.defaults: + if d.edition == descriptor_pb2.Edition.EDITION_UNKNOWN: + raise ValueError('Invalid edition EDITION_UNKNOWN specified') + if prev_edition >= d.edition: + raise ValueError( + 'Feature set defaults are not strictly increasing. %s is greater' + ' than or equal to %s' + % ( + descriptor_pb2.Edition.Name(prev_edition), + descriptor_pb2.Edition.Name(d.edition), + ) + ) + prev_edition = d.edition + self._edition_defaults = defaults + + def _CreateDefaultFeatures(self, edition): + """Creates a FeatureSet message with defaults for a specific edition. + + Args: + edition: the edition to generate defaults for. + + Returns: + A FeatureSet message with defaults for a specific edition. + """ + # pylint: disable=g-import-not-at-top + from google.protobuf import descriptor_pb2 + + with _edition_defaults_lock: + if not self._edition_defaults: + self._edition_defaults = descriptor_pb2.FeatureSetDefaults() + self._edition_defaults.ParseFromString( + self._serialized_edition_defaults + ) + + if edition < self._edition_defaults.minimum_edition: + raise TypeError( + 'Edition %s is earlier than the minimum supported edition %s!' + % ( + descriptor_pb2.Edition.Name(edition), + descriptor_pb2.Edition.Name( + self._edition_defaults.minimum_edition + ), + ) + ) + if edition > self._edition_defaults.maximum_edition: + raise TypeError( + 'Edition %s is later than the maximum supported edition %s!' + % ( + descriptor_pb2.Edition.Name(edition), + descriptor_pb2.Edition.Name( + self._edition_defaults.maximum_edition + ), + ) + ) + found = None + for d in self._edition_defaults.defaults: + if d.edition > edition: + break + found = d + if found is None: + raise TypeError( + 'No valid default found for edition %s!' + % descriptor_pb2.Edition.Name(edition) + ) + + defaults = descriptor_pb2.FeatureSet() + defaults.CopyFrom(found.fixed_features) + defaults.MergeFrom(found.overridable_features) + return defaults + + def _InternFeatures(self, features): + serialized = features.SerializeToString() + with _edition_defaults_lock: + cached = self._feature_cache.get(serialized) + if cached is None: + self._feature_cache[serialized] = features + cached = features + return cached + + def _FindFileContainingSymbolInDb(self, symbol): + """Finds the file in descriptor DB containing the specified symbol. + + Args: + symbol (str): The name of the symbol to search for. + + Returns: + FileDescriptor: The file that contains the specified symbol. + + Raises: + KeyError: if the file cannot be found in the descriptor database. + """ + try: + file_proto = self._internal_db.FindFileContainingSymbol(symbol) + except KeyError as error: + if self._descriptor_db: + file_proto = self._descriptor_db.FindFileContainingSymbol(symbol) + else: + raise error + if not file_proto: + raise KeyError('Cannot find a file containing %s' % symbol) + return self._ConvertFileProtoToFileDescriptor(file_proto) + + def _ConvertFileProtoToFileDescriptor(self, file_proto): + """Creates a FileDescriptor from a proto or returns a cached copy. + + This method also has the side effect of loading all the symbols found in + the file into the appropriate dictionaries in the pool. + + Args: + file_proto: The proto to convert. + + Returns: + A FileDescriptor matching the passed in proto. + """ + if file_proto.name not in self._file_descriptors: + built_deps = list(self._GetDeps(file_proto.dependency)) + direct_deps = [self.FindFileByName(n) for n in file_proto.dependency] + public_deps = [direct_deps[i] for i in file_proto.public_dependency] + + # pylint: disable=g-import-not-at-top + from google.protobuf import descriptor_pb2 + + file_descriptor = descriptor.FileDescriptor( + pool=self, + name=file_proto.name, + package=file_proto.package, + syntax=file_proto.syntax, + edition=descriptor_pb2.Edition.Name(file_proto.edition), + options=_OptionsOrNone(file_proto), + serialized_pb=file_proto.SerializeToString(), + dependencies=direct_deps, + public_dependencies=public_deps, + # pylint: disable=protected-access + create_key=descriptor._internal_create_key, + ) + scope = {} + + # This loop extracts all the message and enum types from all the + # dependencies of the file_proto. This is necessary to create the + # scope of available message types when defining the passed in + # file proto. + for dependency in built_deps: + scope.update(self._ExtractSymbols( + dependency.message_types_by_name.values())) + scope.update((_PrefixWithDot(enum.full_name), enum) + for enum in dependency.enum_types_by_name.values()) + + for message_type in file_proto.message_type: + message_desc = self._ConvertMessageDescriptor( + message_type, file_proto.package, file_descriptor, scope, + file_proto.syntax) + file_descriptor.message_types_by_name[message_desc.name] = ( + message_desc) + + for enum_type in file_proto.enum_type: + file_descriptor.enum_types_by_name[enum_type.name] = ( + self._ConvertEnumDescriptor(enum_type, file_proto.package, + file_descriptor, None, scope, True)) + + for index, extension_proto in enumerate(file_proto.extension): + extension_desc = self._MakeFieldDescriptor( + extension_proto, file_proto.package, index, file_descriptor, + is_extension=True) + extension_desc.containing_type = self._GetTypeFromScope( + file_descriptor.package, extension_proto.extendee, scope) + self._SetFieldType(extension_proto, extension_desc, + file_descriptor.package, scope) + file_descriptor.extensions_by_name[extension_desc.name] = ( + extension_desc) + + for desc_proto in file_proto.message_type: + self._SetAllFieldTypes(file_proto.package, desc_proto, scope) + + if file_proto.package: + desc_proto_prefix = _PrefixWithDot(file_proto.package) + else: + desc_proto_prefix = '' + + for desc_proto in file_proto.message_type: + desc = self._GetTypeFromScope( + desc_proto_prefix, desc_proto.name, scope) + file_descriptor.message_types_by_name[desc_proto.name] = desc + + for index, service_proto in enumerate(file_proto.service): + file_descriptor.services_by_name[service_proto.name] = ( + self._MakeServiceDescriptor(service_proto, index, scope, + file_proto.package, file_descriptor)) + + self._file_descriptors[file_proto.name] = file_descriptor + + # Add extensions to the pool + def AddExtensionForNested(message_type): + for nested in message_type.nested_types: + AddExtensionForNested(nested) + for extension in message_type.extensions: + self._AddExtensionDescriptor(extension) + + file_desc = self._file_descriptors[file_proto.name] + for extension in file_desc.extensions_by_name.values(): + self._AddExtensionDescriptor(extension) + for message_type in file_desc.message_types_by_name.values(): + AddExtensionForNested(message_type) + + return file_desc + + def _ConvertMessageDescriptor(self, desc_proto, package=None, file_desc=None, + scope=None, syntax=None): + """Adds the proto to the pool in the specified package. + + Args: + desc_proto: The descriptor_pb2.DescriptorProto protobuf message. + package: The package the proto should be located in. + file_desc: The file containing this message. + scope: Dict mapping short and full symbols to message and enum types. + syntax: string indicating syntax of the file ("proto2" or "proto3") + + Returns: + The added descriptor. + """ + + if package: + desc_name = '.'.join((package, desc_proto.name)) + else: + desc_name = desc_proto.name + + if file_desc is None: + file_name = None + else: + file_name = file_desc.name + + if scope is None: + scope = {} + + nested = [ + self._ConvertMessageDescriptor( + nested, desc_name, file_desc, scope, syntax) + for nested in desc_proto.nested_type] + enums = [ + self._ConvertEnumDescriptor(enum, desc_name, file_desc, None, + scope, False) + for enum in desc_proto.enum_type] + fields = [self._MakeFieldDescriptor(field, desc_name, index, file_desc) + for index, field in enumerate(desc_proto.field)] + extensions = [ + self._MakeFieldDescriptor(extension, desc_name, index, file_desc, + is_extension=True) + for index, extension in enumerate(desc_proto.extension)] + oneofs = [ + # pylint: disable=g-complex-comprehension + descriptor.OneofDescriptor( + desc.name, + '.'.join((desc_name, desc.name)), + index, + None, + [], + _OptionsOrNone(desc), + # pylint: disable=protected-access + create_key=descriptor._internal_create_key) + for index, desc in enumerate(desc_proto.oneof_decl) + ] + extension_ranges = [(r.start, r.end) for r in desc_proto.extension_range] + if extension_ranges: + is_extendable = True + else: + is_extendable = False + desc = descriptor.Descriptor( + name=desc_proto.name, + full_name=desc_name, + filename=file_name, + containing_type=None, + fields=fields, + oneofs=oneofs, + nested_types=nested, + enum_types=enums, + extensions=extensions, + options=_OptionsOrNone(desc_proto), + is_extendable=is_extendable, + extension_ranges=extension_ranges, + file=file_desc, + serialized_start=None, + serialized_end=None, + is_map_entry=desc_proto.options.map_entry, + # pylint: disable=protected-access + create_key=descriptor._internal_create_key, + ) + for nested in desc.nested_types: + nested.containing_type = desc + for enum in desc.enum_types: + enum.containing_type = desc + for field_index, field_desc in enumerate(desc_proto.field): + if field_desc.HasField('oneof_index'): + oneof_index = field_desc.oneof_index + oneofs[oneof_index].fields.append(fields[field_index]) + fields[field_index].containing_oneof = oneofs[oneof_index] + + scope[_PrefixWithDot(desc_name)] = desc + self._CheckConflictRegister(desc, desc.full_name, desc.file.name) + self._descriptors[desc_name] = desc + return desc + + def _ConvertEnumDescriptor(self, enum_proto, package=None, file_desc=None, + containing_type=None, scope=None, top_level=False): + """Make a protobuf EnumDescriptor given an EnumDescriptorProto protobuf. + + Args: + enum_proto: The descriptor_pb2.EnumDescriptorProto protobuf message. + package: Optional package name for the new message EnumDescriptor. + file_desc: The file containing the enum descriptor. + containing_type: The type containing this enum. + scope: Scope containing available types. + top_level: If True, the enum is a top level symbol. If False, the enum + is defined inside a message. + + Returns: + The added descriptor + """ + + if package: + enum_name = '.'.join((package, enum_proto.name)) + else: + enum_name = enum_proto.name + + if file_desc is None: + file_name = None + else: + file_name = file_desc.name + + values = [self._MakeEnumValueDescriptor(value, index) + for index, value in enumerate(enum_proto.value)] + desc = descriptor.EnumDescriptor(name=enum_proto.name, + full_name=enum_name, + filename=file_name, + file=file_desc, + values=values, + containing_type=containing_type, + options=_OptionsOrNone(enum_proto), + # pylint: disable=protected-access + create_key=descriptor._internal_create_key) + scope['.%s' % enum_name] = desc + self._CheckConflictRegister(desc, desc.full_name, desc.file.name) + self._enum_descriptors[enum_name] = desc + + # Add top level enum values. + if top_level: + for value in values: + full_name = _NormalizeFullyQualifiedName( + '.'.join((package, value.name))) + self._CheckConflictRegister(value, full_name, file_name) + self._top_enum_values[full_name] = value + + return desc + + def _MakeFieldDescriptor(self, field_proto, message_name, index, + file_desc, is_extension=False): + """Creates a field descriptor from a FieldDescriptorProto. + + For message and enum type fields, this method will do a look up + in the pool for the appropriate descriptor for that type. If it + is unavailable, it will fall back to the _source function to + create it. If this type is still unavailable, construction will + fail. + + Args: + field_proto: The proto describing the field. + message_name: The name of the containing message. + index: Index of the field + file_desc: The file containing the field descriptor. + is_extension: Indication that this field is for an extension. + + Returns: + An initialized FieldDescriptor object + """ + + if message_name: + full_name = '.'.join((message_name, field_proto.name)) + else: + full_name = field_proto.name + + if field_proto.json_name: + json_name = field_proto.json_name + else: + json_name = None + + return descriptor.FieldDescriptor( + name=field_proto.name, + full_name=full_name, + index=index, + number=field_proto.number, + type=field_proto.type, + cpp_type=None, + message_type=None, + enum_type=None, + containing_type=None, + label=field_proto.label, + has_default_value=False, + default_value=None, + is_extension=is_extension, + extension_scope=None, + options=_OptionsOrNone(field_proto), + json_name=json_name, + file=file_desc, + # pylint: disable=protected-access + create_key=descriptor._internal_create_key) + + def _SetAllFieldTypes(self, package, desc_proto, scope): + """Sets all the descriptor's fields's types. + + This method also sets the containing types on any extensions. + + Args: + package: The current package of desc_proto. + desc_proto: The message descriptor to update. + scope: Enclosing scope of available types. + """ + + package = _PrefixWithDot(package) + + main_desc = self._GetTypeFromScope(package, desc_proto.name, scope) + + if package == '.': + nested_package = _PrefixWithDot(desc_proto.name) + else: + nested_package = '.'.join([package, desc_proto.name]) + + for field_proto, field_desc in zip(desc_proto.field, main_desc.fields): + self._SetFieldType(field_proto, field_desc, nested_package, scope) + + for extension_proto, extension_desc in ( + zip(desc_proto.extension, main_desc.extensions)): + extension_desc.containing_type = self._GetTypeFromScope( + nested_package, extension_proto.extendee, scope) + self._SetFieldType(extension_proto, extension_desc, nested_package, scope) + + for nested_type in desc_proto.nested_type: + self._SetAllFieldTypes(nested_package, nested_type, scope) + + def _SetFieldType(self, field_proto, field_desc, package, scope): + """Sets the field's type, cpp_type, message_type and enum_type. + + Args: + field_proto: Data about the field in proto format. + field_desc: The descriptor to modify. + package: The package the field's container is in. + scope: Enclosing scope of available types. + """ + if field_proto.type_name: + desc = self._GetTypeFromScope(package, field_proto.type_name, scope) + else: + desc = None + + if not field_proto.HasField('type'): + if isinstance(desc, descriptor.Descriptor): + field_proto.type = descriptor.FieldDescriptor.TYPE_MESSAGE + else: + field_proto.type = descriptor.FieldDescriptor.TYPE_ENUM + + field_desc.cpp_type = descriptor.FieldDescriptor.ProtoTypeToCppProtoType( + field_proto.type) + + if (field_proto.type == descriptor.FieldDescriptor.TYPE_MESSAGE + or field_proto.type == descriptor.FieldDescriptor.TYPE_GROUP): + field_desc.message_type = desc + + if field_proto.type == descriptor.FieldDescriptor.TYPE_ENUM: + field_desc.enum_type = desc + + if field_proto.label == descriptor.FieldDescriptor.LABEL_REPEATED: + field_desc.has_default_value = False + field_desc.default_value = [] + elif field_proto.HasField('default_value'): + field_desc.has_default_value = True + if (field_proto.type == descriptor.FieldDescriptor.TYPE_DOUBLE or + field_proto.type == descriptor.FieldDescriptor.TYPE_FLOAT): + field_desc.default_value = float(field_proto.default_value) + elif field_proto.type == descriptor.FieldDescriptor.TYPE_STRING: + field_desc.default_value = field_proto.default_value + elif field_proto.type == descriptor.FieldDescriptor.TYPE_BOOL: + field_desc.default_value = field_proto.default_value.lower() == 'true' + elif field_proto.type == descriptor.FieldDescriptor.TYPE_ENUM: + field_desc.default_value = field_desc.enum_type.values_by_name[ + field_proto.default_value].number + elif field_proto.type == descriptor.FieldDescriptor.TYPE_BYTES: + field_desc.default_value = text_encoding.CUnescape( + field_proto.default_value) + elif field_proto.type == descriptor.FieldDescriptor.TYPE_MESSAGE: + field_desc.default_value = None + else: + # All other types are of the "int" type. + field_desc.default_value = int(field_proto.default_value) + else: + field_desc.has_default_value = False + if (field_proto.type == descriptor.FieldDescriptor.TYPE_DOUBLE or + field_proto.type == descriptor.FieldDescriptor.TYPE_FLOAT): + field_desc.default_value = 0.0 + elif field_proto.type == descriptor.FieldDescriptor.TYPE_STRING: + field_desc.default_value = u'' + elif field_proto.type == descriptor.FieldDescriptor.TYPE_BOOL: + field_desc.default_value = False + elif field_proto.type == descriptor.FieldDescriptor.TYPE_ENUM: + field_desc.default_value = field_desc.enum_type.values[0].number + elif field_proto.type == descriptor.FieldDescriptor.TYPE_BYTES: + field_desc.default_value = b'' + elif field_proto.type == descriptor.FieldDescriptor.TYPE_MESSAGE: + field_desc.default_value = None + elif field_proto.type == descriptor.FieldDescriptor.TYPE_GROUP: + field_desc.default_value = None + else: + # All other types are of the "int" type. + field_desc.default_value = 0 + + field_desc.type = field_proto.type + + def _MakeEnumValueDescriptor(self, value_proto, index): + """Creates a enum value descriptor object from a enum value proto. + + Args: + value_proto: The proto describing the enum value. + index: The index of the enum value. + + Returns: + An initialized EnumValueDescriptor object. + """ + + return descriptor.EnumValueDescriptor( + name=value_proto.name, + index=index, + number=value_proto.number, + options=_OptionsOrNone(value_proto), + type=None, + # pylint: disable=protected-access + create_key=descriptor._internal_create_key) + + def _MakeServiceDescriptor(self, service_proto, service_index, scope, + package, file_desc): + """Make a protobuf ServiceDescriptor given a ServiceDescriptorProto. + + Args: + service_proto: The descriptor_pb2.ServiceDescriptorProto protobuf message. + service_index: The index of the service in the File. + scope: Dict mapping short and full symbols to message and enum types. + package: Optional package name for the new message EnumDescriptor. + file_desc: The file containing the service descriptor. + + Returns: + The added descriptor. + """ + + if package: + service_name = '.'.join((package, service_proto.name)) + else: + service_name = service_proto.name + + methods = [self._MakeMethodDescriptor(method_proto, service_name, package, + scope, index) + for index, method_proto in enumerate(service_proto.method)] + desc = descriptor.ServiceDescriptor( + name=service_proto.name, + full_name=service_name, + index=service_index, + methods=methods, + options=_OptionsOrNone(service_proto), + file=file_desc, + # pylint: disable=protected-access + create_key=descriptor._internal_create_key) + self._CheckConflictRegister(desc, desc.full_name, desc.file.name) + self._service_descriptors[service_name] = desc + return desc + + def _MakeMethodDescriptor(self, method_proto, service_name, package, scope, + index): + """Creates a method descriptor from a MethodDescriptorProto. + + Args: + method_proto: The proto describing the method. + service_name: The name of the containing service. + package: Optional package name to look up for types. + scope: Scope containing available types. + index: Index of the method in the service. + + Returns: + An initialized MethodDescriptor object. + """ + full_name = '.'.join((service_name, method_proto.name)) + input_type = self._GetTypeFromScope( + package, method_proto.input_type, scope) + output_type = self._GetTypeFromScope( + package, method_proto.output_type, scope) + return descriptor.MethodDescriptor( + name=method_proto.name, + full_name=full_name, + index=index, + containing_service=None, + input_type=input_type, + output_type=output_type, + client_streaming=method_proto.client_streaming, + server_streaming=method_proto.server_streaming, + options=_OptionsOrNone(method_proto), + # pylint: disable=protected-access + create_key=descriptor._internal_create_key) + + def _ExtractSymbols(self, descriptors): + """Pulls out all the symbols from descriptor protos. + + Args: + descriptors: The messages to extract descriptors from. + Yields: + A two element tuple of the type name and descriptor object. + """ + + for desc in descriptors: + yield (_PrefixWithDot(desc.full_name), desc) + for symbol in self._ExtractSymbols(desc.nested_types): + yield symbol + for enum in desc.enum_types: + yield (_PrefixWithDot(enum.full_name), enum) + + def _GetDeps(self, dependencies, visited=None): + """Recursively finds dependencies for file protos. + + Args: + dependencies: The names of the files being depended on. + visited: The names of files already found. + + Yields: + Each direct and indirect dependency. + """ + + visited = visited or set() + for dependency in dependencies: + if dependency not in visited: + visited.add(dependency) + dep_desc = self.FindFileByName(dependency) + yield dep_desc + public_files = [d.name for d in dep_desc.public_dependencies] + yield from self._GetDeps(public_files, visited) + + def _GetTypeFromScope(self, package, type_name, scope): + """Finds a given type name in the current scope. + + Args: + package: The package the proto should be located in. + type_name: The name of the type to be found in the scope. + scope: Dict mapping short and full symbols to message and enum types. + + Returns: + The descriptor for the requested type. + """ + if type_name not in scope: + components = _PrefixWithDot(package).split('.') + while components: + possible_match = '.'.join(components + [type_name]) + if possible_match in scope: + type_name = possible_match + break + else: + components.pop(-1) + return scope[type_name] + + +def _PrefixWithDot(name): + return name if name.startswith('.') else '.%s' % name + + +if _USE_C_DESCRIPTORS: + # TODO: This pool could be constructed from Python code, when we + # support a flag like 'use_cpp_generated_pool=True'. + # pylint: disable=protected-access + _DEFAULT = descriptor._message.default_pool +else: + _DEFAULT = DescriptorPool() + + +def Default(): + return _DEFAULT diff --git a/google/protobuf/duration_pb2.py b/google/protobuf/duration_pb2.py new file mode 100644 index 0000000..f012f1c --- /dev/null +++ b/google/protobuf/duration_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: google/protobuf/duration.proto +# Protobuf Python Version: 5.27.3 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 3, + '', + 'google/protobuf/duration.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1egoogle/protobuf/duration.proto\x12\x0fgoogle.protobuf\":\n\x08\x44uration\x12\x18\n\x07seconds\x18\x01 \x01(\x03R\x07seconds\x12\x14\n\x05nanos\x18\x02 \x01(\x05R\x05nanosB\x83\x01\n\x13\x63om.google.protobufB\rDurationProtoP\x01Z1google.golang.org/protobuf/types/known/durationpb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.duration_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\023com.google.protobufB\rDurationProtoP\001Z1google.golang.org/protobuf/types/known/durationpb\370\001\001\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' + _globals['_DURATION']._serialized_start=51 + _globals['_DURATION']._serialized_end=109 +# @@protoc_insertion_point(module_scope) diff --git a/google/protobuf/empty_pb2.py b/google/protobuf/empty_pb2.py new file mode 100644 index 0000000..1035fe4 --- /dev/null +++ b/google/protobuf/empty_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: google/protobuf/empty.proto +# Protobuf Python Version: 5.27.3 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 3, + '', + 'google/protobuf/empty.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bgoogle/protobuf/empty.proto\x12\x0fgoogle.protobuf\"\x07\n\x05\x45mptyB}\n\x13\x63om.google.protobufB\nEmptyProtoP\x01Z.google.golang.org/protobuf/types/known/emptypb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.empty_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\023com.google.protobufB\nEmptyProtoP\001Z.google.golang.org/protobuf/types/known/emptypb\370\001\001\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' + _globals['_EMPTY']._serialized_start=48 + _globals['_EMPTY']._serialized_end=55 +# @@protoc_insertion_point(module_scope) diff --git a/google/protobuf/field_mask_pb2.py b/google/protobuf/field_mask_pb2.py new file mode 100644 index 0000000..d8bc54d --- /dev/null +++ b/google/protobuf/field_mask_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: google/protobuf/field_mask.proto +# Protobuf Python Version: 5.27.3 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 3, + '', + 'google/protobuf/field_mask.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n google/protobuf/field_mask.proto\x12\x0fgoogle.protobuf\"!\n\tFieldMask\x12\x14\n\x05paths\x18\x01 \x03(\tR\x05pathsB\x85\x01\n\x13\x63om.google.protobufB\x0e\x46ieldMaskProtoP\x01Z2google.golang.org/protobuf/types/known/fieldmaskpb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.field_mask_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\023com.google.protobufB\016FieldMaskProtoP\001Z2google.golang.org/protobuf/types/known/fieldmaskpb\370\001\001\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' + _globals['_FIELDMASK']._serialized_start=53 + _globals['_FIELDMASK']._serialized_end=86 +# @@protoc_insertion_point(module_scope) diff --git a/google/protobuf/internal/__init__.py b/google/protobuf/internal/__init__.py new file mode 100644 index 0000000..e676e28 --- /dev/null +++ b/google/protobuf/internal/__init__.py @@ -0,0 +1,7 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + diff --git a/google/protobuf/internal/_parameterized.py b/google/protobuf/internal/_parameterized.py new file mode 100644 index 0000000..4cb2cb1 --- /dev/null +++ b/google/protobuf/internal/_parameterized.py @@ -0,0 +1,420 @@ +#! /usr/bin/env python +# +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Adds support for parameterized tests to Python's unittest TestCase class. + +A parameterized test is a method in a test case that is invoked with different +argument tuples. + +A simple example: + + class AdditionExample(_parameterized.TestCase): + @_parameterized.parameters( + (1, 2, 3), + (4, 5, 9), + (1, 1, 3)) + def testAddition(self, op1, op2, result): + self.assertEqual(result, op1 + op2) + + +Each invocation is a separate test case and properly isolated just +like a normal test method, with its own setUp/tearDown cycle. In the +example above, there are three separate testcases, one of which will +fail due to an assertion error (1 + 1 != 3). + +Parameters for individual test cases can be tuples (with positional parameters) +or dictionaries (with named parameters): + + class AdditionExample(_parameterized.TestCase): + @_parameterized.parameters( + {'op1': 1, 'op2': 2, 'result': 3}, + {'op1': 4, 'op2': 5, 'result': 9}, + ) + def testAddition(self, op1, op2, result): + self.assertEqual(result, op1 + op2) + +If a parameterized test fails, the error message will show the +original test name (which is modified internally) and the arguments +for the specific invocation, which are part of the string returned by +the shortDescription() method on test cases. + +The id method of the test, used internally by the unittest framework, +is also modified to show the arguments. To make sure that test names +stay the same across several invocations, object representations like + + >>> class Foo(object): + ... pass + >>> repr(Foo()) + '<__main__.Foo object at 0x23d8610>' + +are turned into '<__main__.Foo>'. For even more descriptive names, +especially in test logs, you can use the named_parameters decorator. In +this case, only tuples are supported, and the first parameters has to +be a string (or an object that returns an apt name when converted via +str()): + + class NamedExample(_parameterized.TestCase): + @_parameterized.named_parameters( + ('Normal', 'aa', 'aaa', True), + ('EmptyPrefix', '', 'abc', True), + ('BothEmpty', '', '', True)) + def testStartsWith(self, prefix, string, result): + self.assertEqual(result, strings.startswith(prefix)) + +Named tests also have the benefit that they can be run individually +from the command line: + + $ testmodule.py NamedExample.testStartsWithNormal + . + -------------------------------------------------------------------- + Ran 1 test in 0.000s + + OK + +Parameterized Classes +===================== +If invocation arguments are shared across test methods in a single +TestCase class, instead of decorating all test methods +individually, the class itself can be decorated: + + @_parameterized.parameters( + (1, 2, 3) + (4, 5, 9)) + class ArithmeticTest(_parameterized.TestCase): + def testAdd(self, arg1, arg2, result): + self.assertEqual(arg1 + arg2, result) + + def testSubtract(self, arg2, arg2, result): + self.assertEqual(result - arg1, arg2) + +Inputs from Iterables +===================== +If parameters should be shared across several test cases, or are dynamically +created from other sources, a single non-tuple iterable can be passed into +the decorator. This iterable will be used to obtain the test cases: + + class AdditionExample(_parameterized.TestCase): + @_parameterized.parameters( + c.op1, c.op2, c.result for c in testcases + ) + def testAddition(self, op1, op2, result): + self.assertEqual(result, op1 + op2) + + +Single-Argument Test Methods +============================ +If a test method takes only one argument, the single argument does not need to +be wrapped into a tuple: + + class NegativeNumberExample(_parameterized.TestCase): + @_parameterized.parameters( + -1, -3, -4, -5 + ) + def testIsNegative(self, arg): + self.assertTrue(IsNegative(arg)) +""" + +__author__ = 'tmarek@google.com (Torsten Marek)' + +import functools +import re +import types +import unittest +import uuid + +try: + # Since python 3 + import collections.abc as collections_abc +except ImportError: + # Won't work after python 3.8 + import collections as collections_abc + +ADDR_RE = re.compile(r'\<([a-zA-Z0-9_\-\.]+) object at 0x[a-fA-F0-9]+\>') +_SEPARATOR = uuid.uuid1().hex +_FIRST_ARG = object() +_ARGUMENT_REPR = object() + + +def _CleanRepr(obj): + return ADDR_RE.sub(r'<\1>', repr(obj)) + + +# Helper function formerly from the unittest module, removed from it in +# Python 2.7. +def _StrClass(cls): + return '%s.%s' % (cls.__module__, cls.__name__) + + +def _NonStringIterable(obj): + return (isinstance(obj, collections_abc.Iterable) and + not isinstance(obj, str)) + + +def _FormatParameterList(testcase_params): + if isinstance(testcase_params, collections_abc.Mapping): + return ', '.join('%s=%s' % (argname, _CleanRepr(value)) + for argname, value in testcase_params.items()) + elif _NonStringIterable(testcase_params): + return ', '.join(map(_CleanRepr, testcase_params)) + else: + return _FormatParameterList((testcase_params,)) + + +class _ParameterizedTestIter(object): + """Callable and iterable class for producing new test cases.""" + + def __init__(self, test_method, testcases, naming_type): + """Returns concrete test functions for a test and a list of parameters. + + The naming_type is used to determine the name of the concrete + functions as reported by the unittest framework. If naming_type is + _FIRST_ARG, the testcases must be tuples, and the first element must + have a string representation that is a valid Python identifier. + + Args: + test_method: The decorated test method. + testcases: (list of tuple/dict) A list of parameter + tuples/dicts for individual test invocations. + naming_type: The test naming type, either _NAMED or _ARGUMENT_REPR. + """ + self._test_method = test_method + self.testcases = testcases + self._naming_type = naming_type + + def __call__(self, *args, **kwargs): + raise RuntimeError('You appear to be running a parameterized test case ' + 'without having inherited from parameterized.' + 'TestCase. This is bad because none of ' + 'your test cases are actually being run.') + + def __iter__(self): + test_method = self._test_method + naming_type = self._naming_type + + def MakeBoundParamTest(testcase_params): + @functools.wraps(test_method) + def BoundParamTest(self): + if isinstance(testcase_params, collections_abc.Mapping): + test_method(self, **testcase_params) + elif _NonStringIterable(testcase_params): + test_method(self, *testcase_params) + else: + test_method(self, testcase_params) + + if naming_type is _FIRST_ARG: + # Signal the metaclass that the name of the test function is unique + # and descriptive. + BoundParamTest.__x_use_name__ = True + BoundParamTest.__name__ += str(testcase_params[0]) + testcase_params = testcase_params[1:] + elif naming_type is _ARGUMENT_REPR: + # __x_extra_id__ is used to pass naming information to the __new__ + # method of TestGeneratorMetaclass. + # The metaclass will make sure to create a unique, but nondescriptive + # name for this test. + BoundParamTest.__x_extra_id__ = '(%s)' % ( + _FormatParameterList(testcase_params),) + else: + raise RuntimeError('%s is not a valid naming type.' % (naming_type,)) + + BoundParamTest.__doc__ = '%s(%s)' % ( + BoundParamTest.__name__, _FormatParameterList(testcase_params)) + if test_method.__doc__: + BoundParamTest.__doc__ += '\n%s' % (test_method.__doc__,) + return BoundParamTest + return (MakeBoundParamTest(c) for c in self.testcases) + + +def _IsSingletonList(testcases): + """True iff testcases contains only a single non-tuple element.""" + return len(testcases) == 1 and not isinstance(testcases[0], tuple) + + +def _ModifyClass(class_object, testcases, naming_type): + assert not getattr(class_object, '_id_suffix', None), ( + 'Cannot add parameters to %s,' + ' which already has parameterized methods.' % (class_object,)) + class_object._id_suffix = id_suffix = {} + # We change the size of __dict__ while we iterate over it, + # which Python 3.x will complain about, so use copy(). + for name, obj in class_object.__dict__.copy().items(): + if (name.startswith(unittest.TestLoader.testMethodPrefix) + and isinstance(obj, types.FunctionType)): + delattr(class_object, name) + methods = {} + _UpdateClassDictForParamTestCase( + methods, id_suffix, name, + _ParameterizedTestIter(obj, testcases, naming_type)) + for name, meth in methods.items(): + setattr(class_object, name, meth) + + +def _ParameterDecorator(naming_type, testcases): + """Implementation of the parameterization decorators. + + Args: + naming_type: The naming type. + testcases: Testcase parameters. + + Returns: + A function for modifying the decorated object. + """ + def _Apply(obj): + if isinstance(obj, type): + _ModifyClass( + obj, + list(testcases) if not isinstance(testcases, collections_abc.Sequence) + else testcases, + naming_type) + return obj + else: + return _ParameterizedTestIter(obj, testcases, naming_type) + + if _IsSingletonList(testcases): + assert _NonStringIterable(testcases[0]), ( + 'Single parameter argument must be a non-string iterable') + testcases = testcases[0] + + return _Apply + + +def parameters(*testcases): # pylint: disable=invalid-name + """A decorator for creating parameterized tests. + + See the module docstring for a usage example. + Args: + *testcases: Parameters for the decorated method, either a single + iterable, or a list of tuples/dicts/objects (for tests + with only one argument). + + Returns: + A test generator to be handled by TestGeneratorMetaclass. + """ + return _ParameterDecorator(_ARGUMENT_REPR, testcases) + + +def named_parameters(*testcases): # pylint: disable=invalid-name + """A decorator for creating parameterized tests. + + See the module docstring for a usage example. The first element of + each parameter tuple should be a string and will be appended to the + name of the test method. + + Args: + *testcases: Parameters for the decorated method, either a single + iterable, or a list of tuples. + + Returns: + A test generator to be handled by TestGeneratorMetaclass. + """ + return _ParameterDecorator(_FIRST_ARG, testcases) + + +class TestGeneratorMetaclass(type): + """Metaclass for test cases with test generators. + + A test generator is an iterable in a testcase that produces callables. These + callables must be single-argument methods. These methods are injected into + the class namespace and the original iterable is removed. If the name of the + iterable conforms to the test pattern, the injected methods will be picked + up as tests by the unittest framework. + + In general, it is supposed to be used in conjunction with the + parameters decorator. + """ + + def __new__(mcs, class_name, bases, dct): + dct['_id_suffix'] = id_suffix = {} + for name, obj in dct.copy().items(): + if (name.startswith(unittest.TestLoader.testMethodPrefix) and + _NonStringIterable(obj)): + iterator = iter(obj) + dct.pop(name) + _UpdateClassDictForParamTestCase(dct, id_suffix, name, iterator) + + return type.__new__(mcs, class_name, bases, dct) + + +def _UpdateClassDictForParamTestCase(dct, id_suffix, name, iterator): + """Adds individual test cases to a dictionary. + + Args: + dct: The target dictionary. + id_suffix: The dictionary for mapping names to test IDs. + name: The original name of the test case. + iterator: The iterator generating the individual test cases. + """ + for idx, func in enumerate(iterator): + assert callable(func), 'Test generators must yield callables, got %r' % ( + func,) + if getattr(func, '__x_use_name__', False): + new_name = func.__name__ + else: + new_name = '%s%s%d' % (name, _SEPARATOR, idx) + assert new_name not in dct, ( + 'Name of parameterized test case "%s" not unique' % (new_name,)) + dct[new_name] = func + id_suffix[new_name] = getattr(func, '__x_extra_id__', '') + + +class TestCase(unittest.TestCase, metaclass=TestGeneratorMetaclass): + """Base class for test cases using the parameters decorator.""" + + def _OriginalName(self): + return self._testMethodName.split(_SEPARATOR)[0] + + def __str__(self): + return '%s (%s)' % (self._OriginalName(), _StrClass(self.__class__)) + + def id(self): # pylint: disable=invalid-name + """Returns the descriptive ID of the test. + + This is used internally by the unittesting framework to get a name + for the test to be used in reports. + + Returns: + The test id. + """ + return '%s.%s%s' % (_StrClass(self.__class__), + self._OriginalName(), + self._id_suffix.get(self._testMethodName, '')) + + +def CoopTestCase(other_base_class): + """Returns a new base class with a cooperative metaclass base. + + This enables the TestCase to be used in combination + with other base classes that have custom metaclasses, such as + mox.MoxTestBase. + + Only works with metaclasses that do not override type.__new__. + + Example: + + import google3 + import mox + + from google.protobuf.internal import _parameterized + + class ExampleTest(parameterized.CoopTestCase(mox.MoxTestBase)): + ... + + Args: + other_base_class: (class) A test case base class. + + Returns: + A new class object. + """ + metaclass = type( + 'CoopMetaclass', + (other_base_class.__metaclass__, + TestGeneratorMetaclass), {}) + return metaclass( + 'CoopTestCase', + (other_base_class, TestCase), {}) diff --git a/google/protobuf/internal/api_implementation.py b/google/protobuf/internal/api_implementation.py new file mode 100644 index 0000000..b40446b --- /dev/null +++ b/google/protobuf/internal/api_implementation.py @@ -0,0 +1,142 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Determine which implementation of the protobuf API is used in this process. +""" + +import importlib +import os +import sys +import warnings + +_GOOGLE3_PYTHON_UPB_DEFAULT = True + + +def _ApiVersionToImplementationType(api_version): + if api_version == 2: + return 'cpp' + if api_version == 1: + raise ValueError('api_version=1 is no longer supported.') + if api_version == 0: + return 'python' + return None + + +_implementation_type = None +try: + # pylint: disable=g-import-not-at-top + from google.protobuf.internal import _api_implementation + # The compile-time constants in the _api_implementation module can be used to + # switch to a certain implementation of the Python API at build time. + _implementation_type = _ApiVersionToImplementationType( + _api_implementation.api_version) +except ImportError: + pass # Unspecified by compiler flags. + + +def _CanImport(mod_name): + try: + mod = importlib.import_module(mod_name) + # Work around a known issue in the classic bootstrap .par import hook. + if not mod: + raise ImportError(mod_name + ' import succeeded but was None') + return True + except ImportError: + return False + + +if _implementation_type is None: + if _CanImport('google._upb._message'): + _implementation_type = 'upb' + elif _CanImport('google.protobuf.pyext._message'): + _implementation_type = 'cpp' + else: + _implementation_type = 'python' + + +# This environment variable can be used to switch to a certain implementation +# of the Python API, overriding the compile-time constants in the +# _api_implementation module. Right now only 'python', 'cpp' and 'upb' are +# valid values. Any other value will raise error. +_implementation_type = os.getenv('PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION', + _implementation_type) + +if _implementation_type not in ('python', 'cpp', 'upb'): + raise ValueError('PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION {0} is not ' + 'supported. Please set to \'python\', \'cpp\' or ' + '\'upb\'.'.format(_implementation_type)) + +if 'PyPy' in sys.version and _implementation_type == 'cpp': + warnings.warn('PyPy does not work yet with cpp protocol buffers. ' + 'Falling back to the python implementation.') + _implementation_type = 'python' + +_c_module = None + +if _implementation_type == 'cpp': + try: + # pylint: disable=g-import-not-at-top + from google.protobuf.pyext import _message + sys.modules['google3.net.proto2.python.internal.cpp._message'] = _message + _c_module = _message + del _message + except ImportError: + # TODO: fail back to python + warnings.warn( + 'Selected implementation cpp is not available.') + pass + +if _implementation_type == 'upb': + try: + # pylint: disable=g-import-not-at-top + from google._upb import _message + _c_module = _message + del _message + except ImportError: + warnings.warn('Selected implementation upb is not available. ' + 'Falling back to the python implementation.') + _implementation_type = 'python' + pass + +# Detect if serialization should be deterministic by default +try: + # The presence of this module in a build allows the proto implementation to + # be upgraded merely via build deps. + # + # NOTE: Merely importing this automatically enables deterministic proto + # serialization for C++ code, but we still need to export it as a boolean so + # that we can do the same for `_implementation_type == 'python'`. + # + # NOTE2: It is possible for C++ code to enable deterministic serialization by + # default _without_ affecting Python code, if the C++ implementation is not in + # use by this module. That is intended behavior, so we don't actually expose + # this boolean outside of this module. + # + # pylint: disable=g-import-not-at-top,unused-import + from google.protobuf import enable_deterministic_proto_serialization + _python_deterministic_proto_serialization = True +except ImportError: + _python_deterministic_proto_serialization = False + + +# Usage of this function is discouraged. Clients shouldn't care which +# implementation of the API is in use. Note that there is no guarantee +# that differences between APIs will be maintained. +# Please don't use this function if possible. +def Type(): + return _implementation_type + + +# See comment on 'Type' above. +# TODO: Remove the API, it returns a constant. b/228102101 +def Version(): + return 2 + + +# For internal use only +def IsPythonDefaultSerializationDeterministic(): + return _python_deterministic_proto_serialization diff --git a/google/protobuf/internal/builder.py b/google/protobuf/internal/builder.py new file mode 100644 index 0000000..c22d994 --- /dev/null +++ b/google/protobuf/internal/builder.py @@ -0,0 +1,118 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Builds descriptors, message classes and services for generated _pb2.py. + +This file is only called in python generated _pb2.py files. It builds +descriptors, message classes and services that users can directly use +in generated code. +""" + +__author__ = 'jieluo@google.com (Jie Luo)' + +from google.protobuf.internal import enum_type_wrapper +from google.protobuf.internal import python_message +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database + +_sym_db = _symbol_database.Default() + + +def BuildMessageAndEnumDescriptors(file_des, module): + """Builds message and enum descriptors. + + Args: + file_des: FileDescriptor of the .proto file + module: Generated _pb2 module + """ + + def BuildNestedDescriptors(msg_des, prefix): + for (name, nested_msg) in msg_des.nested_types_by_name.items(): + module_name = prefix + name.upper() + module[module_name] = nested_msg + BuildNestedDescriptors(nested_msg, module_name + '_') + for enum_des in msg_des.enum_types: + module[prefix + enum_des.name.upper()] = enum_des + + for (name, msg_des) in file_des.message_types_by_name.items(): + module_name = '_' + name.upper() + module[module_name] = msg_des + BuildNestedDescriptors(msg_des, module_name + '_') + + +def BuildTopDescriptorsAndMessages(file_des, module_name, module): + """Builds top level descriptors and message classes. + + Args: + file_des: FileDescriptor of the .proto file + module_name: str, the name of generated _pb2 module + module: Generated _pb2 module + """ + + def BuildMessage(msg_des): + create_dict = {} + for (name, nested_msg) in msg_des.nested_types_by_name.items(): + create_dict[name] = BuildMessage(nested_msg) + create_dict['DESCRIPTOR'] = msg_des + create_dict['__module__'] = module_name + message_class = _reflection.GeneratedProtocolMessageType( + msg_des.name, (_message.Message,), create_dict) + _sym_db.RegisterMessage(message_class) + return message_class + + # top level enums + for (name, enum_des) in file_des.enum_types_by_name.items(): + module['_' + name.upper()] = enum_des + module[name] = enum_type_wrapper.EnumTypeWrapper(enum_des) + for enum_value in enum_des.values: + module[enum_value.name] = enum_value.number + + # top level extensions + for (name, extension_des) in file_des.extensions_by_name.items(): + module[name.upper() + '_FIELD_NUMBER'] = extension_des.number + module[name] = extension_des + + # services + for (name, service) in file_des.services_by_name.items(): + module['_' + name.upper()] = service + + # Build messages. + for (name, msg_des) in file_des.message_types_by_name.items(): + module[name] = BuildMessage(msg_des) + + +def AddHelpersToExtensions(file_des): + """no-op to keep old generated code work with new runtime. + + Args: + file_des: FileDescriptor of the .proto file + """ + # TODO: Remove this on-op + return + + +def BuildServices(file_des, module_name, module): + """Builds services classes and services stub class. + + Args: + file_des: FileDescriptor of the .proto file + module_name: str, the name of generated _pb2 module + module: Generated _pb2 module + """ + # pylint: disable=g-import-not-at-top + from google.protobuf import service as _service + from google.protobuf import service_reflection + # pylint: enable=g-import-not-at-top + for (name, service) in file_des.services_by_name.items(): + module[name] = service_reflection.GeneratedServiceType( + name, (_service.Service,), + dict(DESCRIPTOR=service, __module__=module_name)) + stub_name = name + '_Stub' + module[stub_name] = service_reflection.GeneratedServiceStubType( + stub_name, (module[name],), + dict(DESCRIPTOR=service, __module__=module_name)) diff --git a/google/protobuf/internal/containers.py b/google/protobuf/internal/containers.py new file mode 100644 index 0000000..2335781 --- /dev/null +++ b/google/protobuf/internal/containers.py @@ -0,0 +1,677 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Contains container classes to represent different protocol buffer types. + +This file defines container classes which represent categories of protocol +buffer field types which need extra maintenance. Currently these categories +are: + +- Repeated scalar fields - These are all repeated fields which aren't + composite (e.g. they are of simple types like int32, string, etc). +- Repeated composite fields - Repeated fields which are composite. This + includes groups and nested messages. +""" + +import collections.abc +import copy +import pickle +from typing import ( + Any, + Iterable, + Iterator, + List, + MutableMapping, + MutableSequence, + NoReturn, + Optional, + Sequence, + TypeVar, + Union, + overload, +) + + +_T = TypeVar('_T') +_K = TypeVar('_K') +_V = TypeVar('_V') + + +class BaseContainer(Sequence[_T]): + """Base container class.""" + + # Minimizes memory usage and disallows assignment to other attributes. + __slots__ = ['_message_listener', '_values'] + + def __init__(self, message_listener: Any) -> None: + """ + Args: + message_listener: A MessageListener implementation. + The RepeatedScalarFieldContainer will call this object's + Modified() method when it is modified. + """ + self._message_listener = message_listener + self._values = [] + + @overload + def __getitem__(self, key: int) -> _T: + ... + + @overload + def __getitem__(self, key: slice) -> List[_T]: + ... + + def __getitem__(self, key): + """Retrieves item by the specified key.""" + return self._values[key] + + def __len__(self) -> int: + """Returns the number of elements in the container.""" + return len(self._values) + + def __ne__(self, other: Any) -> bool: + """Checks if another instance isn't equal to this one.""" + # The concrete classes should define __eq__. + return not self == other + + __hash__ = None + + def __repr__(self) -> str: + return repr(self._values) + + def sort(self, *args, **kwargs) -> None: + # Continue to support the old sort_function keyword argument. + # This is expected to be a rare occurrence, so use LBYL to avoid + # the overhead of actually catching KeyError. + if 'sort_function' in kwargs: + kwargs['cmp'] = kwargs.pop('sort_function') + self._values.sort(*args, **kwargs) + + def reverse(self) -> None: + self._values.reverse() + + +# TODO: Remove this. BaseContainer does *not* conform to +# MutableSequence, only its subclasses do. +collections.abc.MutableSequence.register(BaseContainer) + + +class RepeatedScalarFieldContainer(BaseContainer[_T], MutableSequence[_T]): + """Simple, type-checked, list-like container for holding repeated scalars.""" + + # Disallows assignment to other attributes. + __slots__ = ['_type_checker'] + + def __init__( + self, + message_listener: Any, + type_checker: Any, + ) -> None: + """Args: + + message_listener: A MessageListener implementation. The + RepeatedScalarFieldContainer will call this object's Modified() method + when it is modified. + type_checker: A type_checkers.ValueChecker instance to run on elements + inserted into this container. + """ + super().__init__(message_listener) + self._type_checker = type_checker + + def append(self, value: _T) -> None: + """Appends an item to the list. Similar to list.append().""" + self._values.append(self._type_checker.CheckValue(value)) + if not self._message_listener.dirty: + self._message_listener.Modified() + + def insert(self, key: int, value: _T) -> None: + """Inserts the item at the specified position. Similar to list.insert().""" + self._values.insert(key, self._type_checker.CheckValue(value)) + if not self._message_listener.dirty: + self._message_listener.Modified() + + def extend(self, elem_seq: Iterable[_T]) -> None: + """Extends by appending the given iterable. Similar to list.extend().""" + elem_seq_iter = iter(elem_seq) + new_values = [self._type_checker.CheckValue(elem) for elem in elem_seq_iter] + if new_values: + self._values.extend(new_values) + self._message_listener.Modified() + + def MergeFrom( + self, + other: Union['RepeatedScalarFieldContainer[_T]', Iterable[_T]], + ) -> None: + """Appends the contents of another repeated field of the same type to this + one. We do not check the types of the individual fields. + """ + self._values.extend(other) + self._message_listener.Modified() + + def remove(self, elem: _T): + """Removes an item from the list. Similar to list.remove().""" + self._values.remove(elem) + self._message_listener.Modified() + + def pop(self, key: Optional[int] = -1) -> _T: + """Removes and returns an item at a given index. Similar to list.pop().""" + value = self._values[key] + self.__delitem__(key) + return value + + @overload + def __setitem__(self, key: int, value: _T) -> None: + ... + + @overload + def __setitem__(self, key: slice, value: Iterable[_T]) -> None: + ... + + def __setitem__(self, key, value) -> None: + """Sets the item on the specified position.""" + if isinstance(key, slice): + if key.step is not None: + raise ValueError('Extended slices not supported') + self._values[key] = map(self._type_checker.CheckValue, value) + self._message_listener.Modified() + else: + self._values[key] = self._type_checker.CheckValue(value) + self._message_listener.Modified() + + def __delitem__(self, key: Union[int, slice]) -> None: + """Deletes the item at the specified position.""" + del self._values[key] + self._message_listener.Modified() + + def __eq__(self, other: Any) -> bool: + """Compares the current instance with another one.""" + if self is other: + return True + # Special case for the same type which should be common and fast. + if isinstance(other, self.__class__): + return other._values == self._values + # We are presumably comparing against some other sequence type. + return other == self._values + + def __deepcopy__( + self, + unused_memo: Any = None, + ) -> 'RepeatedScalarFieldContainer[_T]': + clone = RepeatedScalarFieldContainer( + copy.deepcopy(self._message_listener), self._type_checker) + clone.MergeFrom(self) + return clone + + def __reduce__(self, **kwargs) -> NoReturn: + raise pickle.PickleError( + "Can't pickle repeated scalar fields, convert to list first") + + +# TODO: Constrain T to be a subtype of Message. +class RepeatedCompositeFieldContainer(BaseContainer[_T], MutableSequence[_T]): + """Simple, list-like container for holding repeated composite fields.""" + + # Disallows assignment to other attributes. + __slots__ = ['_message_descriptor'] + + def __init__(self, message_listener: Any, message_descriptor: Any) -> None: + """ + Note that we pass in a descriptor instead of the generated directly, + since at the time we construct a _RepeatedCompositeFieldContainer we + haven't yet necessarily initialized the type that will be contained in the + container. + + Args: + message_listener: A MessageListener implementation. + The RepeatedCompositeFieldContainer will call this object's + Modified() method when it is modified. + message_descriptor: A Descriptor instance describing the protocol type + that should be present in this container. We'll use the + _concrete_class field of this descriptor when the client calls add(). + """ + super().__init__(message_listener) + self._message_descriptor = message_descriptor + + def add(self, **kwargs: Any) -> _T: + """Adds a new element at the end of the list and returns it. Keyword + arguments may be used to initialize the element. + """ + new_element = self._message_descriptor._concrete_class(**kwargs) + new_element._SetListener(self._message_listener) + self._values.append(new_element) + if not self._message_listener.dirty: + self._message_listener.Modified() + return new_element + + def append(self, value: _T) -> None: + """Appends one element by copying the message.""" + new_element = self._message_descriptor._concrete_class() + new_element._SetListener(self._message_listener) + new_element.CopyFrom(value) + self._values.append(new_element) + if not self._message_listener.dirty: + self._message_listener.Modified() + + def insert(self, key: int, value: _T) -> None: + """Inserts the item at the specified position by copying.""" + new_element = self._message_descriptor._concrete_class() + new_element._SetListener(self._message_listener) + new_element.CopyFrom(value) + self._values.insert(key, new_element) + if not self._message_listener.dirty: + self._message_listener.Modified() + + def extend(self, elem_seq: Iterable[_T]) -> None: + """Extends by appending the given sequence of elements of the same type + + as this one, copying each individual message. + """ + message_class = self._message_descriptor._concrete_class + listener = self._message_listener + values = self._values + for message in elem_seq: + new_element = message_class() + new_element._SetListener(listener) + new_element.MergeFrom(message) + values.append(new_element) + listener.Modified() + + def MergeFrom( + self, + other: Union['RepeatedCompositeFieldContainer[_T]', Iterable[_T]], + ) -> None: + """Appends the contents of another repeated field of the same type to this + one, copying each individual message. + """ + self.extend(other) + + def remove(self, elem: _T) -> None: + """Removes an item from the list. Similar to list.remove().""" + self._values.remove(elem) + self._message_listener.Modified() + + def pop(self, key: Optional[int] = -1) -> _T: + """Removes and returns an item at a given index. Similar to list.pop().""" + value = self._values[key] + self.__delitem__(key) + return value + + @overload + def __setitem__(self, key: int, value: _T) -> None: + ... + + @overload + def __setitem__(self, key: slice, value: Iterable[_T]) -> None: + ... + + def __setitem__(self, key, value): + # This method is implemented to make RepeatedCompositeFieldContainer + # structurally compatible with typing.MutableSequence. It is + # otherwise unsupported and will always raise an error. + raise TypeError( + f'{self.__class__.__name__} object does not support item assignment') + + def __delitem__(self, key: Union[int, slice]) -> None: + """Deletes the item at the specified position.""" + del self._values[key] + self._message_listener.Modified() + + def __eq__(self, other: Any) -> bool: + """Compares the current instance with another one.""" + if self is other: + return True + if not isinstance(other, self.__class__): + raise TypeError('Can only compare repeated composite fields against ' + 'other repeated composite fields.') + return self._values == other._values + + +class ScalarMap(MutableMapping[_K, _V]): + """Simple, type-checked, dict-like container for holding repeated scalars.""" + + # Disallows assignment to other attributes. + __slots__ = ['_key_checker', '_value_checker', '_values', '_message_listener', + '_entry_descriptor'] + + def __init__( + self, + message_listener: Any, + key_checker: Any, + value_checker: Any, + entry_descriptor: Any, + ) -> None: + """ + Args: + message_listener: A MessageListener implementation. + The ScalarMap will call this object's Modified() method when it + is modified. + key_checker: A type_checkers.ValueChecker instance to run on keys + inserted into this container. + value_checker: A type_checkers.ValueChecker instance to run on values + inserted into this container. + entry_descriptor: The MessageDescriptor of a map entry: key and value. + """ + self._message_listener = message_listener + self._key_checker = key_checker + self._value_checker = value_checker + self._entry_descriptor = entry_descriptor + self._values = {} + + def __getitem__(self, key: _K) -> _V: + try: + return self._values[key] + except KeyError: + key = self._key_checker.CheckValue(key) + val = self._value_checker.DefaultValue() + self._values[key] = val + return val + + def __contains__(self, item: _K) -> bool: + # We check the key's type to match the strong-typing flavor of the API. + # Also this makes it easier to match the behavior of the C++ implementation. + self._key_checker.CheckValue(item) + return item in self._values + + @overload + def get(self, key: _K) -> Optional[_V]: + ... + + @overload + def get(self, key: _K, default: _T) -> Union[_V, _T]: + ... + + # We need to override this explicitly, because our defaultdict-like behavior + # will make the default implementation (from our base class) always insert + # the key. + def get(self, key, default=None): + if key in self: + return self[key] + else: + return default + + def __setitem__(self, key: _K, value: _V) -> _T: + checked_key = self._key_checker.CheckValue(key) + checked_value = self._value_checker.CheckValue(value) + self._values[checked_key] = checked_value + self._message_listener.Modified() + + def __delitem__(self, key: _K) -> None: + del self._values[key] + self._message_listener.Modified() + + def __len__(self) -> int: + return len(self._values) + + def __iter__(self) -> Iterator[_K]: + return iter(self._values) + + def __repr__(self) -> str: + return repr(self._values) + + def MergeFrom(self, other: 'ScalarMap[_K, _V]') -> None: + self._values.update(other._values) + self._message_listener.Modified() + + def InvalidateIterators(self) -> None: + # It appears that the only way to reliably invalidate iterators to + # self._values is to ensure that its size changes. + original = self._values + self._values = original.copy() + original[None] = None + + # This is defined in the abstract base, but we can do it much more cheaply. + def clear(self) -> None: + self._values.clear() + self._message_listener.Modified() + + def GetEntryClass(self) -> Any: + return self._entry_descriptor._concrete_class + + +class MessageMap(MutableMapping[_K, _V]): + """Simple, type-checked, dict-like container for with submessage values.""" + + # Disallows assignment to other attributes. + __slots__ = ['_key_checker', '_values', '_message_listener', + '_message_descriptor', '_entry_descriptor'] + + def __init__( + self, + message_listener: Any, + message_descriptor: Any, + key_checker: Any, + entry_descriptor: Any, + ) -> None: + """ + Args: + message_listener: A MessageListener implementation. + The ScalarMap will call this object's Modified() method when it + is modified. + key_checker: A type_checkers.ValueChecker instance to run on keys + inserted into this container. + value_checker: A type_checkers.ValueChecker instance to run on values + inserted into this container. + entry_descriptor: The MessageDescriptor of a map entry: key and value. + """ + self._message_listener = message_listener + self._message_descriptor = message_descriptor + self._key_checker = key_checker + self._entry_descriptor = entry_descriptor + self._values = {} + + def __getitem__(self, key: _K) -> _V: + key = self._key_checker.CheckValue(key) + try: + return self._values[key] + except KeyError: + new_element = self._message_descriptor._concrete_class() + new_element._SetListener(self._message_listener) + self._values[key] = new_element + self._message_listener.Modified() + return new_element + + def get_or_create(self, key: _K) -> _V: + """get_or_create() is an alias for getitem (ie. map[key]). + + Args: + key: The key to get or create in the map. + + This is useful in cases where you want to be explicit that the call is + mutating the map. This can avoid lint errors for statements like this + that otherwise would appear to be pointless statements: + + msg.my_map[key] + """ + return self[key] + + @overload + def get(self, key: _K) -> Optional[_V]: + ... + + @overload + def get(self, key: _K, default: _T) -> Union[_V, _T]: + ... + + # We need to override this explicitly, because our defaultdict-like behavior + # will make the default implementation (from our base class) always insert + # the key. + def get(self, key, default=None): + if key in self: + return self[key] + else: + return default + + def __contains__(self, item: _K) -> bool: + item = self._key_checker.CheckValue(item) + return item in self._values + + def __setitem__(self, key: _K, value: _V) -> NoReturn: + raise ValueError('May not set values directly, call my_map[key].foo = 5') + + def __delitem__(self, key: _K) -> None: + key = self._key_checker.CheckValue(key) + del self._values[key] + self._message_listener.Modified() + + def __len__(self) -> int: + return len(self._values) + + def __iter__(self) -> Iterator[_K]: + return iter(self._values) + + def __repr__(self) -> str: + return repr(self._values) + + def MergeFrom(self, other: 'MessageMap[_K, _V]') -> None: + # pylint: disable=protected-access + for key in other._values: + # According to documentation: "When parsing from the wire or when merging, + # if there are duplicate map keys the last key seen is used". + if key in self: + del self[key] + self[key].CopyFrom(other[key]) + # self._message_listener.Modified() not required here, because + # mutations to submessages already propagate. + + def InvalidateIterators(self) -> None: + # It appears that the only way to reliably invalidate iterators to + # self._values is to ensure that its size changes. + original = self._values + self._values = original.copy() + original[None] = None + + # This is defined in the abstract base, but we can do it much more cheaply. + def clear(self) -> None: + self._values.clear() + self._message_listener.Modified() + + def GetEntryClass(self) -> Any: + return self._entry_descriptor._concrete_class + + +class _UnknownField: + """A parsed unknown field.""" + + # Disallows assignment to other attributes. + __slots__ = ['_field_number', '_wire_type', '_data'] + + def __init__(self, field_number, wire_type, data): + self._field_number = field_number + self._wire_type = wire_type + self._data = data + return + + def __lt__(self, other): + # pylint: disable=protected-access + return self._field_number < other._field_number + + def __eq__(self, other): + if self is other: + return True + # pylint: disable=protected-access + return (self._field_number == other._field_number and + self._wire_type == other._wire_type and + self._data == other._data) + + +class UnknownFieldRef: # pylint: disable=missing-class-docstring + + def __init__(self, parent, index): + self._parent = parent + self._index = index + + def _check_valid(self): + if not self._parent: + raise ValueError('UnknownField does not exist. ' + 'The parent message might be cleared.') + if self._index >= len(self._parent): + raise ValueError('UnknownField does not exist. ' + 'The parent message might be cleared.') + + @property + def field_number(self): + self._check_valid() + # pylint: disable=protected-access + return self._parent._internal_get(self._index)._field_number + + @property + def wire_type(self): + self._check_valid() + # pylint: disable=protected-access + return self._parent._internal_get(self._index)._wire_type + + @property + def data(self): + self._check_valid() + # pylint: disable=protected-access + return self._parent._internal_get(self._index)._data + + +class UnknownFieldSet: + """UnknownField container""" + + # Disallows assignment to other attributes. + __slots__ = ['_values'] + + def __init__(self): + self._values = [] + + def __getitem__(self, index): + if self._values is None: + raise ValueError('UnknownFields does not exist. ' + 'The parent message might be cleared.') + size = len(self._values) + if index < 0: + index += size + if index < 0 or index >= size: + raise IndexError('index %d out of range'.index) + + return UnknownFieldRef(self, index) + + def _internal_get(self, index): + return self._values[index] + + def __len__(self): + if self._values is None: + raise ValueError('UnknownFields does not exist. ' + 'The parent message might be cleared.') + return len(self._values) + + def _add(self, field_number, wire_type, data): + unknown_field = _UnknownField(field_number, wire_type, data) + self._values.append(unknown_field) + return unknown_field + + def __iter__(self): + for i in range(len(self)): + yield UnknownFieldRef(self, i) + + def _extend(self, other): + if other is None: + return + # pylint: disable=protected-access + self._values.extend(other._values) + + def __eq__(self, other): + if self is other: + return True + # Sort unknown fields because their order shouldn't + # affect equality test. + values = list(self._values) + if other is None: + return not values + values.sort() + # pylint: disable=protected-access + other_values = sorted(other._values) + return values == other_values + + def _clear(self): + for value in self._values: + # pylint: disable=protected-access + if isinstance(value._data, UnknownFieldSet): + value._data._clear() # pylint: disable=protected-access + self._values = None diff --git a/google/protobuf/internal/decoder.py b/google/protobuf/internal/decoder.py new file mode 100644 index 0000000..ddaced2 --- /dev/null +++ b/google/protobuf/internal/decoder.py @@ -0,0 +1,1024 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Code for decoding protocol buffer primitives. + +This code is very similar to encoder.py -- read the docs for that module first. + +A "decoder" is a function with the signature: + Decode(buffer, pos, end, message, field_dict) +The arguments are: + buffer: The string containing the encoded message. + pos: The current position in the string. + end: The position in the string where the current message ends. May be + less than len(buffer) if we're reading a sub-message. + message: The message object into which we're parsing. + field_dict: message._fields (avoids a hashtable lookup). +The decoder reads the field and stores it into field_dict, returning the new +buffer position. A decoder for a repeated field may proactively decode all of +the elements of that field, if they appear consecutively. + +Note that decoders may throw any of the following: + IndexError: Indicates a truncated message. + struct.error: Unpacking of a fixed-width field failed. + message.DecodeError: Other errors. + +Decoders are expected to raise an exception if they are called with pos > end. +This allows callers to be lax about bounds checking: it's fineto read past +"end" as long as you are sure that someone else will notice and throw an +exception later on. + +Something up the call stack is expected to catch IndexError and struct.error +and convert them to message.DecodeError. + +Decoders are constructed using decoder constructors with the signature: + MakeDecoder(field_number, is_repeated, is_packed, key, new_default) +The arguments are: + field_number: The field number of the field we want to decode. + is_repeated: Is the field a repeated field? (bool) + is_packed: Is the field a packed field? (bool) + key: The key to use when looking up the field within field_dict. + (This is actually the FieldDescriptor but nothing in this + file should depend on that.) + new_default: A function which takes a message object as a parameter and + returns a new instance of the default value for this field. + (This is called for repeated fields and sub-messages, when an + instance does not already exist.) + +As with encoders, we define a decoder constructor for every type of field. +Then, for every field of every message class we construct an actual decoder. +That decoder goes into a dict indexed by tag, so when we decode a message +we repeatedly read a tag, look up the corresponding decoder, and invoke it. +""" + +__author__ = 'kenton@google.com (Kenton Varda)' + +import math +import struct + +from google.protobuf.internal import containers +from google.protobuf.internal import encoder +from google.protobuf.internal import wire_format +from google.protobuf import message + + +# This is not for optimization, but rather to avoid conflicts with local +# variables named "message". +_DecodeError = message.DecodeError + + +def _VarintDecoder(mask, result_type): + """Return an encoder for a basic varint value (does not include tag). + + Decoded values will be bitwise-anded with the given mask before being + returned, e.g. to limit them to 32 bits. The returned decoder does not + take the usual "end" parameter -- the caller is expected to do bounds checking + after the fact (often the caller can defer such checking until later). The + decoder returns a (value, new_pos) pair. + """ + + def DecodeVarint(buffer, pos): + result = 0 + shift = 0 + while 1: + b = buffer[pos] + result |= ((b & 0x7f) << shift) + pos += 1 + if not (b & 0x80): + result &= mask + result = result_type(result) + return (result, pos) + shift += 7 + if shift >= 64: + raise _DecodeError('Too many bytes when decoding varint.') + return DecodeVarint + + +def _SignedVarintDecoder(bits, result_type): + """Like _VarintDecoder() but decodes signed values.""" + + signbit = 1 << (bits - 1) + mask = (1 << bits) - 1 + + def DecodeVarint(buffer, pos): + result = 0 + shift = 0 + while 1: + b = buffer[pos] + result |= ((b & 0x7f) << shift) + pos += 1 + if not (b & 0x80): + result &= mask + result = (result ^ signbit) - signbit + result = result_type(result) + return (result, pos) + shift += 7 + if shift >= 64: + raise _DecodeError('Too many bytes when decoding varint.') + return DecodeVarint + +# All 32-bit and 64-bit values are represented as int. +_DecodeVarint = _VarintDecoder((1 << 64) - 1, int) +_DecodeSignedVarint = _SignedVarintDecoder(64, int) + +# Use these versions for values which must be limited to 32 bits. +_DecodeVarint32 = _VarintDecoder((1 << 32) - 1, int) +_DecodeSignedVarint32 = _SignedVarintDecoder(32, int) + + +def ReadTag(buffer, pos): + """Read a tag from the memoryview, and return a (tag_bytes, new_pos) tuple. + + We return the raw bytes of the tag rather than decoding them. The raw + bytes can then be used to look up the proper decoder. This effectively allows + us to trade some work that would be done in pure-python (decoding a varint) + for work that is done in C (searching for a byte string in a hash table). + In a low-level language it would be much cheaper to decode the varint and + use that, but not in Python. + + Args: + buffer: memoryview object of the encoded bytes + pos: int of the current position to start from + + Returns: + Tuple[bytes, int] of the tag data and new position. + """ + start = pos + while buffer[pos] & 0x80: + pos += 1 + pos += 1 + + tag_bytes = buffer[start:pos].tobytes() + return tag_bytes, pos + + +# -------------------------------------------------------------------- + + +def _SimpleDecoder(wire_type, decode_value): + """Return a constructor for a decoder for fields of a particular type. + + Args: + wire_type: The field's wire type. + decode_value: A function which decodes an individual value, e.g. + _DecodeVarint() + """ + + def SpecificDecoder(field_number, is_repeated, is_packed, key, new_default, + clear_if_default=False): + if is_packed: + local_DecodeVarint = _DecodeVarint + def DecodePackedField(buffer, pos, end, message, field_dict): + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) + (endpoint, pos) = local_DecodeVarint(buffer, pos) + endpoint += pos + if endpoint > end: + raise _DecodeError('Truncated message.') + while pos < endpoint: + (element, pos) = decode_value(buffer, pos) + value.append(element) + if pos > endpoint: + del value[-1] # Discard corrupt value. + raise _DecodeError('Packed element was truncated.') + return pos + return DecodePackedField + elif is_repeated: + tag_bytes = encoder.TagBytes(field_number, wire_type) + tag_len = len(tag_bytes) + def DecodeRepeatedField(buffer, pos, end, message, field_dict): + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) + while 1: + (element, new_pos) = decode_value(buffer, pos) + value.append(element) + # Predict that the next tag is another copy of the same repeated + # field. + pos = new_pos + tag_len + if buffer[new_pos:pos] != tag_bytes or new_pos >= end: + # Prediction failed. Return. + if new_pos > end: + raise _DecodeError('Truncated message.') + return new_pos + return DecodeRepeatedField + else: + def DecodeField(buffer, pos, end, message, field_dict): + (new_value, pos) = decode_value(buffer, pos) + if pos > end: + raise _DecodeError('Truncated message.') + if clear_if_default and not new_value: + field_dict.pop(key, None) + else: + field_dict[key] = new_value + return pos + return DecodeField + + return SpecificDecoder + + +def _ModifiedDecoder(wire_type, decode_value, modify_value): + """Like SimpleDecoder but additionally invokes modify_value on every value + before storing it. Usually modify_value is ZigZagDecode. + """ + + # Reusing _SimpleDecoder is slightly slower than copying a bunch of code, but + # not enough to make a significant difference. + + def InnerDecode(buffer, pos): + (result, new_pos) = decode_value(buffer, pos) + return (modify_value(result), new_pos) + return _SimpleDecoder(wire_type, InnerDecode) + + +def _StructPackDecoder(wire_type, format): + """Return a constructor for a decoder for a fixed-width field. + + Args: + wire_type: The field's wire type. + format: The format string to pass to struct.unpack(). + """ + + value_size = struct.calcsize(format) + local_unpack = struct.unpack + + # Reusing _SimpleDecoder is slightly slower than copying a bunch of code, but + # not enough to make a significant difference. + + # Note that we expect someone up-stack to catch struct.error and convert + # it to _DecodeError -- this way we don't have to set up exception- + # handling blocks every time we parse one value. + + def InnerDecode(buffer, pos): + new_pos = pos + value_size + result = local_unpack(format, buffer[pos:new_pos])[0] + return (result, new_pos) + return _SimpleDecoder(wire_type, InnerDecode) + + +def _FloatDecoder(): + """Returns a decoder for a float field. + + This code works around a bug in struct.unpack for non-finite 32-bit + floating-point values. + """ + + local_unpack = struct.unpack + + def InnerDecode(buffer, pos): + """Decode serialized float to a float and new position. + + Args: + buffer: memoryview of the serialized bytes + pos: int, position in the memory view to start at. + + Returns: + Tuple[float, int] of the deserialized float value and new position + in the serialized data. + """ + # We expect a 32-bit value in little-endian byte order. Bit 1 is the sign + # bit, bits 2-9 represent the exponent, and bits 10-32 are the significand. + new_pos = pos + 4 + float_bytes = buffer[pos:new_pos].tobytes() + + # If this value has all its exponent bits set, then it's non-finite. + # In Python 2.4, struct.unpack will convert it to a finite 64-bit value. + # To avoid that, we parse it specially. + if (float_bytes[3:4] in b'\x7F\xFF' and float_bytes[2:3] >= b'\x80'): + # If at least one significand bit is set... + if float_bytes[0:3] != b'\x00\x00\x80': + return (math.nan, new_pos) + # If sign bit is set... + if float_bytes[3:4] == b'\xFF': + return (-math.inf, new_pos) + return (math.inf, new_pos) + + # Note that we expect someone up-stack to catch struct.error and convert + # it to _DecodeError -- this way we don't have to set up exception- + # handling blocks every time we parse one value. + result = local_unpack('= b'\xF0') + and (double_bytes[0:7] != b'\x00\x00\x00\x00\x00\x00\xF0')): + return (math.nan, new_pos) + + # Note that we expect someone up-stack to catch struct.error and convert + # it to _DecodeError -- this way we don't have to set up exception- + # handling blocks every time we parse one value. + result = local_unpack(' end: + raise _DecodeError('Truncated message.') + while pos < endpoint: + value_start_pos = pos + (element, pos) = _DecodeSignedVarint32(buffer, pos) + # pylint: disable=protected-access + if element in enum_type.values_by_number: + value.append(element) + else: + if not message._unknown_fields: + message._unknown_fields = [] + tag_bytes = encoder.TagBytes(field_number, + wire_format.WIRETYPE_VARINT) + + message._unknown_fields.append( + (tag_bytes, buffer[value_start_pos:pos].tobytes())) + # pylint: enable=protected-access + if pos > endpoint: + if element in enum_type.values_by_number: + del value[-1] # Discard corrupt value. + else: + del message._unknown_fields[-1] + # pylint: enable=protected-access + raise _DecodeError('Packed element was truncated.') + return pos + return DecodePackedField + elif is_repeated: + tag_bytes = encoder.TagBytes(field_number, wire_format.WIRETYPE_VARINT) + tag_len = len(tag_bytes) + def DecodeRepeatedField(buffer, pos, end, message, field_dict): + """Decode serialized repeated enum to its value and a new position. + + Args: + buffer: memoryview of the serialized bytes. + pos: int, position in the memory view to start at. + end: int, end position of serialized data + message: Message object to store unknown fields in + field_dict: Map[Descriptor, Any] to store decoded values in. + + Returns: + int, new position in serialized data. + """ + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) + while 1: + (element, new_pos) = _DecodeSignedVarint32(buffer, pos) + # pylint: disable=protected-access + if element in enum_type.values_by_number: + value.append(element) + else: + if not message._unknown_fields: + message._unknown_fields = [] + message._unknown_fields.append( + (tag_bytes, buffer[pos:new_pos].tobytes())) + # pylint: enable=protected-access + # Predict that the next tag is another copy of the same repeated + # field. + pos = new_pos + tag_len + if buffer[new_pos:pos] != tag_bytes or new_pos >= end: + # Prediction failed. Return. + if new_pos > end: + raise _DecodeError('Truncated message.') + return new_pos + return DecodeRepeatedField + else: + def DecodeField(buffer, pos, end, message, field_dict): + """Decode serialized repeated enum to its value and a new position. + + Args: + buffer: memoryview of the serialized bytes. + pos: int, position in the memory view to start at. + end: int, end position of serialized data + message: Message object to store unknown fields in + field_dict: Map[Descriptor, Any] to store decoded values in. + + Returns: + int, new position in serialized data. + """ + value_start_pos = pos + (enum_value, pos) = _DecodeSignedVarint32(buffer, pos) + if pos > end: + raise _DecodeError('Truncated message.') + if clear_if_default and not enum_value: + field_dict.pop(key, None) + return pos + # pylint: disable=protected-access + if enum_value in enum_type.values_by_number: + field_dict[key] = enum_value + else: + if not message._unknown_fields: + message._unknown_fields = [] + tag_bytes = encoder.TagBytes(field_number, + wire_format.WIRETYPE_VARINT) + message._unknown_fields.append( + (tag_bytes, buffer[value_start_pos:pos].tobytes())) + # pylint: enable=protected-access + return pos + return DecodeField + + +# -------------------------------------------------------------------- + + +Int32Decoder = _SimpleDecoder( + wire_format.WIRETYPE_VARINT, _DecodeSignedVarint32) + +Int64Decoder = _SimpleDecoder( + wire_format.WIRETYPE_VARINT, _DecodeSignedVarint) + +UInt32Decoder = _SimpleDecoder(wire_format.WIRETYPE_VARINT, _DecodeVarint32) +UInt64Decoder = _SimpleDecoder(wire_format.WIRETYPE_VARINT, _DecodeVarint) + +SInt32Decoder = _ModifiedDecoder( + wire_format.WIRETYPE_VARINT, _DecodeVarint32, wire_format.ZigZagDecode) +SInt64Decoder = _ModifiedDecoder( + wire_format.WIRETYPE_VARINT, _DecodeVarint, wire_format.ZigZagDecode) + +# Note that Python conveniently guarantees that when using the '<' prefix on +# formats, they will also have the same size across all platforms (as opposed +# to without the prefix, where their sizes depend on the C compiler's basic +# type sizes). +Fixed32Decoder = _StructPackDecoder(wire_format.WIRETYPE_FIXED32, ' end: + raise _DecodeError('Truncated string.') + value.append(_ConvertToUnicode(buffer[pos:new_pos])) + # Predict that the next tag is another copy of the same repeated field. + pos = new_pos + tag_len + if buffer[new_pos:pos] != tag_bytes or new_pos == end: + # Prediction failed. Return. + return new_pos + return DecodeRepeatedField + else: + def DecodeField(buffer, pos, end, message, field_dict): + (size, pos) = local_DecodeVarint(buffer, pos) + new_pos = pos + size + if new_pos > end: + raise _DecodeError('Truncated string.') + if clear_if_default and not size: + field_dict.pop(key, None) + else: + field_dict[key] = _ConvertToUnicode(buffer[pos:new_pos]) + return new_pos + return DecodeField + + +def BytesDecoder(field_number, is_repeated, is_packed, key, new_default, + clear_if_default=False): + """Returns a decoder for a bytes field.""" + + local_DecodeVarint = _DecodeVarint + + assert not is_packed + if is_repeated: + tag_bytes = encoder.TagBytes(field_number, + wire_format.WIRETYPE_LENGTH_DELIMITED) + tag_len = len(tag_bytes) + def DecodeRepeatedField(buffer, pos, end, message, field_dict): + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) + while 1: + (size, pos) = local_DecodeVarint(buffer, pos) + new_pos = pos + size + if new_pos > end: + raise _DecodeError('Truncated string.') + value.append(buffer[pos:new_pos].tobytes()) + # Predict that the next tag is another copy of the same repeated field. + pos = new_pos + tag_len + if buffer[new_pos:pos] != tag_bytes or new_pos == end: + # Prediction failed. Return. + return new_pos + return DecodeRepeatedField + else: + def DecodeField(buffer, pos, end, message, field_dict): + (size, pos) = local_DecodeVarint(buffer, pos) + new_pos = pos + size + if new_pos > end: + raise _DecodeError('Truncated string.') + if clear_if_default and not size: + field_dict.pop(key, None) + else: + field_dict[key] = buffer[pos:new_pos].tobytes() + return new_pos + return DecodeField + + +def GroupDecoder(field_number, is_repeated, is_packed, key, new_default): + """Returns a decoder for a group field.""" + + end_tag_bytes = encoder.TagBytes(field_number, + wire_format.WIRETYPE_END_GROUP) + end_tag_len = len(end_tag_bytes) + + assert not is_packed + if is_repeated: + tag_bytes = encoder.TagBytes(field_number, + wire_format.WIRETYPE_START_GROUP) + tag_len = len(tag_bytes) + def DecodeRepeatedField(buffer, pos, end, message, field_dict): + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) + while 1: + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) + # Read sub-message. + pos = value.add()._InternalParse(buffer, pos, end) + # Read end tag. + new_pos = pos+end_tag_len + if buffer[pos:new_pos] != end_tag_bytes or new_pos > end: + raise _DecodeError('Missing group end tag.') + # Predict that the next tag is another copy of the same repeated field. + pos = new_pos + tag_len + if buffer[new_pos:pos] != tag_bytes or new_pos == end: + # Prediction failed. Return. + return new_pos + return DecodeRepeatedField + else: + def DecodeField(buffer, pos, end, message, field_dict): + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) + # Read sub-message. + pos = value._InternalParse(buffer, pos, end) + # Read end tag. + new_pos = pos+end_tag_len + if buffer[pos:new_pos] != end_tag_bytes or new_pos > end: + raise _DecodeError('Missing group end tag.') + return new_pos + return DecodeField + + +def MessageDecoder(field_number, is_repeated, is_packed, key, new_default): + """Returns a decoder for a message field.""" + + local_DecodeVarint = _DecodeVarint + + assert not is_packed + if is_repeated: + tag_bytes = encoder.TagBytes(field_number, + wire_format.WIRETYPE_LENGTH_DELIMITED) + tag_len = len(tag_bytes) + def DecodeRepeatedField(buffer, pos, end, message, field_dict): + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) + while 1: + # Read length. + (size, pos) = local_DecodeVarint(buffer, pos) + new_pos = pos + size + if new_pos > end: + raise _DecodeError('Truncated message.') + # Read sub-message. + if value.add()._InternalParse(buffer, pos, new_pos) != new_pos: + # The only reason _InternalParse would return early is if it + # encountered an end-group tag. + raise _DecodeError('Unexpected end-group tag.') + # Predict that the next tag is another copy of the same repeated field. + pos = new_pos + tag_len + if buffer[new_pos:pos] != tag_bytes or new_pos == end: + # Prediction failed. Return. + return new_pos + return DecodeRepeatedField + else: + def DecodeField(buffer, pos, end, message, field_dict): + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) + # Read length. + (size, pos) = local_DecodeVarint(buffer, pos) + new_pos = pos + size + if new_pos > end: + raise _DecodeError('Truncated message.') + # Read sub-message. + if value._InternalParse(buffer, pos, new_pos) != new_pos: + # The only reason _InternalParse would return early is if it encountered + # an end-group tag. + raise _DecodeError('Unexpected end-group tag.') + return new_pos + return DecodeField + + +# -------------------------------------------------------------------- + +MESSAGE_SET_ITEM_TAG = encoder.TagBytes(1, wire_format.WIRETYPE_START_GROUP) + +def MessageSetItemDecoder(descriptor): + """Returns a decoder for a MessageSet item. + + The parameter is the message Descriptor. + + The message set message looks like this: + message MessageSet { + repeated group Item = 1 { + required int32 type_id = 2; + required string message = 3; + } + } + """ + + type_id_tag_bytes = encoder.TagBytes(2, wire_format.WIRETYPE_VARINT) + message_tag_bytes = encoder.TagBytes(3, wire_format.WIRETYPE_LENGTH_DELIMITED) + item_end_tag_bytes = encoder.TagBytes(1, wire_format.WIRETYPE_END_GROUP) + + local_ReadTag = ReadTag + local_DecodeVarint = _DecodeVarint + local_SkipField = SkipField + + def DecodeItem(buffer, pos, end, message, field_dict): + """Decode serialized message set to its value and new position. + + Args: + buffer: memoryview of the serialized bytes. + pos: int, position in the memory view to start at. + end: int, end position of serialized data + message: Message object to store unknown fields in + field_dict: Map[Descriptor, Any] to store decoded values in. + + Returns: + int, new position in serialized data. + """ + message_set_item_start = pos + type_id = -1 + message_start = -1 + message_end = -1 + + # Technically, type_id and message can appear in any order, so we need + # a little loop here. + while 1: + (tag_bytes, pos) = local_ReadTag(buffer, pos) + if tag_bytes == type_id_tag_bytes: + (type_id, pos) = local_DecodeVarint(buffer, pos) + elif tag_bytes == message_tag_bytes: + (size, message_start) = local_DecodeVarint(buffer, pos) + pos = message_end = message_start + size + elif tag_bytes == item_end_tag_bytes: + break + else: + pos = SkipField(buffer, pos, end, tag_bytes) + if pos == -1: + raise _DecodeError('Missing group end tag.') + + if pos > end: + raise _DecodeError('Truncated message.') + + if type_id == -1: + raise _DecodeError('MessageSet item missing type_id.') + if message_start == -1: + raise _DecodeError('MessageSet item missing message.') + + extension = message.Extensions._FindExtensionByNumber(type_id) + # pylint: disable=protected-access + if extension is not None: + value = field_dict.get(extension) + if value is None: + message_type = extension.message_type + if not hasattr(message_type, '_concrete_class'): + message_factory.GetMessageClass(message_type) + value = field_dict.setdefault( + extension, message_type._concrete_class()) + if value._InternalParse(buffer, message_start,message_end) != message_end: + # The only reason _InternalParse would return early is if it encountered + # an end-group tag. + raise _DecodeError('Unexpected end-group tag.') + else: + if not message._unknown_fields: + message._unknown_fields = [] + message._unknown_fields.append( + (MESSAGE_SET_ITEM_TAG, buffer[message_set_item_start:pos].tobytes())) + # pylint: enable=protected-access + + return pos + + return DecodeItem + + +def UnknownMessageSetItemDecoder(): + """Returns a decoder for a Unknown MessageSet item.""" + + type_id_tag_bytes = encoder.TagBytes(2, wire_format.WIRETYPE_VARINT) + message_tag_bytes = encoder.TagBytes(3, wire_format.WIRETYPE_LENGTH_DELIMITED) + item_end_tag_bytes = encoder.TagBytes(1, wire_format.WIRETYPE_END_GROUP) + + def DecodeUnknownItem(buffer): + pos = 0 + end = len(buffer) + message_start = -1 + message_end = -1 + while 1: + (tag_bytes, pos) = ReadTag(buffer, pos) + if tag_bytes == type_id_tag_bytes: + (type_id, pos) = _DecodeVarint(buffer, pos) + elif tag_bytes == message_tag_bytes: + (size, message_start) = _DecodeVarint(buffer, pos) + pos = message_end = message_start + size + elif tag_bytes == item_end_tag_bytes: + break + else: + pos = SkipField(buffer, pos, end, tag_bytes) + if pos == -1: + raise _DecodeError('Missing group end tag.') + + if pos > end: + raise _DecodeError('Truncated message.') + + if type_id == -1: + raise _DecodeError('MessageSet item missing type_id.') + if message_start == -1: + raise _DecodeError('MessageSet item missing message.') + + return (type_id, buffer[message_start:message_end].tobytes()) + + return DecodeUnknownItem + +# -------------------------------------------------------------------- + +def MapDecoder(field_descriptor, new_default, is_message_map): + """Returns a decoder for a map field.""" + + key = field_descriptor + tag_bytes = encoder.TagBytes(field_descriptor.number, + wire_format.WIRETYPE_LENGTH_DELIMITED) + tag_len = len(tag_bytes) + local_DecodeVarint = _DecodeVarint + # Can't read _concrete_class yet; might not be initialized. + message_type = field_descriptor.message_type + + def DecodeMap(buffer, pos, end, message, field_dict): + submsg = message_type._concrete_class() + value = field_dict.get(key) + if value is None: + value = field_dict.setdefault(key, new_default(message)) + while 1: + # Read length. + (size, pos) = local_DecodeVarint(buffer, pos) + new_pos = pos + size + if new_pos > end: + raise _DecodeError('Truncated message.') + # Read sub-message. + submsg.Clear() + if submsg._InternalParse(buffer, pos, new_pos) != new_pos: + # The only reason _InternalParse would return early is if it + # encountered an end-group tag. + raise _DecodeError('Unexpected end-group tag.') + + if is_message_map: + value[submsg.key].CopyFrom(submsg.value) + else: + value[submsg.key] = submsg.value + + # Predict that the next tag is another copy of the same repeated field. + pos = new_pos + tag_len + if buffer[new_pos:pos] != tag_bytes or new_pos == end: + # Prediction failed. Return. + return new_pos + + return DecodeMap + +# -------------------------------------------------------------------- +# Optimization is not as heavy here because calls to SkipField() are rare, +# except for handling end-group tags. + +def _SkipVarint(buffer, pos, end): + """Skip a varint value. Returns the new position.""" + # Previously ord(buffer[pos]) raised IndexError when pos is out of range. + # With this code, ord(b'') raises TypeError. Both are handled in + # python_message.py to generate a 'Truncated message' error. + while ord(buffer[pos:pos+1].tobytes()) & 0x80: + pos += 1 + pos += 1 + if pos > end: + raise _DecodeError('Truncated message.') + return pos + +def _SkipFixed64(buffer, pos, end): + """Skip a fixed64 value. Returns the new position.""" + + pos += 8 + if pos > end: + raise _DecodeError('Truncated message.') + return pos + + +def _DecodeFixed64(buffer, pos): + """Decode a fixed64.""" + new_pos = pos + 8 + return (struct.unpack(' end: + raise _DecodeError('Truncated message.') + return pos + + +def _SkipGroup(buffer, pos, end): + """Skip sub-group. Returns the new position.""" + + while 1: + (tag_bytes, pos) = ReadTag(buffer, pos) + new_pos = SkipField(buffer, pos, end, tag_bytes) + if new_pos == -1: + return pos + pos = new_pos + + +def _DecodeUnknownFieldSet(buffer, pos, end_pos=None): + """Decode UnknownFieldSet. Returns the UnknownFieldSet and new position.""" + + unknown_field_set = containers.UnknownFieldSet() + while end_pos is None or pos < end_pos: + (tag_bytes, pos) = ReadTag(buffer, pos) + (tag, _) = _DecodeVarint(tag_bytes, 0) + field_number, wire_type = wire_format.UnpackTag(tag) + if wire_type == wire_format.WIRETYPE_END_GROUP: + break + (data, pos) = _DecodeUnknownField(buffer, pos, wire_type) + # pylint: disable=protected-access + unknown_field_set._add(field_number, wire_type, data) + + return (unknown_field_set, pos) + + +def _DecodeUnknownField(buffer, pos, wire_type): + """Decode a unknown field. Returns the UnknownField and new position.""" + + if wire_type == wire_format.WIRETYPE_VARINT: + (data, pos) = _DecodeVarint(buffer, pos) + elif wire_type == wire_format.WIRETYPE_FIXED64: + (data, pos) = _DecodeFixed64(buffer, pos) + elif wire_type == wire_format.WIRETYPE_FIXED32: + (data, pos) = _DecodeFixed32(buffer, pos) + elif wire_type == wire_format.WIRETYPE_LENGTH_DELIMITED: + (size, pos) = _DecodeVarint(buffer, pos) + data = buffer[pos:pos+size].tobytes() + pos += size + elif wire_type == wire_format.WIRETYPE_START_GROUP: + (data, pos) = _DecodeUnknownFieldSet(buffer, pos) + elif wire_type == wire_format.WIRETYPE_END_GROUP: + return (0, -1) + else: + raise _DecodeError('Wrong wire type in tag.') + + return (data, pos) + + +def _EndGroup(buffer, pos, end): + """Skipping an END_GROUP tag returns -1 to tell the parent loop to break.""" + + return -1 + + +def _SkipFixed32(buffer, pos, end): + """Skip a fixed32 value. Returns the new position.""" + + pos += 4 + if pos > end: + raise _DecodeError('Truncated message.') + return pos + + +def _DecodeFixed32(buffer, pos): + """Decode a fixed32.""" + + new_pos = pos + 4 + return (struct.unpack('B').pack + + def EncodeVarint(write, value, unused_deterministic=None): + bits = value & 0x7f + value >>= 7 + while value: + write(local_int2byte(0x80|bits)) + bits = value & 0x7f + value >>= 7 + return write(local_int2byte(bits)) + + return EncodeVarint + + +def _SignedVarintEncoder(): + """Return an encoder for a basic signed varint value (does not include + tag).""" + + local_int2byte = struct.Struct('>B').pack + + def EncodeSignedVarint(write, value, unused_deterministic=None): + if value < 0: + value += (1 << 64) + bits = value & 0x7f + value >>= 7 + while value: + write(local_int2byte(0x80|bits)) + bits = value & 0x7f + value >>= 7 + return write(local_int2byte(bits)) + + return EncodeSignedVarint + + +_EncodeVarint = _VarintEncoder() +_EncodeSignedVarint = _SignedVarintEncoder() + + +def _VarintBytes(value): + """Encode the given integer as a varint and return the bytes. This is only + called at startup time so it doesn't need to be fast.""" + + pieces = [] + _EncodeVarint(pieces.append, value, True) + return b"".join(pieces) + + +def TagBytes(field_number, wire_type): + """Encode the given tag and return the bytes. Only called at startup.""" + + return bytes(_VarintBytes(wire_format.PackTag(field_number, wire_type))) + +# -------------------------------------------------------------------- +# As with sizers (see above), we have a number of common encoder +# implementations. + + +def _SimpleEncoder(wire_type, encode_value, compute_value_size): + """Return a constructor for an encoder for fields of a particular type. + + Args: + wire_type: The field's wire type, for encoding tags. + encode_value: A function which encodes an individual value, e.g. + _EncodeVarint(). + compute_value_size: A function which computes the size of an individual + value, e.g. _VarintSize(). + """ + + def SpecificEncoder(field_number, is_repeated, is_packed): + if is_packed: + tag_bytes = TagBytes(field_number, wire_format.WIRETYPE_LENGTH_DELIMITED) + local_EncodeVarint = _EncodeVarint + def EncodePackedField(write, value, deterministic): + write(tag_bytes) + size = 0 + for element in value: + size += compute_value_size(element) + local_EncodeVarint(write, size, deterministic) + for element in value: + encode_value(write, element, deterministic) + return EncodePackedField + elif is_repeated: + tag_bytes = TagBytes(field_number, wire_type) + def EncodeRepeatedField(write, value, deterministic): + for element in value: + write(tag_bytes) + encode_value(write, element, deterministic) + return EncodeRepeatedField + else: + tag_bytes = TagBytes(field_number, wire_type) + def EncodeField(write, value, deterministic): + write(tag_bytes) + return encode_value(write, value, deterministic) + return EncodeField + + return SpecificEncoder + + +def _ModifiedEncoder(wire_type, encode_value, compute_value_size, modify_value): + """Like SimpleEncoder but additionally invokes modify_value on every value + before passing it to encode_value. Usually modify_value is ZigZagEncode.""" + + def SpecificEncoder(field_number, is_repeated, is_packed): + if is_packed: + tag_bytes = TagBytes(field_number, wire_format.WIRETYPE_LENGTH_DELIMITED) + local_EncodeVarint = _EncodeVarint + def EncodePackedField(write, value, deterministic): + write(tag_bytes) + size = 0 + for element in value: + size += compute_value_size(modify_value(element)) + local_EncodeVarint(write, size, deterministic) + for element in value: + encode_value(write, modify_value(element), deterministic) + return EncodePackedField + elif is_repeated: + tag_bytes = TagBytes(field_number, wire_type) + def EncodeRepeatedField(write, value, deterministic): + for element in value: + write(tag_bytes) + encode_value(write, modify_value(element), deterministic) + return EncodeRepeatedField + else: + tag_bytes = TagBytes(field_number, wire_type) + def EncodeField(write, value, deterministic): + write(tag_bytes) + return encode_value(write, modify_value(value), deterministic) + return EncodeField + + return SpecificEncoder + + +def _StructPackEncoder(wire_type, format): + """Return a constructor for an encoder for a fixed-width field. + + Args: + wire_type: The field's wire type, for encoding tags. + format: The format string to pass to struct.pack(). + """ + + value_size = struct.calcsize(format) + + def SpecificEncoder(field_number, is_repeated, is_packed): + local_struct_pack = struct.pack + if is_packed: + tag_bytes = TagBytes(field_number, wire_format.WIRETYPE_LENGTH_DELIMITED) + local_EncodeVarint = _EncodeVarint + def EncodePackedField(write, value, deterministic): + write(tag_bytes) + local_EncodeVarint(write, len(value) * value_size, deterministic) + for element in value: + write(local_struct_pack(format, element)) + return EncodePackedField + elif is_repeated: + tag_bytes = TagBytes(field_number, wire_type) + def EncodeRepeatedField(write, value, unused_deterministic=None): + for element in value: + write(tag_bytes) + write(local_struct_pack(format, element)) + return EncodeRepeatedField + else: + tag_bytes = TagBytes(field_number, wire_type) + def EncodeField(write, value, unused_deterministic=None): + write(tag_bytes) + return write(local_struct_pack(format, value)) + return EncodeField + + return SpecificEncoder + + +def _FloatingPointEncoder(wire_type, format): + """Return a constructor for an encoder for float fields. + + This is like StructPackEncoder, but catches errors that may be due to + passing non-finite floating-point values to struct.pack, and makes a + second attempt to encode those values. + + Args: + wire_type: The field's wire type, for encoding tags. + format: The format string to pass to struct.pack(). + """ + + value_size = struct.calcsize(format) + if value_size == 4: + def EncodeNonFiniteOrRaise(write, value): + # Remember that the serialized form uses little-endian byte order. + if value == _POS_INF: + write(b'\x00\x00\x80\x7F') + elif value == _NEG_INF: + write(b'\x00\x00\x80\xFF') + elif value != value: # NaN + write(b'\x00\x00\xC0\x7F') + else: + raise + elif value_size == 8: + def EncodeNonFiniteOrRaise(write, value): + if value == _POS_INF: + write(b'\x00\x00\x00\x00\x00\x00\xF0\x7F') + elif value == _NEG_INF: + write(b'\x00\x00\x00\x00\x00\x00\xF0\xFF') + elif value != value: # NaN + write(b'\x00\x00\x00\x00\x00\x00\xF8\x7F') + else: + raise + else: + raise ValueError('Can\'t encode floating-point values that are ' + '%d bytes long (only 4 or 8)' % value_size) + + def SpecificEncoder(field_number, is_repeated, is_packed): + local_struct_pack = struct.pack + if is_packed: + tag_bytes = TagBytes(field_number, wire_format.WIRETYPE_LENGTH_DELIMITED) + local_EncodeVarint = _EncodeVarint + def EncodePackedField(write, value, deterministic): + write(tag_bytes) + local_EncodeVarint(write, len(value) * value_size, deterministic) + for element in value: + # This try/except block is going to be faster than any code that + # we could write to check whether element is finite. + try: + write(local_struct_pack(format, element)) + except SystemError: + EncodeNonFiniteOrRaise(write, element) + return EncodePackedField + elif is_repeated: + tag_bytes = TagBytes(field_number, wire_type) + def EncodeRepeatedField(write, value, unused_deterministic=None): + for element in value: + write(tag_bytes) + try: + write(local_struct_pack(format, element)) + except SystemError: + EncodeNonFiniteOrRaise(write, element) + return EncodeRepeatedField + else: + tag_bytes = TagBytes(field_number, wire_type) + def EncodeField(write, value, unused_deterministic=None): + write(tag_bytes) + try: + write(local_struct_pack(format, value)) + except SystemError: + EncodeNonFiniteOrRaise(write, value) + return EncodeField + + return SpecificEncoder + + +# ==================================================================== +# Here we declare an encoder constructor for each field type. These work +# very similarly to sizer constructors, described earlier. + + +Int32Encoder = Int64Encoder = EnumEncoder = _SimpleEncoder( + wire_format.WIRETYPE_VARINT, _EncodeSignedVarint, _SignedVarintSize) + +UInt32Encoder = UInt64Encoder = _SimpleEncoder( + wire_format.WIRETYPE_VARINT, _EncodeVarint, _VarintSize) + +SInt32Encoder = SInt64Encoder = _ModifiedEncoder( + wire_format.WIRETYPE_VARINT, _EncodeVarint, _VarintSize, + wire_format.ZigZagEncode) + +# Note that Python conveniently guarantees that when using the '<' prefix on +# formats, they will also have the same size across all platforms (as opposed +# to without the prefix, where their sizes depend on the C compiler's basic +# type sizes). +Fixed32Encoder = _StructPackEncoder(wire_format.WIRETYPE_FIXED32, ' str + ValueType = int + + def __init__(self, enum_type): + """Inits EnumTypeWrapper with an EnumDescriptor.""" + self._enum_type = enum_type + self.DESCRIPTOR = enum_type # pylint: disable=invalid-name + + def Name(self, number): # pylint: disable=invalid-name + """Returns a string containing the name of an enum value.""" + try: + return self._enum_type.values_by_number[number].name + except KeyError: + pass # fall out to break exception chaining + + if not isinstance(number, int): + raise TypeError( + 'Enum value for {} must be an int, but got {} {!r}.'.format( + self._enum_type.name, type(number), number)) + else: + # repr here to handle the odd case when you pass in a boolean. + raise ValueError('Enum {} has no name defined for value {!r}'.format( + self._enum_type.name, number)) + + def Value(self, name): # pylint: disable=invalid-name + """Returns the value corresponding to the given enum name.""" + try: + return self._enum_type.values_by_name[name].number + except KeyError: + pass # fall out to break exception chaining + raise ValueError('Enum {} has no value defined for name {!r}'.format( + self._enum_type.name, name)) + + def keys(self): + """Return a list of the string names in the enum. + + Returns: + A list of strs, in the order they were defined in the .proto file. + """ + + return [value_descriptor.name + for value_descriptor in self._enum_type.values] + + def values(self): + """Return a list of the integer values in the enum. + + Returns: + A list of ints, in the order they were defined in the .proto file. + """ + + return [value_descriptor.number + for value_descriptor in self._enum_type.values] + + def items(self): + """Return a list of the (name, value) pairs of the enum. + + Returns: + A list of (str, int) pairs, in the order they were defined + in the .proto file. + """ + return [(value_descriptor.name, value_descriptor.number) + for value_descriptor in self._enum_type.values] + + def __getattr__(self, name): + """Returns the value corresponding to the given enum name.""" + try: + return super( + EnumTypeWrapper, + self).__getattribute__('_enum_type').values_by_name[name].number + except KeyError: + pass # fall out to break exception chaining + raise AttributeError('Enum {} has no value defined for name {!r}'.format( + self._enum_type.name, name)) diff --git a/google/protobuf/internal/extension_dict.py b/google/protobuf/internal/extension_dict.py new file mode 100644 index 0000000..89e64d3 --- /dev/null +++ b/google/protobuf/internal/extension_dict.py @@ -0,0 +1,194 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Contains _ExtensionDict class to represent extensions. +""" + +from google.protobuf.internal import type_checkers +from google.protobuf.descriptor import FieldDescriptor + + +def _VerifyExtensionHandle(message, extension_handle): + """Verify that the given extension handle is valid.""" + + if not isinstance(extension_handle, FieldDescriptor): + raise KeyError('HasExtension() expects an extension handle, got: %s' % + extension_handle) + + if not extension_handle.is_extension: + raise KeyError('"%s" is not an extension.' % extension_handle.full_name) + + if not extension_handle.containing_type: + raise KeyError('"%s" is missing a containing_type.' + % extension_handle.full_name) + + if extension_handle.containing_type is not message.DESCRIPTOR: + raise KeyError('Extension "%s" extends message type "%s", but this ' + 'message is of type "%s".' % + (extension_handle.full_name, + extension_handle.containing_type.full_name, + message.DESCRIPTOR.full_name)) + + +# TODO: Unify error handling of "unknown extension" crap. +# TODO: Support iteritems()-style iteration over all +# extensions with the "has" bits turned on? +class _ExtensionDict(object): + + """Dict-like container for Extension fields on proto instances. + + Note that in all cases we expect extension handles to be + FieldDescriptors. + """ + + def __init__(self, extended_message): + """ + Args: + extended_message: Message instance for which we are the Extensions dict. + """ + self._extended_message = extended_message + + def __getitem__(self, extension_handle): + """Returns the current value of the given extension handle.""" + + _VerifyExtensionHandle(self._extended_message, extension_handle) + + result = self._extended_message._fields.get(extension_handle) + if result is not None: + return result + + if extension_handle.label == FieldDescriptor.LABEL_REPEATED: + result = extension_handle._default_constructor(self._extended_message) + elif extension_handle.cpp_type == FieldDescriptor.CPPTYPE_MESSAGE: + message_type = extension_handle.message_type + if not hasattr(message_type, '_concrete_class'): + # pylint: disable=g-import-not-at-top + from google.protobuf import message_factory + message_factory.GetMessageClass(message_type) + if not hasattr(extension_handle.message_type, '_concrete_class'): + from google.protobuf import message_factory + message_factory.GetMessageClass(extension_handle.message_type) + result = extension_handle.message_type._concrete_class() + try: + result._SetListener(self._extended_message._listener_for_children) + except ReferenceError: + pass + else: + # Singular scalar -- just return the default without inserting into the + # dict. + return extension_handle.default_value + + # Atomically check if another thread has preempted us and, if not, swap + # in the new object we just created. If someone has preempted us, we + # take that object and discard ours. + # WARNING: We are relying on setdefault() being atomic. This is true + # in CPython but we haven't investigated others. This warning appears + # in several other locations in this file. + result = self._extended_message._fields.setdefault( + extension_handle, result) + + return result + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return False + + my_fields = self._extended_message.ListFields() + other_fields = other._extended_message.ListFields() + + # Get rid of non-extension fields. + my_fields = [field for field in my_fields if field.is_extension] + other_fields = [field for field in other_fields if field.is_extension] + + return my_fields == other_fields + + def __ne__(self, other): + return not self == other + + def __len__(self): + fields = self._extended_message.ListFields() + # Get rid of non-extension fields. + extension_fields = [field for field in fields if field[0].is_extension] + return len(extension_fields) + + def __hash__(self): + raise TypeError('unhashable object') + + # Note that this is only meaningful for non-repeated, scalar extension + # fields. Note also that we may have to call _Modified() when we do + # successfully set a field this way, to set any necessary "has" bits in the + # ancestors of the extended message. + def __setitem__(self, extension_handle, value): + """If extension_handle specifies a non-repeated, scalar extension + field, sets the value of that field. + """ + + _VerifyExtensionHandle(self._extended_message, extension_handle) + + if (extension_handle.label == FieldDescriptor.LABEL_REPEATED or + extension_handle.cpp_type == FieldDescriptor.CPPTYPE_MESSAGE): + raise TypeError( + 'Cannot assign to extension "%s" because it is a repeated or ' + 'composite type.' % extension_handle.full_name) + + # It's slightly wasteful to lookup the type checker each time, + # but we expect this to be a vanishingly uncommon case anyway. + type_checker = type_checkers.GetTypeChecker(extension_handle) + # pylint: disable=protected-access + self._extended_message._fields[extension_handle] = ( + type_checker.CheckValue(value)) + self._extended_message._Modified() + + def __delitem__(self, extension_handle): + self._extended_message.ClearExtension(extension_handle) + + def _FindExtensionByName(self, name): + """Tries to find a known extension with the specified name. + + Args: + name: Extension full name. + + Returns: + Extension field descriptor. + """ + descriptor = self._extended_message.DESCRIPTOR + extensions = descriptor.file.pool._extensions_by_name[descriptor] + return extensions.get(name, None) + + def _FindExtensionByNumber(self, number): + """Tries to find a known extension with the field number. + + Args: + number: Extension field number. + + Returns: + Extension field descriptor. + """ + descriptor = self._extended_message.DESCRIPTOR + extensions = descriptor.file.pool._extensions_by_number[descriptor] + return extensions.get(number, None) + + def __iter__(self): + # Return a generator over the populated extension fields + return (f[0] for f in self._extended_message.ListFields() + if f[0].is_extension) + + def __contains__(self, extension_handle): + _VerifyExtensionHandle(self._extended_message, extension_handle) + + if extension_handle not in self._extended_message._fields: + return False + + if extension_handle.label == FieldDescriptor.LABEL_REPEATED: + return bool(self._extended_message._fields.get(extension_handle)) + + if extension_handle.cpp_type == FieldDescriptor.CPPTYPE_MESSAGE: + value = self._extended_message._fields.get(extension_handle) + # pylint: disable=protected-access + return value is not None and value._is_present_in_parent + + return True diff --git a/google/protobuf/internal/field_mask.py b/google/protobuf/internal/field_mask.py new file mode 100644 index 0000000..ae34f08 --- /dev/null +++ b/google/protobuf/internal/field_mask.py @@ -0,0 +1,310 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Contains FieldMask class.""" + +from google.protobuf.descriptor import FieldDescriptor + + +class FieldMask(object): + """Class for FieldMask message type.""" + + __slots__ = () + + def ToJsonString(self): + """Converts FieldMask to string according to proto3 JSON spec.""" + camelcase_paths = [] + for path in self.paths: + camelcase_paths.append(_SnakeCaseToCamelCase(path)) + return ','.join(camelcase_paths) + + def FromJsonString(self, value): + """Converts string to FieldMask according to proto3 JSON spec.""" + if not isinstance(value, str): + raise ValueError('FieldMask JSON value not a string: {!r}'.format(value)) + self.Clear() + if value: + for path in value.split(','): + self.paths.append(_CamelCaseToSnakeCase(path)) + + def IsValidForDescriptor(self, message_descriptor): + """Checks whether the FieldMask is valid for Message Descriptor.""" + for path in self.paths: + if not _IsValidPath(message_descriptor, path): + return False + return True + + def AllFieldsFromDescriptor(self, message_descriptor): + """Gets all direct fields of Message Descriptor to FieldMask.""" + self.Clear() + for field in message_descriptor.fields: + self.paths.append(field.name) + + def CanonicalFormFromMask(self, mask): + """Converts a FieldMask to the canonical form. + + Removes paths that are covered by another path. For example, + "foo.bar" is covered by "foo" and will be removed if "foo" + is also in the FieldMask. Then sorts all paths in alphabetical order. + + Args: + mask: The original FieldMask to be converted. + """ + tree = _FieldMaskTree(mask) + tree.ToFieldMask(self) + + def Union(self, mask1, mask2): + """Merges mask1 and mask2 into this FieldMask.""" + _CheckFieldMaskMessage(mask1) + _CheckFieldMaskMessage(mask2) + tree = _FieldMaskTree(mask1) + tree.MergeFromFieldMask(mask2) + tree.ToFieldMask(self) + + def Intersect(self, mask1, mask2): + """Intersects mask1 and mask2 into this FieldMask.""" + _CheckFieldMaskMessage(mask1) + _CheckFieldMaskMessage(mask2) + tree = _FieldMaskTree(mask1) + intersection = _FieldMaskTree() + for path in mask2.paths: + tree.IntersectPath(path, intersection) + intersection.ToFieldMask(self) + + def MergeMessage( + self, source, destination, + replace_message_field=False, replace_repeated_field=False): + """Merges fields specified in FieldMask from source to destination. + + Args: + source: Source message. + destination: The destination message to be merged into. + replace_message_field: Replace message field if True. Merge message + field if False. + replace_repeated_field: Replace repeated field if True. Append + elements of repeated field if False. + """ + tree = _FieldMaskTree(self) + tree.MergeMessage( + source, destination, replace_message_field, replace_repeated_field) + + +def _IsValidPath(message_descriptor, path): + """Checks whether the path is valid for Message Descriptor.""" + parts = path.split('.') + last = parts.pop() + for name in parts: + field = message_descriptor.fields_by_name.get(name) + if (field is None or + field.label == FieldDescriptor.LABEL_REPEATED or + field.type != FieldDescriptor.TYPE_MESSAGE): + return False + message_descriptor = field.message_type + return last in message_descriptor.fields_by_name + + +def _CheckFieldMaskMessage(message): + """Raises ValueError if message is not a FieldMask.""" + message_descriptor = message.DESCRIPTOR + if (message_descriptor.name != 'FieldMask' or + message_descriptor.file.name != 'google/protobuf/field_mask.proto'): + raise ValueError('Message {0} is not a FieldMask.'.format( + message_descriptor.full_name)) + + +def _SnakeCaseToCamelCase(path_name): + """Converts a path name from snake_case to camelCase.""" + result = [] + after_underscore = False + for c in path_name: + if c.isupper(): + raise ValueError( + 'Fail to print FieldMask to Json string: Path name ' + '{0} must not contain uppercase letters.'.format(path_name)) + if after_underscore: + if c.islower(): + result.append(c.upper()) + after_underscore = False + else: + raise ValueError( + 'Fail to print FieldMask to Json string: The ' + 'character after a "_" must be a lowercase letter ' + 'in path name {0}.'.format(path_name)) + elif c == '_': + after_underscore = True + else: + result += c + + if after_underscore: + raise ValueError('Fail to print FieldMask to Json string: Trailing "_" ' + 'in path name {0}.'.format(path_name)) + return ''.join(result) + + +def _CamelCaseToSnakeCase(path_name): + """Converts a field name from camelCase to snake_case.""" + result = [] + for c in path_name: + if c == '_': + raise ValueError('Fail to parse FieldMask: Path name ' + '{0} must not contain "_"s.'.format(path_name)) + if c.isupper(): + result += '_' + result += c.lower() + else: + result += c + return ''.join(result) + + +class _FieldMaskTree(object): + """Represents a FieldMask in a tree structure. + + For example, given a FieldMask "foo.bar,foo.baz,bar.baz", + the FieldMaskTree will be: + [_root] -+- foo -+- bar + | | + | +- baz + | + +- bar --- baz + In the tree, each leaf node represents a field path. + """ + + __slots__ = ('_root',) + + def __init__(self, field_mask=None): + """Initializes the tree by FieldMask.""" + self._root = {} + if field_mask: + self.MergeFromFieldMask(field_mask) + + def MergeFromFieldMask(self, field_mask): + """Merges a FieldMask to the tree.""" + for path in field_mask.paths: + self.AddPath(path) + + def AddPath(self, path): + """Adds a field path into the tree. + + If the field path to add is a sub-path of an existing field path + in the tree (i.e., a leaf node), it means the tree already matches + the given path so nothing will be added to the tree. If the path + matches an existing non-leaf node in the tree, that non-leaf node + will be turned into a leaf node with all its children removed because + the path matches all the node's children. Otherwise, a new path will + be added. + + Args: + path: The field path to add. + """ + node = self._root + for name in path.split('.'): + if name not in node: + node[name] = {} + elif not node[name]: + # Pre-existing empty node implies we already have this entire tree. + return + node = node[name] + # Remove any sub-trees we might have had. + node.clear() + + def ToFieldMask(self, field_mask): + """Converts the tree to a FieldMask.""" + field_mask.Clear() + _AddFieldPaths(self._root, '', field_mask) + + def IntersectPath(self, path, intersection): + """Calculates the intersection part of a field path with this tree. + + Args: + path: The field path to calculates. + intersection: The out tree to record the intersection part. + """ + node = self._root + for name in path.split('.'): + if name not in node: + return + elif not node[name]: + intersection.AddPath(path) + return + node = node[name] + intersection.AddLeafNodes(path, node) + + def AddLeafNodes(self, prefix, node): + """Adds leaf nodes begin with prefix to this tree.""" + if not node: + self.AddPath(prefix) + for name in node: + child_path = prefix + '.' + name + self.AddLeafNodes(child_path, node[name]) + + def MergeMessage( + self, source, destination, + replace_message, replace_repeated): + """Merge all fields specified by this tree from source to destination.""" + _MergeMessage( + self._root, source, destination, replace_message, replace_repeated) + + +def _StrConvert(value): + """Converts value to str if it is not.""" + # This file is imported by c extension and some methods like ClearField + # requires string for the field name. py2/py3 has different text + # type and may use unicode. + if not isinstance(value, str): + return value.encode('utf-8') + return value + + +def _MergeMessage( + node, source, destination, replace_message, replace_repeated): + """Merge all fields specified by a sub-tree from source to destination.""" + source_descriptor = source.DESCRIPTOR + for name in node: + child = node[name] + field = source_descriptor.fields_by_name[name] + if field is None: + raise ValueError('Error: Can\'t find field {0} in message {1}.'.format( + name, source_descriptor.full_name)) + if child: + # Sub-paths are only allowed for singular message fields. + if (field.label == FieldDescriptor.LABEL_REPEATED or + field.cpp_type != FieldDescriptor.CPPTYPE_MESSAGE): + raise ValueError('Error: Field {0} in message {1} is not a singular ' + 'message field and cannot have sub-fields.'.format( + name, source_descriptor.full_name)) + if source.HasField(name): + _MergeMessage( + child, getattr(source, name), getattr(destination, name), + replace_message, replace_repeated) + continue + if field.label == FieldDescriptor.LABEL_REPEATED: + if replace_repeated: + destination.ClearField(_StrConvert(name)) + repeated_source = getattr(source, name) + repeated_destination = getattr(destination, name) + repeated_destination.MergeFrom(repeated_source) + else: + if field.cpp_type == FieldDescriptor.CPPTYPE_MESSAGE: + if replace_message: + destination.ClearField(_StrConvert(name)) + if source.HasField(name): + getattr(destination, name).MergeFrom(getattr(source, name)) + else: + setattr(destination, name, getattr(source, name)) + + +def _AddFieldPaths(node, prefix, field_mask): + """Adds the field paths descended from node to field_mask.""" + if not node and prefix: + field_mask.paths.append(prefix) + return + for name in sorted(node): + if prefix: + child_path = prefix + '.' + name + else: + child_path = name + _AddFieldPaths(node[name], child_path, field_mask) diff --git a/google/protobuf/internal/message_listener.py b/google/protobuf/internal/message_listener.py new file mode 100644 index 0000000..ff1c127 --- /dev/null +++ b/google/protobuf/internal/message_listener.py @@ -0,0 +1,55 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Defines a listener interface for observing certain +state transitions on Message objects. + +Also defines a null implementation of this interface. +""" + +__author__ = 'robinson@google.com (Will Robinson)' + + +class MessageListener(object): + + """Listens for modifications made to a message. Meant to be registered via + Message._SetListener(). + + Attributes: + dirty: If True, then calling Modified() would be a no-op. This can be + used to avoid these calls entirely in the common case. + """ + + def Modified(self): + """Called every time the message is modified in such a way that the parent + message may need to be updated. This currently means either: + (a) The message was modified for the first time, so the parent message + should henceforth mark the message as present. + (b) The message's cached byte size became dirty -- i.e. the message was + modified for the first time after a previous call to ByteSize(). + Therefore the parent should also mark its byte size as dirty. + Note that (a) implies (b), since new objects start out with a client cached + size (zero). However, we document (a) explicitly because it is important. + + Modified() will *only* be called in response to one of these two events -- + not every time the sub-message is modified. + + Note that if the listener's |dirty| attribute is true, then calling + Modified at the moment would be a no-op, so it can be skipped. Performance- + sensitive callers should check this attribute directly before calling since + it will be true most of the time. + """ + + raise NotImplementedError + + +class NullMessageListener(object): + + """No-op MessageListener implementation.""" + + def Modified(self): + pass diff --git a/google/protobuf/internal/python_edition_defaults.py b/google/protobuf/internal/python_edition_defaults.py new file mode 100644 index 0000000..57cb98a --- /dev/null +++ b/google/protobuf/internal/python_edition_defaults.py @@ -0,0 +1,5 @@ +""" +This file contains the serialized FeatureSetDefaults object corresponding to +the Pure Python runtime. This is used for feature resolution under Editions. +""" +_PROTOBUF_INTERNAL_PYTHON_EDITION_DEFAULTS = b"\n\023\030\346\007\"\000*\014\010\001\020\002\030\002 \003(\0010\002\n\023\030\347\007\"\000*\014\010\002\020\001\030\001 \002(\0010\001\n\023\030\350\007\"\014\010\001\020\001\030\001 \002(\0010\001*\000 \346\007(\350\007" diff --git a/google/protobuf/internal/python_message.py b/google/protobuf/internal/python_message.py new file mode 100644 index 0000000..34efbfe --- /dev/null +++ b/google/protobuf/internal/python_message.py @@ -0,0 +1,1508 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +# This code is meant to work on Python 2.4 and above only. +# +# TODO: Helpers for verbose, common checks like seeing if a +# descriptor's cpp_type is CPPTYPE_MESSAGE. + +"""Contains a metaclass and helper functions used to create +protocol message classes from Descriptor objects at runtime. + +Recall that a metaclass is the "type" of a class. +(A class is to a metaclass what an instance is to a class.) + +In this case, we use the GeneratedProtocolMessageType metaclass +to inject all the useful functionality into the classes +output by the protocol compiler at compile-time. + +The upshot of all this is that the real implementation +details for ALL pure-Python protocol buffers are *here in +this file*. +""" + +__author__ = 'robinson@google.com (Will Robinson)' + +from io import BytesIO +import struct +import sys +import warnings +import weakref + +from google.protobuf import descriptor as descriptor_mod +from google.protobuf import message as message_mod +from google.protobuf import text_format +# We use "as" to avoid name collisions with variables. +from google.protobuf.internal import api_implementation +from google.protobuf.internal import containers +from google.protobuf.internal import decoder +from google.protobuf.internal import encoder +from google.protobuf.internal import enum_type_wrapper +from google.protobuf.internal import extension_dict +from google.protobuf.internal import message_listener as message_listener_mod +from google.protobuf.internal import type_checkers +from google.protobuf.internal import well_known_types +from google.protobuf.internal import wire_format + +_FieldDescriptor = descriptor_mod.FieldDescriptor +_AnyFullTypeName = 'google.protobuf.Any' +_ExtensionDict = extension_dict._ExtensionDict + +class GeneratedProtocolMessageType(type): + + """Metaclass for protocol message classes created at runtime from Descriptors. + + We add implementations for all methods described in the Message class. We + also create properties to allow getting/setting all fields in the protocol + message. Finally, we create slots to prevent users from accidentally + "setting" nonexistent fields in the protocol message, which then wouldn't get + serialized / deserialized properly. + + The protocol compiler currently uses this metaclass to create protocol + message classes at runtime. Clients can also manually create their own + classes at runtime, as in this example: + + mydescriptor = Descriptor(.....) + factory = symbol_database.Default() + factory.pool.AddDescriptor(mydescriptor) + MyProtoClass = factory.GetPrototype(mydescriptor) + myproto_instance = MyProtoClass() + myproto.foo_field = 23 + ... + """ + + # Must be consistent with the protocol-compiler code in + # proto2/compiler/internal/generator.*. + _DESCRIPTOR_KEY = 'DESCRIPTOR' + + def __new__(cls, name, bases, dictionary): + """Custom allocation for runtime-generated class types. + + We override __new__ because this is apparently the only place + where we can meaningfully set __slots__ on the class we're creating(?). + (The interplay between metaclasses and slots is not very well-documented). + + Args: + name: Name of the class (ignored, but required by the + metaclass protocol). + bases: Base classes of the class we're constructing. + (Should be message.Message). We ignore this field, but + it's required by the metaclass protocol + dictionary: The class dictionary of the class we're + constructing. dictionary[_DESCRIPTOR_KEY] must contain + a Descriptor object describing this protocol message + type. + + Returns: + Newly-allocated class. + + Raises: + RuntimeError: Generated code only work with python cpp extension. + """ + descriptor = dictionary[GeneratedProtocolMessageType._DESCRIPTOR_KEY] + + if isinstance(descriptor, str): + raise RuntimeError('The generated code only work with python cpp ' + 'extension, but it is using pure python runtime.') + + # If a concrete class already exists for this descriptor, don't try to + # create another. Doing so will break any messages that already exist with + # the existing class. + # + # The C++ implementation appears to have its own internal `PyMessageFactory` + # to achieve similar results. + # + # This most commonly happens in `text_format.py` when using descriptors from + # a custom pool; it calls symbol_database.Global().getPrototype() on a + # descriptor which already has an existing concrete class. + new_class = getattr(descriptor, '_concrete_class', None) + if new_class: + return new_class + + if descriptor.full_name in well_known_types.WKTBASES: + bases += (well_known_types.WKTBASES[descriptor.full_name],) + _AddClassAttributesForNestedExtensions(descriptor, dictionary) + _AddSlots(descriptor, dictionary) + + superclass = super(GeneratedProtocolMessageType, cls) + new_class = superclass.__new__(cls, name, bases, dictionary) + return new_class + + def __init__(cls, name, bases, dictionary): + """Here we perform the majority of our work on the class. + We add enum getters, an __init__ method, implementations + of all Message methods, and properties for all fields + in the protocol type. + + Args: + name: Name of the class (ignored, but required by the + metaclass protocol). + bases: Base classes of the class we're constructing. + (Should be message.Message). We ignore this field, but + it's required by the metaclass protocol + dictionary: The class dictionary of the class we're + constructing. dictionary[_DESCRIPTOR_KEY] must contain + a Descriptor object describing this protocol message + type. + """ + descriptor = dictionary[GeneratedProtocolMessageType._DESCRIPTOR_KEY] + + # If this is an _existing_ class looked up via `_concrete_class` in the + # __new__ method above, then we don't need to re-initialize anything. + existing_class = getattr(descriptor, '_concrete_class', None) + if existing_class: + assert existing_class is cls, ( + 'Duplicate `GeneratedProtocolMessageType` created for descriptor %r' + % (descriptor.full_name)) + return + + cls._message_set_decoders_by_tag = {} + cls._fields_by_tag = {} + if (descriptor.has_options and + descriptor.GetOptions().message_set_wire_format): + cls._message_set_decoders_by_tag[decoder.MESSAGE_SET_ITEM_TAG] = ( + decoder.MessageSetItemDecoder(descriptor), + None, + ) + + # Attach stuff to each FieldDescriptor for quick lookup later on. + for field in descriptor.fields: + _AttachFieldHelpers(cls, field) + + if descriptor.is_extendable and hasattr(descriptor.file, 'pool'): + extensions = descriptor.file.pool.FindAllExtensions(descriptor) + for ext in extensions: + _AttachFieldHelpers(cls, ext) + + descriptor._concrete_class = cls # pylint: disable=protected-access + _AddEnumValues(descriptor, cls) + _AddInitMethod(descriptor, cls) + _AddPropertiesForFields(descriptor, cls) + _AddPropertiesForExtensions(descriptor, cls) + _AddStaticMethods(cls) + _AddMessageMethods(descriptor, cls) + _AddPrivateHelperMethods(descriptor, cls) + + superclass = super(GeneratedProtocolMessageType, cls) + superclass.__init__(name, bases, dictionary) + + +# Stateless helpers for GeneratedProtocolMessageType below. +# Outside clients should not access these directly. +# +# I opted not to make any of these methods on the metaclass, to make it more +# clear that I'm not really using any state there and to keep clients from +# thinking that they have direct access to these construction helpers. + + +def _PropertyName(proto_field_name): + """Returns the name of the public property attribute which + clients can use to get and (in some cases) set the value + of a protocol message field. + + Args: + proto_field_name: The protocol message field name, exactly + as it appears (or would appear) in a .proto file. + """ + # TODO: Escape Python keywords (e.g., yield), and test this support. + # nnorwitz makes my day by writing: + # """ + # FYI. See the keyword module in the stdlib. This could be as simple as: + # + # if keyword.iskeyword(proto_field_name): + # return proto_field_name + "_" + # return proto_field_name + # """ + # Kenton says: The above is a BAD IDEA. People rely on being able to use + # getattr() and setattr() to reflectively manipulate field values. If we + # rename the properties, then every such user has to also make sure to apply + # the same transformation. Note that currently if you name a field "yield", + # you can still access it just fine using getattr/setattr -- it's not even + # that cumbersome to do so. + # TODO: Remove this method entirely if/when everyone agrees with my + # position. + return proto_field_name + + +def _AddSlots(message_descriptor, dictionary): + """Adds a __slots__ entry to dictionary, containing the names of all valid + attributes for this message type. + + Args: + message_descriptor: A Descriptor instance describing this message type. + dictionary: Class dictionary to which we'll add a '__slots__' entry. + """ + dictionary['__slots__'] = ['_cached_byte_size', + '_cached_byte_size_dirty', + '_fields', + '_unknown_fields', + '_is_present_in_parent', + '_listener', + '_listener_for_children', + '__weakref__', + '_oneofs'] + + +def _IsMessageSetExtension(field): + return (field.is_extension and + field.containing_type.has_options and + field.containing_type.GetOptions().message_set_wire_format and + field.type == _FieldDescriptor.TYPE_MESSAGE and + field.label == _FieldDescriptor.LABEL_OPTIONAL) + + +def _IsMapField(field): + return (field.type == _FieldDescriptor.TYPE_MESSAGE and + field.message_type._is_map_entry) + + +def _IsMessageMapField(field): + value_type = field.message_type.fields_by_name['value'] + return value_type.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE + +def _AttachFieldHelpers(cls, field_descriptor): + is_repeated = field_descriptor.label == _FieldDescriptor.LABEL_REPEATED + field_descriptor._default_constructor = _DefaultValueConstructorForField( + field_descriptor + ) + + def AddFieldByTag(wiretype, is_packed): + tag_bytes = encoder.TagBytes(field_descriptor.number, wiretype) + cls._fields_by_tag[tag_bytes] = (field_descriptor, is_packed) + + AddFieldByTag( + type_checkers.FIELD_TYPE_TO_WIRE_TYPE[field_descriptor.type], False + ) + + if is_repeated and wire_format.IsTypePackable(field_descriptor.type): + # To support wire compatibility of adding packed = true, add a decoder for + # packed values regardless of the field's options. + AddFieldByTag(wire_format.WIRETYPE_LENGTH_DELIMITED, True) + + +def _MaybeAddEncoder(cls, field_descriptor): + if hasattr(field_descriptor, '_encoder'): + return + is_repeated = (field_descriptor.label == _FieldDescriptor.LABEL_REPEATED) + is_map_entry = _IsMapField(field_descriptor) + is_packed = field_descriptor.is_packed + + if is_map_entry: + field_encoder = encoder.MapEncoder(field_descriptor) + sizer = encoder.MapSizer(field_descriptor, + _IsMessageMapField(field_descriptor)) + elif _IsMessageSetExtension(field_descriptor): + field_encoder = encoder.MessageSetItemEncoder(field_descriptor.number) + sizer = encoder.MessageSetItemSizer(field_descriptor.number) + else: + field_encoder = type_checkers.TYPE_TO_ENCODER[field_descriptor.type]( + field_descriptor.number, is_repeated, is_packed) + sizer = type_checkers.TYPE_TO_SIZER[field_descriptor.type]( + field_descriptor.number, is_repeated, is_packed) + + field_descriptor._sizer = sizer + field_descriptor._encoder = field_encoder + + +def _MaybeAddDecoder(cls, field_descriptor): + if hasattr(field_descriptor, '_decoders'): + return + + is_repeated = field_descriptor.label == _FieldDescriptor.LABEL_REPEATED + is_map_entry = _IsMapField(field_descriptor) + helper_decoders = {} + + def AddDecoder(is_packed): + decode_type = field_descriptor.type + if (decode_type == _FieldDescriptor.TYPE_ENUM and + not field_descriptor.enum_type.is_closed): + decode_type = _FieldDescriptor.TYPE_INT32 + + oneof_descriptor = None + if field_descriptor.containing_oneof is not None: + oneof_descriptor = field_descriptor + + if is_map_entry: + is_message_map = _IsMessageMapField(field_descriptor) + + field_decoder = decoder.MapDecoder( + field_descriptor, _GetInitializeDefaultForMap(field_descriptor), + is_message_map) + elif decode_type == _FieldDescriptor.TYPE_STRING: + field_decoder = decoder.StringDecoder( + field_descriptor.number, is_repeated, is_packed, + field_descriptor, field_descriptor._default_constructor, + not field_descriptor.has_presence) + elif field_descriptor.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE: + field_decoder = type_checkers.TYPE_TO_DECODER[decode_type]( + field_descriptor.number, is_repeated, is_packed, + field_descriptor, field_descriptor._default_constructor) + else: + field_decoder = type_checkers.TYPE_TO_DECODER[decode_type]( + field_descriptor.number, is_repeated, is_packed, + # pylint: disable=protected-access + field_descriptor, field_descriptor._default_constructor, + not field_descriptor.has_presence) + + helper_decoders[is_packed] = field_decoder + + AddDecoder(False) + + if is_repeated and wire_format.IsTypePackable(field_descriptor.type): + # To support wire compatibility of adding packed = true, add a decoder for + # packed values regardless of the field's options. + AddDecoder(True) + + field_descriptor._decoders = helper_decoders + + +def _AddClassAttributesForNestedExtensions(descriptor, dictionary): + extensions = descriptor.extensions_by_name + for extension_name, extension_field in extensions.items(): + assert extension_name not in dictionary + dictionary[extension_name] = extension_field + + +def _AddEnumValues(descriptor, cls): + """Sets class-level attributes for all enum fields defined in this message. + + Also exporting a class-level object that can name enum values. + + Args: + descriptor: Descriptor object for this message type. + cls: Class we're constructing for this message type. + """ + for enum_type in descriptor.enum_types: + setattr(cls, enum_type.name, enum_type_wrapper.EnumTypeWrapper(enum_type)) + for enum_value in enum_type.values: + setattr(cls, enum_value.name, enum_value.number) + + +def _GetInitializeDefaultForMap(field): + if field.label != _FieldDescriptor.LABEL_REPEATED: + raise ValueError('map_entry set on non-repeated field %s' % ( + field.name)) + fields_by_name = field.message_type.fields_by_name + key_checker = type_checkers.GetTypeChecker(fields_by_name['key']) + + value_field = fields_by_name['value'] + if _IsMessageMapField(field): + def MakeMessageMapDefault(message): + return containers.MessageMap( + message._listener_for_children, value_field.message_type, key_checker, + field.message_type) + return MakeMessageMapDefault + else: + value_checker = type_checkers.GetTypeChecker(value_field) + def MakePrimitiveMapDefault(message): + return containers.ScalarMap( + message._listener_for_children, key_checker, value_checker, + field.message_type) + return MakePrimitiveMapDefault + +def _DefaultValueConstructorForField(field): + """Returns a function which returns a default value for a field. + + Args: + field: FieldDescriptor object for this field. + + The returned function has one argument: + message: Message instance containing this field, or a weakref proxy + of same. + + That function in turn returns a default value for this field. The default + value may refer back to |message| via a weak reference. + """ + + if _IsMapField(field): + return _GetInitializeDefaultForMap(field) + + if field.label == _FieldDescriptor.LABEL_REPEATED: + if field.has_default_value and field.default_value != []: + raise ValueError('Repeated field default value not empty list: %s' % ( + field.default_value)) + if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE: + # We can't look at _concrete_class yet since it might not have + # been set. (Depends on order in which we initialize the classes). + message_type = field.message_type + def MakeRepeatedMessageDefault(message): + return containers.RepeatedCompositeFieldContainer( + message._listener_for_children, field.message_type) + return MakeRepeatedMessageDefault + else: + type_checker = type_checkers.GetTypeChecker(field) + def MakeRepeatedScalarDefault(message): + return containers.RepeatedScalarFieldContainer( + message._listener_for_children, type_checker) + return MakeRepeatedScalarDefault + + if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE: + message_type = field.message_type + def MakeSubMessageDefault(message): + # _concrete_class may not yet be initialized. + if not hasattr(message_type, '_concrete_class'): + from google.protobuf import message_factory + message_factory.GetMessageClass(message_type) + result = message_type._concrete_class() + result._SetListener( + _OneofListener(message, field) + if field.containing_oneof is not None + else message._listener_for_children) + return result + return MakeSubMessageDefault + + def MakeScalarDefault(message): + # TODO: This may be broken since there may not be + # default_value. Combine with has_default_value somehow. + return field.default_value + return MakeScalarDefault + + +def _ReraiseTypeErrorWithFieldName(message_name, field_name): + """Re-raise the currently-handled TypeError with the field name added.""" + exc = sys.exc_info()[1] + if len(exc.args) == 1 and type(exc) is TypeError: + # simple TypeError; add field name to exception message + exc = TypeError('%s for field %s.%s' % (str(exc), message_name, field_name)) + + # re-raise possibly-amended exception with original traceback: + raise exc.with_traceback(sys.exc_info()[2]) + + +def _AddInitMethod(message_descriptor, cls): + """Adds an __init__ method to cls.""" + + def _GetIntegerEnumValue(enum_type, value): + """Convert a string or integer enum value to an integer. + + If the value is a string, it is converted to the enum value in + enum_type with the same name. If the value is not a string, it's + returned as-is. (No conversion or bounds-checking is done.) + """ + if isinstance(value, str): + try: + return enum_type.values_by_name[value].number + except KeyError: + raise ValueError('Enum type %s: unknown label "%s"' % ( + enum_type.full_name, value)) + return value + + def init(self, **kwargs): + self._cached_byte_size = 0 + self._cached_byte_size_dirty = len(kwargs) > 0 + self._fields = {} + # Contains a mapping from oneof field descriptors to the descriptor + # of the currently set field in that oneof field. + self._oneofs = {} + + # _unknown_fields is () when empty for efficiency, and will be turned into + # a list if fields are added. + self._unknown_fields = () + self._is_present_in_parent = False + self._listener = message_listener_mod.NullMessageListener() + self._listener_for_children = _Listener(self) + for field_name, field_value in kwargs.items(): + field = _GetFieldByName(message_descriptor, field_name) + if field is None: + raise TypeError('%s() got an unexpected keyword argument "%s"' % + (message_descriptor.name, field_name)) + if field_value is None: + # field=None is the same as no field at all. + continue + if field.label == _FieldDescriptor.LABEL_REPEATED: + copy = field._default_constructor(self) + if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE: # Composite + if _IsMapField(field): + if _IsMessageMapField(field): + for key in field_value: + copy[key].MergeFrom(field_value[key]) + else: + copy.update(field_value) + else: + for val in field_value: + if isinstance(val, dict): + copy.add(**val) + else: + copy.add().MergeFrom(val) + else: # Scalar + if field.cpp_type == _FieldDescriptor.CPPTYPE_ENUM: + field_value = [_GetIntegerEnumValue(field.enum_type, val) + for val in field_value] + copy.extend(field_value) + self._fields[field] = copy + elif field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE: + copy = field._default_constructor(self) + new_val = field_value + if isinstance(field_value, dict): + new_val = field.message_type._concrete_class(**field_value) + try: + copy.MergeFrom(new_val) + except TypeError: + _ReraiseTypeErrorWithFieldName(message_descriptor.name, field_name) + self._fields[field] = copy + else: + if field.cpp_type == _FieldDescriptor.CPPTYPE_ENUM: + field_value = _GetIntegerEnumValue(field.enum_type, field_value) + try: + setattr(self, field_name, field_value) + except TypeError: + _ReraiseTypeErrorWithFieldName(message_descriptor.name, field_name) + + init.__module__ = None + init.__doc__ = None + cls.__init__ = init + + +def _GetFieldByName(message_descriptor, field_name): + """Returns a field descriptor by field name. + + Args: + message_descriptor: A Descriptor describing all fields in message. + field_name: The name of the field to retrieve. + Returns: + The field descriptor associated with the field name. + """ + try: + return message_descriptor.fields_by_name[field_name] + except KeyError: + raise ValueError('Protocol message %s has no "%s" field.' % + (message_descriptor.name, field_name)) + + +def _AddPropertiesForFields(descriptor, cls): + """Adds properties for all fields in this protocol message type.""" + for field in descriptor.fields: + _AddPropertiesForField(field, cls) + + if descriptor.is_extendable: + # _ExtensionDict is just an adaptor with no state so we allocate a new one + # every time it is accessed. + cls.Extensions = property(lambda self: _ExtensionDict(self)) + + +def _AddPropertiesForField(field, cls): + """Adds a public property for a protocol message field. + Clients can use this property to get and (in the case + of non-repeated scalar fields) directly set the value + of a protocol message field. + + Args: + field: A FieldDescriptor for this field. + cls: The class we're constructing. + """ + # Catch it if we add other types that we should + # handle specially here. + assert _FieldDescriptor.MAX_CPPTYPE == 10 + + constant_name = field.name.upper() + '_FIELD_NUMBER' + setattr(cls, constant_name, field.number) + + if field.label == _FieldDescriptor.LABEL_REPEATED: + _AddPropertiesForRepeatedField(field, cls) + elif field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE: + _AddPropertiesForNonRepeatedCompositeField(field, cls) + else: + _AddPropertiesForNonRepeatedScalarField(field, cls) + + +class _FieldProperty(property): + __slots__ = ('DESCRIPTOR',) + + def __init__(self, descriptor, getter, setter, doc): + property.__init__(self, getter, setter, doc=doc) + self.DESCRIPTOR = descriptor + + +def _AddPropertiesForRepeatedField(field, cls): + """Adds a public property for a "repeated" protocol message field. Clients + can use this property to get the value of the field, which will be either a + RepeatedScalarFieldContainer or RepeatedCompositeFieldContainer (see + below). + + Note that when clients add values to these containers, we perform + type-checking in the case of repeated scalar fields, and we also set any + necessary "has" bits as a side-effect. + + Args: + field: A FieldDescriptor for this field. + cls: The class we're constructing. + """ + proto_field_name = field.name + property_name = _PropertyName(proto_field_name) + + def getter(self): + field_value = self._fields.get(field) + if field_value is None: + # Construct a new object to represent this field. + field_value = field._default_constructor(self) + + # Atomically check if another thread has preempted us and, if not, swap + # in the new object we just created. If someone has preempted us, we + # take that object and discard ours. + # WARNING: We are relying on setdefault() being atomic. This is true + # in CPython but we haven't investigated others. This warning appears + # in several other locations in this file. + field_value = self._fields.setdefault(field, field_value) + return field_value + getter.__module__ = None + getter.__doc__ = 'Getter for %s.' % proto_field_name + + # We define a setter just so we can throw an exception with a more + # helpful error message. + def setter(self, new_value): + raise AttributeError('Assignment not allowed to repeated field ' + '"%s" in protocol message object.' % proto_field_name) + + doc = 'Magic attribute generated for "%s" proto field.' % proto_field_name + setattr(cls, property_name, _FieldProperty(field, getter, setter, doc=doc)) + + +def _AddPropertiesForNonRepeatedScalarField(field, cls): + """Adds a public property for a nonrepeated, scalar protocol message field. + Clients can use this property to get and directly set the value of the field. + Note that when the client sets the value of a field by using this property, + all necessary "has" bits are set as a side-effect, and we also perform + type-checking. + + Args: + field: A FieldDescriptor for this field. + cls: The class we're constructing. + """ + proto_field_name = field.name + property_name = _PropertyName(proto_field_name) + type_checker = type_checkers.GetTypeChecker(field) + default_value = field.default_value + + def getter(self): + # TODO: This may be broken since there may not be + # default_value. Combine with has_default_value somehow. + return self._fields.get(field, default_value) + getter.__module__ = None + getter.__doc__ = 'Getter for %s.' % proto_field_name + + def field_setter(self, new_value): + # pylint: disable=protected-access + # Testing the value for truthiness captures all of the proto3 defaults + # (0, 0.0, enum 0, and False). + try: + new_value = type_checker.CheckValue(new_value) + except TypeError as e: + raise TypeError( + 'Cannot set %s to %.1024r: %s' % (field.full_name, new_value, e)) + if not field.has_presence and not new_value: + self._fields.pop(field, None) + else: + self._fields[field] = new_value + # Check _cached_byte_size_dirty inline to improve performance, since scalar + # setters are called frequently. + if not self._cached_byte_size_dirty: + self._Modified() + + if field.containing_oneof: + def setter(self, new_value): + field_setter(self, new_value) + self._UpdateOneofState(field) + else: + setter = field_setter + + setter.__module__ = None + setter.__doc__ = 'Setter for %s.' % proto_field_name + + # Add a property to encapsulate the getter/setter. + doc = 'Magic attribute generated for "%s" proto field.' % proto_field_name + setattr(cls, property_name, _FieldProperty(field, getter, setter, doc=doc)) + + +def _AddPropertiesForNonRepeatedCompositeField(field, cls): + """Adds a public property for a nonrepeated, composite protocol message field. + A composite field is a "group" or "message" field. + + Clients can use this property to get the value of the field, but cannot + assign to the property directly. + + Args: + field: A FieldDescriptor for this field. + cls: The class we're constructing. + """ + # TODO: Remove duplication with similar method + # for non-repeated scalars. + proto_field_name = field.name + property_name = _PropertyName(proto_field_name) + + def getter(self): + field_value = self._fields.get(field) + if field_value is None: + # Construct a new object to represent this field. + field_value = field._default_constructor(self) + + # Atomically check if another thread has preempted us and, if not, swap + # in the new object we just created. If someone has preempted us, we + # take that object and discard ours. + # WARNING: We are relying on setdefault() being atomic. This is true + # in CPython but we haven't investigated others. This warning appears + # in several other locations in this file. + field_value = self._fields.setdefault(field, field_value) + return field_value + getter.__module__ = None + getter.__doc__ = 'Getter for %s.' % proto_field_name + + # We define a setter just so we can throw an exception with a more + # helpful error message. + def setter(self, new_value): + raise AttributeError('Assignment not allowed to composite field ' + '"%s" in protocol message object.' % proto_field_name) + + # Add a property to encapsulate the getter. + doc = 'Magic attribute generated for "%s" proto field.' % proto_field_name + setattr(cls, property_name, _FieldProperty(field, getter, setter, doc=doc)) + + +def _AddPropertiesForExtensions(descriptor, cls): + """Adds properties for all fields in this protocol message type.""" + extensions = descriptor.extensions_by_name + for extension_name, extension_field in extensions.items(): + constant_name = extension_name.upper() + '_FIELD_NUMBER' + setattr(cls, constant_name, extension_field.number) + + # TODO: Migrate all users of these attributes to functions like + # pool.FindExtensionByNumber(descriptor). + if descriptor.file is not None: + # TODO: Use cls.MESSAGE_FACTORY.pool when available. + pool = descriptor.file.pool + +def _AddStaticMethods(cls): + def FromString(s): + message = cls() + message.MergeFromString(s) + return message + cls.FromString = staticmethod(FromString) + + +def _IsPresent(item): + """Given a (FieldDescriptor, value) tuple from _fields, return true if the + value should be included in the list returned by ListFields().""" + + if item[0].label == _FieldDescriptor.LABEL_REPEATED: + return bool(item[1]) + elif item[0].cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE: + return item[1]._is_present_in_parent + else: + return True + + +def _AddListFieldsMethod(message_descriptor, cls): + """Helper for _AddMessageMethods().""" + + def ListFields(self): + all_fields = [item for item in self._fields.items() if _IsPresent(item)] + all_fields.sort(key = lambda item: item[0].number) + return all_fields + + cls.ListFields = ListFields + + +def _AddHasFieldMethod(message_descriptor, cls): + """Helper for _AddMessageMethods().""" + + hassable_fields = {} + for field in message_descriptor.fields: + if field.label == _FieldDescriptor.LABEL_REPEATED: + continue + # For proto3, only submessages and fields inside a oneof have presence. + if not field.has_presence: + continue + hassable_fields[field.name] = field + + # Has methods are supported for oneof descriptors. + for oneof in message_descriptor.oneofs: + hassable_fields[oneof.name] = oneof + + def HasField(self, field_name): + try: + field = hassable_fields[field_name] + except KeyError as exc: + raise ValueError('Protocol message %s has no non-repeated field "%s" ' + 'nor has presence is not available for this field.' % ( + message_descriptor.full_name, field_name)) from exc + + if isinstance(field, descriptor_mod.OneofDescriptor): + try: + return HasField(self, self._oneofs[field].name) + except KeyError: + return False + else: + if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE: + value = self._fields.get(field) + return value is not None and value._is_present_in_parent + else: + return field in self._fields + + cls.HasField = HasField + + +def _AddClearFieldMethod(message_descriptor, cls): + """Helper for _AddMessageMethods().""" + def ClearField(self, field_name): + try: + field = message_descriptor.fields_by_name[field_name] + except KeyError: + try: + field = message_descriptor.oneofs_by_name[field_name] + if field in self._oneofs: + field = self._oneofs[field] + else: + return + except KeyError: + raise ValueError('Protocol message %s has no "%s" field.' % + (message_descriptor.name, field_name)) + + if field in self._fields: + # To match the C++ implementation, we need to invalidate iterators + # for map fields when ClearField() happens. + if hasattr(self._fields[field], 'InvalidateIterators'): + self._fields[field].InvalidateIterators() + + # Note: If the field is a sub-message, its listener will still point + # at us. That's fine, because the worst than can happen is that it + # will call _Modified() and invalidate our byte size. Big deal. + del self._fields[field] + + if self._oneofs.get(field.containing_oneof, None) is field: + del self._oneofs[field.containing_oneof] + + # Always call _Modified() -- even if nothing was changed, this is + # a mutating method, and thus calling it should cause the field to become + # present in the parent message. + self._Modified() + + cls.ClearField = ClearField + + +def _AddClearExtensionMethod(cls): + """Helper for _AddMessageMethods().""" + def ClearExtension(self, field_descriptor): + extension_dict._VerifyExtensionHandle(self, field_descriptor) + + # Similar to ClearField(), above. + if field_descriptor in self._fields: + del self._fields[field_descriptor] + self._Modified() + cls.ClearExtension = ClearExtension + + +def _AddHasExtensionMethod(cls): + """Helper for _AddMessageMethods().""" + def HasExtension(self, field_descriptor): + extension_dict._VerifyExtensionHandle(self, field_descriptor) + if field_descriptor.label == _FieldDescriptor.LABEL_REPEATED: + raise KeyError('"%s" is repeated.' % field_descriptor.full_name) + + if field_descriptor.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE: + value = self._fields.get(field_descriptor) + return value is not None and value._is_present_in_parent + else: + return field_descriptor in self._fields + cls.HasExtension = HasExtension + +def _InternalUnpackAny(msg): + """Unpacks Any message and returns the unpacked message. + + This internal method is different from public Any Unpack method which takes + the target message as argument. _InternalUnpackAny method does not have + target message type and need to find the message type in descriptor pool. + + Args: + msg: An Any message to be unpacked. + + Returns: + The unpacked message. + """ + # TODO: Don't use the factory of generated messages. + # To make Any work with custom factories, use the message factory of the + # parent message. + # pylint: disable=g-import-not-at-top + from google.protobuf import symbol_database + factory = symbol_database.Default() + + type_url = msg.type_url + + if not type_url: + return None + + # TODO: For now we just strip the hostname. Better logic will be + # required. + type_name = type_url.split('/')[-1] + descriptor = factory.pool.FindMessageTypeByName(type_name) + + if descriptor is None: + return None + + message_class = factory.GetPrototype(descriptor) + message = message_class() + + message.ParseFromString(msg.value) + return message + + +def _AddEqualsMethod(message_descriptor, cls): + """Helper for _AddMessageMethods().""" + def __eq__(self, other): + if (not isinstance(other, message_mod.Message) or + other.DESCRIPTOR != self.DESCRIPTOR): + return NotImplemented + + if self is other: + return True + + if self.DESCRIPTOR.full_name == _AnyFullTypeName: + any_a = _InternalUnpackAny(self) + any_b = _InternalUnpackAny(other) + if any_a and any_b: + return any_a == any_b + + if not self.ListFields() == other.ListFields(): + return False + + # TODO: Fix UnknownFieldSet to consider MessageSet extensions, + # then use it for the comparison. + unknown_fields = list(self._unknown_fields) + unknown_fields.sort() + other_unknown_fields = list(other._unknown_fields) + other_unknown_fields.sort() + return unknown_fields == other_unknown_fields + + cls.__eq__ = __eq__ + + +def _AddStrMethod(message_descriptor, cls): + """Helper for _AddMessageMethods().""" + def __str__(self): + return text_format.MessageToString(self) + cls.__str__ = __str__ + + +def _AddReprMethod(message_descriptor, cls): + """Helper for _AddMessageMethods().""" + def __repr__(self): + return text_format.MessageToString(self) + cls.__repr__ = __repr__ + + +def _AddUnicodeMethod(unused_message_descriptor, cls): + """Helper for _AddMessageMethods().""" + + def __unicode__(self): + return text_format.MessageToString(self, as_utf8=True).decode('utf-8') + cls.__unicode__ = __unicode__ + + +def _BytesForNonRepeatedElement(value, field_number, field_type): + """Returns the number of bytes needed to serialize a non-repeated element. + The returned byte count includes space for tag information and any + other additional space associated with serializing value. + + Args: + value: Value we're serializing. + field_number: Field number of this value. (Since the field number + is stored as part of a varint-encoded tag, this has an impact + on the total bytes required to serialize the value). + field_type: The type of the field. One of the TYPE_* constants + within FieldDescriptor. + """ + try: + fn = type_checkers.TYPE_TO_BYTE_SIZE_FN[field_type] + return fn(field_number, value) + except KeyError: + raise message_mod.EncodeError('Unrecognized field type: %d' % field_type) + + +def _AddByteSizeMethod(message_descriptor, cls): + """Helper for _AddMessageMethods().""" + + def ByteSize(self): + if not self._cached_byte_size_dirty: + return self._cached_byte_size + + size = 0 + descriptor = self.DESCRIPTOR + if descriptor._is_map_entry: + # Fields of map entry should always be serialized. + key_field = descriptor.fields_by_name['key'] + _MaybeAddEncoder(cls, key_field) + size = key_field._sizer(self.key) + value_field = descriptor.fields_by_name['value'] + _MaybeAddEncoder(cls, value_field) + size += value_field._sizer(self.value) + else: + for field_descriptor, field_value in self.ListFields(): + _MaybeAddEncoder(cls, field_descriptor) + size += field_descriptor._sizer(field_value) + for tag_bytes, value_bytes in self._unknown_fields: + size += len(tag_bytes) + len(value_bytes) + + self._cached_byte_size = size + self._cached_byte_size_dirty = False + self._listener_for_children.dirty = False + return size + + cls.ByteSize = ByteSize + + +def _AddSerializeToStringMethod(message_descriptor, cls): + """Helper for _AddMessageMethods().""" + + def SerializeToString(self, **kwargs): + # Check if the message has all of its required fields set. + if not self.IsInitialized(): + raise message_mod.EncodeError( + 'Message %s is missing required fields: %s' % ( + self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors()))) + return self.SerializePartialToString(**kwargs) + cls.SerializeToString = SerializeToString + + +def _AddSerializePartialToStringMethod(message_descriptor, cls): + """Helper for _AddMessageMethods().""" + + def SerializePartialToString(self, **kwargs): + out = BytesIO() + self._InternalSerialize(out.write, **kwargs) + return out.getvalue() + cls.SerializePartialToString = SerializePartialToString + + def InternalSerialize(self, write_bytes, deterministic=None): + if deterministic is None: + deterministic = ( + api_implementation.IsPythonDefaultSerializationDeterministic()) + else: + deterministic = bool(deterministic) + + descriptor = self.DESCRIPTOR + if descriptor._is_map_entry: + # Fields of map entry should always be serialized. + key_field = descriptor.fields_by_name['key'] + _MaybeAddEncoder(cls, key_field) + key_field._encoder(write_bytes, self.key, deterministic) + value_field = descriptor.fields_by_name['value'] + _MaybeAddEncoder(cls, value_field) + value_field._encoder(write_bytes, self.value, deterministic) + else: + for field_descriptor, field_value in self.ListFields(): + _MaybeAddEncoder(cls, field_descriptor) + field_descriptor._encoder(write_bytes, field_value, deterministic) + for tag_bytes, value_bytes in self._unknown_fields: + write_bytes(tag_bytes) + write_bytes(value_bytes) + cls._InternalSerialize = InternalSerialize + + +def _AddMergeFromStringMethod(message_descriptor, cls): + """Helper for _AddMessageMethods().""" + def MergeFromString(self, serialized): + serialized = memoryview(serialized) + length = len(serialized) + try: + if self._InternalParse(serialized, 0, length) != length: + # The only reason _InternalParse would return early is if it + # encountered an end-group tag. + raise message_mod.DecodeError('Unexpected end-group tag.') + except (IndexError, TypeError): + # Now ord(buf[p:p+1]) == ord('') gets TypeError. + raise message_mod.DecodeError('Truncated message.') + except struct.error as e: + raise message_mod.DecodeError(e) + return length # Return this for legacy reasons. + cls.MergeFromString = MergeFromString + + local_ReadTag = decoder.ReadTag + local_SkipField = decoder.SkipField + fields_by_tag = cls._fields_by_tag + message_set_decoders_by_tag = cls._message_set_decoders_by_tag + + def InternalParse(self, buffer, pos, end): + """Create a message from serialized bytes. + + Args: + self: Message, instance of the proto message object. + buffer: memoryview of the serialized data. + pos: int, position to start in the serialized data. + end: int, end position of the serialized data. + + Returns: + Message object. + """ + # Guard against internal misuse, since this function is called internally + # quite extensively, and its easy to accidentally pass bytes. + assert isinstance(buffer, memoryview) + self._Modified() + field_dict = self._fields + while pos != end: + (tag_bytes, new_pos) = local_ReadTag(buffer, pos) + field_decoder, field_des = message_set_decoders_by_tag.get( + tag_bytes, (None, None) + ) + if field_decoder: + pos = field_decoder(buffer, new_pos, end, self, field_dict) + continue + field_des, is_packed = fields_by_tag.get(tag_bytes, (None, None)) + if field_des is None: + if not self._unknown_fields: # pylint: disable=protected-access + self._unknown_fields = [] # pylint: disable=protected-access + # pylint: disable=protected-access + (tag, _) = decoder._DecodeVarint(tag_bytes, 0) + field_number, wire_type = wire_format.UnpackTag(tag) + if field_number == 0: + raise message_mod.DecodeError('Field number 0 is illegal.') + # TODO: remove old_pos. + old_pos = new_pos + (data, new_pos) = decoder._DecodeUnknownField( + buffer, new_pos, wire_type) # pylint: disable=protected-access + if new_pos == -1: + return pos + # TODO: remove _unknown_fields. + new_pos = local_SkipField(buffer, old_pos, end, tag_bytes) + if new_pos == -1: + return pos + self._unknown_fields.append( + (tag_bytes, buffer[old_pos:new_pos].tobytes())) + pos = new_pos + else: + _MaybeAddDecoder(cls, field_des) + field_decoder = field_des._decoders[is_packed] + pos = field_decoder(buffer, new_pos, end, self, field_dict) + if field_des.containing_oneof: + self._UpdateOneofState(field_des) + return pos + cls._InternalParse = InternalParse + + +def _AddIsInitializedMethod(message_descriptor, cls): + """Adds the IsInitialized and FindInitializationError methods to the + protocol message class.""" + + required_fields = [field for field in message_descriptor.fields + if field.label == _FieldDescriptor.LABEL_REQUIRED] + + def IsInitialized(self, errors=None): + """Checks if all required fields of a message are set. + + Args: + errors: A list which, if provided, will be populated with the field + paths of all missing required fields. + + Returns: + True iff the specified message has all required fields set. + """ + + # Performance is critical so we avoid HasField() and ListFields(). + + for field in required_fields: + if (field not in self._fields or + (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and + not self._fields[field]._is_present_in_parent)): + if errors is not None: + errors.extend(self.FindInitializationErrors()) + return False + + for field, value in list(self._fields.items()): # dict can change size! + if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE: + if field.label == _FieldDescriptor.LABEL_REPEATED: + if (field.message_type._is_map_entry): + continue + for element in value: + if not element.IsInitialized(): + if errors is not None: + errors.extend(self.FindInitializationErrors()) + return False + elif value._is_present_in_parent and not value.IsInitialized(): + if errors is not None: + errors.extend(self.FindInitializationErrors()) + return False + + return True + + cls.IsInitialized = IsInitialized + + def FindInitializationErrors(self): + """Finds required fields which are not initialized. + + Returns: + A list of strings. Each string is a path to an uninitialized field from + the top-level message, e.g. "foo.bar[5].baz". + """ + + errors = [] # simplify things + + for field in required_fields: + if not self.HasField(field.name): + errors.append(field.name) + + for field, value in self.ListFields(): + if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE: + if field.is_extension: + name = '(%s)' % field.full_name + else: + name = field.name + + if _IsMapField(field): + if _IsMessageMapField(field): + for key in value: + element = value[key] + prefix = '%s[%s].' % (name, key) + sub_errors = element.FindInitializationErrors() + errors += [prefix + error for error in sub_errors] + else: + # ScalarMaps can't have any initialization errors. + pass + elif field.label == _FieldDescriptor.LABEL_REPEATED: + for i in range(len(value)): + element = value[i] + prefix = '%s[%d].' % (name, i) + sub_errors = element.FindInitializationErrors() + errors += [prefix + error for error in sub_errors] + else: + prefix = name + '.' + sub_errors = value.FindInitializationErrors() + errors += [prefix + error for error in sub_errors] + + return errors + + cls.FindInitializationErrors = FindInitializationErrors + + +def _FullyQualifiedClassName(klass): + module = klass.__module__ + name = getattr(klass, '__qualname__', klass.__name__) + if module in (None, 'builtins', '__builtin__'): + return name + return module + '.' + name + + +def _AddMergeFromMethod(cls): + LABEL_REPEATED = _FieldDescriptor.LABEL_REPEATED + CPPTYPE_MESSAGE = _FieldDescriptor.CPPTYPE_MESSAGE + + def MergeFrom(self, msg): + if not isinstance(msg, cls): + raise TypeError( + 'Parameter to MergeFrom() must be instance of same class: ' + 'expected %s got %s.' % (_FullyQualifiedClassName(cls), + _FullyQualifiedClassName(msg.__class__))) + + assert msg is not self + self._Modified() + + fields = self._fields + + for field, value in msg._fields.items(): + if field.label == LABEL_REPEATED: + field_value = fields.get(field) + if field_value is None: + # Construct a new object to represent this field. + field_value = field._default_constructor(self) + fields[field] = field_value + field_value.MergeFrom(value) + elif field.cpp_type == CPPTYPE_MESSAGE: + if value._is_present_in_parent: + field_value = fields.get(field) + if field_value is None: + # Construct a new object to represent this field. + field_value = field._default_constructor(self) + fields[field] = field_value + field_value.MergeFrom(value) + else: + self._fields[field] = value + if field.containing_oneof: + self._UpdateOneofState(field) + + if msg._unknown_fields: + if not self._unknown_fields: + self._unknown_fields = [] + self._unknown_fields.extend(msg._unknown_fields) + + cls.MergeFrom = MergeFrom + + +def _AddWhichOneofMethod(message_descriptor, cls): + def WhichOneof(self, oneof_name): + """Returns the name of the currently set field inside a oneof, or None.""" + try: + field = message_descriptor.oneofs_by_name[oneof_name] + except KeyError: + raise ValueError( + 'Protocol message has no oneof "%s" field.' % oneof_name) + + nested_field = self._oneofs.get(field, None) + if nested_field is not None and self.HasField(nested_field.name): + return nested_field.name + else: + return None + + cls.WhichOneof = WhichOneof + + +def _Clear(self): + # Clear fields. + self._fields = {} + self._unknown_fields = () + + self._oneofs = {} + self._Modified() + + +def _UnknownFields(self): + raise NotImplementedError('Please use the add-on feaure ' + 'unknown_fields.UnknownFieldSet(message) in ' + 'unknown_fields.py instead.') + + +def _DiscardUnknownFields(self): + self._unknown_fields = [] + for field, value in self.ListFields(): + if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE: + if _IsMapField(field): + if _IsMessageMapField(field): + for key in value: + value[key].DiscardUnknownFields() + elif field.label == _FieldDescriptor.LABEL_REPEATED: + for sub_message in value: + sub_message.DiscardUnknownFields() + else: + value.DiscardUnknownFields() + + +def _SetListener(self, listener): + if listener is None: + self._listener = message_listener_mod.NullMessageListener() + else: + self._listener = listener + + +def _AddMessageMethods(message_descriptor, cls): + """Adds implementations of all Message methods to cls.""" + _AddListFieldsMethod(message_descriptor, cls) + _AddHasFieldMethod(message_descriptor, cls) + _AddClearFieldMethod(message_descriptor, cls) + if message_descriptor.is_extendable: + _AddClearExtensionMethod(cls) + _AddHasExtensionMethod(cls) + _AddEqualsMethod(message_descriptor, cls) + _AddStrMethod(message_descriptor, cls) + _AddReprMethod(message_descriptor, cls) + _AddUnicodeMethod(message_descriptor, cls) + _AddByteSizeMethod(message_descriptor, cls) + _AddSerializeToStringMethod(message_descriptor, cls) + _AddSerializePartialToStringMethod(message_descriptor, cls) + _AddMergeFromStringMethod(message_descriptor, cls) + _AddIsInitializedMethod(message_descriptor, cls) + _AddMergeFromMethod(cls) + _AddWhichOneofMethod(message_descriptor, cls) + # Adds methods which do not depend on cls. + cls.Clear = _Clear + cls.DiscardUnknownFields = _DiscardUnknownFields + cls._SetListener = _SetListener + + +def _AddPrivateHelperMethods(message_descriptor, cls): + """Adds implementation of private helper methods to cls.""" + + def Modified(self): + """Sets the _cached_byte_size_dirty bit to true, + and propagates this to our listener iff this was a state change. + """ + + # Note: Some callers check _cached_byte_size_dirty before calling + # _Modified() as an extra optimization. So, if this method is ever + # changed such that it does stuff even when _cached_byte_size_dirty is + # already true, the callers need to be updated. + if not self._cached_byte_size_dirty: + self._cached_byte_size_dirty = True + self._listener_for_children.dirty = True + self._is_present_in_parent = True + self._listener.Modified() + + def _UpdateOneofState(self, field): + """Sets field as the active field in its containing oneof. + + Will also delete currently active field in the oneof, if it is different + from the argument. Does not mark the message as modified. + """ + other_field = self._oneofs.setdefault(field.containing_oneof, field) + if other_field is not field: + del self._fields[other_field] + self._oneofs[field.containing_oneof] = field + + cls._Modified = Modified + cls.SetInParent = Modified + cls._UpdateOneofState = _UpdateOneofState + + +class _Listener(object): + + """MessageListener implementation that a parent message registers with its + child message. + + In order to support semantics like: + + foo.bar.baz.moo = 23 + assert foo.HasField('bar') + + ...child objects must have back references to their parents. + This helper class is at the heart of this support. + """ + + def __init__(self, parent_message): + """Args: + parent_message: The message whose _Modified() method we should call when + we receive Modified() messages. + """ + # This listener establishes a back reference from a child (contained) object + # to its parent (containing) object. We make this a weak reference to avoid + # creating cyclic garbage when the client finishes with the 'parent' object + # in the tree. + if isinstance(parent_message, weakref.ProxyType): + self._parent_message_weakref = parent_message + else: + self._parent_message_weakref = weakref.proxy(parent_message) + + # As an optimization, we also indicate directly on the listener whether + # or not the parent message is dirty. This way we can avoid traversing + # up the tree in the common case. + self.dirty = False + + def Modified(self): + if self.dirty: + return + try: + # Propagate the signal to our parents iff this is the first field set. + self._parent_message_weakref._Modified() + except ReferenceError: + # We can get here if a client has kept a reference to a child object, + # and is now setting a field on it, but the child's parent has been + # garbage-collected. This is not an error. + pass + + +class _OneofListener(_Listener): + """Special listener implementation for setting composite oneof fields.""" + + def __init__(self, parent_message, field): + """Args: + parent_message: The message whose _Modified() method we should call when + we receive Modified() messages. + field: The descriptor of the field being set in the parent message. + """ + super(_OneofListener, self).__init__(parent_message) + self._field = field + + def Modified(self): + """Also updates the state of the containing oneof in the parent message.""" + try: + self._parent_message_weakref._UpdateOneofState(self._field) + super(_OneofListener, self).Modified() + except ReferenceError: + pass diff --git a/google/protobuf/internal/testing_refleaks.py b/google/protobuf/internal/testing_refleaks.py new file mode 100644 index 0000000..ca0f0b9 --- /dev/null +++ b/google/protobuf/internal/testing_refleaks.py @@ -0,0 +1,119 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""A subclass of unittest.TestCase which checks for reference leaks. + +To use: +- Use testing_refleak.BaseTestCase instead of unittest.TestCase +- Configure and compile Python with --with-pydebug + +If sys.gettotalrefcount() is not available (because Python was built without +the Py_DEBUG option), then this module is a no-op and tests will run normally. +""" + +import copyreg +import gc +import sys +import unittest + + +class LocalTestResult(unittest.TestResult): + """A TestResult which forwards events to a parent object, except for Skips.""" + + def __init__(self, parent_result): + unittest.TestResult.__init__(self) + self.parent_result = parent_result + + def addError(self, test, error): + self.parent_result.addError(test, error) + + def addFailure(self, test, error): + self.parent_result.addFailure(test, error) + + def addSkip(self, test, reason): + pass + + +class ReferenceLeakCheckerMixin(object): + """A mixin class for TestCase, which checks reference counts.""" + + NB_RUNS = 3 + + def run(self, result=None): + testMethod = getattr(self, self._testMethodName) + expecting_failure_method = getattr(testMethod, "__unittest_expecting_failure__", False) + expecting_failure_class = getattr(self, "__unittest_expecting_failure__", False) + if expecting_failure_class or expecting_failure_method: + return + + # python_message.py registers all Message classes to some pickle global + # registry, which makes the classes immortal. + # We save a copy of this registry, and reset it before we could references. + self._saved_pickle_registry = copyreg.dispatch_table.copy() + + # Run the test twice, to warm up the instance attributes. + super(ReferenceLeakCheckerMixin, self).run(result=result) + super(ReferenceLeakCheckerMixin, self).run(result=result) + + oldrefcount = 0 + local_result = LocalTestResult(result) + num_flakes = 0 + + refcount_deltas = [] + while len(refcount_deltas) < self.NB_RUNS: + oldrefcount = self._getRefcounts() + super(ReferenceLeakCheckerMixin, self).run(result=local_result) + newrefcount = self._getRefcounts() + # If the GC was able to collect some objects after the call to run() that + # it could not collect before the call, then the counts won't match. + if newrefcount < oldrefcount and num_flakes < 2: + # This result is (probably) a flake -- garbage collectors aren't very + # predictable, but a lower ending refcount is the opposite of the + # failure we are testing for. If the result is repeatable, then we will + # eventually report it, but not after trying to eliminate it. + num_flakes += 1 + continue + num_flakes = 0 + refcount_deltas.append(newrefcount - oldrefcount) + print(refcount_deltas, self) + + try: + self.assertEqual(refcount_deltas, [0] * self.NB_RUNS) + except Exception: # pylint: disable=broad-except + result.addError(self, sys.exc_info()) + + def _getRefcounts(self): + copyreg.dispatch_table.clear() + copyreg.dispatch_table.update(self._saved_pickle_registry) + # It is sometimes necessary to gc.collect() multiple times, to ensure + # that all objects can be collected. + gc.collect() + gc.collect() + gc.collect() + return sys.gettotalrefcount() + + +if hasattr(sys, 'gettotalrefcount'): + + def TestCase(test_class): + new_bases = (ReferenceLeakCheckerMixin,) + test_class.__bases__ + new_class = type(test_class)( + test_class.__name__, new_bases, dict(test_class.__dict__)) + return new_class + SkipReferenceLeakChecker = unittest.skip + +else: + # When PyDEBUG is not enabled, run the tests normally. + + def TestCase(test_class): + return test_class + + def SkipReferenceLeakChecker(reason): + del reason # Don't skip, so don't need a reason. + def Same(func): + return func + return Same diff --git a/google/protobuf/internal/type_checkers.py b/google/protobuf/internal/type_checkers.py new file mode 100644 index 0000000..e152a43 --- /dev/null +++ b/google/protobuf/internal/type_checkers.py @@ -0,0 +1,408 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Provides type checking routines. + +This module defines type checking utilities in the forms of dictionaries: + +VALUE_CHECKERS: A dictionary of field types and a value validation object. +TYPE_TO_BYTE_SIZE_FN: A dictionary with field types and a size computing + function. +TYPE_TO_SERIALIZE_METHOD: A dictionary with field types and serialization + function. +FIELD_TYPE_TO_WIRE_TYPE: A dictionary with field typed and their + corresponding wire types. +TYPE_TO_DESERIALIZE_METHOD: A dictionary with field types and deserialization + function. +""" + +__author__ = 'robinson@google.com (Will Robinson)' + +import ctypes +import numbers + +from google.protobuf.internal import decoder +from google.protobuf.internal import encoder +from google.protobuf.internal import wire_format +from google.protobuf import descriptor + +_FieldDescriptor = descriptor.FieldDescriptor + + +def TruncateToFourByteFloat(original): + return ctypes.c_float(original).value + + +def ToShortestFloat(original): + """Returns the shortest float that has same value in wire.""" + # All 4 byte floats have between 6 and 9 significant digits, so we + # start with 6 as the lower bound. + # It has to be iterative because use '.9g' directly can not get rid + # of the noises for most values. For example if set a float_field=0.9 + # use '.9g' will print 0.899999976. + precision = 6 + rounded = float('{0:.{1}g}'.format(original, precision)) + while TruncateToFourByteFloat(rounded) != original: + precision += 1 + rounded = float('{0:.{1}g}'.format(original, precision)) + return rounded + + +def GetTypeChecker(field): + """Returns a type checker for a message field of the specified types. + + Args: + field: FieldDescriptor object for this field. + + Returns: + An instance of TypeChecker which can be used to verify the types + of values assigned to a field of the specified type. + """ + if (field.cpp_type == _FieldDescriptor.CPPTYPE_STRING and + field.type == _FieldDescriptor.TYPE_STRING): + return UnicodeValueChecker() + if field.cpp_type == _FieldDescriptor.CPPTYPE_ENUM: + if field.enum_type.is_closed: + return EnumValueChecker(field.enum_type) + else: + # When open enums are supported, any int32 can be assigned. + return _VALUE_CHECKERS[_FieldDescriptor.CPPTYPE_INT32] + return _VALUE_CHECKERS[field.cpp_type] + + +# None of the typecheckers below make any attempt to guard against people +# subclassing builtin types and doing weird things. We're not trying to +# protect against malicious clients here, just people accidentally shooting +# themselves in the foot in obvious ways. +class TypeChecker(object): + + """Type checker used to catch type errors as early as possible + when the client is setting scalar fields in protocol messages. + """ + + def __init__(self, *acceptable_types): + self._acceptable_types = acceptable_types + + def CheckValue(self, proposed_value): + """Type check the provided value and return it. + + The returned value might have been normalized to another type. + """ + if not isinstance(proposed_value, self._acceptable_types): + message = ('%.1024r has type %s, but expected one of: %s' % + (proposed_value, type(proposed_value), self._acceptable_types)) + raise TypeError(message) + return proposed_value + + +class TypeCheckerWithDefault(TypeChecker): + + def __init__(self, default_value, *acceptable_types): + TypeChecker.__init__(self, *acceptable_types) + self._default_value = default_value + + def DefaultValue(self): + return self._default_value + + +class BoolValueChecker(object): + """Type checker used for bool fields.""" + + def CheckValue(self, proposed_value): + if not hasattr(proposed_value, '__index__') or ( + type(proposed_value).__module__ == 'numpy' and + type(proposed_value).__name__ == 'ndarray'): + message = ('%.1024r has type %s, but expected one of: %s' % + (proposed_value, type(proposed_value), (bool, int))) + raise TypeError(message) + return bool(proposed_value) + + def DefaultValue(self): + return False + + +# IntValueChecker and its subclasses perform integer type-checks +# and bounds-checks. +class IntValueChecker(object): + + """Checker used for integer fields. Performs type-check and range check.""" + + def CheckValue(self, proposed_value): + if not hasattr(proposed_value, '__index__') or ( + type(proposed_value).__module__ == 'numpy' and + type(proposed_value).__name__ == 'ndarray'): + message = ('%.1024r has type %s, but expected one of: %s' % + (proposed_value, type(proposed_value), (int,))) + raise TypeError(message) + + if not self._MIN <= int(proposed_value) <= self._MAX: + raise ValueError('Value out of range: %d' % proposed_value) + # We force all values to int to make alternate implementations where the + # distinction is more significant (e.g. the C++ implementation) simpler. + proposed_value = int(proposed_value) + return proposed_value + + def DefaultValue(self): + return 0 + + +class EnumValueChecker(object): + + """Checker used for enum fields. Performs type-check and range check.""" + + def __init__(self, enum_type): + self._enum_type = enum_type + + def CheckValue(self, proposed_value): + if not isinstance(proposed_value, numbers.Integral): + message = ('%.1024r has type %s, but expected one of: %s' % + (proposed_value, type(proposed_value), (int,))) + raise TypeError(message) + if int(proposed_value) not in self._enum_type.values_by_number: + raise ValueError('Unknown enum value: %d' % proposed_value) + return proposed_value + + def DefaultValue(self): + return self._enum_type.values[0].number + + +class UnicodeValueChecker(object): + + """Checker used for string fields. + + Always returns a unicode value, even if the input is of type str. + """ + + def CheckValue(self, proposed_value): + if not isinstance(proposed_value, (bytes, str)): + message = ('%.1024r has type %s, but expected one of: %s' % + (proposed_value, type(proposed_value), (bytes, str))) + raise TypeError(message) + + # If the value is of type 'bytes' make sure that it is valid UTF-8 data. + if isinstance(proposed_value, bytes): + try: + proposed_value = proposed_value.decode('utf-8') + except UnicodeDecodeError: + raise ValueError('%.1024r has type bytes, but isn\'t valid UTF-8 ' + 'encoding. Non-UTF-8 strings must be converted to ' + 'unicode objects before being added.' % + (proposed_value)) + else: + try: + proposed_value.encode('utf8') + except UnicodeEncodeError: + raise ValueError('%.1024r isn\'t a valid unicode string and ' + 'can\'t be encoded in UTF-8.'% + (proposed_value)) + + return proposed_value + + def DefaultValue(self): + return u"" + + +class Int32ValueChecker(IntValueChecker): + # We're sure to use ints instead of longs here since comparison may be more + # efficient. + _MIN = -2147483648 + _MAX = 2147483647 + + +class Uint32ValueChecker(IntValueChecker): + _MIN = 0 + _MAX = (1 << 32) - 1 + + +class Int64ValueChecker(IntValueChecker): + _MIN = -(1 << 63) + _MAX = (1 << 63) - 1 + + +class Uint64ValueChecker(IntValueChecker): + _MIN = 0 + _MAX = (1 << 64) - 1 + + +# The max 4 bytes float is about 3.4028234663852886e+38 +_FLOAT_MAX = float.fromhex('0x1.fffffep+127') +_FLOAT_MIN = -_FLOAT_MAX +_INF = float('inf') +_NEG_INF = float('-inf') + + +class DoubleValueChecker(object): + """Checker used for double fields. + + Performs type-check and range check. + """ + + def CheckValue(self, proposed_value): + """Check and convert proposed_value to float.""" + if (not hasattr(proposed_value, '__float__') and + not hasattr(proposed_value, '__index__')) or ( + type(proposed_value).__module__ == 'numpy' and + type(proposed_value).__name__ == 'ndarray'): + message = ('%.1024r has type %s, but expected one of: int, float' % + (proposed_value, type(proposed_value))) + raise TypeError(message) + return float(proposed_value) + + def DefaultValue(self): + return 0.0 + + +class FloatValueChecker(DoubleValueChecker): + """Checker used for float fields. + + Performs type-check and range check. + + Values exceeding a 32-bit float will be converted to inf/-inf. + """ + + def CheckValue(self, proposed_value): + """Check and convert proposed_value to float.""" + converted_value = super().CheckValue(proposed_value) + # This inf rounding matches the C++ proto SafeDoubleToFloat logic. + if converted_value > _FLOAT_MAX: + return _INF + if converted_value < _FLOAT_MIN: + return _NEG_INF + + return TruncateToFourByteFloat(converted_value) + +# Type-checkers for all scalar CPPTYPEs. +_VALUE_CHECKERS = { + _FieldDescriptor.CPPTYPE_INT32: Int32ValueChecker(), + _FieldDescriptor.CPPTYPE_INT64: Int64ValueChecker(), + _FieldDescriptor.CPPTYPE_UINT32: Uint32ValueChecker(), + _FieldDescriptor.CPPTYPE_UINT64: Uint64ValueChecker(), + _FieldDescriptor.CPPTYPE_DOUBLE: DoubleValueChecker(), + _FieldDescriptor.CPPTYPE_FLOAT: FloatValueChecker(), + _FieldDescriptor.CPPTYPE_BOOL: BoolValueChecker(), + _FieldDescriptor.CPPTYPE_STRING: TypeCheckerWithDefault(b'', bytes), +} + + +# Map from field type to a function F, such that F(field_num, value) +# gives the total byte size for a value of the given type. This +# byte size includes tag information and any other additional space +# associated with serializing "value". +TYPE_TO_BYTE_SIZE_FN = { + _FieldDescriptor.TYPE_DOUBLE: wire_format.DoubleByteSize, + _FieldDescriptor.TYPE_FLOAT: wire_format.FloatByteSize, + _FieldDescriptor.TYPE_INT64: wire_format.Int64ByteSize, + _FieldDescriptor.TYPE_UINT64: wire_format.UInt64ByteSize, + _FieldDescriptor.TYPE_INT32: wire_format.Int32ByteSize, + _FieldDescriptor.TYPE_FIXED64: wire_format.Fixed64ByteSize, + _FieldDescriptor.TYPE_FIXED32: wire_format.Fixed32ByteSize, + _FieldDescriptor.TYPE_BOOL: wire_format.BoolByteSize, + _FieldDescriptor.TYPE_STRING: wire_format.StringByteSize, + _FieldDescriptor.TYPE_GROUP: wire_format.GroupByteSize, + _FieldDescriptor.TYPE_MESSAGE: wire_format.MessageByteSize, + _FieldDescriptor.TYPE_BYTES: wire_format.BytesByteSize, + _FieldDescriptor.TYPE_UINT32: wire_format.UInt32ByteSize, + _FieldDescriptor.TYPE_ENUM: wire_format.EnumByteSize, + _FieldDescriptor.TYPE_SFIXED32: wire_format.SFixed32ByteSize, + _FieldDescriptor.TYPE_SFIXED64: wire_format.SFixed64ByteSize, + _FieldDescriptor.TYPE_SINT32: wire_format.SInt32ByteSize, + _FieldDescriptor.TYPE_SINT64: wire_format.SInt64ByteSize + } + + +# Maps from field types to encoder constructors. +TYPE_TO_ENCODER = { + _FieldDescriptor.TYPE_DOUBLE: encoder.DoubleEncoder, + _FieldDescriptor.TYPE_FLOAT: encoder.FloatEncoder, + _FieldDescriptor.TYPE_INT64: encoder.Int64Encoder, + _FieldDescriptor.TYPE_UINT64: encoder.UInt64Encoder, + _FieldDescriptor.TYPE_INT32: encoder.Int32Encoder, + _FieldDescriptor.TYPE_FIXED64: encoder.Fixed64Encoder, + _FieldDescriptor.TYPE_FIXED32: encoder.Fixed32Encoder, + _FieldDescriptor.TYPE_BOOL: encoder.BoolEncoder, + _FieldDescriptor.TYPE_STRING: encoder.StringEncoder, + _FieldDescriptor.TYPE_GROUP: encoder.GroupEncoder, + _FieldDescriptor.TYPE_MESSAGE: encoder.MessageEncoder, + _FieldDescriptor.TYPE_BYTES: encoder.BytesEncoder, + _FieldDescriptor.TYPE_UINT32: encoder.UInt32Encoder, + _FieldDescriptor.TYPE_ENUM: encoder.EnumEncoder, + _FieldDescriptor.TYPE_SFIXED32: encoder.SFixed32Encoder, + _FieldDescriptor.TYPE_SFIXED64: encoder.SFixed64Encoder, + _FieldDescriptor.TYPE_SINT32: encoder.SInt32Encoder, + _FieldDescriptor.TYPE_SINT64: encoder.SInt64Encoder, + } + + +# Maps from field types to sizer constructors. +TYPE_TO_SIZER = { + _FieldDescriptor.TYPE_DOUBLE: encoder.DoubleSizer, + _FieldDescriptor.TYPE_FLOAT: encoder.FloatSizer, + _FieldDescriptor.TYPE_INT64: encoder.Int64Sizer, + _FieldDescriptor.TYPE_UINT64: encoder.UInt64Sizer, + _FieldDescriptor.TYPE_INT32: encoder.Int32Sizer, + _FieldDescriptor.TYPE_FIXED64: encoder.Fixed64Sizer, + _FieldDescriptor.TYPE_FIXED32: encoder.Fixed32Sizer, + _FieldDescriptor.TYPE_BOOL: encoder.BoolSizer, + _FieldDescriptor.TYPE_STRING: encoder.StringSizer, + _FieldDescriptor.TYPE_GROUP: encoder.GroupSizer, + _FieldDescriptor.TYPE_MESSAGE: encoder.MessageSizer, + _FieldDescriptor.TYPE_BYTES: encoder.BytesSizer, + _FieldDescriptor.TYPE_UINT32: encoder.UInt32Sizer, + _FieldDescriptor.TYPE_ENUM: encoder.EnumSizer, + _FieldDescriptor.TYPE_SFIXED32: encoder.SFixed32Sizer, + _FieldDescriptor.TYPE_SFIXED64: encoder.SFixed64Sizer, + _FieldDescriptor.TYPE_SINT32: encoder.SInt32Sizer, + _FieldDescriptor.TYPE_SINT64: encoder.SInt64Sizer, + } + + +# Maps from field type to a decoder constructor. +TYPE_TO_DECODER = { + _FieldDescriptor.TYPE_DOUBLE: decoder.DoubleDecoder, + _FieldDescriptor.TYPE_FLOAT: decoder.FloatDecoder, + _FieldDescriptor.TYPE_INT64: decoder.Int64Decoder, + _FieldDescriptor.TYPE_UINT64: decoder.UInt64Decoder, + _FieldDescriptor.TYPE_INT32: decoder.Int32Decoder, + _FieldDescriptor.TYPE_FIXED64: decoder.Fixed64Decoder, + _FieldDescriptor.TYPE_FIXED32: decoder.Fixed32Decoder, + _FieldDescriptor.TYPE_BOOL: decoder.BoolDecoder, + _FieldDescriptor.TYPE_STRING: decoder.StringDecoder, + _FieldDescriptor.TYPE_GROUP: decoder.GroupDecoder, + _FieldDescriptor.TYPE_MESSAGE: decoder.MessageDecoder, + _FieldDescriptor.TYPE_BYTES: decoder.BytesDecoder, + _FieldDescriptor.TYPE_UINT32: decoder.UInt32Decoder, + _FieldDescriptor.TYPE_ENUM: decoder.EnumDecoder, + _FieldDescriptor.TYPE_SFIXED32: decoder.SFixed32Decoder, + _FieldDescriptor.TYPE_SFIXED64: decoder.SFixed64Decoder, + _FieldDescriptor.TYPE_SINT32: decoder.SInt32Decoder, + _FieldDescriptor.TYPE_SINT64: decoder.SInt64Decoder, + } + +# Maps from field type to expected wiretype. +FIELD_TYPE_TO_WIRE_TYPE = { + _FieldDescriptor.TYPE_DOUBLE: wire_format.WIRETYPE_FIXED64, + _FieldDescriptor.TYPE_FLOAT: wire_format.WIRETYPE_FIXED32, + _FieldDescriptor.TYPE_INT64: wire_format.WIRETYPE_VARINT, + _FieldDescriptor.TYPE_UINT64: wire_format.WIRETYPE_VARINT, + _FieldDescriptor.TYPE_INT32: wire_format.WIRETYPE_VARINT, + _FieldDescriptor.TYPE_FIXED64: wire_format.WIRETYPE_FIXED64, + _FieldDescriptor.TYPE_FIXED32: wire_format.WIRETYPE_FIXED32, + _FieldDescriptor.TYPE_BOOL: wire_format.WIRETYPE_VARINT, + _FieldDescriptor.TYPE_STRING: + wire_format.WIRETYPE_LENGTH_DELIMITED, + _FieldDescriptor.TYPE_GROUP: wire_format.WIRETYPE_START_GROUP, + _FieldDescriptor.TYPE_MESSAGE: + wire_format.WIRETYPE_LENGTH_DELIMITED, + _FieldDescriptor.TYPE_BYTES: + wire_format.WIRETYPE_LENGTH_DELIMITED, + _FieldDescriptor.TYPE_UINT32: wire_format.WIRETYPE_VARINT, + _FieldDescriptor.TYPE_ENUM: wire_format.WIRETYPE_VARINT, + _FieldDescriptor.TYPE_SFIXED32: wire_format.WIRETYPE_FIXED32, + _FieldDescriptor.TYPE_SFIXED64: wire_format.WIRETYPE_FIXED64, + _FieldDescriptor.TYPE_SINT32: wire_format.WIRETYPE_VARINT, + _FieldDescriptor.TYPE_SINT64: wire_format.WIRETYPE_VARINT, + } diff --git a/google/protobuf/internal/well_known_types.py b/google/protobuf/internal/well_known_types.py new file mode 100644 index 0000000..8a0a3b1 --- /dev/null +++ b/google/protobuf/internal/well_known_types.py @@ -0,0 +1,600 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Contains well known classes. + +This files defines well known classes which need extra maintenance including: + - Any + - Duration + - FieldMask + - Struct + - Timestamp +""" + +__author__ = 'jieluo@google.com (Jie Luo)' + +import calendar +import collections.abc +import datetime +import warnings + +from google.protobuf.internal import field_mask + +FieldMask = field_mask.FieldMask + +_TIMESTAMPFOMAT = '%Y-%m-%dT%H:%M:%S' +_NANOS_PER_SECOND = 1000000000 +_NANOS_PER_MILLISECOND = 1000000 +_NANOS_PER_MICROSECOND = 1000 +_MILLIS_PER_SECOND = 1000 +_MICROS_PER_SECOND = 1000000 +_SECONDS_PER_DAY = 24 * 3600 +_DURATION_SECONDS_MAX = 315576000000 +_TIMESTAMP_SECONDS_MIN = -62135596800 +_TIMESTAMP_SECONDS_MAX = 253402300799 + +_EPOCH_DATETIME_NAIVE = datetime.datetime(1970, 1, 1, tzinfo=None) +_EPOCH_DATETIME_AWARE = _EPOCH_DATETIME_NAIVE.replace( + tzinfo=datetime.timezone.utc +) + + +class Any(object): + """Class for Any Message type.""" + + __slots__ = () + + def Pack(self, msg, type_url_prefix='type.googleapis.com/', + deterministic=None): + """Packs the specified message into current Any message.""" + if len(type_url_prefix) < 1 or type_url_prefix[-1] != '/': + self.type_url = '%s/%s' % (type_url_prefix, msg.DESCRIPTOR.full_name) + else: + self.type_url = '%s%s' % (type_url_prefix, msg.DESCRIPTOR.full_name) + self.value = msg.SerializeToString(deterministic=deterministic) + + def Unpack(self, msg): + """Unpacks the current Any message into specified message.""" + descriptor = msg.DESCRIPTOR + if not self.Is(descriptor): + return False + msg.ParseFromString(self.value) + return True + + def TypeName(self): + """Returns the protobuf type name of the inner message.""" + # Only last part is to be used: b/25630112 + return self.type_url.split('/')[-1] + + def Is(self, descriptor): + """Checks if this Any represents the given protobuf type.""" + return '/' in self.type_url and self.TypeName() == descriptor.full_name + + +class Timestamp(object): + """Class for Timestamp message type.""" + + __slots__ = () + + def ToJsonString(self): + """Converts Timestamp to RFC 3339 date string format. + + Returns: + A string converted from timestamp. The string is always Z-normalized + and uses 3, 6 or 9 fractional digits as required to represent the + exact time. Example of the return format: '1972-01-01T10:00:20.021Z' + """ + _CheckTimestampValid(self.seconds, self.nanos) + nanos = self.nanos + seconds = self.seconds % _SECONDS_PER_DAY + days = (self.seconds - seconds) // _SECONDS_PER_DAY + dt = datetime.datetime(1970, 1, 1) + datetime.timedelta(days, seconds) + + result = dt.isoformat() + if (nanos % 1e9) == 0: + # If there are 0 fractional digits, the fractional + # point '.' should be omitted when serializing. + return result + 'Z' + if (nanos % 1e6) == 0: + # Serialize 3 fractional digits. + return result + '.%03dZ' % (nanos / 1e6) + if (nanos % 1e3) == 0: + # Serialize 6 fractional digits. + return result + '.%06dZ' % (nanos / 1e3) + # Serialize 9 fractional digits. + return result + '.%09dZ' % nanos + + def FromJsonString(self, value): + """Parse a RFC 3339 date string format to Timestamp. + + Args: + value: A date string. Any fractional digits (or none) and any offset are + accepted as long as they fit into nano-seconds precision. + Example of accepted format: '1972-01-01T10:00:20.021-05:00' + + Raises: + ValueError: On parsing problems. + """ + if not isinstance(value, str): + raise ValueError('Timestamp JSON value not a string: {!r}'.format(value)) + timezone_offset = value.find('Z') + if timezone_offset == -1: + timezone_offset = value.find('+') + if timezone_offset == -1: + timezone_offset = value.rfind('-') + if timezone_offset == -1: + raise ValueError( + 'Failed to parse timestamp: missing valid timezone offset.') + time_value = value[0:timezone_offset] + # Parse datetime and nanos. + point_position = time_value.find('.') + if point_position == -1: + second_value = time_value + nano_value = '' + else: + second_value = time_value[:point_position] + nano_value = time_value[point_position + 1:] + if 't' in second_value: + raise ValueError( + 'time data \'{0}\' does not match format \'%Y-%m-%dT%H:%M:%S\', ' + 'lowercase \'t\' is not accepted'.format(second_value)) + date_object = datetime.datetime.strptime(second_value, _TIMESTAMPFOMAT) + td = date_object - datetime.datetime(1970, 1, 1) + seconds = td.seconds + td.days * _SECONDS_PER_DAY + if len(nano_value) > 9: + raise ValueError( + 'Failed to parse Timestamp: nanos {0} more than ' + '9 fractional digits.'.format(nano_value)) + if nano_value: + nanos = round(float('0.' + nano_value) * 1e9) + else: + nanos = 0 + # Parse timezone offsets. + if value[timezone_offset] == 'Z': + if len(value) != timezone_offset + 1: + raise ValueError('Failed to parse timestamp: invalid trailing' + ' data {0}.'.format(value)) + else: + timezone = value[timezone_offset:] + pos = timezone.find(':') + if pos == -1: + raise ValueError( + 'Invalid timezone offset value: {0}.'.format(timezone)) + if timezone[0] == '+': + seconds -= (int(timezone[1:pos])*60+int(timezone[pos+1:]))*60 + else: + seconds += (int(timezone[1:pos])*60+int(timezone[pos+1:]))*60 + # Set seconds and nanos + _CheckTimestampValid(seconds, nanos) + self.seconds = int(seconds) + self.nanos = int(nanos) + + def GetCurrentTime(self): + """Get the current UTC into Timestamp.""" + self.FromDatetime(datetime.datetime.utcnow()) + + def ToNanoseconds(self): + """Converts Timestamp to nanoseconds since epoch.""" + _CheckTimestampValid(self.seconds, self.nanos) + return self.seconds * _NANOS_PER_SECOND + self.nanos + + def ToMicroseconds(self): + """Converts Timestamp to microseconds since epoch.""" + _CheckTimestampValid(self.seconds, self.nanos) + return (self.seconds * _MICROS_PER_SECOND + + self.nanos // _NANOS_PER_MICROSECOND) + + def ToMilliseconds(self): + """Converts Timestamp to milliseconds since epoch.""" + _CheckTimestampValid(self.seconds, self.nanos) + return (self.seconds * _MILLIS_PER_SECOND + + self.nanos // _NANOS_PER_MILLISECOND) + + def ToSeconds(self): + """Converts Timestamp to seconds since epoch.""" + _CheckTimestampValid(self.seconds, self.nanos) + return self.seconds + + def FromNanoseconds(self, nanos): + """Converts nanoseconds since epoch to Timestamp.""" + seconds = nanos // _NANOS_PER_SECOND + nanos = nanos % _NANOS_PER_SECOND + _CheckTimestampValid(seconds, nanos) + self.seconds = seconds + self.nanos = nanos + + def FromMicroseconds(self, micros): + """Converts microseconds since epoch to Timestamp.""" + seconds = micros // _MICROS_PER_SECOND + nanos = (micros % _MICROS_PER_SECOND) * _NANOS_PER_MICROSECOND + _CheckTimestampValid(seconds, nanos) + self.seconds = seconds + self.nanos = nanos + + def FromMilliseconds(self, millis): + """Converts milliseconds since epoch to Timestamp.""" + seconds = millis // _MILLIS_PER_SECOND + nanos = (millis % _MILLIS_PER_SECOND) * _NANOS_PER_MILLISECOND + _CheckTimestampValid(seconds, nanos) + self.seconds = seconds + self.nanos = nanos + + def FromSeconds(self, seconds): + """Converts seconds since epoch to Timestamp.""" + _CheckTimestampValid(seconds, 0) + self.seconds = seconds + self.nanos = 0 + + def ToDatetime(self, tzinfo=None): + """Converts Timestamp to a datetime. + + Args: + tzinfo: A datetime.tzinfo subclass; defaults to None. + + Returns: + If tzinfo is None, returns a timezone-naive UTC datetime (with no timezone + information, i.e. not aware that it's UTC). + + Otherwise, returns a timezone-aware datetime in the input timezone. + """ + # Using datetime.fromtimestamp for this would avoid constructing an extra + # timedelta object and possibly an extra datetime. Unfortuantely, that has + # the disadvantage of not handling the full precision (on all platforms, see + # https://github.com/python/cpython/issues/109849) or full range (on some + # platforms, see https://github.com/python/cpython/issues/110042) of + # datetime. + _CheckTimestampValid(self.seconds, self.nanos) + delta = datetime.timedelta( + seconds=self.seconds, + microseconds=_RoundTowardZero(self.nanos, _NANOS_PER_MICROSECOND), + ) + if tzinfo is None: + return _EPOCH_DATETIME_NAIVE + delta + else: + # Note the tz conversion has to come after the timedelta arithmetic. + return (_EPOCH_DATETIME_AWARE + delta).astimezone(tzinfo) + + def FromDatetime(self, dt): + """Converts datetime to Timestamp. + + Args: + dt: A datetime. If it's timezone-naive, it's assumed to be in UTC. + """ + # Using this guide: http://wiki.python.org/moin/WorkingWithTime + # And this conversion guide: http://docs.python.org/library/time.html + + # Turn the date parameter into a tuple (struct_time) that can then be + # manipulated into a long value of seconds. During the conversion from + # struct_time to long, the source date in UTC, and so it follows that the + # correct transformation is calendar.timegm() + seconds = calendar.timegm(dt.utctimetuple()) + nanos = dt.microsecond * _NANOS_PER_MICROSECOND + _CheckTimestampValid(seconds, nanos) + self.seconds = seconds + self.nanos = nanos + + +def _CheckTimestampValid(seconds, nanos): + if seconds < _TIMESTAMP_SECONDS_MIN or seconds > _TIMESTAMP_SECONDS_MAX: + raise ValueError( + 'Timestamp is not valid: Seconds {0} must be in range ' + '[-62135596800, 253402300799].'.format(seconds)) + if nanos < 0 or nanos >= _NANOS_PER_SECOND: + raise ValueError( + 'Timestamp is not valid: Nanos {} must be in a range ' + '[0, 999999].'.format(nanos)) + + +class Duration(object): + """Class for Duration message type.""" + + __slots__ = () + + def ToJsonString(self): + """Converts Duration to string format. + + Returns: + A string converted from self. The string format will contains + 3, 6, or 9 fractional digits depending on the precision required to + represent the exact Duration value. For example: "1s", "1.010s", + "1.000000100s", "-3.100s" + """ + _CheckDurationValid(self.seconds, self.nanos) + if self.seconds < 0 or self.nanos < 0: + result = '-' + seconds = - self.seconds + int((0 - self.nanos) // 1e9) + nanos = (0 - self.nanos) % 1e9 + else: + result = '' + seconds = self.seconds + int(self.nanos // 1e9) + nanos = self.nanos % 1e9 + result += '%d' % seconds + if (nanos % 1e9) == 0: + # If there are 0 fractional digits, the fractional + # point '.' should be omitted when serializing. + return result + 's' + if (nanos % 1e6) == 0: + # Serialize 3 fractional digits. + return result + '.%03ds' % (nanos / 1e6) + if (nanos % 1e3) == 0: + # Serialize 6 fractional digits. + return result + '.%06ds' % (nanos / 1e3) + # Serialize 9 fractional digits. + return result + '.%09ds' % nanos + + def FromJsonString(self, value): + """Converts a string to Duration. + + Args: + value: A string to be converted. The string must end with 's'. Any + fractional digits (or none) are accepted as long as they fit into + precision. For example: "1s", "1.01s", "1.0000001s", "-3.100s + + Raises: + ValueError: On parsing problems. + """ + if not isinstance(value, str): + raise ValueError('Duration JSON value not a string: {!r}'.format(value)) + if len(value) < 1 or value[-1] != 's': + raise ValueError( + 'Duration must end with letter "s": {0}.'.format(value)) + try: + pos = value.find('.') + if pos == -1: + seconds = int(value[:-1]) + nanos = 0 + else: + seconds = int(value[:pos]) + if value[0] == '-': + nanos = int(round(float('-0{0}'.format(value[pos: -1])) *1e9)) + else: + nanos = int(round(float('0{0}'.format(value[pos: -1])) *1e9)) + _CheckDurationValid(seconds, nanos) + self.seconds = seconds + self.nanos = nanos + except ValueError as e: + raise ValueError( + 'Couldn\'t parse duration: {0} : {1}.'.format(value, e)) + + def ToNanoseconds(self): + """Converts a Duration to nanoseconds.""" + return self.seconds * _NANOS_PER_SECOND + self.nanos + + def ToMicroseconds(self): + """Converts a Duration to microseconds.""" + micros = _RoundTowardZero(self.nanos, _NANOS_PER_MICROSECOND) + return self.seconds * _MICROS_PER_SECOND + micros + + def ToMilliseconds(self): + """Converts a Duration to milliseconds.""" + millis = _RoundTowardZero(self.nanos, _NANOS_PER_MILLISECOND) + return self.seconds * _MILLIS_PER_SECOND + millis + + def ToSeconds(self): + """Converts a Duration to seconds.""" + return self.seconds + + def FromNanoseconds(self, nanos): + """Converts nanoseconds to Duration.""" + self._NormalizeDuration(nanos // _NANOS_PER_SECOND, + nanos % _NANOS_PER_SECOND) + + def FromMicroseconds(self, micros): + """Converts microseconds to Duration.""" + self._NormalizeDuration( + micros // _MICROS_PER_SECOND, + (micros % _MICROS_PER_SECOND) * _NANOS_PER_MICROSECOND) + + def FromMilliseconds(self, millis): + """Converts milliseconds to Duration.""" + self._NormalizeDuration( + millis // _MILLIS_PER_SECOND, + (millis % _MILLIS_PER_SECOND) * _NANOS_PER_MILLISECOND) + + def FromSeconds(self, seconds): + """Converts seconds to Duration.""" + self.seconds = seconds + self.nanos = 0 + + def ToTimedelta(self) -> datetime.timedelta: + """Converts Duration to timedelta.""" + return datetime.timedelta( + seconds=self.seconds, microseconds=_RoundTowardZero( + self.nanos, _NANOS_PER_MICROSECOND)) + + def FromTimedelta(self, td): + """Converts timedelta to Duration.""" + self._NormalizeDuration(td.seconds + td.days * _SECONDS_PER_DAY, + td.microseconds * _NANOS_PER_MICROSECOND) + + def _NormalizeDuration(self, seconds, nanos): + """Set Duration by seconds and nanos.""" + # Force nanos to be negative if the duration is negative. + if seconds < 0 and nanos > 0: + seconds += 1 + nanos -= _NANOS_PER_SECOND + self.seconds = seconds + self.nanos = nanos + + +def _CheckDurationValid(seconds, nanos): + if seconds < -_DURATION_SECONDS_MAX or seconds > _DURATION_SECONDS_MAX: + raise ValueError( + 'Duration is not valid: Seconds {0} must be in range ' + '[-315576000000, 315576000000].'.format(seconds)) + if nanos <= -_NANOS_PER_SECOND or nanos >= _NANOS_PER_SECOND: + raise ValueError( + 'Duration is not valid: Nanos {0} must be in range ' + '[-999999999, 999999999].'.format(nanos)) + if (nanos < 0 and seconds > 0) or (nanos > 0 and seconds < 0): + raise ValueError( + 'Duration is not valid: Sign mismatch.') + + +def _RoundTowardZero(value, divider): + """Truncates the remainder part after division.""" + # For some languages, the sign of the remainder is implementation + # dependent if any of the operands is negative. Here we enforce + # "rounded toward zero" semantics. For example, for (-5) / 2 an + # implementation may give -3 as the result with the remainder being + # 1. This function ensures we always return -2 (closer to zero). + result = value // divider + remainder = value % divider + if result < 0 and remainder > 0: + return result + 1 + else: + return result + + +def _SetStructValue(struct_value, value): + if value is None: + struct_value.null_value = 0 + elif isinstance(value, bool): + # Note: this check must come before the number check because in Python + # True and False are also considered numbers. + struct_value.bool_value = value + elif isinstance(value, str): + struct_value.string_value = value + elif isinstance(value, (int, float)): + struct_value.number_value = value + elif isinstance(value, (dict, Struct)): + struct_value.struct_value.Clear() + struct_value.struct_value.update(value) + elif isinstance(value, (list, tuple, ListValue)): + struct_value.list_value.Clear() + struct_value.list_value.extend(value) + else: + raise ValueError('Unexpected type') + + +def _GetStructValue(struct_value): + which = struct_value.WhichOneof('kind') + if which == 'struct_value': + return struct_value.struct_value + elif which == 'null_value': + return None + elif which == 'number_value': + return struct_value.number_value + elif which == 'string_value': + return struct_value.string_value + elif which == 'bool_value': + return struct_value.bool_value + elif which == 'list_value': + return struct_value.list_value + elif which is None: + raise ValueError('Value not set') + + +class Struct(object): + """Class for Struct message type.""" + + __slots__ = () + + def __getitem__(self, key): + return _GetStructValue(self.fields[key]) + + def __contains__(self, item): + return item in self.fields + + def __setitem__(self, key, value): + _SetStructValue(self.fields[key], value) + + def __delitem__(self, key): + del self.fields[key] + + def __len__(self): + return len(self.fields) + + def __iter__(self): + return iter(self.fields) + + def keys(self): # pylint: disable=invalid-name + return self.fields.keys() + + def values(self): # pylint: disable=invalid-name + return [self[key] for key in self] + + def items(self): # pylint: disable=invalid-name + return [(key, self[key]) for key in self] + + def get_or_create_list(self, key): + """Returns a list for this key, creating if it didn't exist already.""" + if not self.fields[key].HasField('list_value'): + # Clear will mark list_value modified which will indeed create a list. + self.fields[key].list_value.Clear() + return self.fields[key].list_value + + def get_or_create_struct(self, key): + """Returns a struct for this key, creating if it didn't exist already.""" + if not self.fields[key].HasField('struct_value'): + # Clear will mark struct_value modified which will indeed create a struct. + self.fields[key].struct_value.Clear() + return self.fields[key].struct_value + + def update(self, dictionary): # pylint: disable=invalid-name + for key, value in dictionary.items(): + _SetStructValue(self.fields[key], value) + +collections.abc.MutableMapping.register(Struct) + + +class ListValue(object): + """Class for ListValue message type.""" + + __slots__ = () + + def __len__(self): + return len(self.values) + + def append(self, value): + _SetStructValue(self.values.add(), value) + + def extend(self, elem_seq): + for value in elem_seq: + self.append(value) + + def __getitem__(self, index): + """Retrieves item by the specified index.""" + return _GetStructValue(self.values.__getitem__(index)) + + def __setitem__(self, index, value): + _SetStructValue(self.values.__getitem__(index), value) + + def __delitem__(self, key): + del self.values[key] + + def items(self): + for i in range(len(self)): + yield self[i] + + def add_struct(self): + """Appends and returns a struct value as the next value in the list.""" + struct_value = self.values.add().struct_value + # Clear will mark struct_value modified which will indeed create a struct. + struct_value.Clear() + return struct_value + + def add_list(self): + """Appends and returns a list value as the next value in the list.""" + list_value = self.values.add().list_value + # Clear will mark list_value modified which will indeed create a list. + list_value.Clear() + return list_value + +collections.abc.MutableSequence.register(ListValue) + + +# LINT.IfChange(wktbases) +WKTBASES = { + 'google.protobuf.Any': Any, + 'google.protobuf.Duration': Duration, + 'google.protobuf.FieldMask': FieldMask, + 'google.protobuf.ListValue': ListValue, + 'google.protobuf.Struct': Struct, + 'google.protobuf.Timestamp': Timestamp, +} +# LINT.ThenChange(//depot/google.protobuf/compiler/python/pyi_generator.cc:wktbases) diff --git a/google/protobuf/internal/wire_format.py b/google/protobuf/internal/wire_format.py new file mode 100644 index 0000000..6237dab --- /dev/null +++ b/google/protobuf/internal/wire_format.py @@ -0,0 +1,245 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Constants and static functions to support protocol buffer wire format.""" + +__author__ = 'robinson@google.com (Will Robinson)' + +import struct +from google.protobuf import descriptor +from google.protobuf import message + + +TAG_TYPE_BITS = 3 # Number of bits used to hold type info in a proto tag. +TAG_TYPE_MASK = (1 << TAG_TYPE_BITS) - 1 # 0x7 + +# These numbers identify the wire type of a protocol buffer value. +# We use the least-significant TAG_TYPE_BITS bits of the varint-encoded +# tag-and-type to store one of these WIRETYPE_* constants. +# These values must match WireType enum in //google/protobuf/wire_format.h. +WIRETYPE_VARINT = 0 +WIRETYPE_FIXED64 = 1 +WIRETYPE_LENGTH_DELIMITED = 2 +WIRETYPE_START_GROUP = 3 +WIRETYPE_END_GROUP = 4 +WIRETYPE_FIXED32 = 5 +_WIRETYPE_MAX = 5 + + +# Bounds for various integer types. +INT32_MAX = int((1 << 31) - 1) +INT32_MIN = int(-(1 << 31)) +UINT32_MAX = (1 << 32) - 1 + +INT64_MAX = (1 << 63) - 1 +INT64_MIN = -(1 << 63) +UINT64_MAX = (1 << 64) - 1 + +# "struct" format strings that will encode/decode the specified formats. +FORMAT_UINT32_LITTLE_ENDIAN = '> TAG_TYPE_BITS), (tag & TAG_TYPE_MASK) + + +def ZigZagEncode(value): + """ZigZag Transform: Encodes signed integers so that they can be + effectively used with varint encoding. See wire_format.h for + more details. + """ + if value >= 0: + return value << 1 + return (value << 1) ^ (~0) + + +def ZigZagDecode(value): + """Inverse of ZigZagEncode().""" + if not value & 0x1: + return value >> 1 + return (value >> 1) ^ (~0) + + + +# The *ByteSize() functions below return the number of bytes required to +# serialize "field number + type" information and then serialize the value. + + +def Int32ByteSize(field_number, int32): + return Int64ByteSize(field_number, int32) + + +def Int32ByteSizeNoTag(int32): + return _VarUInt64ByteSizeNoTag(0xffffffffffffffff & int32) + + +def Int64ByteSize(field_number, int64): + # Have to convert to uint before calling UInt64ByteSize(). + return UInt64ByteSize(field_number, 0xffffffffffffffff & int64) + + +def UInt32ByteSize(field_number, uint32): + return UInt64ByteSize(field_number, uint32) + + +def UInt64ByteSize(field_number, uint64): + return TagByteSize(field_number) + _VarUInt64ByteSizeNoTag(uint64) + + +def SInt32ByteSize(field_number, int32): + return UInt32ByteSize(field_number, ZigZagEncode(int32)) + + +def SInt64ByteSize(field_number, int64): + return UInt64ByteSize(field_number, ZigZagEncode(int64)) + + +def Fixed32ByteSize(field_number, fixed32): + return TagByteSize(field_number) + 4 + + +def Fixed64ByteSize(field_number, fixed64): + return TagByteSize(field_number) + 8 + + +def SFixed32ByteSize(field_number, sfixed32): + return TagByteSize(field_number) + 4 + + +def SFixed64ByteSize(field_number, sfixed64): + return TagByteSize(field_number) + 8 + + +def FloatByteSize(field_number, flt): + return TagByteSize(field_number) + 4 + + +def DoubleByteSize(field_number, double): + return TagByteSize(field_number) + 8 + + +def BoolByteSize(field_number, b): + return TagByteSize(field_number) + 1 + + +def EnumByteSize(field_number, enum): + return UInt32ByteSize(field_number, enum) + + +def StringByteSize(field_number, string): + return BytesByteSize(field_number, string.encode('utf-8')) + + +def BytesByteSize(field_number, b): + return (TagByteSize(field_number) + + _VarUInt64ByteSizeNoTag(len(b)) + + len(b)) + + +def GroupByteSize(field_number, message): + return (2 * TagByteSize(field_number) # START and END group. + + message.ByteSize()) + + +def MessageByteSize(field_number, message): + return (TagByteSize(field_number) + + _VarUInt64ByteSizeNoTag(message.ByteSize()) + + message.ByteSize()) + + +def MessageSetItemByteSize(field_number, msg): + # First compute the sizes of the tags. + # There are 2 tags for the beginning and ending of the repeated group, that + # is field number 1, one with field number 2 (type_id) and one with field + # number 3 (message). + total_size = (2 * TagByteSize(1) + TagByteSize(2) + TagByteSize(3)) + + # Add the number of bytes for type_id. + total_size += _VarUInt64ByteSizeNoTag(field_number) + + message_size = msg.ByteSize() + + # The number of bytes for encoding the length of the message. + total_size += _VarUInt64ByteSizeNoTag(message_size) + + # The size of the message. + total_size += message_size + return total_size + + +def TagByteSize(field_number): + """Returns the bytes required to serialize a tag with this field number.""" + # Just pass in type 0, since the type won't affect the tag+type size. + return _VarUInt64ByteSizeNoTag(PackTag(field_number, 0)) + + +# Private helper function for the *ByteSize() functions above. + +def _VarUInt64ByteSizeNoTag(uint64): + """Returns the number of bytes required to serialize a single varint + using boundary value comparisons. (unrolled loop optimization -WPierce) + uint64 must be unsigned. + """ + if uint64 <= 0x7f: return 1 + if uint64 <= 0x3fff: return 2 + if uint64 <= 0x1fffff: return 3 + if uint64 <= 0xfffffff: return 4 + if uint64 <= 0x7ffffffff: return 5 + if uint64 <= 0x3ffffffffff: return 6 + if uint64 <= 0x1ffffffffffff: return 7 + if uint64 <= 0xffffffffffffff: return 8 + if uint64 <= 0x7fffffffffffffff: return 9 + if uint64 > UINT64_MAX: + raise message.EncodeError('Value out of range: %d' % uint64) + return 10 + + +NON_PACKABLE_TYPES = ( + descriptor.FieldDescriptor.TYPE_STRING, + descriptor.FieldDescriptor.TYPE_GROUP, + descriptor.FieldDescriptor.TYPE_MESSAGE, + descriptor.FieldDescriptor.TYPE_BYTES +) + + +def IsTypePackable(field_type): + """Return true iff packable = true is valid for fields of this type. + + Args: + field_type: a FieldDescriptor::Type value. + + Returns: + True iff fields of this type are packable. + """ + return field_type not in NON_PACKABLE_TYPES diff --git a/google/protobuf/json_format.py b/google/protobuf/json_format.py new file mode 100644 index 0000000..8a2e249 --- /dev/null +++ b/google/protobuf/json_format.py @@ -0,0 +1,1060 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Contains routines for printing protocol messages in JSON format. + +Simple usage example: + + # Create a proto object and serialize it to a json format string. + message = my_proto_pb2.MyMessage(foo='bar') + json_string = json_format.MessageToJson(message) + + # Parse a json format string to proto object. + message = json_format.Parse(json_string, my_proto_pb2.MyMessage()) +""" + +__author__ = 'jieluo@google.com (Jie Luo)' + + +import base64 +from collections import OrderedDict +import json +import math +from operator import methodcaller +import re + +from google.protobuf import descriptor +from google.protobuf import message_factory +from google.protobuf import symbol_database +from google.protobuf.internal import type_checkers + + +_INT_TYPES = frozenset([ + descriptor.FieldDescriptor.CPPTYPE_INT32, + descriptor.FieldDescriptor.CPPTYPE_UINT32, + descriptor.FieldDescriptor.CPPTYPE_INT64, + descriptor.FieldDescriptor.CPPTYPE_UINT64, +]) +_INT64_TYPES = frozenset([ + descriptor.FieldDescriptor.CPPTYPE_INT64, + descriptor.FieldDescriptor.CPPTYPE_UINT64, +]) +_FLOAT_TYPES = frozenset([ + descriptor.FieldDescriptor.CPPTYPE_FLOAT, + descriptor.FieldDescriptor.CPPTYPE_DOUBLE, +]) +_INFINITY = 'Infinity' +_NEG_INFINITY = '-Infinity' +_NAN = 'NaN' + +_UNPAIRED_SURROGATE_PATTERN = re.compile( + '[\ud800-\udbff](?![\udc00-\udfff])|(? self.max_recursion_depth: + raise ParseError( + 'Message too deep. Max recursion depth is {0}'.format( + self.max_recursion_depth + ) + ) + message_descriptor = message.DESCRIPTOR + full_name = message_descriptor.full_name + if not path: + path = message_descriptor.name + if _IsWrapperMessage(message_descriptor): + self._ConvertWrapperMessage(value, message, path) + elif full_name in _WKTJSONMETHODS: + methodcaller(_WKTJSONMETHODS[full_name][1], value, message, path)(self) + else: + self._ConvertFieldValuePair(value, message, path) + self.recursion_depth -= 1 + + def _ConvertFieldValuePair(self, js, message, path): + """Convert field value pairs into regular message. + + Args: + js: A JSON object to convert the field value pairs. + message: A regular protocol message to record the data. + path: parent path to log parse error info. + + Raises: + ParseError: In case of problems converting. + """ + names = [] + message_descriptor = message.DESCRIPTOR + fields_by_json_name = dict( + (f.json_name, f) for f in message_descriptor.fields + ) + for name in js: + try: + field = fields_by_json_name.get(name, None) + if not field: + field = message_descriptor.fields_by_name.get(name, None) + if not field and _VALID_EXTENSION_NAME.match(name): + if not message_descriptor.is_extendable: + raise ParseError( + 'Message type {0} does not have extensions at {1}'.format( + message_descriptor.full_name, path + ) + ) + identifier = name[1:-1] # strip [] brackets + # pylint: disable=protected-access + field = message.Extensions._FindExtensionByName(identifier) + # pylint: enable=protected-access + if not field: + # Try looking for extension by the message type name, dropping the + # field name following the final . separator in full_name. + identifier = '.'.join(identifier.split('.')[:-1]) + # pylint: disable=protected-access + field = message.Extensions._FindExtensionByName(identifier) + # pylint: enable=protected-access + if not field: + if self.ignore_unknown_fields: + continue + raise ParseError( + ( + 'Message type "{0}" has no field named "{1}" at "{2}".\n' + ' Available Fields(except extensions): "{3}"' + ).format( + message_descriptor.full_name, + name, + path, + [f.json_name for f in message_descriptor.fields], + ) + ) + if name in names: + raise ParseError( + 'Message type "{0}" should not have multiple ' + '"{1}" fields at "{2}".'.format( + message.DESCRIPTOR.full_name, name, path + ) + ) + names.append(name) + value = js[name] + # Check no other oneof field is parsed. + if field.containing_oneof is not None and value is not None: + oneof_name = field.containing_oneof.name + if oneof_name in names: + raise ParseError( + 'Message type "{0}" should not have multiple ' + '"{1}" oneof fields at "{2}".'.format( + message.DESCRIPTOR.full_name, oneof_name, path + ) + ) + names.append(oneof_name) + + if value is None: + if ( + field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE + and field.message_type.full_name == 'google.protobuf.Value' + ): + sub_message = getattr(message, field.name) + sub_message.null_value = 0 + elif ( + field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM + and field.enum_type.full_name == 'google.protobuf.NullValue' + ): + setattr(message, field.name, 0) + else: + message.ClearField(field.name) + continue + + # Parse field value. + if _IsMapEntry(field): + message.ClearField(field.name) + self._ConvertMapFieldValue( + value, message, field, '{0}.{1}'.format(path, name) + ) + elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: + message.ClearField(field.name) + if not isinstance(value, list): + raise ParseError( + 'repeated field {0} must be in [] which is {1} at {2}'.format( + name, value, path + ) + ) + if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: + # Repeated message field. + for index, item in enumerate(value): + sub_message = getattr(message, field.name).add() + # None is a null_value in Value. + if ( + item is None + and sub_message.DESCRIPTOR.full_name + != 'google.protobuf.Value' + ): + raise ParseError( + 'null is not allowed to be used as an element' + ' in a repeated field at {0}.{1}[{2}]'.format( + path, name, index + ) + ) + self.ConvertMessage( + item, sub_message, '{0}.{1}[{2}]'.format(path, name, index) + ) + else: + # Repeated scalar field. + for index, item in enumerate(value): + if item is None: + raise ParseError( + 'null is not allowed to be used as an element' + ' in a repeated field at {0}.{1}[{2}]'.format( + path, name, index + ) + ) + self._ConvertAndAppendScalar( + message, field, item, '{0}.{1}[{2}]'.format(path, name, index)) + elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: + if field.is_extension: + sub_message = message.Extensions[field] + else: + sub_message = getattr(message, field.name) + sub_message.SetInParent() + self.ConvertMessage(value, sub_message, '{0}.{1}'.format(path, name)) + else: + if field.is_extension: + self._ConvertAndSetScalarExtension(message, field, value, '{0}.{1}'.format(path, name)) + else: + self._ConvertAndSetScalar(message, field, value, '{0}.{1}'.format(path, name)) + except ParseError as e: + if field and field.containing_oneof is None: + raise ParseError( + 'Failed to parse {0} field: {1}.'.format(name, e) + ) from e + else: + raise ParseError(str(e)) from e + except ValueError as e: + raise ParseError( + 'Failed to parse {0} field: {1}.'.format(name, e) + ) from e + except TypeError as e: + raise ParseError( + 'Failed to parse {0} field: {1}.'.format(name, e) + ) from e + + def _ConvertAnyMessage(self, value, message, path): + """Convert a JSON representation into Any message.""" + if isinstance(value, dict) and not value: + return + try: + type_url = value['@type'] + except KeyError as e: + raise ParseError( + '@type is missing when parsing any message at {0}'.format(path) + ) from e + + try: + sub_message = _CreateMessageFromTypeUrl(type_url, self.descriptor_pool) + except TypeError as e: + raise ParseError('{0} at {1}'.format(e, path)) from e + message_descriptor = sub_message.DESCRIPTOR + full_name = message_descriptor.full_name + if _IsWrapperMessage(message_descriptor): + self._ConvertWrapperMessage( + value['value'], sub_message, '{0}.value'.format(path) + ) + elif full_name in _WKTJSONMETHODS: + methodcaller( + _WKTJSONMETHODS[full_name][1], + value['value'], + sub_message, + '{0}.value'.format(path), + )(self) + else: + del value['@type'] + self._ConvertFieldValuePair(value, sub_message, path) + value['@type'] = type_url + # Sets Any message + message.value = sub_message.SerializeToString() + message.type_url = type_url + + def _ConvertGenericMessage(self, value, message, path): + """Convert a JSON representation into message with FromJsonString.""" + # Duration, Timestamp, FieldMask have a FromJsonString method to do the + # conversion. Users can also call the method directly. + try: + message.FromJsonString(value) + except ValueError as e: + raise ParseError('{0} at {1}'.format(e, path)) from e + + def _ConvertValueMessage(self, value, message, path): + """Convert a JSON representation into Value message.""" + if isinstance(value, dict): + self._ConvertStructMessage(value, message.struct_value, path) + elif isinstance(value, list): + self._ConvertListValueMessage(value, message.list_value, path) + elif value is None: + message.null_value = 0 + elif isinstance(value, bool): + message.bool_value = value + elif isinstance(value, str): + message.string_value = value + elif isinstance(value, _INT_OR_FLOAT): + message.number_value = value + else: + raise ParseError( + 'Value {0} has unexpected type {1} at {2}'.format( + value, type(value), path + ) + ) + + def _ConvertListValueMessage(self, value, message, path): + """Convert a JSON representation into ListValue message.""" + if not isinstance(value, list): + raise ParseError( + 'ListValue must be in [] which is {0} at {1}'.format(value, path) + ) + message.ClearField('values') + for index, item in enumerate(value): + self._ConvertValueMessage( + item, message.values.add(), '{0}[{1}]'.format(path, index) + ) + + def _ConvertStructMessage(self, value, message, path): + """Convert a JSON representation into Struct message.""" + if not isinstance(value, dict): + raise ParseError( + 'Struct must be in a dict which is {0} at {1}'.format(value, path) + ) + # Clear will mark the struct as modified so it will be created even if + # there are no values. + message.Clear() + for key in value: + self._ConvertValueMessage( + value[key], message.fields[key], '{0}.{1}'.format(path, key) + ) + return + + def _ConvertWrapperMessage(self, value, message, path): + """Convert a JSON representation into Wrapper message.""" + field = message.DESCRIPTOR.fields_by_name['value'] + self._ConvertAndSetScalar(message, field, value, path='{0}.value'.format(path)) + + def _ConvertMapFieldValue(self, value, message, field, path): + """Convert map field value for a message map field. + + Args: + value: A JSON object to convert the map field value. + message: A protocol message to record the converted data. + field: The descriptor of the map field to be converted. + path: parent path to log parse error info. + + Raises: + ParseError: In case of convert problems. + """ + if not isinstance(value, dict): + raise ParseError( + 'Map field {0} must be in a dict which is {1} at {2}'.format( + field.name, value, path + ) + ) + key_field = field.message_type.fields_by_name['key'] + value_field = field.message_type.fields_by_name['value'] + for key in value: + key_value = _ConvertScalarFieldValue( + key, key_field, '{0}.key'.format(path), True + ) + if value_field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: + self.ConvertMessage( + value[key], + getattr(message, field.name)[key_value], + '{0}[{1}]'.format(path, key_value), + ) + else: + self._ConvertAndSetScalarToMapKey( + message, + field, + key_value, + value[key], + path='{0}[{1}]'.format(path, key_value)) + + def _ConvertAndSetScalarExtension(self, message, extension_field, js_value, path): + """Convert scalar from js_value and assign it to message.Extensions[extension_field].""" + try: + message.Extensions[extension_field] = _ConvertScalarFieldValue( + js_value, extension_field, path) + except EnumStringValueParseError: + if not self.ignore_unknown_fields: + raise + + def _ConvertAndSetScalar(self, message, field, js_value, path): + """Convert scalar from js_value and assign it to message.field.""" + try: + setattr( + message, + field.name, + _ConvertScalarFieldValue(js_value, field, path)) + except EnumStringValueParseError: + if not self.ignore_unknown_fields: + raise + + def _ConvertAndAppendScalar(self, message, repeated_field, js_value, path): + """Convert scalar from js_value and append it to message.repeated_field.""" + try: + getattr(message, repeated_field.name).append( + _ConvertScalarFieldValue(js_value, repeated_field, path)) + except EnumStringValueParseError: + if not self.ignore_unknown_fields: + raise + + def _ConvertAndSetScalarToMapKey(self, message, map_field, converted_key, js_value, path): + """Convert scalar from 'js_value' and add it to message.map_field[converted_key].""" + try: + getattr(message, map_field.name)[converted_key] = _ConvertScalarFieldValue( + js_value, map_field.message_type.fields_by_name['value'], path, + ) + except EnumStringValueParseError: + if not self.ignore_unknown_fields: + raise + + +def _ConvertScalarFieldValue(value, field, path, require_str=False): + """Convert a single scalar field value. + + Args: + value: A scalar value to convert the scalar field value. + field: The descriptor of the field to convert. + path: parent path to log parse error info. + require_str: If True, the field value must be a str. + + Returns: + The converted scalar field value + + Raises: + ParseError: In case of convert problems. + EnumStringValueParseError: In case of unknown enum string value. + """ + try: + if field.cpp_type in _INT_TYPES: + return _ConvertInteger(value) + elif field.cpp_type in _FLOAT_TYPES: + return _ConvertFloat(value, field) + elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL: + return _ConvertBool(value, require_str) + elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING: + if field.type == descriptor.FieldDescriptor.TYPE_BYTES: + if isinstance(value, str): + encoded = value.encode('utf-8') + else: + encoded = value + # Add extra padding '=' + padded_value = encoded + b'=' * (4 - len(encoded) % 4) + return base64.urlsafe_b64decode(padded_value) + else: + # Checking for unpaired surrogates appears to be unreliable, + # depending on the specific Python version, so we check manually. + if _UNPAIRED_SURROGATE_PATTERN.search(value): + raise ParseError('Unpaired surrogate') + return value + elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM: + # Convert an enum value. + enum_value = field.enum_type.values_by_name.get(value, None) + if enum_value is None: + try: + number = int(value) + enum_value = field.enum_type.values_by_number.get(number, None) + except ValueError as e: + # Since parsing to integer failed and lookup in values_by_name didn't + # find this name, we have an enum string value which is unknown. + raise EnumStringValueParseError( + 'Invalid enum value {0} for enum type {1}'.format( + value, field.enum_type.full_name + ) + ) from e + if enum_value is None: + if field.enum_type.is_closed: + raise ParseError( + 'Invalid enum value {0} for enum type {1}'.format( + value, field.enum_type.full_name + ) + ) + else: + return number + return enum_value.number + except EnumStringValueParseError as e: + raise EnumStringValueParseError('{0} at {1}'.format(e, path)) from e + except ParseError as e: + raise ParseError('{0} at {1}'.format(e, path)) from e + + +def _ConvertInteger(value): + """Convert an integer. + + Args: + value: A scalar value to convert. + + Returns: + The integer value. + + Raises: + ParseError: If an integer couldn't be consumed. + """ + if isinstance(value, float) and not value.is_integer(): + raise ParseError("Couldn't parse integer: {0}".format(value)) + + if isinstance(value, str) and value.find(' ') != -1: + raise ParseError('Couldn\'t parse integer: "{0}"'.format(value)) + + if isinstance(value, bool): + raise ParseError( + 'Bool value {0} is not acceptable for integer field'.format(value) + ) + + return int(value) + + +def _ConvertFloat(value, field): + """Convert an floating point number.""" + if isinstance(value, float): + if math.isnan(value): + raise ParseError('Couldn\'t parse NaN, use quoted "NaN" instead') + if math.isinf(value): + if value > 0: + raise ParseError( + "Couldn't parse Infinity or value too large, " + 'use quoted "Infinity" instead' + ) + else: + raise ParseError( + "Couldn't parse -Infinity or value too small, " + 'use quoted "-Infinity" instead' + ) + if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_FLOAT: + # pylint: disable=protected-access + if value > type_checkers._FLOAT_MAX: + raise ParseError('Float value too large') + # pylint: disable=protected-access + if value < type_checkers._FLOAT_MIN: + raise ParseError('Float value too small') + if value == 'nan': + raise ParseError('Couldn\'t parse float "nan", use "NaN" instead') + try: + # Assume Python compatible syntax. + return float(value) + except ValueError as e: + # Check alternative spellings. + if value == _NEG_INFINITY: + return float('-inf') + elif value == _INFINITY: + return float('inf') + elif value == _NAN: + return float('nan') + else: + raise ParseError("Couldn't parse float: {0}".format(value)) from e + + +def _ConvertBool(value, require_str): + """Convert a boolean value. + + Args: + value: A scalar value to convert. + require_str: If True, value must be a str. + + Returns: + The bool parsed. + + Raises: + ParseError: If a boolean value couldn't be consumed. + """ + if require_str: + if value == 'true': + return True + elif value == 'false': + return False + else: + raise ParseError('Expected "true" or "false", not {0}'.format(value)) + + if not isinstance(value, bool): + raise ParseError('Expected true or false without quotes') + return value + + +_WKTJSONMETHODS = { + 'google.protobuf.Any': ['_AnyMessageToJsonObject', '_ConvertAnyMessage'], + 'google.protobuf.Duration': [ + '_GenericMessageToJsonObject', + '_ConvertGenericMessage', + ], + 'google.protobuf.FieldMask': [ + '_GenericMessageToJsonObject', + '_ConvertGenericMessage', + ], + 'google.protobuf.ListValue': [ + '_ListValueMessageToJsonObject', + '_ConvertListValueMessage', + ], + 'google.protobuf.Struct': [ + '_StructMessageToJsonObject', + '_ConvertStructMessage', + ], + 'google.protobuf.Timestamp': [ + '_GenericMessageToJsonObject', + '_ConvertGenericMessage', + ], + 'google.protobuf.Value': [ + '_ValueMessageToJsonObject', + '_ConvertValueMessage', + ], +} diff --git a/google/protobuf/message.py b/google/protobuf/message.py new file mode 100644 index 0000000..3226b6e --- /dev/null +++ b/google/protobuf/message.py @@ -0,0 +1,394 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +# TODO: We should just make these methods all "pure-virtual" and move +# all implementation out, into reflection.py for now. + + +"""Contains an abstract base class for protocol messages.""" + +__author__ = 'robinson@google.com (Will Robinson)' + +class Error(Exception): + """Base error type for this module.""" + pass + + +class DecodeError(Error): + """Exception raised when deserializing messages.""" + pass + + +class EncodeError(Error): + """Exception raised when serializing messages.""" + pass + + +class Message(object): + + """Abstract base class for protocol messages. + + Protocol message classes are almost always generated by the protocol + compiler. These generated types subclass Message and implement the methods + shown below. + """ + + # TODO: Link to an HTML document here. + + # TODO: Document that instances of this class will also + # have an Extensions attribute with __getitem__ and __setitem__. + # Again, not sure how to best convey this. + + # TODO: Document these fields and methods. + + __slots__ = [] + + #: The :class:`google.protobuf.Descriptor` + # for this message type. + DESCRIPTOR = None + + def __deepcopy__(self, memo=None): + clone = type(self)() + clone.MergeFrom(self) + return clone + + def __eq__(self, other_msg): + """Recursively compares two messages by value and structure.""" + raise NotImplementedError + + def __ne__(self, other_msg): + # Can't just say self != other_msg, since that would infinitely recurse. :) + return not self == other_msg + + def __hash__(self): + raise TypeError('unhashable object') + + def __str__(self): + """Outputs a human-readable representation of the message.""" + raise NotImplementedError + + def __unicode__(self): + """Outputs a human-readable representation of the message.""" + raise NotImplementedError + + def MergeFrom(self, other_msg): + """Merges the contents of the specified message into current message. + + This method merges the contents of the specified message into the current + message. Singular fields that are set in the specified message overwrite + the corresponding fields in the current message. Repeated fields are + appended. Singular sub-messages and groups are recursively merged. + + Args: + other_msg (Message): A message to merge into the current message. + """ + raise NotImplementedError + + def CopyFrom(self, other_msg): + """Copies the content of the specified message into the current message. + + The method clears the current message and then merges the specified + message using MergeFrom. + + Args: + other_msg (Message): A message to copy into the current one. + """ + if self is other_msg: + return + self.Clear() + self.MergeFrom(other_msg) + + def Clear(self): + """Clears all data that was set in the message.""" + raise NotImplementedError + + def SetInParent(self): + """Mark this as present in the parent. + + This normally happens automatically when you assign a field of a + sub-message, but sometimes you want to make the sub-message + present while keeping it empty. If you find yourself using this, + you may want to reconsider your design. + """ + raise NotImplementedError + + def IsInitialized(self): + """Checks if the message is initialized. + + Returns: + bool: The method returns True if the message is initialized (i.e. all of + its required fields are set). + """ + raise NotImplementedError + + # TODO: MergeFromString() should probably return None and be + # implemented in terms of a helper that returns the # of bytes read. Our + # deserialization routines would use the helper when recursively + # deserializing, but the end user would almost always just want the no-return + # MergeFromString(). + + def MergeFromString(self, serialized): + """Merges serialized protocol buffer data into this message. + + When we find a field in `serialized` that is already present + in this message: + + - If it's a "repeated" field, we append to the end of our list. + - Else, if it's a scalar, we overwrite our field. + - Else, (it's a nonrepeated composite), we recursively merge + into the existing composite. + + Args: + serialized (bytes): Any object that allows us to call + ``memoryview(serialized)`` to access a string of bytes using the + buffer interface. + + Returns: + int: The number of bytes read from `serialized`. + For non-group messages, this will always be `len(serialized)`, + but for messages which are actually groups, this will + generally be less than `len(serialized)`, since we must + stop when we reach an ``END_GROUP`` tag. Note that if + we *do* stop because of an ``END_GROUP`` tag, the number + of bytes returned does not include the bytes + for the ``END_GROUP`` tag information. + + Raises: + DecodeError: if the input cannot be parsed. + """ + # TODO: Document handling of unknown fields. + # TODO: When we switch to a helper, this will return None. + raise NotImplementedError + + def ParseFromString(self, serialized): + """Parse serialized protocol buffer data in binary form into this message. + + Like :func:`MergeFromString()`, except we clear the object first. + + Raises: + message.DecodeError if the input cannot be parsed. + """ + self.Clear() + return self.MergeFromString(serialized) + + def SerializeToString(self, **kwargs): + """Serializes the protocol message to a binary string. + + Keyword Args: + deterministic (bool): If true, requests deterministic serialization + of the protobuf, with predictable ordering of map keys. + + Returns: + A binary string representation of the message if all of the required + fields in the message are set (i.e. the message is initialized). + + Raises: + EncodeError: if the message isn't initialized (see :func:`IsInitialized`). + """ + raise NotImplementedError + + def SerializePartialToString(self, **kwargs): + """Serializes the protocol message to a binary string. + + This method is similar to SerializeToString but doesn't check if the + message is initialized. + + Keyword Args: + deterministic (bool): If true, requests deterministic serialization + of the protobuf, with predictable ordering of map keys. + + Returns: + bytes: A serialized representation of the partial message. + """ + raise NotImplementedError + + # TODO: Decide whether we like these better + # than auto-generated has_foo() and clear_foo() methods + # on the instances themselves. This way is less consistent + # with C++, but it makes reflection-type access easier and + # reduces the number of magically autogenerated things. + # + # TODO: Be sure to document (and test) exactly + # which field names are accepted here. Are we case-sensitive? + # What do we do with fields that share names with Python keywords + # like 'lambda' and 'yield'? + # + # nnorwitz says: + # """ + # Typically (in python), an underscore is appended to names that are + # keywords. So they would become lambda_ or yield_. + # """ + def ListFields(self): + """Returns a list of (FieldDescriptor, value) tuples for present fields. + + A message field is non-empty if HasField() would return true. A singular + primitive field is non-empty if HasField() would return true in proto2 or it + is non zero in proto3. A repeated field is non-empty if it contains at least + one element. The fields are ordered by field number. + + Returns: + list[tuple(FieldDescriptor, value)]: field descriptors and values + for all fields in the message which are not empty. The values vary by + field type. + """ + raise NotImplementedError + + def HasField(self, field_name): + """Checks if a certain field is set for the message. + + For a oneof group, checks if any field inside is set. Note that if the + field_name is not defined in the message descriptor, :exc:`ValueError` will + be raised. + + Args: + field_name (str): The name of the field to check for presence. + + Returns: + bool: Whether a value has been set for the named field. + + Raises: + ValueError: if the `field_name` is not a member of this message. + """ + raise NotImplementedError + + def ClearField(self, field_name): + """Clears the contents of a given field. + + Inside a oneof group, clears the field set. If the name neither refers to a + defined field or oneof group, :exc:`ValueError` is raised. + + Args: + field_name (str): The name of the field to check for presence. + + Raises: + ValueError: if the `field_name` is not a member of this message. + """ + raise NotImplementedError + + def WhichOneof(self, oneof_group): + """Returns the name of the field that is set inside a oneof group. + + If no field is set, returns None. + + Args: + oneof_group (str): the name of the oneof group to check. + + Returns: + str or None: The name of the group that is set, or None. + + Raises: + ValueError: no group with the given name exists + """ + raise NotImplementedError + + def HasExtension(self, field_descriptor): + """Checks if a certain extension is present for this message. + + Extensions are retrieved using the :attr:`Extensions` mapping (if present). + + Args: + field_descriptor: The field descriptor for the extension to check. + + Returns: + bool: Whether the extension is present for this message. + + Raises: + KeyError: if the extension is repeated. Similar to repeated fields, + there is no separate notion of presence: a "not present" repeated + extension is an empty list. + """ + raise NotImplementedError + + def ClearExtension(self, field_descriptor): + """Clears the contents of a given extension. + + Args: + field_descriptor: The field descriptor for the extension to clear. + """ + raise NotImplementedError + + def UnknownFields(self): + """Returns the UnknownFieldSet. + + Returns: + UnknownFieldSet: The unknown fields stored in this message. + """ + raise NotImplementedError + + def DiscardUnknownFields(self): + """Clears all fields in the :class:`UnknownFieldSet`. + + This operation is recursive for nested message. + """ + raise NotImplementedError + + def ByteSize(self): + """Returns the serialized size of this message. + + Recursively calls ByteSize() on all contained messages. + + Returns: + int: The number of bytes required to serialize this message. + """ + raise NotImplementedError + + @classmethod + def FromString(cls, s): + raise NotImplementedError + + def _SetListener(self, message_listener): + """Internal method used by the protocol message implementation. + Clients should not call this directly. + + Sets a listener that this message will call on certain state transitions. + + The purpose of this method is to register back-edges from children to + parents at runtime, for the purpose of setting "has" bits and + byte-size-dirty bits in the parent and ancestor objects whenever a child or + descendant object is modified. + + If the client wants to disconnect this Message from the object tree, she + explicitly sets callback to None. + + If message_listener is None, unregisters any existing listener. Otherwise, + message_listener must implement the MessageListener interface in + internal/message_listener.py, and we discard any listener registered + via a previous _SetListener() call. + """ + raise NotImplementedError + + def __getstate__(self): + """Support the pickle protocol.""" + return dict(serialized=self.SerializePartialToString()) + + def __setstate__(self, state): + """Support the pickle protocol.""" + self.__init__() + serialized = state['serialized'] + # On Python 3, using encoding='latin1' is required for unpickling + # protos pickled by Python 2. + if not isinstance(serialized, bytes): + serialized = serialized.encode('latin1') + self.ParseFromString(serialized) + + def __reduce__(self): + message_descriptor = self.DESCRIPTOR + if message_descriptor.containing_type is None: + return type(self), (), self.__getstate__() + # the message type must be nested. + # Python does not pickle nested classes; use the symbol_database on the + # receiving end. + container = message_descriptor + return (_InternalConstructMessage, (container.full_name,), + self.__getstate__()) + + +def _InternalConstructMessage(full_name): + """Constructs a nested message.""" + from google.protobuf import symbol_database # pylint:disable=g-import-not-at-top + + return symbol_database.Default().GetSymbol(full_name)() diff --git a/google/protobuf/message_factory.py b/google/protobuf/message_factory.py new file mode 100644 index 0000000..56fff6d --- /dev/null +++ b/google/protobuf/message_factory.py @@ -0,0 +1,233 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Provides a factory class for generating dynamic messages. + +The easiest way to use this class is if you have access to the FileDescriptor +protos containing the messages you want to create you can just do the following: + +message_classes = message_factory.GetMessages(iterable_of_file_descriptors) +my_proto_instance = message_classes['some.proto.package.MessageName']() +""" + +__author__ = 'matthewtoia@google.com (Matt Toia)' + +import warnings + +from google.protobuf.internal import api_implementation +from google.protobuf import descriptor_pool +from google.protobuf import message + +if api_implementation.Type() == 'python': + from google.protobuf.internal import python_message as message_impl +else: + from google.protobuf.pyext import cpp_message as message_impl # pylint: disable=g-import-not-at-top + + +# The type of all Message classes. +_GENERATED_PROTOCOL_MESSAGE_TYPE = message_impl.GeneratedProtocolMessageType + + +def GetMessageClass(descriptor): + """Obtains a proto2 message class based on the passed in descriptor. + + Passing a descriptor with a fully qualified name matching a previous + invocation will cause the same class to be returned. + + Args: + descriptor: The descriptor to build from. + + Returns: + A class describing the passed in descriptor. + """ + concrete_class = getattr(descriptor, '_concrete_class', None) + if concrete_class: + return concrete_class + return _InternalCreateMessageClass(descriptor) + + +def GetMessageClassesForFiles(files, pool): + """Gets all the messages from specified files. + + This will find and resolve dependencies, failing if the descriptor + pool cannot satisfy them. + + Args: + files: The file names to extract messages from. + pool: The descriptor pool to find the files including the dependent + files. + + Returns: + A dictionary mapping proto names to the message classes. + """ + result = {} + for file_name in files: + file_desc = pool.FindFileByName(file_name) + for desc in file_desc.message_types_by_name.values(): + result[desc.full_name] = GetMessageClass(desc) + + # While the extension FieldDescriptors are created by the descriptor pool, + # the python classes created in the factory need them to be registered + # explicitly, which is done below. + # + # The call to RegisterExtension will specifically check if the + # extension was already registered on the object and either + # ignore the registration if the original was the same, or raise + # an error if they were different. + + for extension in file_desc.extensions_by_name.values(): + extended_class = GetMessageClass(extension.containing_type) + if api_implementation.Type() != 'python': + # TODO: Remove this check here. Duplicate extension + # register check should be in descriptor_pool. + if extension is not pool.FindExtensionByNumber( + extension.containing_type, extension.number + ): + raise ValueError('Double registration of Extensions') + # Recursively load protos for extension field, in order to be able to + # fully represent the extension. This matches the behavior for regular + # fields too. + if extension.message_type: + GetMessageClass(extension.message_type) + return result + + +def _InternalCreateMessageClass(descriptor): + """Builds a proto2 message class based on the passed in descriptor. + + Args: + descriptor: The descriptor to build from. + + Returns: + A class describing the passed in descriptor. + """ + descriptor_name = descriptor.name + result_class = _GENERATED_PROTOCOL_MESSAGE_TYPE( + descriptor_name, + (message.Message,), + { + 'DESCRIPTOR': descriptor, + # If module not set, it wrongly points to message_factory module. + '__module__': None, + }) + for field in descriptor.fields: + if field.message_type: + GetMessageClass(field.message_type) + for extension in result_class.DESCRIPTOR.extensions: + extended_class = GetMessageClass(extension.containing_type) + if api_implementation.Type() != 'python': + # TODO: Remove this check here. Duplicate extension + # register check should be in descriptor_pool. + pool = extension.containing_type.file.pool + if extension is not pool.FindExtensionByNumber( + extension.containing_type, extension.number + ): + raise ValueError('Double registration of Extensions') + if extension.message_type: + GetMessageClass(extension.message_type) + return result_class + + +# Deprecated. Please use GetMessageClass() or GetMessageClassesForFiles() +# method above instead. +class MessageFactory(object): + """Factory for creating Proto2 messages from descriptors in a pool.""" + + def __init__(self, pool=None): + """Initializes a new factory.""" + self.pool = pool or descriptor_pool.DescriptorPool() + + def GetPrototype(self, descriptor): + """Obtains a proto2 message class based on the passed in descriptor. + + Passing a descriptor with a fully qualified name matching a previous + invocation will cause the same class to be returned. + + Args: + descriptor: The descriptor to build from. + + Returns: + A class describing the passed in descriptor. + """ + warnings.warn( + 'MessageFactory class is deprecated. Please use ' + 'GetMessageClass() instead of MessageFactory.GetPrototype. ' + 'MessageFactory class will be removed after 2024.', + stacklevel=2, + ) + return GetMessageClass(descriptor) + + def CreatePrototype(self, descriptor): + """Builds a proto2 message class based on the passed in descriptor. + + Don't call this function directly, it always creates a new class. Call + GetMessageClass() instead. + + Args: + descriptor: The descriptor to build from. + + Returns: + A class describing the passed in descriptor. + """ + warnings.warn( + 'Directly call CreatePrototype is wrong. Please use ' + 'GetMessageClass() method instead. Directly use ' + 'CreatePrototype will raise error after July 2023.', + stacklevel=2, + ) + return _InternalCreateMessageClass(descriptor) + + def GetMessages(self, files): + """Gets all the messages from a specified file. + + This will find and resolve dependencies, failing if the descriptor + pool cannot satisfy them. + + Args: + files: The file names to extract messages from. + + Returns: + A dictionary mapping proto names to the message classes. This will include + any dependent messages as well as any messages defined in the same file as + a specified message. + """ + warnings.warn( + 'MessageFactory class is deprecated. Please use ' + 'GetMessageClassesForFiles() instead of ' + 'MessageFactory.GetMessages(). MessageFactory class ' + 'will be removed after 2024.', + stacklevel=2, + ) + return GetMessageClassesForFiles(files, self.pool) + + +def GetMessages(file_protos, pool=None): + """Builds a dictionary of all the messages available in a set of files. + + Args: + file_protos: Iterable of FileDescriptorProto to build messages out of. + pool: The descriptor pool to add the file protos. + + Returns: + A dictionary mapping proto names to the message classes. This will include + any dependent messages as well as any messages defined in the same file as + a specified message. + """ + # The cpp implementation of the protocol buffer library requires to add the + # message in topological order of the dependency graph. + des_pool = pool or descriptor_pool.DescriptorPool() + file_by_name = {file_proto.name: file_proto for file_proto in file_protos} + def _AddFile(file_proto): + for dependency in file_proto.dependency: + if dependency in file_by_name: + # Remove from elements to be visited, in order to cut cycles. + _AddFile(file_by_name.pop(dependency)) + des_pool.Add(file_proto) + while file_by_name: + _AddFile(file_by_name.popitem()[1]) + return GetMessageClassesForFiles( + [file_proto.name for file_proto in file_protos], des_pool) diff --git a/google/protobuf/proto_builder.py b/google/protobuf/proto_builder.py new file mode 100644 index 0000000..803d004 --- /dev/null +++ b/google/protobuf/proto_builder.py @@ -0,0 +1,111 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Dynamic Protobuf class creator.""" + +from collections import OrderedDict +import hashlib +import os + +from google.protobuf import descriptor_pb2 +from google.protobuf import descriptor +from google.protobuf import descriptor_pool +from google.protobuf import message_factory + + +def _GetMessageFromFactory(pool, full_name): + """Get a proto class from the MessageFactory by name. + + Args: + pool: a descriptor pool. + full_name: str, the fully qualified name of the proto type. + Returns: + A class, for the type identified by full_name. + Raises: + KeyError, if the proto is not found in the factory's descriptor pool. + """ + proto_descriptor = pool.FindMessageTypeByName(full_name) + proto_cls = message_factory.GetMessageClass(proto_descriptor) + return proto_cls + + +def MakeSimpleProtoClass(fields, full_name=None, pool=None): + """Create a Protobuf class whose fields are basic types. + + Note: this doesn't validate field names! + + Args: + fields: dict of {name: field_type} mappings for each field in the proto. If + this is an OrderedDict the order will be maintained, otherwise the + fields will be sorted by name. + full_name: optional str, the fully-qualified name of the proto type. + pool: optional DescriptorPool instance. + Returns: + a class, the new protobuf class with a FileDescriptor. + """ + pool_instance = pool or descriptor_pool.DescriptorPool() + if full_name is not None: + try: + proto_cls = _GetMessageFromFactory(pool_instance, full_name) + return proto_cls + except KeyError: + # The factory's DescriptorPool doesn't know about this class yet. + pass + + # Get a list of (name, field_type) tuples from the fields dict. If fields was + # an OrderedDict we keep the order, but otherwise we sort the field to ensure + # consistent ordering. + field_items = fields.items() + if not isinstance(fields, OrderedDict): + field_items = sorted(field_items) + + # Use a consistent file name that is unlikely to conflict with any imported + # proto files. + fields_hash = hashlib.sha1() + for f_name, f_type in field_items: + fields_hash.update(f_name.encode('utf-8')) + fields_hash.update(str(f_type).encode('utf-8')) + proto_file_name = fields_hash.hexdigest() + '.proto' + + # If the proto is anonymous, use the same hash to name it. + if full_name is None: + full_name = ('net.proto2.python.public.proto_builder.AnonymousProto_' + + fields_hash.hexdigest()) + try: + proto_cls = _GetMessageFromFactory(pool_instance, full_name) + return proto_cls + except KeyError: + # The factory's DescriptorPool doesn't know about this class yet. + pass + + # This is the first time we see this proto: add a new descriptor to the pool. + pool_instance.Add( + _MakeFileDescriptorProto(proto_file_name, full_name, field_items)) + return _GetMessageFromFactory(pool_instance, full_name) + + +def _MakeFileDescriptorProto(proto_file_name, full_name, field_items): + """Populate FileDescriptorProto for MessageFactory's DescriptorPool.""" + package, name = full_name.rsplit('.', 1) + file_proto = descriptor_pb2.FileDescriptorProto() + file_proto.name = os.path.join(package.replace('.', '/'), proto_file_name) + file_proto.package = package + desc_proto = file_proto.message_type.add() + desc_proto.name = name + for f_number, (f_name, f_type) in enumerate(field_items, 1): + field_proto = desc_proto.field.add() + field_proto.name = f_name + # # If the number falls in the reserved range, reassign it to the correct + # # number after the range. + if f_number >= descriptor.FieldDescriptor.FIRST_RESERVED_FIELD_NUMBER: + f_number += ( + descriptor.FieldDescriptor.LAST_RESERVED_FIELD_NUMBER - + descriptor.FieldDescriptor.FIRST_RESERVED_FIELD_NUMBER + 1) + field_proto.number = f_number + field_proto.label = descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL + field_proto.type = f_type + return file_proto diff --git a/google/protobuf/pyext/__init__.py b/google/protobuf/pyext/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/google/protobuf/pyext/cpp_message.py b/google/protobuf/pyext/cpp_message.py new file mode 100644 index 0000000..623b52f --- /dev/null +++ b/google/protobuf/pyext/cpp_message.py @@ -0,0 +1,49 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Protocol message implementation hooks for C++ implementation. + +Contains helper functions used to create protocol message classes from +Descriptor objects at runtime backed by the protocol buffer C++ API. +""" + +__author__ = 'tibell@google.com (Johan Tibell)' + +from google.protobuf.internal import api_implementation + + +# pylint: disable=protected-access +_message = api_implementation._c_module +# TODO: Remove this import after fix api_implementation +if _message is None: + from google.protobuf.pyext import _message + + +class GeneratedProtocolMessageType(_message.MessageMeta): + + """Metaclass for protocol message classes created at runtime from Descriptors. + + The protocol compiler currently uses this metaclass to create protocol + message classes at runtime. Clients can also manually create their own + classes at runtime, as in this example: + + mydescriptor = Descriptor(.....) + factory = symbol_database.Default() + factory.pool.AddDescriptor(mydescriptor) + MyProtoClass = factory.GetPrototype(mydescriptor) + myproto_instance = MyProtoClass() + myproto.foo_field = 23 + ... + + The above example will not work for nested types. If you wish to include them, + use reflection.MakeClass() instead of manually instantiating the class in + order to create the appropriate class structure. + """ + + # Must be consistent with the protocol-compiler code in + # proto2/compiler/internal/generator.*. + _DESCRIPTOR_KEY = 'DESCRIPTOR' diff --git a/google/protobuf/reflection.py b/google/protobuf/reflection.py new file mode 100644 index 0000000..2089f01 --- /dev/null +++ b/google/protobuf/reflection.py @@ -0,0 +1,72 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +# This code is meant to work on Python 2.4 and above only. + +"""Contains a metaclass and helper functions used to create +protocol message classes from Descriptor objects at runtime. + +Recall that a metaclass is the "type" of a class. +(A class is to a metaclass what an instance is to a class.) + +In this case, we use the GeneratedProtocolMessageType metaclass +to inject all the useful functionality into the classes +output by the protocol compiler at compile-time. + +The upshot of all this is that the real implementation +details for ALL pure-Python protocol buffers are *here in +this file*. +""" + +__author__ = 'robinson@google.com (Will Robinson)' + + +from google.protobuf import message_factory +from google.protobuf import symbol_database + +# The type of all Message classes. +# Part of the public interface, but normally only used by message factories. +GeneratedProtocolMessageType = message_factory._GENERATED_PROTOCOL_MESSAGE_TYPE + +MESSAGE_CLASS_CACHE = {} + + +# Deprecated. Please NEVER use reflection.ParseMessage(). +def ParseMessage(descriptor, byte_str): + """Generate a new Message instance from this Descriptor and a byte string. + + DEPRECATED: ParseMessage is deprecated because it is using MakeClass(). + Please use MessageFactory.GetPrototype() instead. + + Args: + descriptor: Protobuf Descriptor object + byte_str: Serialized protocol buffer byte string + + Returns: + Newly created protobuf Message object. + """ + result_class = MakeClass(descriptor) + new_msg = result_class() + new_msg.ParseFromString(byte_str) + return new_msg + + +# Deprecated. Please NEVER use reflection.MakeClass(). +def MakeClass(descriptor): + """Construct a class object for a protobuf described by descriptor. + + DEPRECATED: use MessageFactory.GetPrototype() instead. + + Args: + descriptor: A descriptor.Descriptor object describing the protobuf. + Returns: + The Message class object described by the descriptor. + """ + # Original implementation leads to duplicate message classes, which won't play + # well with extensions. Message factory info is also missing. + # Redirect to message_factory. + return message_factory.GetMessageClass(descriptor) diff --git a/google/protobuf/runtime_version.py b/google/protobuf/runtime_version.py new file mode 100644 index 0000000..36d67dc --- /dev/null +++ b/google/protobuf/runtime_version.py @@ -0,0 +1,97 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Protobuf Runtime versions and validators. + +It should only be accessed by Protobuf gencodes and tests. DO NOT USE it +elsewhere. +""" + +__author__ = 'shaod@google.com (Dennis Shao)' + +from enum import Enum +import os + + +class Domain(Enum): + GOOGLE_INTERNAL = 1 + PUBLIC = 2 + + +class VersionError(Exception): + """Exception class for version violation.""" + + +# The versions of this Python Protobuf runtime to be changed automatically by +# the Protobuf release process. Do not edit them manually. +DOMAIN = Domain.PUBLIC +MAJOR = 5 +MINOR = 27 +PATCH = 3 +SUFFIX = '' + + +def ValidateProtobufRuntimeVersion( + gen_domain, gen_major, gen_minor, gen_patch, gen_suffix, location +): + """Function to validate versions. + + Args: + gen_domain: The domain where the code was generated from. + gen_major: The major version number of the gencode. + gen_minor: The minor version number of the gencode. + gen_patch: The patch version number of the gencode. + gen_suffix: The version suffix e.g. '-dev', '-rc1' of the gencode. + location: The proto location that causes the version violation. + + Raises: + VersionError: if gencode version is invalid or incompatible with the + runtime. + """ + + disable_flag = os.getenv('TEMORARILY_DISABLE_PROTOBUF_VERSION_CHECK') + if disable_flag is not None and disable_flag.lower() == 'true': + return + + version = f'{MAJOR}.{MINOR}.{PATCH}{SUFFIX}' + gen_version = f'{gen_major}.{gen_minor}.{gen_patch}{gen_suffix}' + + if gen_major < 0 or gen_minor < 0 or gen_patch < 0: + raise VersionError(f'Invalid gencode version: {gen_version}') + + error_prompt = ( + 'See Protobuf version guarantees at' + ' https://protobuf.dev/support/cross-version-runtime-guarantee.' + ) + + if gen_domain != DOMAIN: + raise VersionError( + 'Detected mismatched Protobuf Gencode/Runtime domains when loading' + f' {location}: gencode {gen_domain.name} runtime {DOMAIN.name}.' + ' Cross-domain usage of Protobuf is not supported.' + ) + + if gen_major != MAJOR: + raise VersionError( + 'Detected mismatched Protobuf Gencode/Runtime major versions when' + f' loading {location}: gencode {gen_version} runtime {version}.' + f' Same major version is required. {error_prompt}' + ) + + if MINOR < gen_minor or (MINOR == gen_minor and PATCH < gen_patch): + raise VersionError( + 'Detected incompatible Protobuf Gencode/Runtime versions when loading' + f' {location}: gencode {gen_version} runtime {version}. Runtime version' + f' cannot be older than the linked gencode version. {error_prompt}' + ) + + if gen_suffix != SUFFIX: + raise VersionError( + 'Detected mismatched Protobuf Gencode/Runtime version suffixes when' + f' loading {location}: gencode {gen_version} runtime {version}.' + f' Version suffixes must be the same. {error_prompt}' + ) diff --git a/google/protobuf/service.py b/google/protobuf/service.py new file mode 100644 index 0000000..d3e1920 --- /dev/null +++ b/google/protobuf/service.py @@ -0,0 +1,205 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""DEPRECATED: Declares the RPC service interfaces. + +This module declares the abstract interfaces underlying proto2 RPC +services. These are intended to be independent of any particular RPC +implementation, so that proto2 services can be used on top of a variety +of implementations. Starting with version 2.3.0, RPC implementations should +not try to build on these, but should instead provide code generator plugins +which generate code specific to the particular RPC implementation. This way +the generated code can be more appropriate for the implementation in use +and can avoid unnecessary layers of indirection. +""" + +__author__ = 'petar@google.com (Petar Petrov)' + + +class RpcException(Exception): + """Exception raised on failed blocking RPC method call.""" + pass + + +class Service(object): + + """Abstract base interface for protocol-buffer-based RPC services. + + Services themselves are abstract classes (implemented either by servers or as + stubs), but they subclass this base interface. The methods of this + interface can be used to call the methods of the service without knowing + its exact type at compile time (analogous to the Message interface). + """ + + def GetDescriptor(): + """Retrieves this service's descriptor.""" + raise NotImplementedError + + def CallMethod(self, method_descriptor, rpc_controller, + request, done): + """Calls a method of the service specified by method_descriptor. + + If "done" is None then the call is blocking and the response + message will be returned directly. Otherwise the call is asynchronous + and "done" will later be called with the response value. + + In the blocking case, RpcException will be raised on error. + + Preconditions: + + * method_descriptor.service == GetDescriptor + * request is of the exact same classes as returned by + GetRequestClass(method). + * After the call has started, the request must not be modified. + * "rpc_controller" is of the correct type for the RPC implementation being + used by this Service. For stubs, the "correct type" depends on the + RpcChannel which the stub is using. + + Postconditions: + + * "done" will be called when the method is complete. This may be + before CallMethod() returns or it may be at some point in the future. + * If the RPC failed, the response value passed to "done" will be None. + Further details about the failure can be found by querying the + RpcController. + """ + raise NotImplementedError + + def GetRequestClass(self, method_descriptor): + """Returns the class of the request message for the specified method. + + CallMethod() requires that the request is of a particular subclass of + Message. GetRequestClass() gets the default instance of this required + type. + + Example: + method = service.GetDescriptor().FindMethodByName("Foo") + request = stub.GetRequestClass(method)() + request.ParseFromString(input) + service.CallMethod(method, request, callback) + """ + raise NotImplementedError + + def GetResponseClass(self, method_descriptor): + """Returns the class of the response message for the specified method. + + This method isn't really needed, as the RpcChannel's CallMethod constructs + the response protocol message. It's provided anyway in case it is useful + for the caller to know the response type in advance. + """ + raise NotImplementedError + + +class RpcController(object): + + """An RpcController mediates a single method call. + + The primary purpose of the controller is to provide a way to manipulate + settings specific to the RPC implementation and to find out about RPC-level + errors. The methods provided by the RpcController interface are intended + to be a "least common denominator" set of features which we expect all + implementations to support. Specific implementations may provide more + advanced features (e.g. deadline propagation). + """ + + # Client-side methods below + + def Reset(self): + """Resets the RpcController to its initial state. + + After the RpcController has been reset, it may be reused in + a new call. Must not be called while an RPC is in progress. + """ + raise NotImplementedError + + def Failed(self): + """Returns true if the call failed. + + After a call has finished, returns true if the call failed. The possible + reasons for failure depend on the RPC implementation. Failed() must not + be called before a call has finished. If Failed() returns true, the + contents of the response message are undefined. + """ + raise NotImplementedError + + def ErrorText(self): + """If Failed is true, returns a human-readable description of the error.""" + raise NotImplementedError + + def StartCancel(self): + """Initiate cancellation. + + Advises the RPC system that the caller desires that the RPC call be + canceled. The RPC system may cancel it immediately, may wait awhile and + then cancel it, or may not even cancel the call at all. If the call is + canceled, the "done" callback will still be called and the RpcController + will indicate that the call failed at that time. + """ + raise NotImplementedError + + # Server-side methods below + + def SetFailed(self, reason): + """Sets a failure reason. + + Causes Failed() to return true on the client side. "reason" will be + incorporated into the message returned by ErrorText(). If you find + you need to return machine-readable information about failures, you + should incorporate it into your response protocol buffer and should + NOT call SetFailed(). + """ + raise NotImplementedError + + def IsCanceled(self): + """Checks if the client cancelled the RPC. + + If true, indicates that the client canceled the RPC, so the server may + as well give up on replying to it. The server should still call the + final "done" callback. + """ + raise NotImplementedError + + def NotifyOnCancel(self, callback): + """Sets a callback to invoke on cancel. + + Asks that the given callback be called when the RPC is canceled. The + callback will always be called exactly once. If the RPC completes without + being canceled, the callback will be called after completion. If the RPC + has already been canceled when NotifyOnCancel() is called, the callback + will be called immediately. + + NotifyOnCancel() must be called no more than once per request. + """ + raise NotImplementedError + + +class RpcChannel(object): + + """Abstract interface for an RPC channel. + + An RpcChannel represents a communication line to a service which can be used + to call that service's methods. The service may be running on another + machine. Normally, you should not use an RpcChannel directly, but instead + construct a stub {@link Service} wrapping it. Example: + + Example: + RpcChannel channel = rpcImpl.Channel("remotehost.example.com:1234") + RpcController controller = rpcImpl.Controller() + MyService service = MyService_Stub(channel) + service.MyMethod(controller, request, callback) + """ + + def CallMethod(self, method_descriptor, rpc_controller, + request, response_class, done): + """Calls the method identified by the descriptor. + + Call the given method of the remote service. The signature of this + procedure looks the same as Service.CallMethod(), but the requirements + are less strict in one important way: the request object doesn't have to + be of any specific class as long as its descriptor is method.input_type. + """ + raise NotImplementedError diff --git a/google/protobuf/service_reflection.py b/google/protobuf/service_reflection.py new file mode 100644 index 0000000..7ba3d0b --- /dev/null +++ b/google/protobuf/service_reflection.py @@ -0,0 +1,272 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Contains metaclasses used to create protocol service and service stub +classes from ServiceDescriptor objects at runtime. + +The GeneratedServiceType and GeneratedServiceStubType metaclasses are used to +inject all useful functionality into the classes output by the protocol +compiler at compile-time. +""" + +__author__ = 'petar@google.com (Petar Petrov)' + + +class GeneratedServiceType(type): + + """Metaclass for service classes created at runtime from ServiceDescriptors. + + Implementations for all methods described in the Service class are added here + by this class. We also create properties to allow getting/setting all fields + in the protocol message. + + The protocol compiler currently uses this metaclass to create protocol service + classes at runtime. Clients can also manually create their own classes at + runtime, as in this example:: + + mydescriptor = ServiceDescriptor(.....) + class MyProtoService(service.Service): + __metaclass__ = GeneratedServiceType + DESCRIPTOR = mydescriptor + myservice_instance = MyProtoService() + # ... + """ + + _DESCRIPTOR_KEY = 'DESCRIPTOR' + + def __init__(cls, name, bases, dictionary): + """Creates a message service class. + + Args: + name: Name of the class (ignored, but required by the metaclass + protocol). + bases: Base classes of the class being constructed. + dictionary: The class dictionary of the class being constructed. + dictionary[_DESCRIPTOR_KEY] must contain a ServiceDescriptor object + describing this protocol service type. + """ + # Don't do anything if this class doesn't have a descriptor. This happens + # when a service class is subclassed. + if GeneratedServiceType._DESCRIPTOR_KEY not in dictionary: + return + + descriptor = dictionary[GeneratedServiceType._DESCRIPTOR_KEY] + service_builder = _ServiceBuilder(descriptor) + service_builder.BuildService(cls) + cls.DESCRIPTOR = descriptor + + +class GeneratedServiceStubType(GeneratedServiceType): + + """Metaclass for service stubs created at runtime from ServiceDescriptors. + + This class has similar responsibilities as GeneratedServiceType, except that + it creates the service stub classes. + """ + + _DESCRIPTOR_KEY = 'DESCRIPTOR' + + def __init__(cls, name, bases, dictionary): + """Creates a message service stub class. + + Args: + name: Name of the class (ignored, here). + bases: Base classes of the class being constructed. + dictionary: The class dictionary of the class being constructed. + dictionary[_DESCRIPTOR_KEY] must contain a ServiceDescriptor object + describing this protocol service type. + """ + super(GeneratedServiceStubType, cls).__init__(name, bases, dictionary) + # Don't do anything if this class doesn't have a descriptor. This happens + # when a service stub is subclassed. + if GeneratedServiceStubType._DESCRIPTOR_KEY not in dictionary: + return + + descriptor = dictionary[GeneratedServiceStubType._DESCRIPTOR_KEY] + service_stub_builder = _ServiceStubBuilder(descriptor) + service_stub_builder.BuildServiceStub(cls) + + +class _ServiceBuilder(object): + + """This class constructs a protocol service class using a service descriptor. + + Given a service descriptor, this class constructs a class that represents + the specified service descriptor. One service builder instance constructs + exactly one service class. That means all instances of that class share the + same builder. + """ + + def __init__(self, service_descriptor): + """Initializes an instance of the service class builder. + + Args: + service_descriptor: ServiceDescriptor to use when constructing the + service class. + """ + self.descriptor = service_descriptor + + def BuildService(builder, cls): + """Constructs the service class. + + Args: + cls: The class that will be constructed. + """ + + # CallMethod needs to operate with an instance of the Service class. This + # internal wrapper function exists only to be able to pass the service + # instance to the method that does the real CallMethod work. + # Making sure to use exact argument names from the abstract interface in + # service.py to match the type signature + def _WrapCallMethod(self, method_descriptor, rpc_controller, request, done): + return builder._CallMethod(self, method_descriptor, rpc_controller, + request, done) + + def _WrapGetRequestClass(self, method_descriptor): + return builder._GetRequestClass(method_descriptor) + + def _WrapGetResponseClass(self, method_descriptor): + return builder._GetResponseClass(method_descriptor) + + builder.cls = cls + cls.CallMethod = _WrapCallMethod + cls.GetDescriptor = staticmethod(lambda: builder.descriptor) + cls.GetDescriptor.__doc__ = 'Returns the service descriptor.' + cls.GetRequestClass = _WrapGetRequestClass + cls.GetResponseClass = _WrapGetResponseClass + for method in builder.descriptor.methods: + setattr(cls, method.name, builder._GenerateNonImplementedMethod(method)) + + def _CallMethod(self, srvc, method_descriptor, + rpc_controller, request, callback): + """Calls the method described by a given method descriptor. + + Args: + srvc: Instance of the service for which this method is called. + method_descriptor: Descriptor that represent the method to call. + rpc_controller: RPC controller to use for this method's execution. + request: Request protocol message. + callback: A callback to invoke after the method has completed. + """ + if method_descriptor.containing_service != self.descriptor: + raise RuntimeError( + 'CallMethod() given method descriptor for wrong service type.') + method = getattr(srvc, method_descriptor.name) + return method(rpc_controller, request, callback) + + def _GetRequestClass(self, method_descriptor): + """Returns the class of the request protocol message. + + Args: + method_descriptor: Descriptor of the method for which to return the + request protocol message class. + + Returns: + A class that represents the input protocol message of the specified + method. + """ + if method_descriptor.containing_service != self.descriptor: + raise RuntimeError( + 'GetRequestClass() given method descriptor for wrong service type.') + return method_descriptor.input_type._concrete_class + + def _GetResponseClass(self, method_descriptor): + """Returns the class of the response protocol message. + + Args: + method_descriptor: Descriptor of the method for which to return the + response protocol message class. + + Returns: + A class that represents the output protocol message of the specified + method. + """ + if method_descriptor.containing_service != self.descriptor: + raise RuntimeError( + 'GetResponseClass() given method descriptor for wrong service type.') + return method_descriptor.output_type._concrete_class + + def _GenerateNonImplementedMethod(self, method): + """Generates and returns a method that can be set for a service methods. + + Args: + method: Descriptor of the service method for which a method is to be + generated. + + Returns: + A method that can be added to the service class. + """ + return lambda inst, rpc_controller, request, callback: ( + self._NonImplementedMethod(method.name, rpc_controller, callback)) + + def _NonImplementedMethod(self, method_name, rpc_controller, callback): + """The body of all methods in the generated service class. + + Args: + method_name: Name of the method being executed. + rpc_controller: RPC controller used to execute this method. + callback: A callback which will be invoked when the method finishes. + """ + rpc_controller.SetFailed('Method %s not implemented.' % method_name) + callback(None) + + +class _ServiceStubBuilder(object): + + """Constructs a protocol service stub class using a service descriptor. + + Given a service descriptor, this class constructs a suitable stub class. + A stub is just a type-safe wrapper around an RpcChannel which emulates a + local implementation of the service. + + One service stub builder instance constructs exactly one class. It means all + instances of that class share the same service stub builder. + """ + + def __init__(self, service_descriptor): + """Initializes an instance of the service stub class builder. + + Args: + service_descriptor: ServiceDescriptor to use when constructing the + stub class. + """ + self.descriptor = service_descriptor + + def BuildServiceStub(self, cls): + """Constructs the stub class. + + Args: + cls: The class that will be constructed. + """ + + def _ServiceStubInit(stub, rpc_channel): + stub.rpc_channel = rpc_channel + self.cls = cls + cls.__init__ = _ServiceStubInit + for method in self.descriptor.methods: + setattr(cls, method.name, self._GenerateStubMethod(method)) + + def _GenerateStubMethod(self, method): + return (lambda inst, rpc_controller, request, callback=None: + self._StubMethod(inst, method, rpc_controller, request, callback)) + + def _StubMethod(self, stub, method_descriptor, + rpc_controller, request, callback): + """The body of all service methods in the generated stub class. + + Args: + stub: Stub instance. + method_descriptor: Descriptor of the invoked method. + rpc_controller: Rpc controller to execute the method. + request: Request protocol message. + callback: A callback to execute when the method finishes. + Returns: + Response message (in case of blocking call). + """ + return stub.rpc_channel.CallMethod( + method_descriptor, rpc_controller, request, + method_descriptor.output_type._concrete_class, callback) diff --git a/google/protobuf/source_context_pb2.py b/google/protobuf/source_context_pb2.py new file mode 100644 index 0000000..788a848 --- /dev/null +++ b/google/protobuf/source_context_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: google/protobuf/source_context.proto +# Protobuf Python Version: 5.27.3 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 3, + '', + 'google/protobuf/source_context.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$google/protobuf/source_context.proto\x12\x0fgoogle.protobuf\",\n\rSourceContext\x12\x1b\n\tfile_name\x18\x01 \x01(\tR\x08\x66ileNameB\x8a\x01\n\x13\x63om.google.protobufB\x12SourceContextProtoP\x01Z6google.golang.org/protobuf/types/known/sourcecontextpb\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.source_context_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\023com.google.protobufB\022SourceContextProtoP\001Z6google.golang.org/protobuf/types/known/sourcecontextpb\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' + _globals['_SOURCECONTEXT']._serialized_start=57 + _globals['_SOURCECONTEXT']._serialized_end=101 +# @@protoc_insertion_point(module_scope) diff --git a/google/protobuf/struct_pb2.py b/google/protobuf/struct_pb2.py new file mode 100644 index 0000000..1252c75 --- /dev/null +++ b/google/protobuf/struct_pb2.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: google/protobuf/struct.proto +# Protobuf Python Version: 5.27.3 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 3, + '', + 'google/protobuf/struct.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1cgoogle/protobuf/struct.proto\x12\x0fgoogle.protobuf\"\x98\x01\n\x06Struct\x12;\n\x06\x66ields\x18\x01 \x03(\x0b\x32#.google.protobuf.Struct.FieldsEntryR\x06\x66ields\x1aQ\n\x0b\x46ieldsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12,\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.ValueR\x05value:\x02\x38\x01\"\xb2\x02\n\x05Value\x12;\n\nnull_value\x18\x01 \x01(\x0e\x32\x1a.google.protobuf.NullValueH\x00R\tnullValue\x12#\n\x0cnumber_value\x18\x02 \x01(\x01H\x00R\x0bnumberValue\x12#\n\x0cstring_value\x18\x03 \x01(\tH\x00R\x0bstringValue\x12\x1f\n\nbool_value\x18\x04 \x01(\x08H\x00R\tboolValue\x12<\n\x0cstruct_value\x18\x05 \x01(\x0b\x32\x17.google.protobuf.StructH\x00R\x0bstructValue\x12;\n\nlist_value\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.ListValueH\x00R\tlistValueB\x06\n\x04kind\";\n\tListValue\x12.\n\x06values\x18\x01 \x03(\x0b\x32\x16.google.protobuf.ValueR\x06values*\x1b\n\tNullValue\x12\x0e\n\nNULL_VALUE\x10\x00\x42\x7f\n\x13\x63om.google.protobufB\x0bStructProtoP\x01Z/google.golang.org/protobuf/types/known/structpb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.struct_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\023com.google.protobufB\013StructProtoP\001Z/google.golang.org/protobuf/types/known/structpb\370\001\001\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' + _globals['_STRUCT_FIELDSENTRY']._loaded_options = None + _globals['_STRUCT_FIELDSENTRY']._serialized_options = b'8\001' + _globals['_NULLVALUE']._serialized_start=574 + _globals['_NULLVALUE']._serialized_end=601 + _globals['_STRUCT']._serialized_start=50 + _globals['_STRUCT']._serialized_end=202 + _globals['_STRUCT_FIELDSENTRY']._serialized_start=121 + _globals['_STRUCT_FIELDSENTRY']._serialized_end=202 + _globals['_VALUE']._serialized_start=205 + _globals['_VALUE']._serialized_end=511 + _globals['_LISTVALUE']._serialized_start=513 + _globals['_LISTVALUE']._serialized_end=572 +# @@protoc_insertion_point(module_scope) diff --git a/google/protobuf/symbol_database.py b/google/protobuf/symbol_database.py new file mode 100644 index 0000000..1941e81 --- /dev/null +++ b/google/protobuf/symbol_database.py @@ -0,0 +1,197 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""A database of Python protocol buffer generated symbols. + +SymbolDatabase is the MessageFactory for messages generated at compile time, +and makes it easy to create new instances of a registered type, given only the +type's protocol buffer symbol name. + +Example usage:: + + db = symbol_database.SymbolDatabase() + + # Register symbols of interest, from one or multiple files. + db.RegisterFileDescriptor(my_proto_pb2.DESCRIPTOR) + db.RegisterMessage(my_proto_pb2.MyMessage) + db.RegisterEnumDescriptor(my_proto_pb2.MyEnum.DESCRIPTOR) + + # The database can be used as a MessageFactory, to generate types based on + # their name: + types = db.GetMessages(['my_proto.proto']) + my_message_instance = types['MyMessage']() + + # The database's underlying descriptor pool can be queried, so it's not + # necessary to know a type's filename to be able to generate it: + filename = db.pool.FindFileContainingSymbol('MyMessage') + my_message_instance = db.GetMessages([filename])['MyMessage']() + + # This functionality is also provided directly via a convenience method: + my_message_instance = db.GetSymbol('MyMessage')() +""" + +import warnings + +from google.protobuf.internal import api_implementation +from google.protobuf import descriptor_pool +from google.protobuf import message_factory + + +class SymbolDatabase(): + """A database of Python generated symbols.""" + + # local cache of registered classes. + _classes = {} + + def __init__(self, pool=None): + """Initializes a new SymbolDatabase.""" + self.pool = pool or descriptor_pool.DescriptorPool() + + def GetPrototype(self, descriptor): + warnings.warn('SymbolDatabase.GetPrototype() is deprecated. Please ' + 'use message_factory.GetMessageClass() instead. ' + 'SymbolDatabase.GetPrototype() will be removed soon.') + return message_factory.GetMessageClass(descriptor) + + def CreatePrototype(self, descriptor): + warnings.warn('Directly call CreatePrototype() is wrong. Please use ' + 'message_factory.GetMessageClass() instead. ' + 'SymbolDatabase.CreatePrototype() will be removed soon.') + return message_factory._InternalCreateMessageClass(descriptor) + + def GetMessages(self, files): + warnings.warn('SymbolDatabase.GetMessages() is deprecated. Please use ' + 'message_factory.GetMessageClassedForFiles() instead. ' + 'SymbolDatabase.GetMessages() will be removed soon.') + return message_factory.GetMessageClassedForFiles(files, self.pool) + + def RegisterMessage(self, message): + """Registers the given message type in the local database. + + Calls to GetSymbol() and GetMessages() will return messages registered here. + + Args: + message: A :class:`google.protobuf.message.Message` subclass (or + instance); its descriptor will be registered. + + Returns: + The provided message. + """ + + desc = message.DESCRIPTOR + self._classes[desc] = message + self.RegisterMessageDescriptor(desc) + return message + + def RegisterMessageDescriptor(self, message_descriptor): + """Registers the given message descriptor in the local database. + + Args: + message_descriptor (Descriptor): the message descriptor to add. + """ + if api_implementation.Type() == 'python': + # pylint: disable=protected-access + self.pool._AddDescriptor(message_descriptor) + + def RegisterEnumDescriptor(self, enum_descriptor): + """Registers the given enum descriptor in the local database. + + Args: + enum_descriptor (EnumDescriptor): The enum descriptor to register. + + Returns: + EnumDescriptor: The provided descriptor. + """ + if api_implementation.Type() == 'python': + # pylint: disable=protected-access + self.pool._AddEnumDescriptor(enum_descriptor) + return enum_descriptor + + def RegisterServiceDescriptor(self, service_descriptor): + """Registers the given service descriptor in the local database. + + Args: + service_descriptor (ServiceDescriptor): the service descriptor to + register. + """ + if api_implementation.Type() == 'python': + # pylint: disable=protected-access + self.pool._AddServiceDescriptor(service_descriptor) + + def RegisterFileDescriptor(self, file_descriptor): + """Registers the given file descriptor in the local database. + + Args: + file_descriptor (FileDescriptor): The file descriptor to register. + """ + if api_implementation.Type() == 'python': + # pylint: disable=protected-access + self.pool._InternalAddFileDescriptor(file_descriptor) + + def GetSymbol(self, symbol): + """Tries to find a symbol in the local database. + + Currently, this method only returns message.Message instances, however, if + may be extended in future to support other symbol types. + + Args: + symbol (str): a protocol buffer symbol. + + Returns: + A Python class corresponding to the symbol. + + Raises: + KeyError: if the symbol could not be found. + """ + + return self._classes[self.pool.FindMessageTypeByName(symbol)] + + def GetMessages(self, files): + # TODO: Fix the differences with MessageFactory. + """Gets all registered messages from a specified file. + + Only messages already created and registered will be returned; (this is the + case for imported _pb2 modules) + But unlike MessageFactory, this version also returns already defined nested + messages, but does not register any message extensions. + + Args: + files (list[str]): The file names to extract messages from. + + Returns: + A dictionary mapping proto names to the message classes. + + Raises: + KeyError: if a file could not be found. + """ + + def _GetAllMessages(desc): + """Walk a message Descriptor and recursively yields all message names.""" + yield desc + for msg_desc in desc.nested_types: + for nested_desc in _GetAllMessages(msg_desc): + yield nested_desc + + result = {} + for file_name in files: + file_desc = self.pool.FindFileByName(file_name) + for msg_desc in file_desc.message_types_by_name.values(): + for desc in _GetAllMessages(msg_desc): + try: + result[desc.full_name] = self._classes[desc] + except KeyError: + # This descriptor has no registered class, skip it. + pass + return result + + +_DEFAULT = SymbolDatabase(pool=descriptor_pool.Default()) + + +def Default(): + """Returns the default SymbolDatabase.""" + return _DEFAULT diff --git a/google/protobuf/testdata/__init__.py b/google/protobuf/testdata/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/google/protobuf/text_encoding.py b/google/protobuf/text_encoding.py new file mode 100644 index 0000000..03c27dc --- /dev/null +++ b/google/protobuf/text_encoding.py @@ -0,0 +1,106 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Encoding related utilities.""" +import re + +def _AsciiIsPrint(i): + return i >= 32 and i < 127 + +def _MakeStrEscapes(): + ret = {} + for i in range(0, 128): + if not _AsciiIsPrint(i): + ret[i] = r'\%03o' % i + ret[ord('\t')] = r'\t' # optional escape + ret[ord('\n')] = r'\n' # optional escape + ret[ord('\r')] = r'\r' # optional escape + ret[ord('"')] = r'\"' # necessary escape + ret[ord('\'')] = r"\'" # optional escape + ret[ord('\\')] = r'\\' # necessary escape + return ret + +# Maps int -> char, performing string escapes. +_str_escapes = _MakeStrEscapes() + +# Maps int -> char, performing byte escaping and string escapes +_byte_escapes = {i: chr(i) for i in range(0, 256)} +_byte_escapes.update(_str_escapes) +_byte_escapes.update({i: r'\%03o' % i for i in range(128, 256)}) + + +def _DecodeUtf8EscapeErrors(text_bytes): + ret = '' + while text_bytes: + try: + ret += text_bytes.decode('utf-8').translate(_str_escapes) + text_bytes = '' + except UnicodeDecodeError as e: + ret += text_bytes[:e.start].decode('utf-8').translate(_str_escapes) + ret += _byte_escapes[text_bytes[e.start]] + text_bytes = text_bytes[e.start+1:] + return ret + + +def CEscape(text, as_utf8) -> str: + """Escape a bytes string for use in an text protocol buffer. + + Args: + text: A byte string to be escaped. + as_utf8: Specifies if result may contain non-ASCII characters. + In Python 3 this allows unescaped non-ASCII Unicode characters. + In Python 2 the return value will be valid UTF-8 rather than only ASCII. + Returns: + Escaped string (str). + """ + # Python's text.encode() 'string_escape' or 'unicode_escape' codecs do not + # satisfy our needs; they encodes unprintable characters using two-digit hex + # escapes whereas our C++ unescaping function allows hex escapes to be any + # length. So, "\0011".encode('string_escape') ends up being "\\x011", which + # will be decoded in C++ as a single-character string with char code 0x11. + text_is_unicode = isinstance(text, str) + if as_utf8: + if text_is_unicode: + return text.translate(_str_escapes) + else: + return _DecodeUtf8EscapeErrors(text) + else: + if text_is_unicode: + text = text.encode('utf-8') + return ''.join([_byte_escapes[c] for c in text]) + + +_CUNESCAPE_HEX = re.compile(r'(\\+)x([0-9a-fA-F])(?![0-9a-fA-F])') + + +def CUnescape(text: str) -> bytes: + """Unescape a text string with C-style escape sequences to UTF-8 bytes. + + Args: + text: The data to parse in a str. + Returns: + A byte string. + """ + + def ReplaceHex(m): + # Only replace the match if the number of leading back slashes is odd. i.e. + # the slash itself is not escaped. + if len(m.group(1)) & 1: + return m.group(1) + 'x0' + m.group(2) + return m.group(0) + + # This is required because the 'string_escape' encoding doesn't + # allow single-digit hex escapes (like '\xf'). + result = _CUNESCAPE_HEX.sub(ReplaceHex, text) + + # Replaces Unicode escape sequences with their character equivalents. + result = result.encode('raw_unicode_escape').decode('raw_unicode_escape') + # Encode Unicode characters as UTF-8, then decode to Latin-1 escaping + # unprintable characters. + result = result.encode('utf-8').decode('unicode_escape') + # Convert Latin-1 text back to a byte string (latin-1 codec also works here). + return result.encode('latin-1') diff --git a/google/protobuf/text_format.py b/google/protobuf/text_format.py new file mode 100644 index 0000000..bc61109 --- /dev/null +++ b/google/protobuf/text_format.py @@ -0,0 +1,1864 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Contains routines for printing protocol messages in text format. + +Simple usage example:: + + # Create a proto object and serialize it to a text proto string. + message = my_proto_pb2.MyMessage(foo='bar') + text_proto = text_format.MessageToString(message) + + # Parse a text proto string. + message = text_format.Parse(text_proto, my_proto_pb2.MyMessage()) +""" + +__author__ = 'kenton@google.com (Kenton Varda)' + +# TODO Import thread contention leads to test failures. +import encodings.raw_unicode_escape # pylint: disable=unused-import +import encodings.unicode_escape # pylint: disable=unused-import +import io +import math +import re + +from google.protobuf.internal import decoder +from google.protobuf.internal import type_checkers +from google.protobuf import descriptor +from google.protobuf import text_encoding +from google.protobuf import unknown_fields + +# pylint: disable=g-import-not-at-top +__all__ = ['MessageToString', 'Parse', 'PrintMessage', 'PrintField', + 'PrintFieldValue', 'Merge', 'MessageToBytes'] + +_INTEGER_CHECKERS = (type_checkers.Uint32ValueChecker(), + type_checkers.Int32ValueChecker(), + type_checkers.Uint64ValueChecker(), + type_checkers.Int64ValueChecker()) +_FLOAT_INFINITY = re.compile('-?inf(?:inity)?f?$', re.IGNORECASE) +_FLOAT_NAN = re.compile('nanf?$', re.IGNORECASE) +_QUOTES = frozenset(("'", '"')) +_ANY_FULL_TYPE_NAME = 'google.protobuf.Any' +_DEBUG_STRING_SILENT_MARKER = '\t ' + +_as_utf8_default = True + + +class Error(Exception): + """Top-level module error for text_format.""" + + +class ParseError(Error): + """Thrown in case of text parsing or tokenizing error.""" + + def __init__(self, message=None, line=None, column=None): + if message is not None and line is not None: + loc = str(line) + if column is not None: + loc += ':{0}'.format(column) + message = '{0} : {1}'.format(loc, message) + if message is not None: + super(ParseError, self).__init__(message) + else: + super(ParseError, self).__init__() + self._line = line + self._column = column + + def GetLine(self): + return self._line + + def GetColumn(self): + return self._column + + +class TextWriter(object): + + def __init__(self, as_utf8): + self._writer = io.StringIO() + + def write(self, val): + return self._writer.write(val) + + def close(self): + return self._writer.close() + + def getvalue(self): + return self._writer.getvalue() + + +def MessageToString( + message, + as_utf8=_as_utf8_default, + as_one_line=False, + use_short_repeated_primitives=False, + pointy_brackets=False, + use_index_order=False, + float_format=None, + double_format=None, + use_field_number=False, + descriptor_pool=None, + indent=0, + message_formatter=None, + print_unknown_fields=False, + force_colon=False) -> str: + """Convert protobuf message to text format. + + Double values can be formatted compactly with 15 digits of + precision (which is the most that IEEE 754 "double" can guarantee) + using double_format='.15g'. To ensure that converting to text and back to a + proto will result in an identical value, double_format='.17g' should be used. + + Args: + message: The protocol buffers message. + as_utf8: Return unescaped Unicode for non-ASCII characters. + as_one_line: Don't introduce newlines between fields. + use_short_repeated_primitives: Use short repeated format for primitives. + pointy_brackets: If True, use angle brackets instead of curly braces for + nesting. + use_index_order: If True, fields of a proto message will be printed using + the order defined in source code instead of the field number, extensions + will be printed at the end of the message and their relative order is + determined by the extension number. By default, use the field number + order. + float_format (str): If set, use this to specify float field formatting + (per the "Format Specification Mini-Language"); otherwise, shortest float + that has same value in wire will be printed. Also affect double field + if double_format is not set but float_format is set. + double_format (str): If set, use this to specify double field formatting + (per the "Format Specification Mini-Language"); if it is not set but + float_format is set, use float_format. Otherwise, use ``str()`` + use_field_number: If True, print field numbers instead of names. + descriptor_pool (DescriptorPool): Descriptor pool used to resolve Any types. + indent (int): The initial indent level, in terms of spaces, for pretty + print. + message_formatter (function(message, indent, as_one_line) -> unicode|None): + Custom formatter for selected sub-messages (usually based on message + type). Use to pretty print parts of the protobuf for easier diffing. + print_unknown_fields: If True, unknown fields will be printed. + force_colon: If set, a colon will be added after the field name even if the + field is a proto message. + + Returns: + str: A string of the text formatted protocol buffer message. + """ + out = TextWriter(as_utf8) + printer = _Printer( + out, + indent, + as_utf8, + as_one_line, + use_short_repeated_primitives, + pointy_brackets, + use_index_order, + float_format, + double_format, + use_field_number, + descriptor_pool, + message_formatter, + print_unknown_fields=print_unknown_fields, + force_colon=force_colon) + printer.PrintMessage(message) + result = out.getvalue() + out.close() + if as_one_line: + return result.rstrip() + return result + + +def MessageToBytes(message, **kwargs) -> bytes: + """Convert protobuf message to encoded text format. See MessageToString.""" + text = MessageToString(message, **kwargs) + if isinstance(text, bytes): + return text + codec = 'utf-8' if kwargs.get('as_utf8') else 'ascii' + return text.encode(codec) + + +def _IsMapEntry(field): + return (field.type == descriptor.FieldDescriptor.TYPE_MESSAGE and + field.message_type.has_options and + field.message_type.GetOptions().map_entry) + + +def _IsGroupLike(field): + """Determines if a field is consistent with a proto2 group. + + Args: + field: The field descriptor. + + Returns: + True if this field is group-like, false otherwise. + """ + # Groups are always tag-delimited. + if field.type != descriptor.FieldDescriptor.TYPE_GROUP: + return False + + # Group fields always are always the lowercase type name. + if field.name != field.message_type.name.lower(): + return False + + if field.message_type.file != field.file: + return False + + # Group messages are always defined in the same scope as the field. File + # level extensions will compare NULL == NULL here, which is why the file + # comparison above is necessary to ensure both come from the same file. + return ( + field.message_type.containing_type == field.extension_scope + if field.is_extension + else field.message_type.containing_type == field.containing_type + ) + + +def PrintMessage(message, + out, + indent=0, + as_utf8=_as_utf8_default, + as_one_line=False, + use_short_repeated_primitives=False, + pointy_brackets=False, + use_index_order=False, + float_format=None, + double_format=None, + use_field_number=False, + descriptor_pool=None, + message_formatter=None, + print_unknown_fields=False, + force_colon=False): + """Convert the message to text format and write it to the out stream. + + Args: + message: The Message object to convert to text format. + out: A file handle to write the message to. + indent: The initial indent level for pretty print. + as_utf8: Return unescaped Unicode for non-ASCII characters. + as_one_line: Don't introduce newlines between fields. + use_short_repeated_primitives: Use short repeated format for primitives. + pointy_brackets: If True, use angle brackets instead of curly braces for + nesting. + use_index_order: If True, print fields of a proto message using the order + defined in source code instead of the field number. By default, use the + field number order. + float_format: If set, use this to specify float field formatting + (per the "Format Specification Mini-Language"); otherwise, shortest + float that has same value in wire will be printed. Also affect double + field if double_format is not set but float_format is set. + double_format: If set, use this to specify double field formatting + (per the "Format Specification Mini-Language"); if it is not set but + float_format is set, use float_format. Otherwise, str() is used. + use_field_number: If True, print field numbers instead of names. + descriptor_pool: A DescriptorPool used to resolve Any types. + message_formatter: A function(message, indent, as_one_line): unicode|None + to custom format selected sub-messages (usually based on message type). + Use to pretty print parts of the protobuf for easier diffing. + print_unknown_fields: If True, unknown fields will be printed. + force_colon: If set, a colon will be added after the field name even if + the field is a proto message. + """ + printer = _Printer( + out=out, indent=indent, as_utf8=as_utf8, + as_one_line=as_one_line, + use_short_repeated_primitives=use_short_repeated_primitives, + pointy_brackets=pointy_brackets, + use_index_order=use_index_order, + float_format=float_format, + double_format=double_format, + use_field_number=use_field_number, + descriptor_pool=descriptor_pool, + message_formatter=message_formatter, + print_unknown_fields=print_unknown_fields, + force_colon=force_colon) + printer.PrintMessage(message) + + +def PrintField(field, + value, + out, + indent=0, + as_utf8=_as_utf8_default, + as_one_line=False, + use_short_repeated_primitives=False, + pointy_brackets=False, + use_index_order=False, + float_format=None, + double_format=None, + message_formatter=None, + print_unknown_fields=False, + force_colon=False): + """Print a single field name/value pair.""" + printer = _Printer(out, indent, as_utf8, as_one_line, + use_short_repeated_primitives, pointy_brackets, + use_index_order, float_format, double_format, + message_formatter=message_formatter, + print_unknown_fields=print_unknown_fields, + force_colon=force_colon) + printer.PrintField(field, value) + + +def PrintFieldValue(field, + value, + out, + indent=0, + as_utf8=_as_utf8_default, + as_one_line=False, + use_short_repeated_primitives=False, + pointy_brackets=False, + use_index_order=False, + float_format=None, + double_format=None, + message_formatter=None, + print_unknown_fields=False, + force_colon=False): + """Print a single field value (not including name).""" + printer = _Printer(out, indent, as_utf8, as_one_line, + use_short_repeated_primitives, pointy_brackets, + use_index_order, float_format, double_format, + message_formatter=message_formatter, + print_unknown_fields=print_unknown_fields, + force_colon=force_colon) + printer.PrintFieldValue(field, value) + + +def _BuildMessageFromTypeName(type_name, descriptor_pool): + """Returns a protobuf message instance. + + Args: + type_name: Fully-qualified protobuf message type name string. + descriptor_pool: DescriptorPool instance. + + Returns: + A Message instance of type matching type_name, or None if the a Descriptor + wasn't found matching type_name. + """ + # pylint: disable=g-import-not-at-top + if descriptor_pool is None: + from google.protobuf import descriptor_pool as pool_mod + descriptor_pool = pool_mod.Default() + from google.protobuf import message_factory + try: + message_descriptor = descriptor_pool.FindMessageTypeByName(type_name) + except KeyError: + return None + message_type = message_factory.GetMessageClass(message_descriptor) + return message_type() + + +# These values must match WireType enum in //google/protobuf/wire_format.h. +WIRETYPE_LENGTH_DELIMITED = 2 +WIRETYPE_START_GROUP = 3 + + +class _Printer(object): + """Text format printer for protocol message.""" + + def __init__( + self, + out, + indent=0, + as_utf8=_as_utf8_default, + as_one_line=False, + use_short_repeated_primitives=False, + pointy_brackets=False, + use_index_order=False, + float_format=None, + double_format=None, + use_field_number=False, + descriptor_pool=None, + message_formatter=None, + print_unknown_fields=False, + force_colon=False): + """Initialize the Printer. + + Double values can be formatted compactly with 15 digits of precision + (which is the most that IEEE 754 "double" can guarantee) using + double_format='.15g'. To ensure that converting to text and back to a proto + will result in an identical value, double_format='.17g' should be used. + + Args: + out: To record the text format result. + indent: The initial indent level for pretty print. + as_utf8: Return unescaped Unicode for non-ASCII characters. + as_one_line: Don't introduce newlines between fields. + use_short_repeated_primitives: Use short repeated format for primitives. + pointy_brackets: If True, use angle brackets instead of curly braces for + nesting. + use_index_order: If True, print fields of a proto message using the order + defined in source code instead of the field number. By default, use the + field number order. + float_format: If set, use this to specify float field formatting + (per the "Format Specification Mini-Language"); otherwise, shortest + float that has same value in wire will be printed. Also affect double + field if double_format is not set but float_format is set. + double_format: If set, use this to specify double field formatting + (per the "Format Specification Mini-Language"); if it is not set but + float_format is set, use float_format. Otherwise, str() is used. + use_field_number: If True, print field numbers instead of names. + descriptor_pool: A DescriptorPool used to resolve Any types. + message_formatter: A function(message, indent, as_one_line): unicode|None + to custom format selected sub-messages (usually based on message type). + Use to pretty print parts of the protobuf for easier diffing. + print_unknown_fields: If True, unknown fields will be printed. + force_colon: If set, a colon will be added after the field name even if + the field is a proto message. + """ + self.out = out + self.indent = indent + self.as_utf8 = as_utf8 + self.as_one_line = as_one_line + self.use_short_repeated_primitives = use_short_repeated_primitives + self.pointy_brackets = pointy_brackets + self.use_index_order = use_index_order + self.float_format = float_format + if double_format is not None: + self.double_format = double_format + else: + self.double_format = float_format + self.use_field_number = use_field_number + self.descriptor_pool = descriptor_pool + self.message_formatter = message_formatter + self.print_unknown_fields = print_unknown_fields + self.force_colon = force_colon + + def _TryPrintAsAnyMessage(self, message): + """Serializes if message is a google.protobuf.Any field.""" + if '/' not in message.type_url: + return False + packed_message = _BuildMessageFromTypeName(message.TypeName(), + self.descriptor_pool) + if packed_message: + packed_message.MergeFromString(message.value) + colon = ':' if self.force_colon else '' + self.out.write('%s[%s]%s ' % (self.indent * ' ', message.type_url, colon)) + self._PrintMessageFieldValue(packed_message) + self.out.write(' ' if self.as_one_line else '\n') + return True + else: + return False + + def _TryCustomFormatMessage(self, message): + formatted = self.message_formatter(message, self.indent, self.as_one_line) + if formatted is None: + return False + + out = self.out + out.write(' ' * self.indent) + out.write(formatted) + out.write(' ' if self.as_one_line else '\n') + return True + + def PrintMessage(self, message): + """Convert protobuf message to text format. + + Args: + message: The protocol buffers message. + """ + if self.message_formatter and self._TryCustomFormatMessage(message): + return + if (message.DESCRIPTOR.full_name == _ANY_FULL_TYPE_NAME and + self._TryPrintAsAnyMessage(message)): + return + fields = message.ListFields() + if self.use_index_order: + fields.sort( + key=lambda x: x[0].number if x[0].is_extension else x[0].index) + for field, value in fields: + if _IsMapEntry(field): + for key in sorted(value): + # This is slow for maps with submessage entries because it copies the + # entire tree. Unfortunately this would take significant refactoring + # of this file to work around. + # + # TODO: refactor and optimize if this becomes an issue. + entry_submsg = value.GetEntryClass()(key=key, value=value[key]) + self.PrintField(field, entry_submsg) + elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: + if (self.use_short_repeated_primitives + and field.cpp_type != descriptor.FieldDescriptor.CPPTYPE_MESSAGE + and field.cpp_type != descriptor.FieldDescriptor.CPPTYPE_STRING): + self._PrintShortRepeatedPrimitivesValue(field, value) + else: + for element in value: + self.PrintField(field, element) + else: + self.PrintField(field, value) + + if self.print_unknown_fields: + self._PrintUnknownFields(unknown_fields.UnknownFieldSet(message)) + + def _PrintUnknownFields(self, unknown_field_set): + """Print unknown fields.""" + out = self.out + for field in unknown_field_set: + out.write(' ' * self.indent) + out.write(str(field.field_number)) + if field.wire_type == WIRETYPE_START_GROUP: + if self.as_one_line: + out.write(' { ') + else: + out.write(' {\n') + self.indent += 2 + + self._PrintUnknownFields(field.data) + + if self.as_one_line: + out.write('} ') + else: + self.indent -= 2 + out.write(' ' * self.indent + '}\n') + elif field.wire_type == WIRETYPE_LENGTH_DELIMITED: + try: + # If this field is parseable as a Message, it is probably + # an embedded message. + # pylint: disable=protected-access + (embedded_unknown_message, pos) = decoder._DecodeUnknownFieldSet( + memoryview(field.data), 0, len(field.data)) + except Exception: # pylint: disable=broad-except + pos = 0 + + if pos == len(field.data): + if self.as_one_line: + out.write(' { ') + else: + out.write(' {\n') + self.indent += 2 + + self._PrintUnknownFields(embedded_unknown_message) + + if self.as_one_line: + out.write('} ') + else: + self.indent -= 2 + out.write(' ' * self.indent + '}\n') + else: + # A string or bytes field. self.as_utf8 may not work. + out.write(': \"') + out.write(text_encoding.CEscape(field.data, False)) + out.write('\" ' if self.as_one_line else '\"\n') + else: + # varint, fixed32, fixed64 + out.write(': ') + out.write(str(field.data)) + out.write(' ' if self.as_one_line else '\n') + + def _PrintFieldName(self, field): + """Print field name.""" + out = self.out + out.write(' ' * self.indent) + if self.use_field_number: + out.write(str(field.number)) + else: + if field.is_extension: + out.write('[') + if (field.containing_type.GetOptions().message_set_wire_format and + field.type == descriptor.FieldDescriptor.TYPE_MESSAGE and + field.label == descriptor.FieldDescriptor.LABEL_OPTIONAL): + out.write(field.message_type.full_name) + else: + out.write(field.full_name) + out.write(']') + elif _IsGroupLike(field): + # For groups, use the capitalized name. + out.write(field.message_type.name) + else: + out.write(field.name) + + if (self.force_colon or + field.cpp_type != descriptor.FieldDescriptor.CPPTYPE_MESSAGE): + # The colon is optional in this case, but our cross-language golden files + # don't include it. Here, the colon is only included if force_colon is + # set to True + out.write(':') + + def PrintField(self, field, value): + """Print a single field name/value pair.""" + self._PrintFieldName(field) + self.out.write(' ') + self.PrintFieldValue(field, value) + self.out.write(' ' if self.as_one_line else '\n') + + def _PrintShortRepeatedPrimitivesValue(self, field, value): + """"Prints short repeated primitives value.""" + # Note: this is called only when value has at least one element. + self._PrintFieldName(field) + self.out.write(' [') + for i in range(len(value) - 1): + self.PrintFieldValue(field, value[i]) + self.out.write(', ') + self.PrintFieldValue(field, value[-1]) + self.out.write(']') + self.out.write(' ' if self.as_one_line else '\n') + + def _PrintMessageFieldValue(self, value): + if self.pointy_brackets: + openb = '<' + closeb = '>' + else: + openb = '{' + closeb = '}' + + if self.as_one_line: + self.out.write('%s ' % openb) + self.PrintMessage(value) + self.out.write(closeb) + else: + self.out.write('%s\n' % openb) + self.indent += 2 + self.PrintMessage(value) + self.indent -= 2 + self.out.write(' ' * self.indent + closeb) + + def PrintFieldValue(self, field, value): + """Print a single field value (not including name). + + For repeated fields, the value should be a single element. + + Args: + field: The descriptor of the field to be printed. + value: The value of the field. + """ + out = self.out + if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: + self._PrintMessageFieldValue(value) + elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM: + enum_value = field.enum_type.values_by_number.get(value, None) + if enum_value is not None: + out.write(enum_value.name) + else: + out.write(str(value)) + elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING: + out.write('\"') + if isinstance(value, str) and not self.as_utf8: + out_value = value.encode('utf-8') + else: + out_value = value + if field.type == descriptor.FieldDescriptor.TYPE_BYTES: + # We always need to escape all binary data in TYPE_BYTES fields. + out_as_utf8 = False + else: + out_as_utf8 = self.as_utf8 + out.write(text_encoding.CEscape(out_value, out_as_utf8)) + out.write('\"') + elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL: + if value: + out.write('true') + else: + out.write('false') + elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_FLOAT: + if self.float_format is not None: + out.write('{1:{0}}'.format(self.float_format, value)) + else: + if math.isnan(value): + out.write(str(value)) + else: + out.write(str(type_checkers.ToShortestFloat(value))) + elif (field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_DOUBLE and + self.double_format is not None): + out.write('{1:{0}}'.format(self.double_format, value)) + else: + out.write(str(value)) + + +def Parse(text, + message, + allow_unknown_extension=False, + allow_field_number=False, + descriptor_pool=None, + allow_unknown_field=False): + """Parses a text representation of a protocol message into a message. + + NOTE: for historical reasons this function does not clear the input + message. This is different from what the binary msg.ParseFrom(...) does. + If text contains a field already set in message, the value is appended if the + field is repeated. Otherwise, an error is raised. + + Example:: + + a = MyProto() + a.repeated_field.append('test') + b = MyProto() + + # Repeated fields are combined + text_format.Parse(repr(a), b) + text_format.Parse(repr(a), b) # repeated_field contains ["test", "test"] + + # Non-repeated fields cannot be overwritten + a.singular_field = 1 + b.singular_field = 2 + text_format.Parse(repr(a), b) # ParseError + + # Binary version: + b.ParseFromString(a.SerializeToString()) # repeated_field is now "test" + + Caller is responsible for clearing the message as needed. + + Args: + text (str): Message text representation. + message (Message): A protocol buffer message to merge into. + allow_unknown_extension: if True, skip over missing extensions and keep + parsing + allow_field_number: if True, both field number and field name are allowed. + descriptor_pool (DescriptorPool): Descriptor pool used to resolve Any types. + allow_unknown_field: if True, skip over unknown field and keep + parsing. Avoid to use this option if possible. It may hide some + errors (e.g. spelling error on field name) + + Returns: + Message: The same message passed as argument. + + Raises: + ParseError: On text parsing problems. + """ + return ParseLines(text.split(b'\n' if isinstance(text, bytes) else u'\n'), + message, + allow_unknown_extension, + allow_field_number, + descriptor_pool=descriptor_pool, + allow_unknown_field=allow_unknown_field) + + +def Merge(text, + message, + allow_unknown_extension=False, + allow_field_number=False, + descriptor_pool=None, + allow_unknown_field=False): + """Parses a text representation of a protocol message into a message. + + Like Parse(), but allows repeated values for a non-repeated field, and uses + the last one. This means any non-repeated, top-level fields specified in text + replace those in the message. + + Args: + text (str): Message text representation. + message (Message): A protocol buffer message to merge into. + allow_unknown_extension: if True, skip over missing extensions and keep + parsing + allow_field_number: if True, both field number and field name are allowed. + descriptor_pool (DescriptorPool): Descriptor pool used to resolve Any types. + allow_unknown_field: if True, skip over unknown field and keep + parsing. Avoid to use this option if possible. It may hide some + errors (e.g. spelling error on field name) + + Returns: + Message: The same message passed as argument. + + Raises: + ParseError: On text parsing problems. + """ + return MergeLines( + text.split(b'\n' if isinstance(text, bytes) else u'\n'), + message, + allow_unknown_extension, + allow_field_number, + descriptor_pool=descriptor_pool, + allow_unknown_field=allow_unknown_field) + + +def ParseLines(lines, + message, + allow_unknown_extension=False, + allow_field_number=False, + descriptor_pool=None, + allow_unknown_field=False): + """Parses a text representation of a protocol message into a message. + + See Parse() for caveats. + + Args: + lines: An iterable of lines of a message's text representation. + message: A protocol buffer message to merge into. + allow_unknown_extension: if True, skip over missing extensions and keep + parsing + allow_field_number: if True, both field number and field name are allowed. + descriptor_pool: A DescriptorPool used to resolve Any types. + allow_unknown_field: if True, skip over unknown field and keep + parsing. Avoid to use this option if possible. It may hide some + errors (e.g. spelling error on field name) + + Returns: + The same message passed as argument. + + Raises: + ParseError: On text parsing problems. + """ + parser = _Parser(allow_unknown_extension, + allow_field_number, + descriptor_pool=descriptor_pool, + allow_unknown_field=allow_unknown_field) + return parser.ParseLines(lines, message) + + +def MergeLines(lines, + message, + allow_unknown_extension=False, + allow_field_number=False, + descriptor_pool=None, + allow_unknown_field=False): + """Parses a text representation of a protocol message into a message. + + See Merge() for more details. + + Args: + lines: An iterable of lines of a message's text representation. + message: A protocol buffer message to merge into. + allow_unknown_extension: if True, skip over missing extensions and keep + parsing + allow_field_number: if True, both field number and field name are allowed. + descriptor_pool: A DescriptorPool used to resolve Any types. + allow_unknown_field: if True, skip over unknown field and keep + parsing. Avoid to use this option if possible. It may hide some + errors (e.g. spelling error on field name) + + Returns: + The same message passed as argument. + + Raises: + ParseError: On text parsing problems. + """ + parser = _Parser(allow_unknown_extension, + allow_field_number, + descriptor_pool=descriptor_pool, + allow_unknown_field=allow_unknown_field) + return parser.MergeLines(lines, message) + + +class _Parser(object): + """Text format parser for protocol message.""" + + def __init__(self, + allow_unknown_extension=False, + allow_field_number=False, + descriptor_pool=None, + allow_unknown_field=False): + self.allow_unknown_extension = allow_unknown_extension + self.allow_field_number = allow_field_number + self.descriptor_pool = descriptor_pool + self.allow_unknown_field = allow_unknown_field + + def ParseLines(self, lines, message): + """Parses a text representation of a protocol message into a message.""" + self._allow_multiple_scalars = False + self._ParseOrMerge(lines, message) + return message + + def MergeLines(self, lines, message): + """Merges a text representation of a protocol message into a message.""" + self._allow_multiple_scalars = True + self._ParseOrMerge(lines, message) + return message + + def _ParseOrMerge(self, lines, message): + """Converts a text representation of a protocol message into a message. + + Args: + lines: Lines of a message's text representation. + message: A protocol buffer message to merge into. + + Raises: + ParseError: On text parsing problems. + """ + # Tokenize expects native str lines. + try: + str_lines = ( + line if isinstance(line, str) else line.decode('utf-8') + for line in lines) + tokenizer = Tokenizer(str_lines) + except UnicodeDecodeError as e: + raise ParseError from e + if message: + self.root_type = message.DESCRIPTOR.full_name + while not tokenizer.AtEnd(): + self._MergeField(tokenizer, message) + + def _MergeField(self, tokenizer, message): + """Merges a single protocol message field into a message. + + Args: + tokenizer: A tokenizer to parse the field name and values. + message: A protocol message to record the data. + + Raises: + ParseError: In case of text parsing problems. + """ + message_descriptor = message.DESCRIPTOR + if (message_descriptor.full_name == _ANY_FULL_TYPE_NAME and + tokenizer.TryConsume('[')): + type_url_prefix, packed_type_name = self._ConsumeAnyTypeUrl(tokenizer) + tokenizer.Consume(']') + tokenizer.TryConsume(':') + self._DetectSilentMarker(tokenizer, message_descriptor.full_name, + type_url_prefix + '/' + packed_type_name) + if tokenizer.TryConsume('<'): + expanded_any_end_token = '>' + else: + tokenizer.Consume('{') + expanded_any_end_token = '}' + expanded_any_sub_message = _BuildMessageFromTypeName(packed_type_name, + self.descriptor_pool) + # Direct comparison with None is used instead of implicit bool conversion + # to avoid false positives with falsy initial values, e.g. for + # google.protobuf.ListValue. + if expanded_any_sub_message is None: + raise ParseError('Type %s not found in descriptor pool' % + packed_type_name) + while not tokenizer.TryConsume(expanded_any_end_token): + if tokenizer.AtEnd(): + raise tokenizer.ParseErrorPreviousToken('Expected "%s".' % + (expanded_any_end_token,)) + self._MergeField(tokenizer, expanded_any_sub_message) + deterministic = False + + message.Pack(expanded_any_sub_message, + type_url_prefix=type_url_prefix, + deterministic=deterministic) + return + + if tokenizer.TryConsume('['): + name = [tokenizer.ConsumeIdentifier()] + while tokenizer.TryConsume('.'): + name.append(tokenizer.ConsumeIdentifier()) + name = '.'.join(name) + + if not message_descriptor.is_extendable: + raise tokenizer.ParseErrorPreviousToken( + 'Message type "%s" does not have extensions.' % + message_descriptor.full_name) + # pylint: disable=protected-access + field = message.Extensions._FindExtensionByName(name) + # pylint: enable=protected-access + if not field: + if self.allow_unknown_extension: + field = None + else: + raise tokenizer.ParseErrorPreviousToken( + 'Extension "%s" not registered. ' + 'Did you import the _pb2 module which defines it? ' + 'If you are trying to place the extension in the MessageSet ' + 'field of another message that is in an Any or MessageSet field, ' + 'that message\'s _pb2 module must be imported as well' % name) + elif message_descriptor != field.containing_type: + raise tokenizer.ParseErrorPreviousToken( + 'Extension "%s" does not extend message type "%s".' % + (name, message_descriptor.full_name)) + + tokenizer.Consume(']') + + else: + name = tokenizer.ConsumeIdentifierOrNumber() + if self.allow_field_number and name.isdigit(): + number = ParseInteger(name, True, True) + field = message_descriptor.fields_by_number.get(number, None) + if not field and message_descriptor.is_extendable: + field = message.Extensions._FindExtensionByNumber(number) + else: + field = message_descriptor.fields_by_name.get(name, None) + + # Group names are expected to be capitalized as they appear in the + # .proto file, which actually matches their type names, not their field + # names. + if not field: + field = message_descriptor.fields_by_name.get(name.lower(), None) + if field and not _IsGroupLike(field): + field = None + if field and field.message_type.name != name: + field = None + + if not field and not self.allow_unknown_field: + raise tokenizer.ParseErrorPreviousToken( + 'Message type "%s" has no field named "%s".' % + (message_descriptor.full_name, name)) + + if field: + if not self._allow_multiple_scalars and field.containing_oneof: + # Check if there's a different field set in this oneof. + # Note that we ignore the case if the same field was set before, and we + # apply _allow_multiple_scalars to non-scalar fields as well. + which_oneof = message.WhichOneof(field.containing_oneof.name) + if which_oneof is not None and which_oneof != field.name: + raise tokenizer.ParseErrorPreviousToken( + 'Field "%s" is specified along with field "%s", another member ' + 'of oneof "%s" for message type "%s".' % + (field.name, which_oneof, field.containing_oneof.name, + message_descriptor.full_name)) + + if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: + tokenizer.TryConsume(':') + self._DetectSilentMarker(tokenizer, message_descriptor.full_name, + field.full_name) + merger = self._MergeMessageField + else: + tokenizer.Consume(':') + self._DetectSilentMarker(tokenizer, message_descriptor.full_name, + field.full_name) + merger = self._MergeScalarField + + if (field.label == descriptor.FieldDescriptor.LABEL_REPEATED and + tokenizer.TryConsume('[')): + # Short repeated format, e.g. "foo: [1, 2, 3]" + if not tokenizer.TryConsume(']'): + while True: + merger(tokenizer, message, field) + if tokenizer.TryConsume(']'): + break + tokenizer.Consume(',') + + else: + merger(tokenizer, message, field) + + else: # Proto field is unknown. + assert (self.allow_unknown_extension or self.allow_unknown_field) + self._SkipFieldContents(tokenizer, name, message_descriptor.full_name) + + # For historical reasons, fields may optionally be separated by commas or + # semicolons. + if not tokenizer.TryConsume(','): + tokenizer.TryConsume(';') + + def _LogSilentMarker(self, immediate_message_type, field_name): + pass + + def _DetectSilentMarker(self, tokenizer, immediate_message_type, field_name): + if tokenizer.contains_silent_marker_before_current_token: + self._LogSilentMarker(immediate_message_type, field_name) + + def _ConsumeAnyTypeUrl(self, tokenizer): + """Consumes a google.protobuf.Any type URL and returns the type name.""" + # Consume "type.googleapis.com/". + prefix = [tokenizer.ConsumeIdentifier()] + tokenizer.Consume('.') + prefix.append(tokenizer.ConsumeIdentifier()) + tokenizer.Consume('.') + prefix.append(tokenizer.ConsumeIdentifier()) + tokenizer.Consume('/') + # Consume the fully-qualified type name. + name = [tokenizer.ConsumeIdentifier()] + while tokenizer.TryConsume('.'): + name.append(tokenizer.ConsumeIdentifier()) + return '.'.join(prefix), '.'.join(name) + + def _MergeMessageField(self, tokenizer, message, field): + """Merges a single scalar field into a message. + + Args: + tokenizer: A tokenizer to parse the field value. + message: The message of which field is a member. + field: The descriptor of the field to be merged. + + Raises: + ParseError: In case of text parsing problems. + """ + is_map_entry = _IsMapEntry(field) + + if tokenizer.TryConsume('<'): + end_token = '>' + else: + tokenizer.Consume('{') + end_token = '}' + + if field.label == descriptor.FieldDescriptor.LABEL_REPEATED: + if field.is_extension: + sub_message = message.Extensions[field].add() + elif is_map_entry: + sub_message = getattr(message, field.name).GetEntryClass()() + else: + sub_message = getattr(message, field.name).add() + else: + if field.is_extension: + if (not self._allow_multiple_scalars and + message.HasExtension(field)): + raise tokenizer.ParseErrorPreviousToken( + 'Message type "%s" should not have multiple "%s" extensions.' % + (message.DESCRIPTOR.full_name, field.full_name)) + sub_message = message.Extensions[field] + else: + # Also apply _allow_multiple_scalars to message field. + # TODO: Change to _allow_singular_overwrites. + if (not self._allow_multiple_scalars and + message.HasField(field.name)): + raise tokenizer.ParseErrorPreviousToken( + 'Message type "%s" should not have multiple "%s" fields.' % + (message.DESCRIPTOR.full_name, field.name)) + sub_message = getattr(message, field.name) + sub_message.SetInParent() + + while not tokenizer.TryConsume(end_token): + if tokenizer.AtEnd(): + raise tokenizer.ParseErrorPreviousToken('Expected "%s".' % (end_token,)) + self._MergeField(tokenizer, sub_message) + + if is_map_entry: + value_cpptype = field.message_type.fields_by_name['value'].cpp_type + if value_cpptype == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: + value = getattr(message, field.name)[sub_message.key] + value.CopyFrom(sub_message.value) + else: + getattr(message, field.name)[sub_message.key] = sub_message.value + + def _MergeScalarField(self, tokenizer, message, field): + """Merges a single scalar field into a message. + + Args: + tokenizer: A tokenizer to parse the field value. + message: A protocol message to record the data. + field: The descriptor of the field to be merged. + + Raises: + ParseError: In case of text parsing problems. + RuntimeError: On runtime errors. + """ + _ = self.allow_unknown_extension + value = None + + if field.type in (descriptor.FieldDescriptor.TYPE_INT32, + descriptor.FieldDescriptor.TYPE_SINT32, + descriptor.FieldDescriptor.TYPE_SFIXED32): + value = _ConsumeInt32(tokenizer) + elif field.type in (descriptor.FieldDescriptor.TYPE_INT64, + descriptor.FieldDescriptor.TYPE_SINT64, + descriptor.FieldDescriptor.TYPE_SFIXED64): + value = _ConsumeInt64(tokenizer) + elif field.type in (descriptor.FieldDescriptor.TYPE_UINT32, + descriptor.FieldDescriptor.TYPE_FIXED32): + value = _ConsumeUint32(tokenizer) + elif field.type in (descriptor.FieldDescriptor.TYPE_UINT64, + descriptor.FieldDescriptor.TYPE_FIXED64): + value = _ConsumeUint64(tokenizer) + elif field.type in (descriptor.FieldDescriptor.TYPE_FLOAT, + descriptor.FieldDescriptor.TYPE_DOUBLE): + value = tokenizer.ConsumeFloat() + elif field.type == descriptor.FieldDescriptor.TYPE_BOOL: + value = tokenizer.ConsumeBool() + elif field.type == descriptor.FieldDescriptor.TYPE_STRING: + value = tokenizer.ConsumeString() + elif field.type == descriptor.FieldDescriptor.TYPE_BYTES: + value = tokenizer.ConsumeByteString() + elif field.type == descriptor.FieldDescriptor.TYPE_ENUM: + value = tokenizer.ConsumeEnum(field) + else: + raise RuntimeError('Unknown field type %d' % field.type) + + if field.label == descriptor.FieldDescriptor.LABEL_REPEATED: + if field.is_extension: + message.Extensions[field].append(value) + else: + getattr(message, field.name).append(value) + else: + if field.is_extension: + if (not self._allow_multiple_scalars and + field.has_presence and + message.HasExtension(field)): + raise tokenizer.ParseErrorPreviousToken( + 'Message type "%s" should not have multiple "%s" extensions.' % + (message.DESCRIPTOR.full_name, field.full_name)) + else: + message.Extensions[field] = value + else: + duplicate_error = False + if not self._allow_multiple_scalars: + if field.has_presence: + duplicate_error = message.HasField(field.name) + else: + # For field that doesn't represent presence, try best effort to + # check multiple scalars by compare to default values. + duplicate_error = bool(getattr(message, field.name)) + + if duplicate_error: + raise tokenizer.ParseErrorPreviousToken( + 'Message type "%s" should not have multiple "%s" fields.' % + (message.DESCRIPTOR.full_name, field.name)) + else: + setattr(message, field.name, value) + + def _SkipFieldContents(self, tokenizer, field_name, immediate_message_type): + """Skips over contents (value or message) of a field. + + Args: + tokenizer: A tokenizer to parse the field name and values. + field_name: The field name currently being parsed. + immediate_message_type: The type of the message immediately containing + the silent marker. + """ + # Try to guess the type of this field. + # If this field is not a message, there should be a ":" between the + # field name and the field value and also the field value should not + # start with "{" or "<" which indicates the beginning of a message body. + # If there is no ":" or there is a "{" or "<" after ":", this field has + # to be a message or the input is ill-formed. + if tokenizer.TryConsume( + ':') and not tokenizer.LookingAt('{') and not tokenizer.LookingAt('<'): + self._DetectSilentMarker(tokenizer, immediate_message_type, field_name) + if tokenizer.LookingAt('['): + self._SkipRepeatedFieldValue(tokenizer) + else: + self._SkipFieldValue(tokenizer) + else: + self._DetectSilentMarker(tokenizer, immediate_message_type, field_name) + self._SkipFieldMessage(tokenizer, immediate_message_type) + + def _SkipField(self, tokenizer, immediate_message_type): + """Skips over a complete field (name and value/message). + + Args: + tokenizer: A tokenizer to parse the field name and values. + immediate_message_type: The type of the message immediately containing + the silent marker. + """ + field_name = '' + if tokenizer.TryConsume('['): + # Consume extension or google.protobuf.Any type URL + field_name += '[' + tokenizer.ConsumeIdentifier() + num_identifiers = 1 + while tokenizer.TryConsume('.'): + field_name += '.' + tokenizer.ConsumeIdentifier() + num_identifiers += 1 + # This is possibly a type URL for an Any message. + if num_identifiers == 3 and tokenizer.TryConsume('/'): + field_name += '/' + tokenizer.ConsumeIdentifier() + while tokenizer.TryConsume('.'): + field_name += '.' + tokenizer.ConsumeIdentifier() + tokenizer.Consume(']') + field_name += ']' + else: + field_name += tokenizer.ConsumeIdentifierOrNumber() + + self._SkipFieldContents(tokenizer, field_name, immediate_message_type) + + # For historical reasons, fields may optionally be separated by commas or + # semicolons. + if not tokenizer.TryConsume(','): + tokenizer.TryConsume(';') + + def _SkipFieldMessage(self, tokenizer, immediate_message_type): + """Skips over a field message. + + Args: + tokenizer: A tokenizer to parse the field name and values. + immediate_message_type: The type of the message immediately containing + the silent marker + """ + if tokenizer.TryConsume('<'): + delimiter = '>' + else: + tokenizer.Consume('{') + delimiter = '}' + + while not tokenizer.LookingAt('>') and not tokenizer.LookingAt('}'): + self._SkipField(tokenizer, immediate_message_type) + + tokenizer.Consume(delimiter) + + def _SkipFieldValue(self, tokenizer): + """Skips over a field value. + + Args: + tokenizer: A tokenizer to parse the field name and values. + + Raises: + ParseError: In case an invalid field value is found. + """ + if (not tokenizer.TryConsumeByteString()and + not tokenizer.TryConsumeIdentifier() and + not _TryConsumeInt64(tokenizer) and + not _TryConsumeUint64(tokenizer) and + not tokenizer.TryConsumeFloat()): + raise ParseError('Invalid field value: ' + tokenizer.token) + + def _SkipRepeatedFieldValue(self, tokenizer): + """Skips over a repeated field value. + + Args: + tokenizer: A tokenizer to parse the field value. + """ + tokenizer.Consume('[') + if not tokenizer.LookingAt(']'): + self._SkipFieldValue(tokenizer) + while tokenizer.TryConsume(','): + self._SkipFieldValue(tokenizer) + tokenizer.Consume(']') + + +class Tokenizer(object): + """Protocol buffer text representation tokenizer. + + This class handles the lower level string parsing by splitting it into + meaningful tokens. + + It was directly ported from the Java protocol buffer API. + """ + + _WHITESPACE = re.compile(r'\s+') + _COMMENT = re.compile(r'(\s*#.*$)', re.MULTILINE) + _WHITESPACE_OR_COMMENT = re.compile(r'(\s|(#.*$))+', re.MULTILINE) + _TOKEN = re.compile('|'.join([ + r'[a-zA-Z_][0-9a-zA-Z_+-]*', # an identifier + r'([0-9+-]|(\.[0-9]))[0-9a-zA-Z_.+-]*', # a number + ] + [ # quoted str for each quote mark + # Avoid backtracking! https://stackoverflow.com/a/844267 + r'{qt}[^{qt}\n\\]*((\\.)+[^{qt}\n\\]*)*({qt}|\\?$)'.format(qt=mark) + for mark in _QUOTES + ])) + + _IDENTIFIER = re.compile(r'[^\d\W]\w*') + _IDENTIFIER_OR_NUMBER = re.compile(r'\w+') + + def __init__(self, lines, skip_comments=True): + self._position = 0 + self._line = -1 + self._column = 0 + self._token_start = None + self.token = '' + self._lines = iter(lines) + self._current_line = '' + self._previous_line = 0 + self._previous_column = 0 + self._more_lines = True + self._skip_comments = skip_comments + self._whitespace_pattern = (skip_comments and self._WHITESPACE_OR_COMMENT + or self._WHITESPACE) + self.contains_silent_marker_before_current_token = False + + self._SkipWhitespace() + self.NextToken() + + def LookingAt(self, token): + return self.token == token + + def AtEnd(self): + """Checks the end of the text was reached. + + Returns: + True iff the end was reached. + """ + return not self.token + + def _PopLine(self): + while len(self._current_line) <= self._column: + try: + self._current_line = next(self._lines) + except StopIteration: + self._current_line = '' + self._more_lines = False + return + else: + self._line += 1 + self._column = 0 + + def _SkipWhitespace(self): + while True: + self._PopLine() + match = self._whitespace_pattern.match(self._current_line, self._column) + if not match: + break + self.contains_silent_marker_before_current_token = match.group(0) == ( + ' ' + _DEBUG_STRING_SILENT_MARKER) + length = len(match.group(0)) + self._column += length + + def TryConsume(self, token): + """Tries to consume a given piece of text. + + Args: + token: Text to consume. + + Returns: + True iff the text was consumed. + """ + if self.token == token: + self.NextToken() + return True + return False + + def Consume(self, token): + """Consumes a piece of text. + + Args: + token: Text to consume. + + Raises: + ParseError: If the text couldn't be consumed. + """ + if not self.TryConsume(token): + raise self.ParseError('Expected "%s".' % token) + + def ConsumeComment(self): + result = self.token + if not self._COMMENT.match(result): + raise self.ParseError('Expected comment.') + self.NextToken() + return result + + def ConsumeCommentOrTrailingComment(self): + """Consumes a comment, returns a 2-tuple (trailing bool, comment str).""" + + # Tokenizer initializes _previous_line and _previous_column to 0. As the + # tokenizer starts, it looks like there is a previous token on the line. + just_started = self._line == 0 and self._column == 0 + + before_parsing = self._previous_line + comment = self.ConsumeComment() + + # A trailing comment is a comment on the same line than the previous token. + trailing = (self._previous_line == before_parsing + and not just_started) + + return trailing, comment + + def TryConsumeIdentifier(self): + try: + self.ConsumeIdentifier() + return True + except ParseError: + return False + + def ConsumeIdentifier(self): + """Consumes protocol message field identifier. + + Returns: + Identifier string. + + Raises: + ParseError: If an identifier couldn't be consumed. + """ + result = self.token + if not self._IDENTIFIER.match(result): + raise self.ParseError('Expected identifier.') + self.NextToken() + return result + + def TryConsumeIdentifierOrNumber(self): + try: + self.ConsumeIdentifierOrNumber() + return True + except ParseError: + return False + + def ConsumeIdentifierOrNumber(self): + """Consumes protocol message field identifier. + + Returns: + Identifier string. + + Raises: + ParseError: If an identifier couldn't be consumed. + """ + result = self.token + if not self._IDENTIFIER_OR_NUMBER.match(result): + raise self.ParseError('Expected identifier or number, got %s.' % result) + self.NextToken() + return result + + def TryConsumeInteger(self): + try: + self.ConsumeInteger() + return True + except ParseError: + return False + + def ConsumeInteger(self): + """Consumes an integer number. + + Returns: + The integer parsed. + + Raises: + ParseError: If an integer couldn't be consumed. + """ + try: + result = _ParseAbstractInteger(self.token) + except ValueError as e: + raise self.ParseError(str(e)) + self.NextToken() + return result + + def TryConsumeFloat(self): + try: + self.ConsumeFloat() + return True + except ParseError: + return False + + def ConsumeFloat(self): + """Consumes an floating point number. + + Returns: + The number parsed. + + Raises: + ParseError: If a floating point number couldn't be consumed. + """ + try: + result = ParseFloat(self.token) + except ValueError as e: + raise self.ParseError(str(e)) + self.NextToken() + return result + + def ConsumeBool(self): + """Consumes a boolean value. + + Returns: + The bool parsed. + + Raises: + ParseError: If a boolean value couldn't be consumed. + """ + try: + result = ParseBool(self.token) + except ValueError as e: + raise self.ParseError(str(e)) + self.NextToken() + return result + + def TryConsumeByteString(self): + try: + self.ConsumeByteString() + return True + except ParseError: + return False + + def ConsumeString(self): + """Consumes a string value. + + Returns: + The string parsed. + + Raises: + ParseError: If a string value couldn't be consumed. + """ + the_bytes = self.ConsumeByteString() + try: + return str(the_bytes, 'utf-8') + except UnicodeDecodeError as e: + raise self._StringParseError(e) + + def ConsumeByteString(self): + """Consumes a byte array value. + + Returns: + The array parsed (as a string). + + Raises: + ParseError: If a byte array value couldn't be consumed. + """ + the_list = [self._ConsumeSingleByteString()] + while self.token and self.token[0] in _QUOTES: + the_list.append(self._ConsumeSingleByteString()) + return b''.join(the_list) + + def _ConsumeSingleByteString(self): + """Consume one token of a string literal. + + String literals (whether bytes or text) can come in multiple adjacent + tokens which are automatically concatenated, like in C or Python. This + method only consumes one token. + + Returns: + The token parsed. + Raises: + ParseError: When the wrong format data is found. + """ + text = self.token + if len(text) < 1 or text[0] not in _QUOTES: + raise self.ParseError('Expected string but found: %r' % (text,)) + + if len(text) < 2 or text[-1] != text[0]: + raise self.ParseError('String missing ending quote: %r' % (text,)) + + try: + result = text_encoding.CUnescape(text[1:-1]) + except ValueError as e: + raise self.ParseError(str(e)) + self.NextToken() + return result + + def ConsumeEnum(self, field): + try: + result = ParseEnum(field, self.token) + except ValueError as e: + raise self.ParseError(str(e)) + self.NextToken() + return result + + def ParseErrorPreviousToken(self, message): + """Creates and *returns* a ParseError for the previously read token. + + Args: + message: A message to set for the exception. + + Returns: + A ParseError instance. + """ + return ParseError(message, self._previous_line + 1, + self._previous_column + 1) + + def ParseError(self, message): + """Creates and *returns* a ParseError for the current token.""" + return ParseError('\'' + self._current_line + '\': ' + message, + self._line + 1, self._column + 1) + + def _StringParseError(self, e): + return self.ParseError('Couldn\'t parse string: ' + str(e)) + + def NextToken(self): + """Reads the next meaningful token.""" + self._previous_line = self._line + self._previous_column = self._column + self.contains_silent_marker_before_current_token = False + + self._column += len(self.token) + self._SkipWhitespace() + + if not self._more_lines: + self.token = '' + return + + match = self._TOKEN.match(self._current_line, self._column) + if not match and not self._skip_comments: + match = self._COMMENT.match(self._current_line, self._column) + if match: + token = match.group(0) + self.token = token + else: + self.token = self._current_line[self._column] + +# Aliased so it can still be accessed by current visibility violators. +# TODO: Migrate violators to textformat_tokenizer. +_Tokenizer = Tokenizer # pylint: disable=invalid-name + + +def _ConsumeInt32(tokenizer): + """Consumes a signed 32bit integer number from tokenizer. + + Args: + tokenizer: A tokenizer used to parse the number. + + Returns: + The integer parsed. + + Raises: + ParseError: If a signed 32bit integer couldn't be consumed. + """ + return _ConsumeInteger(tokenizer, is_signed=True, is_long=False) + + +def _ConsumeUint32(tokenizer): + """Consumes an unsigned 32bit integer number from tokenizer. + + Args: + tokenizer: A tokenizer used to parse the number. + + Returns: + The integer parsed. + + Raises: + ParseError: If an unsigned 32bit integer couldn't be consumed. + """ + return _ConsumeInteger(tokenizer, is_signed=False, is_long=False) + + +def _TryConsumeInt64(tokenizer): + try: + _ConsumeInt64(tokenizer) + return True + except ParseError: + return False + + +def _ConsumeInt64(tokenizer): + """Consumes a signed 32bit integer number from tokenizer. + + Args: + tokenizer: A tokenizer used to parse the number. + + Returns: + The integer parsed. + + Raises: + ParseError: If a signed 32bit integer couldn't be consumed. + """ + return _ConsumeInteger(tokenizer, is_signed=True, is_long=True) + + +def _TryConsumeUint64(tokenizer): + try: + _ConsumeUint64(tokenizer) + return True + except ParseError: + return False + + +def _ConsumeUint64(tokenizer): + """Consumes an unsigned 64bit integer number from tokenizer. + + Args: + tokenizer: A tokenizer used to parse the number. + + Returns: + The integer parsed. + + Raises: + ParseError: If an unsigned 64bit integer couldn't be consumed. + """ + return _ConsumeInteger(tokenizer, is_signed=False, is_long=True) + + +def _ConsumeInteger(tokenizer, is_signed=False, is_long=False): + """Consumes an integer number from tokenizer. + + Args: + tokenizer: A tokenizer used to parse the number. + is_signed: True if a signed integer must be parsed. + is_long: True if a long integer must be parsed. + + Returns: + The integer parsed. + + Raises: + ParseError: If an integer with given characteristics couldn't be consumed. + """ + try: + result = ParseInteger(tokenizer.token, is_signed=is_signed, is_long=is_long) + except ValueError as e: + raise tokenizer.ParseError(str(e)) + tokenizer.NextToken() + return result + + +def ParseInteger(text, is_signed=False, is_long=False): + """Parses an integer. + + Args: + text: The text to parse. + is_signed: True if a signed integer must be parsed. + is_long: True if a long integer must be parsed. + + Returns: + The integer value. + + Raises: + ValueError: Thrown Iff the text is not a valid integer. + """ + # Do the actual parsing. Exception handling is propagated to caller. + result = _ParseAbstractInteger(text) + + # Check if the integer is sane. Exceptions handled by callers. + checker = _INTEGER_CHECKERS[2 * int(is_long) + int(is_signed)] + checker.CheckValue(result) + return result + + +def _ParseAbstractInteger(text): + """Parses an integer without checking size/signedness. + + Args: + text: The text to parse. + + Returns: + The integer value. + + Raises: + ValueError: Thrown Iff the text is not a valid integer. + """ + # Do the actual parsing. Exception handling is propagated to caller. + orig_text = text + c_octal_match = re.match(r'(-?)0(\d+)$', text) + if c_octal_match: + # Python 3 no longer supports 0755 octal syntax without the 'o', so + # we always use the '0o' prefix for multi-digit numbers starting with 0. + text = c_octal_match.group(1) + '0o' + c_octal_match.group(2) + try: + return int(text, 0) + except ValueError: + raise ValueError('Couldn\'t parse integer: %s' % orig_text) + + +def ParseFloat(text): + """Parse a floating point number. + + Args: + text: Text to parse. + + Returns: + The number parsed. + + Raises: + ValueError: If a floating point number couldn't be parsed. + """ + try: + # Assume Python compatible syntax. + return float(text) + except ValueError: + # Check alternative spellings. + if _FLOAT_INFINITY.match(text): + if text[0] == '-': + return float('-inf') + else: + return float('inf') + elif _FLOAT_NAN.match(text): + return float('nan') + else: + # assume '1.0f' format + try: + return float(text.rstrip('f')) + except ValueError: + raise ValueError('Couldn\'t parse float: %s' % text) + + +def ParseBool(text): + """Parse a boolean value. + + Args: + text: Text to parse. + + Returns: + Boolean values parsed + + Raises: + ValueError: If text is not a valid boolean. + """ + if text in ('true', 't', '1', 'True'): + return True + elif text in ('false', 'f', '0', 'False'): + return False + else: + raise ValueError('Expected "true" or "false".') + + +def ParseEnum(field, value): + """Parse an enum value. + + The value can be specified by a number (the enum value), or by + a string literal (the enum name). + + Args: + field: Enum field descriptor. + value: String value. + + Returns: + Enum value number. + + Raises: + ValueError: If the enum value could not be parsed. + """ + enum_descriptor = field.enum_type + try: + number = int(value, 0) + except ValueError: + # Identifier. + enum_value = enum_descriptor.values_by_name.get(value, None) + if enum_value is None: + raise ValueError('Enum type "%s" has no value named %s.' % + (enum_descriptor.full_name, value)) + else: + if not field.enum_type.is_closed: + return number + enum_value = enum_descriptor.values_by_number.get(number, None) + if enum_value is None: + raise ValueError('Enum type "%s" has no value with number %d.' % + (enum_descriptor.full_name, number)) + return enum_value.number diff --git a/google/protobuf/timestamp_pb2.py b/google/protobuf/timestamp_pb2.py new file mode 100644 index 0000000..5a3b4b5 --- /dev/null +++ b/google/protobuf/timestamp_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: google/protobuf/timestamp.proto +# Protobuf Python Version: 5.27.3 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 3, + '', + 'google/protobuf/timestamp.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fgoogle/protobuf/timestamp.proto\x12\x0fgoogle.protobuf\";\n\tTimestamp\x12\x18\n\x07seconds\x18\x01 \x01(\x03R\x07seconds\x12\x14\n\x05nanos\x18\x02 \x01(\x05R\x05nanosB\x85\x01\n\x13\x63om.google.protobufB\x0eTimestampProtoP\x01Z2google.golang.org/protobuf/types/known/timestamppb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.timestamp_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\023com.google.protobufB\016TimestampProtoP\001Z2google.golang.org/protobuf/types/known/timestamppb\370\001\001\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' + _globals['_TIMESTAMP']._serialized_start=52 + _globals['_TIMESTAMP']._serialized_end=111 +# @@protoc_insertion_point(module_scope) diff --git a/google/protobuf/type_pb2.py b/google/protobuf/type_pb2.py new file mode 100644 index 0000000..e6d651b --- /dev/null +++ b/google/protobuf/type_pb2.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: google/protobuf/type.proto +# Protobuf Python Version: 5.27.3 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 3, + '', + 'google/protobuf/type.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 +from google.protobuf import source_context_pb2 as google_dot_protobuf_dot_source__context__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1agoogle/protobuf/type.proto\x12\x0fgoogle.protobuf\x1a\x19google/protobuf/any.proto\x1a$google/protobuf/source_context.proto\"\xa7\x02\n\x04Type\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12.\n\x06\x66ields\x18\x02 \x03(\x0b\x32\x16.google.protobuf.FieldR\x06\x66ields\x12\x16\n\x06oneofs\x18\x03 \x03(\tR\x06oneofs\x12\x31\n\x07options\x18\x04 \x03(\x0b\x32\x17.google.protobuf.OptionR\x07options\x12\x45\n\x0esource_context\x18\x05 \x01(\x0b\x32\x1e.google.protobuf.SourceContextR\rsourceContext\x12/\n\x06syntax\x18\x06 \x01(\x0e\x32\x17.google.protobuf.SyntaxR\x06syntax\x12\x18\n\x07\x65\x64ition\x18\x07 \x01(\tR\x07\x65\x64ition\"\xb4\x06\n\x05\x46ield\x12/\n\x04kind\x18\x01 \x01(\x0e\x32\x1b.google.protobuf.Field.KindR\x04kind\x12\x44\n\x0b\x63\x61rdinality\x18\x02 \x01(\x0e\x32\".google.protobuf.Field.CardinalityR\x0b\x63\x61rdinality\x12\x16\n\x06number\x18\x03 \x01(\x05R\x06number\x12\x12\n\x04name\x18\x04 \x01(\tR\x04name\x12\x19\n\x08type_url\x18\x06 \x01(\tR\x07typeUrl\x12\x1f\n\x0boneof_index\x18\x07 \x01(\x05R\noneofIndex\x12\x16\n\x06packed\x18\x08 \x01(\x08R\x06packed\x12\x31\n\x07options\x18\t \x03(\x0b\x32\x17.google.protobuf.OptionR\x07options\x12\x1b\n\tjson_name\x18\n \x01(\tR\x08jsonName\x12#\n\rdefault_value\x18\x0b \x01(\tR\x0c\x64\x65\x66\x61ultValue\"\xc8\x02\n\x04Kind\x12\x10\n\x0cTYPE_UNKNOWN\x10\x00\x12\x0f\n\x0bTYPE_DOUBLE\x10\x01\x12\x0e\n\nTYPE_FLOAT\x10\x02\x12\x0e\n\nTYPE_INT64\x10\x03\x12\x0f\n\x0bTYPE_UINT64\x10\x04\x12\x0e\n\nTYPE_INT32\x10\x05\x12\x10\n\x0cTYPE_FIXED64\x10\x06\x12\x10\n\x0cTYPE_FIXED32\x10\x07\x12\r\n\tTYPE_BOOL\x10\x08\x12\x0f\n\x0bTYPE_STRING\x10\t\x12\x0e\n\nTYPE_GROUP\x10\n\x12\x10\n\x0cTYPE_MESSAGE\x10\x0b\x12\x0e\n\nTYPE_BYTES\x10\x0c\x12\x0f\n\x0bTYPE_UINT32\x10\r\x12\r\n\tTYPE_ENUM\x10\x0e\x12\x11\n\rTYPE_SFIXED32\x10\x0f\x12\x11\n\rTYPE_SFIXED64\x10\x10\x12\x0f\n\x0bTYPE_SINT32\x10\x11\x12\x0f\n\x0bTYPE_SINT64\x10\x12\"t\n\x0b\x43\x61rdinality\x12\x17\n\x13\x43\x41RDINALITY_UNKNOWN\x10\x00\x12\x18\n\x14\x43\x41RDINALITY_OPTIONAL\x10\x01\x12\x18\n\x14\x43\x41RDINALITY_REQUIRED\x10\x02\x12\x18\n\x14\x43\x41RDINALITY_REPEATED\x10\x03\"\x99\x02\n\x04\x45num\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x38\n\tenumvalue\x18\x02 \x03(\x0b\x32\x1a.google.protobuf.EnumValueR\tenumvalue\x12\x31\n\x07options\x18\x03 \x03(\x0b\x32\x17.google.protobuf.OptionR\x07options\x12\x45\n\x0esource_context\x18\x04 \x01(\x0b\x32\x1e.google.protobuf.SourceContextR\rsourceContext\x12/\n\x06syntax\x18\x05 \x01(\x0e\x32\x17.google.protobuf.SyntaxR\x06syntax\x12\x18\n\x07\x65\x64ition\x18\x06 \x01(\tR\x07\x65\x64ition\"j\n\tEnumValue\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n\x06number\x18\x02 \x01(\x05R\x06number\x12\x31\n\x07options\x18\x03 \x03(\x0b\x32\x17.google.protobuf.OptionR\x07options\"H\n\x06Option\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12*\n\x05value\x18\x02 \x01(\x0b\x32\x14.google.protobuf.AnyR\x05value*C\n\x06Syntax\x12\x11\n\rSYNTAX_PROTO2\x10\x00\x12\x11\n\rSYNTAX_PROTO3\x10\x01\x12\x13\n\x0fSYNTAX_EDITIONS\x10\x02\x42{\n\x13\x63om.google.protobufB\tTypeProtoP\x01Z-google.golang.org/protobuf/types/known/typepb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.type_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\023com.google.protobufB\tTypeProtoP\001Z-google.golang.org/protobuf/types/known/typepb\370\001\001\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' + _globals['_SYNTAX']._serialized_start=1699 + _globals['_SYNTAX']._serialized_end=1766 + _globals['_TYPE']._serialized_start=113 + _globals['_TYPE']._serialized_end=408 + _globals['_FIELD']._serialized_start=411 + _globals['_FIELD']._serialized_end=1231 + _globals['_FIELD_KIND']._serialized_start=785 + _globals['_FIELD_KIND']._serialized_end=1113 + _globals['_FIELD_CARDINALITY']._serialized_start=1115 + _globals['_FIELD_CARDINALITY']._serialized_end=1231 + _globals['_ENUM']._serialized_start=1234 + _globals['_ENUM']._serialized_end=1515 + _globals['_ENUMVALUE']._serialized_start=1517 + _globals['_ENUMVALUE']._serialized_end=1623 + _globals['_OPTION']._serialized_start=1625 + _globals['_OPTION']._serialized_end=1697 +# @@protoc_insertion_point(module_scope) diff --git a/google/protobuf/unknown_fields.py b/google/protobuf/unknown_fields.py new file mode 100644 index 0000000..9b1e549 --- /dev/null +++ b/google/protobuf/unknown_fields.py @@ -0,0 +1,97 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Contains Unknown Fields APIs. + +Simple usage example: + unknown_field_set = UnknownFieldSet(message) + for unknown_field in unknown_field_set: + wire_type = unknown_field.wire_type + field_number = unknown_field.field_number + data = unknown_field.data +""" + + +from google.protobuf.internal import api_implementation + +if api_implementation._c_module is not None: # pylint: disable=protected-access + UnknownFieldSet = api_implementation._c_module.UnknownFieldSet # pylint: disable=protected-access +else: + from google.protobuf.internal import decoder # pylint: disable=g-import-not-at-top + from google.protobuf.internal import wire_format # pylint: disable=g-import-not-at-top + + class UnknownField: + """A parsed unknown field.""" + + # Disallows assignment to other attributes. + __slots__ = ['_field_number', '_wire_type', '_data'] + + def __init__(self, field_number, wire_type, data): + self._field_number = field_number + self._wire_type = wire_type + self._data = data + return + + @property + def field_number(self): + return self._field_number + + @property + def wire_type(self): + return self._wire_type + + @property + def data(self): + return self._data + + class UnknownFieldSet: + """UnknownField container.""" + + # Disallows assignment to other attributes. + __slots__ = ['_values'] + + def __init__(self, msg): + + def InternalAdd(field_number, wire_type, data): + unknown_field = UnknownField(field_number, wire_type, data) + self._values.append(unknown_field) + + self._values = [] + msg_des = msg.DESCRIPTOR + # pylint: disable=protected-access + unknown_fields = msg._unknown_fields + if (msg_des.has_options and + msg_des.GetOptions().message_set_wire_format): + local_decoder = decoder.UnknownMessageSetItemDecoder() + for _, buffer in unknown_fields: + (field_number, data) = local_decoder(memoryview(buffer)) + InternalAdd(field_number, wire_format.WIRETYPE_LENGTH_DELIMITED, data) + else: + for tag_bytes, buffer in unknown_fields: + # pylint: disable=protected-access + (tag, _) = decoder._DecodeVarint(tag_bytes, 0) + field_number, wire_type = wire_format.UnpackTag(tag) + if field_number == 0: + raise RuntimeError('Field number 0 is illegal.') + (data, _) = decoder._DecodeUnknownField( + memoryview(buffer), 0, wire_type) + InternalAdd(field_number, wire_type, data) + + def __getitem__(self, index): + size = len(self._values) + if index < 0: + index += size + if index < 0 or index >= size: + raise IndexError('index %d out of range'.index) + + return self._values[index] + + def __len__(self): + return len(self._values) + + def __iter__(self): + return iter(self._values) diff --git a/google/protobuf/util/__init__.py b/google/protobuf/util/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/google/protobuf/wrappers_pb2.py b/google/protobuf/wrappers_pb2.py new file mode 100644 index 0000000..24b6421 --- /dev/null +++ b/google/protobuf/wrappers_pb2.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: google/protobuf/wrappers.proto +# Protobuf Python Version: 5.27.3 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 3, + '', + 'google/protobuf/wrappers.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1egoogle/protobuf/wrappers.proto\x12\x0fgoogle.protobuf\"#\n\x0b\x44oubleValue\x12\x14\n\x05value\x18\x01 \x01(\x01R\x05value\"\"\n\nFloatValue\x12\x14\n\x05value\x18\x01 \x01(\x02R\x05value\"\"\n\nInt64Value\x12\x14\n\x05value\x18\x01 \x01(\x03R\x05value\"#\n\x0bUInt64Value\x12\x14\n\x05value\x18\x01 \x01(\x04R\x05value\"\"\n\nInt32Value\x12\x14\n\x05value\x18\x01 \x01(\x05R\x05value\"#\n\x0bUInt32Value\x12\x14\n\x05value\x18\x01 \x01(\rR\x05value\"!\n\tBoolValue\x12\x14\n\x05value\x18\x01 \x01(\x08R\x05value\"#\n\x0bStringValue\x12\x14\n\x05value\x18\x01 \x01(\tR\x05value\"\"\n\nBytesValue\x12\x14\n\x05value\x18\x01 \x01(\x0cR\x05valueB\x83\x01\n\x13\x63om.google.protobufB\rWrappersProtoP\x01Z1google.golang.org/protobuf/types/known/wrapperspb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.wrappers_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\023com.google.protobufB\rWrappersProtoP\001Z1google.golang.org/protobuf/types/known/wrapperspb\370\001\001\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' + _globals['_DOUBLEVALUE']._serialized_start=51 + _globals['_DOUBLEVALUE']._serialized_end=86 + _globals['_FLOATVALUE']._serialized_start=88 + _globals['_FLOATVALUE']._serialized_end=122 + _globals['_INT64VALUE']._serialized_start=124 + _globals['_INT64VALUE']._serialized_end=158 + _globals['_UINT64VALUE']._serialized_start=160 + _globals['_UINT64VALUE']._serialized_end=195 + _globals['_INT32VALUE']._serialized_start=197 + _globals['_INT32VALUE']._serialized_end=231 + _globals['_UINT32VALUE']._serialized_start=233 + _globals['_UINT32VALUE']._serialized_end=268 + _globals['_BOOLVALUE']._serialized_start=270 + _globals['_BOOLVALUE']._serialized_end=303 + _globals['_STRINGVALUE']._serialized_start=305 + _globals['_STRINGVALUE']._serialized_end=340 + _globals['_BYTESVALUE']._serialized_start=342 + _globals['_BYTESVALUE']._serialized_end=376 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/__init__.py b/hoymiles_wifi/__init__.py new file mode 100644 index 0000000..916e9e7 --- /dev/null +++ b/hoymiles_wifi/__init__.py @@ -0,0 +1,9 @@ +"""Init file for hoymiles_wifi package.""" + +import logging +import os + +LOGLEVEL = os.environ.get("LOGLEVEL", "INFO").upper() +logging.basicConfig(level=LOGLEVEL) + +logger = logging.getLogger(__name__) diff --git a/hoymiles_wifi/__main__.py b/hoymiles_wifi/__main__.py new file mode 100644 index 0000000..73d013e --- /dev/null +++ b/hoymiles_wifi/__main__.py @@ -0,0 +1,398 @@ +"""Contains the main functionality of the hoymiles_wifi package.""" + +from __future__ import annotations + +import argparse +import asyncio +import json +import sys +from dataclasses import asdict, dataclass + +from google.protobuf.json_format import MessageToJson +from google.protobuf.message import Message + +from hoymiles_wifi.const import DTU_FIRMWARE_URL_00_01_11, MAX_POWER_LIMIT +from hoymiles_wifi.dtu import DTU +from hoymiles_wifi.hoymiles import ( + generate_dtu_version_string, + generate_inverter_serial_number, + generate_sw_version_string, + generate_version_string, + get_dtu_model_name, + get_inverter_model_name, +) +from hoymiles_wifi.protobuf import ( + AppGetHistPower_pb2, + APPHeartbeatPB_pb2, + APPInfomationData_pb2, + CommandPB_pb2, + GetConfig_pb2, + InfomationData_pb2, + NetworkInfo_pb2, + RealData_pb2, + RealDataNew_pb2, +) + +RED = "\033[91m" +END = "\033[0m" + + +@dataclass +class VersionInfo: + """Represents version information for the hoymiles_wifi package.""" + + dtu_hw_version: str + dtu_sw_version: str + inverter_hw_version: str + inverter_sw_version: str + + def __str__(self: VersionInfo) -> str: + """Return a string representation of the VersionInfo object.""" + + return ( + f'dtu_hw_version: "{self.dtu_hw_version}"\n' + f'dtu_sw_version: "{self.dtu_sw_version}"\n' + f'inverter_hw_version: "{self.inverter_hw_version}"\n' + f'inverter_sw_version: "{self.inverter_sw_version}"\n' + ) + + def to_dict(self: VersionInfo) -> dict: + """Convert the VersionInfo object to a dictionary.""" + + return asdict(self) + + +# Inverter commands +async def async_get_real_data_new( + dtu: DTU, +) -> RealDataNew_pb2.RealDataNewResDTO | None: + """Get real data from the inverter asynchronously.""" + + return await dtu.async_get_real_data_new() + + +async def async_get_real_data(dtu: DTU) -> RealData_pb2.RealDataResDTO | None: + """Get real data from the inverter asynchronously.""" + + return await dtu.async_get_real_data() + + +async def async_get_config(dtu: DTU) -> GetConfig_pb2.GetConfigResDTO | None: + """Get the config from the inverter asynchronously.""" + + return await dtu.async_get_config() + + +async def async_network_info( + dtu: DTU, +) -> NetworkInfo_pb2.NetworkInfoResDTO | None: + """Get network information from the inverter asynchronously.""" + + return await dtu.async_network_info() + + +async def async_app_information_data( + dtu: DTU, +) -> APPInfomationData_pb2.AppInfomationDataResDTO | None: + """Get application information data from the inverter asynchronously.""" + + return await dtu.async_app_information_data() + + +async def async_app_get_hist_power( + dtu: DTU, +) -> AppGetHistPower_pb2.AppGetHistPowerResDTO: + """Get historical power data from the inverter asynchronously.""" + + return await dtu.async_app_get_hist_power() + + +async def async_set_power_limit( + dtu: DTU, +) -> CommandPB_pb2.CommandResDTO | None: + """Set the power limit of the inverter asynchronously.""" + + print( # noqa: T201 + RED + + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" + + "!!! Danger zone! This will change the power limit of the dtu. !!!\n" + + "!!! Please be careful and make sure you know what you are doing. !!!\n" + + "!!! Only proceed if you know what you are doing. !!!\n" + + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" + + END, + ) + + cont = input("Do you want to continue? (y/n): ") + if cont != "y": + return None + + power_limit = int(input("Enter the new power limit (0-100): ")) + + if power_limit < 0 or power_limit > MAX_POWER_LIMIT: + print("Error. Invalid power limit!") # noqa: T201 + return None + + print(f"Setting power limit to {power_limit}%") # noqa: T201 + cont = input("Are you sure? (y/n): ") + + if cont != "y": + return None + + return await dtu.async_set_power_limit(power_limit) + + +async def async_set_wifi(dtu: DTU) -> CommandPB_pb2.CommandResDTO | None: + """Set the wifi SSID and password of the inverter asynchronously.""" + + wifi_ssid = input("Enter the new wifi SSID: ").strip() + wifi_password = input("Enter the new wifi password: ").strip() + print(f'Setting wifi to "{wifi_ssid}"') # noqa: T201 + print(f'Setting wifi password to "{wifi_password}"') # noqa: T201 + cont = input("Are you sure? (y/n): ") + if cont != "y": + return None + return await dtu.async_set_wifi(wifi_ssid, wifi_password) + + +async def async_firmware_update(dtu: DTU) -> CommandPB_pb2.CommandResDTO | None: + """Update the firmware of the DTU asynchronously.""" + + print( # noqa: T201 + RED + + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" + + "!!! Danger zone! This will update the firmeware of the DTU. !!!\n" + + "!!! Please be careful and make sure you know what you are doing. !!!\n" + + "!!! Only proceed if you know what you are doing. !!!\n" + + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" + + END, + ) + + cont = input("Do you want to continue? (y/n): ") + if cont != "y": + return None + + print("Please select a firmware version:") # noqa: T201 + print("1.) V00.01.11") # noqa: T201 + print("2.) Custom URL") # noqa: T201 + + while True: + selection = input("Enter your selection (1 or 2): ") + + if selection == "1": + url = DTU_FIRMWARE_URL_00_01_11 + break + if selection == "2": + url = input("Enter the custom URL: ").strip() + break + + print("Invalid selection. Please enter 1 or 2.") # noqa: T201 + + print() # noqa: T201 + print(f'Firmware update URL: "{url}"') # noqa: T201 + print() # noqa: T201 + + cont = input("Do you want to continue? (y/n): ") + if cont != "y": + return None + + return await dtu.async_update_dtu_firmware() + + +async def async_restart_dtu(dtu: DTU) -> CommandPB_pb2.CommandResDTO | None: + """Restart the DTU asynchronously.""" + + cont = input("Do you want to restart the DTU? (y/n): ") + if cont != "y": + return None + + return await dtu.async_restart_dtu() + + +async def async_turn_on_inverter(dtu: DTU) -> CommandPB_pb2.CommandResDTO | None: + """Turn on the inverter asynchronously.""" + + inverter_serial = input("Enter the inverter serial number to turn *ON*: ") + + cont = input(f"Do you want to turn *ON* the Inverter {inverter_serial}? (y/n): ") + if cont != "y": + return None + + return await dtu.async_turn_on_inverter(inverter_serial) + + +async def async_turn_off_inverter(dtu: DTU) -> CommandPB_pb2.CommandResDTO | None: + """Turn off the inverter asynchronously.""" + + inverter_serial = input("Enter the inverter serial number to turn *OFF*: ") + + cont = input(f"Do you want to turn *OFF* the Inverter {inverter_serial}? (y/n): ") + if cont != "y": + return None + + return await dtu.async_turn_off_inverter(inverter_serial) + + +async def async_get_information_data( + dtu: DTU, +) -> InfomationData_pb2.InfomationDataResDTO: + """Get information data from the dtu asynchronously.""" + + return await dtu.async_get_information_data() + + +async def async_get_version_info(dtu: DTU) -> VersionInfo | None: + """Get version information from the dtu asynchronously.""" + + response = await async_app_information_data(dtu) + + if not response: + return None + + return VersionInfo( + dtu_hw_version="H" + + generate_dtu_version_string(response.dtu_info.dtu_hw_version), + dtu_sw_version="V" + + generate_dtu_version_string(response.dtu_info.dtu_sw_version), + inverter_hw_version="H" + + generate_version_string(response.pv_info[0].pv_hw_version), + inverter_sw_version="V" + + generate_sw_version_string(response.pv_info[0].pv_sw_version), + ) + + +async def async_heatbeat(dtu: DTU) -> APPHeartbeatPB_pb2.APPHeartbeatResDTO | None: + """Request a heartbeat from the dtu asynchronously.""" + + return await dtu.async_heartbeat() + + +async def async_identify_dtu(dtu: DTU) -> str: + """Identify the DTU asynchronously.""" + + real_data = await async_get_real_data_new(dtu) + return get_dtu_model_name(real_data.device_serial_number) + + +async def async_identify_inverters(dtu: DTU) -> list[str]: + """Identify the DTU asynchronously.""" + + inverter_models = [] + real_data = await async_get_real_data_new(dtu) + if real_data: + for sgs_data in real_data.sgs_data: + serial_number = generate_inverter_serial_number(sgs_data.serial_number) + inverter_model = get_inverter_model_name(serial_number) + inverter_models.append(inverter_model) + + for tgs_data in real_data.tgs_data: + serial_number = generate_inverter_serial_number(tgs_data.serial_number) + inverter_model = get_inverter_model_name(serial_number) + inverter_models.append(inverter_model) + + return inverter_models + + +async def async_get_alarm_list(dtu: DTU) -> None: + """Get alarm list from the dtu asynchronously.""" + + return await dtu.async_get_alarm_list() + + +def print_invalid_command(command: str) -> None: + """Print an invalid command message.""" + + print(f"Invalid command: {command}") # noqa: T201 + sys.exit(1) + + +async def main() -> None: + """Execute the main function for the hoymiles_wifi package.""" + + parser = argparse.ArgumentParser(description="Hoymiles DTU Monitoring") + parser.add_argument( + "--host", type=str, required=True, help="IP address or hostname of the DTU" + ) + parser.add_argument( + "--as-json", + action="store_true", + default=False, + help="Format the output as JSON", + ) + parser.add_argument( + "command", + type=str, + choices=[ + "get-real-data-new", + "get-real-data", + "get-config", + "network-info", + "app-information-data", + "app-get-hist-power", + "set-power-limit", + "set-wifi", + "firmware-update", + "restart-dtu", + "turn-on-inverter", + "turn-off-inverter", + "get-information-data", + "get-version-info", + "heartbeat", + "identify-dtu", + "identify-inverters", + "get-alarm-list", + ], + help="Command to execute", + ) + args = parser.parse_args() + + dtu = DTU(args.host) + + # Execute the specified command using a switch case + switch = { + "get-real-data-new": async_get_real_data_new, + "get-real-data": async_get_real_data, + "get-config": async_get_config, + "network-info": async_network_info, + "app-information-data": async_app_information_data, + "app-get-hist-power": async_app_get_hist_power, + "set-power-limit": async_set_power_limit, + "set-wifi": async_set_wifi, + "firmware-update": async_firmware_update, + "restart-dtu": async_restart_dtu, + "turn-on-inverter": async_turn_on_inverter, + "turn-off-inverter": async_turn_off_inverter, + "get-information-data": async_get_information_data, + "get-version-info": async_get_version_info, + "heartbeat": async_heatbeat, + "identify-dtu": async_identify_dtu, + "identify-inverters": async_identify_inverters, + "get-alarm-list": async_get_alarm_list, + } + + command_func = switch.get(args.command, print_invalid_command) + response = await command_func(dtu) + + if response: + if args.as_json: + if isinstance(response, Message): + print(MessageToJson(response)) # noqa: T201 + else: + print(json.dumps(asdict(response), indent=4)) # noqa: T201 + else: + print(f"{args.command.capitalize()} Response: \n{response}") # noqa: T201 + else: + print( # noqa: T201 + f"No response or unable to retrieve response for " + f"{args.command.replace('_', ' ')}", + ) + sys.exit(2) + + +def run_main() -> None: + """Run the main function for the hoymiles_wifi package.""" + + asyncio.run(main()) + + +if __name__ == "__main__": + run_main() diff --git a/hoymiles_wifi/const.py b/hoymiles_wifi/const.py new file mode 100644 index 0000000..c2e14df --- /dev/null +++ b/hoymiles_wifi/const.py @@ -0,0 +1,118 @@ +"""Constants for the Hoymiles WiFi integration.""" + +DTU_PORT = 10081 + +# App -> DTU start with 0xa3, responses start 0xa2 +CMD_HEADER = b"HM" +CMD_APP_INFO_DATA_RES_DTO = b"\xa3\x01" +CMD_HB_RES_DTO = b"\xa3\x02" +CMD_REAL_DATA_RES_DTO = b"\xa3\x03" +CMD_W_INFO_RES_DTO = b"\xa3\x04" +CMD_COMMAND_RES_DTO = b"\xa3\x05" +CMD_COMMAND_STATUS_RES_DTO = b"\xa3\x06" +CMD_DEV_CONFIG_FETCH_RES_DTO = b"\xa3\x07" +CMD_DEV_CONFIG_PUT_RES_DTO = b"\xa3\x08" +CMD_GET_CONFIG = b"\xa3\x09" +CMD_SET_CONFIG = b"\xa3\x10" +CMD_REAL_RES_DTO = b"\xa3\x11" +CMD_GPST_RES_DTO = b"\xa3\x12" +CMD_AUTO_SEARCH = b"\xa3\x13" +CMD_NETWORK_INFO_RES = b"\xa3\x14" +CMD_APP_GET_HIST_POWER_RES = b"\xa3\x15" +CMD_APP_GET_HIST_ED_RES = b"\xa3\x16" +CMD_HB_RES_DTO_ALT = b"\x83\x01" +CMD_REGISTER_RES_DTO = b"\x83\x02" +CMD_STORAGE_DATA_RES = b"\x83\x03" +CMD_COMMAND_RES_DTO_2 = b"\x83\x05" +CMD_COMMAND_STATUS_RES_DTO_2 = b"\x83\x06" +CMD_DEV_CONFIG_FETCH_RES_DTO_2 = b"\x83\x07" +CMD_DEV_CONFIG_PUT_RES_DTO_2 = b"\x83\x08" +CMD_GET_CONFIG_RES = b"\xdb\x08" +CMD_SET_CONFIG_RES = b"\xdb\x07" + +CMD_CLOUD_INFO_DATA_RES_DTO = b"\x23\x01" +CMD_CLOUD_COMMAND_RES_DTO = b"\x23\x05" + +CMD_ACTION_MICRO_DEFAULT = 0 +CMD_ACTION_DTU_REBOOT = 1 +CMD_ACTION_DTU_UPGRADE = 2 +CMD_ACTION_MI_REBOOT = 3 +CMD_ACTION_COLLECT_VERSION = 4 +CMD_ACTION_ANTI_THEFT_SETTING = 5 +CMD_ACTION_MI_START = 6 +CMD_ACTION_MI_SHUTDOWN = 7 +CMD_ACTION_LIMIT_POWER = 8 +CMD_ACTION_REFLUX_CONTROL = 9 +CMD_ACTION_CLEAN_GROUNDING_FAULT = 10 +CMD_ACTION_CT_SET = 11 +CMD_ACTION_MI_LOCK = 12 +CMD_ACTION_MI_UNLOCK = 13 +CMD_ACTION_SET_GRID_FILE = 14 +CMD_ACTION_UPGRADE_MI = 15 +CMD_ACTION_ID_NETWORKING = 16 +CMD_ACTION_REFLUX_NETWORKING = 17 +CMD_ACTION_STOP_CONTROLLER_CMD = 18 +CMD_ACTION_SET_WIFI_PASS = 19 +CMD_ACTION_SET_SVR_DNS_PORT = 20 +CMD_ACTION_SET_GPRS_APN = 21 +CMD_ACTION_ANTI_THEFT_CONTROL = 22 +CMD_ACTION_REPEATER_NETWORKING = 0 +CMD_ACTION_DTU_DEFAULT = 0 +CMD_ACTION_GATEWAY_DEFAULT = 0 +CMD_ACTION_METER_REVERSE = 49 +CMD_ACTION_ALARM_LIST = 50 +CMD_ACTION_GW_REBOOT = 4096 +CMD_ACTION_GW_RESET = 4097 +CMD_ACTION_GW_STOP_RUN = 4098 +CMD_ACTION_GW_COLLECT_REAL_DATA = 4099 +CMD_ACTION_GW_COLLECT_VER = 4100 +CMD_ACTION_GW_AUTO_NETWORKING = 4101 +CMD_ACTION_GW_UPGRADE = 4102 +CMD_ACTION_MICRO_MEMORY_SNAPSHOT = 53 +CMD_ACTION_MICRO_DATA_WAVE = 54 +CMD_ACTION_SET_485_PORT = 36 +CMD_ACTION_THREE_BALANCE_SET = 37 +CMD_ACTION_MI_GRID_PROTECT_SELF = 38 +CMD_ACTION_SUN_SPEC_CONFIG = 39 +CMD_ACTION_POWER_GENERATION_CORRECT = 40 +CMD_ACTION_GRID_FILE_READ = 41 +CMD_ACTION_CLEAN_WARN = 42 +CMD_ACTION_DRM_SETTING = 43 +CMD_ACTION_ES_CONFIG_MANAGER = 0 +CMD_ACTION_ES_USER_SETTING = 0 +CMD_ACTION_READ_MI_HU_WARN = 46 +CMD_ACTION_LIMIT_POWER_PF = 47 +CMD_ACTION_LIMIT_POWER_REACTIVE = 48 +CMD_ACTION_INV_BOOT_UP = 8193 +CMD_ACTION_INV_SHUTDOWN = 8194 +CMD_ACTION_INV_REBOOT = 8195 +CMD_ACTION_INV_RESET = 8196 +CMD_ACTION_INV_CLEAN_WARN = 8197 +CMD_ACTION_INV_CLEAN_HIS_DATA = 8198 +CMD_ACTION_INV_UPLOAD_REAL_DATA = 8199 +CMD_ACTION_INV_FIND_DEV = 8200 +CMD_ACTION_INV_BATTERY_MODE_CONFIG = 0 +CMD_ACTION_BMS_REBOOT = 8224 +CMD_ACTION_BMS_URGENT_CHARGING = 8225 +CMD_ACTION_BMS_BALANCE = 8208 +CMD_ACTION_INV_UPGRADE = 4112 +CMD_ACTION_BMS_UPGRADE = 4112 + + +DEV_DTU = 1 +DEV_REPEATER = 2 +DEV_MICRO = 3 +DEV_MODEL = 4 +DEV_METER = 5 +DEV_INV = 6 +DEV_RSD = 7 +DEV_OP = 8 +DEV_GATEWAY = 9 +DEV_BMS = 10 + +DTU_FIRMWARE_URL_00_01_11 = ( + "http://fwupdate.hoymiles.com/cfs/bin/2311/06/,1488725943932555264.bin" +) + +MAX_POWER_LIMIT = 100 +OFFSET = 28800 diff --git a/hoymiles_wifi/dtu.py b/hoymiles_wifi/dtu.py new file mode 100644 index 0000000..d855698 --- /dev/null +++ b/hoymiles_wifi/dtu.py @@ -0,0 +1,422 @@ +"""DTU communication implementation for Hoymiles WiFi.""" + +from __future__ import annotations + +import asyncio +import struct +import time +from datetime import datetime +from enum import Enum, IntEnum +from typing import Any + +from crcmod import mkCrcFun + +from hoymiles_wifi import logger +from hoymiles_wifi.const import ( + CMD_ACTION_ALARM_LIST, + CMD_ACTION_DTU_REBOOT, + CMD_ACTION_DTU_UPGRADE, + CMD_ACTION_LIMIT_POWER, + CMD_ACTION_MI_SHUTDOWN, + CMD_ACTION_MI_START, + CMD_APP_GET_HIST_POWER_RES, + CMD_APP_INFO_DATA_RES_DTO, + CMD_CLOUD_COMMAND_RES_DTO, + CMD_COMMAND_RES_DTO, + CMD_GET_CONFIG, + CMD_HB_RES_DTO, + CMD_HEADER, + CMD_NETWORK_INFO_RES, + CMD_REAL_DATA_RES_DTO, + CMD_REAL_RES_DTO, + CMD_SET_CONFIG, + DEV_DTU, + DTU_FIRMWARE_URL_00_01_11, + DTU_PORT, + OFFSET, +) +from hoymiles_wifi.hoymiles import convert_inverter_serial_number +from hoymiles_wifi.protobuf import ( + AppGetHistPower_pb2, + APPHeartbeatPB_pb2, + APPInfomationData_pb2, + CommandPB_pb2, + GetConfig_pb2, + InfomationData_pb2, + NetworkInfo_pb2, + RealData_pb2, + RealDataNew_pb2, + SetConfig_pb2, +) +from hoymiles_wifi.utils import initialize_set_config + + +class NetmodeSelect(IntEnum): + """Network mode selection.""" + + WIFI = 1 + SIM = 2 + LAN = 3 + + +class NetworkState(Enum): + """Network state.""" + + Unknown = 0 + Online = 1 + Offline = 2 + + +class DTU: + """DTU class.""" + + def __init__(self, host: str): + """Initialize DTU class.""" + + self.host = host + self.state = NetworkState.Unknown + self.sequence = 0 + self.mutex = asyncio.Lock() + + def get_state(self) -> NetworkState: + """Get DTU state.""" + + return self.state + + def set_state(self, new_state: NetworkState): + """Set DTU state.""" + + if self.state != new_state: + self.state = new_state + logger.debug(f"DTU is {new_state}") + + async def async_get_real_data(self) -> RealData_pb2.RealDataResDTO | None: + """Get real data.""" + + request = RealData_pb2.RealDataResDTO() + request.time_ymd_hms = ( + datetime.now().strftime("%Y-%m-%d %H:%M:%S").encode("utf-8") + ) + request.time = int(time.time()) + request.offset = OFFSET + request.error_code = 0 + + command = CMD_REAL_DATA_RES_DTO + return await self.async_send_request( + command, request, RealData_pb2.RealDataReqDTO + ) + + async def async_get_real_data_new(self) -> RealDataNew_pb2.RealDataNewResDTO | None: + """Get real data new.""" + + request = RealDataNew_pb2.RealDataNewResDTO() + request.time_ymd_hms = ( + datetime.now().strftime("%Y-%m-%d %H:%M:%S").encode("utf-8") + ) + request.offset = OFFSET + request.time = int(time.time()) + command = CMD_REAL_RES_DTO + return await self.async_send_request( + command, request, RealDataNew_pb2.RealDataNewReqDTO + ) + + async def async_get_config(self) -> GetConfig_pb2.GetConfigResDTO | None: + """Get config.""" + + request = GetConfig_pb2.GetConfigResDTO() + request.offset = OFFSET + request.time = int(time.time()) - 60 + command = CMD_GET_CONFIG + return await self.async_send_request( + command, + request, + GetConfig_pb2.GetConfigReqDTO, + ) + + async def async_network_info(self) -> NetworkInfo_pb2.NetworkInfoResDTO | None: + """Get network info.""" + + request = NetworkInfo_pb2.NetworkInfoResDTO() + request.offset = OFFSET + request.time = int(time.time()) + command = CMD_NETWORK_INFO_RES + return await self.async_send_request( + command, request, NetworkInfo_pb2.NetworkInfoReqDTO + ) + + async def async_app_information_data( + self, + ) -> APPInfomationData_pb2.APPInfoDataResDTO: + """Get app information data.""" + request = APPInfomationData_pb2.APPInfoDataResDTO() + request.time_ymd_hms = ( + datetime.now().strftime("%Y-%m-%d %H:%M:%S").encode("utf-8") + ) + request.offset = OFFSET + request.time = int(time.time()) + command = CMD_APP_INFO_DATA_RES_DTO + return await self.async_send_request( + command, request, APPInfomationData_pb2.APPInfoDataReqDTO + ) + + async def async_app_get_hist_power( + self, + ) -> AppGetHistPower_pb2.AppGetHistPowerResDTO | None: + """Get historical power.""" + + request = AppGetHistPower_pb2.AppGetHistPowerResDTO() + request.control_point = 0 + request.offset = OFFSET + request.requested_time = int(time.time()) + request.requested_day = 0 + command = CMD_APP_GET_HIST_POWER_RES + return await self.async_send_request( + command, + request, + AppGetHistPower_pb2.AppGetHistPowerReqDTO, + ) + + async def async_set_power_limit( + self, + power_limit: int, + ) -> CommandPB_pb2.CommandResDTO | None: + """Set power limit.""" + if power_limit < 0 or power_limit > 100: + logger.error("Error. Invalid power limit!") + return + + power_limit = power_limit * 10 + + request = CommandPB_pb2.CommandResDTO() + request.time = int(time.time()) + request.action = CMD_ACTION_LIMIT_POWER + request.package_nub = 1 + request.tid = int(time.time()) + request.data = f"A:{power_limit},B:0,C:0\r".encode() + + command = CMD_COMMAND_RES_DTO + + return await self.async_send_request( + command, request, CommandPB_pb2.CommandReqDTO + ) + + async def async_set_wifi( + self, ssid: str, password: str + ) -> SetConfig_pb2.SetConfigResDTO | None: + """Set wifi.""" + + get_config_req = await self.async_get_config() + + if get_config_req is None: + logger.error("Failed to get config") + return None + + request = initialize_set_config(get_config_req) + + request.time = int(time.time()) + request.offset = OFFSET + request.app_page = 1 + request.netmode_select = NetmodeSelect.WIFI + request.wifi_ssid = ssid.encode("utf-8") + request.wifi_password = password.encode("utf-8") + + command = CMD_SET_CONFIG + return await self.async_send_request( + command, request, SetConfig_pb2.SetConfigReqDTO + ) + + async def async_update_dtu_firmware( + self, + firmware_url: str = DTU_FIRMWARE_URL_00_01_11, + ) -> CommandPB_pb2.CommandResDTO | None: + """Update DTU firmware.""" + + request = CommandPB_pb2.CommandResDTO() + request.action = CMD_ACTION_DTU_UPGRADE + request.package_nub = 1 + request.tid = int(time.time()) + request.data = (firmware_url + "\r").encode("utf-8") + + command = CMD_CLOUD_COMMAND_RES_DTO + return await self.async_send_request( + command, request, CommandPB_pb2.CommandReqDTO + ) + + async def async_restart_dtu(self) -> CommandPB_pb2.CommandResDTO | None: + """Restart DTU.""" + + request = CommandPB_pb2.CommandResDTO() + request.action = CMD_ACTION_DTU_REBOOT + request.package_nub = 1 + request.tid = int(time.time()) + + command = CMD_CLOUD_COMMAND_RES_DTO + return await self.async_send_request( + command, request, CommandPB_pb2.CommandReqDTO + ) + + async def async_turn_on_inverter( + self, inverter_serial: str + ) -> CommandPB_pb2.CommandResDTO | None: + """Turn on Inverter.""" + + inverter_serial_int = convert_inverter_serial_number(inverter_serial) + + request = CommandPB_pb2.CommandResDTO() + request.action = CMD_ACTION_MI_START + request.package_nub = 1 + request.dev_kind = DEV_DTU + request.tid = int(time.time()) + request.mi_to_sn.extend([inverter_serial_int]) + + command = CMD_CLOUD_COMMAND_RES_DTO + + return await self.async_send_request( + command, request, CommandPB_pb2.CommandReqDTO + ) + + async def async_turn_off_inverter( + self, inverter_serial: str + ) -> CommandPB_pb2.CommandResDTO | None: + """Turn off Inverter.""" + + inverter_serial_int = convert_inverter_serial_number(inverter_serial) + + request = CommandPB_pb2.CommandResDTO() + request.action = CMD_ACTION_MI_SHUTDOWN + request.package_nub = 1 + request.dev_kind = DEV_DTU + request.tid = int(time.time()) + request.mi_to_sn.extend([inverter_serial_int]) + + command = CMD_CLOUD_COMMAND_RES_DTO + + return await self.async_send_request( + command, request, CommandPB_pb2.CommandReqDTO + ) + + async def async_get_information_data( + self, + ) -> InfomationData_pb2.InfoDataResDTO | None: + """Get information data.""" + + request = InfomationData_pb2.InfoDataResDTO() + request.time_ymd_hms = ( + datetime.now().strftime("%Y-%m-%d %H:%M:%S").encode("utf-8") + ) + request.offset = OFFSET + request.time = int(time.time()) + command = CMD_APP_INFO_DATA_RES_DTO + return await self.async_send_request( + command, request, InfomationData_pb2.InfoDataReqDTO + ) + + async def async_heartbeat(self) -> APPHeartbeatPB_pb2.HBReqDTO | None: + """Request heartbeat.""" + + request = APPHeartbeatPB_pb2.HBResDTO() + request.time_ymd_hms = ( + datetime.now().strftime("%Y-%m-%d %H:%M:%S").encode("utf-8") + ) + request.offset = OFFSET + request.time = int(time.time()) + + command = CMD_HB_RES_DTO + return await self.async_send_request( + command, request, APPHeartbeatPB_pb2.HBReqDTO + ) + + async def async_get_alarm_list(self) -> CommandPB_pb2.CommandResDTO | None: + """Turn off DTU.""" + + request = CommandPB_pb2.CommandResDTO() + request.action = CMD_ACTION_ALARM_LIST + request.package_nub = 1 + request.dev_kind = 0 + request.tid = int(time.time()) + + command = CMD_COMMAND_RES_DTO + return await self.async_send_request( + command, request, CommandPB_pb2.CommandReqDTO + ) + + async def async_send_request( + self, + command: bytes, + request: Any, + response_type: Any, + dtu_port: int = DTU_PORT, + ): + """Send request to DTU.""" + + self.sequence = (self.sequence + 1) & 0xFFFF + + request_as_bytes = request.SerializeToString() + crc16 = mkCrcFun(0x18005, rev=True, initCrc=0xFFFF, xorOut=0x0000)( + request_as_bytes + ) + length = len(request_as_bytes) + 10 + + # compose request message + header = CMD_HEADER + command + message = ( + header + + struct.pack(">HHH", self.sequence, crc16, length) + + request_as_bytes + ) + + address = (self.host, dtu_port) + + async with self.mutex: + try: + reader, writer = await asyncio.open_connection(*address) + + writer.write(message) + await writer.drain() + + buf = await asyncio.wait_for(reader.read(1024), timeout=5) + except (OSError, asyncio.TimeoutError) as e: + logger.debug(f"{e}") + self.set_state(NetworkState.Offline) + return None + finally: + try: + writer.close() + await writer.wait_closed() + except Exception as e: + logger.debug(f"Error closing writer: {e}") + + try: + if len(buf) < 10: + raise ValueError("Buffer is too short for unpacking") + + crc16_target, read_length = struct.unpack(">HH", buf[6:10]) + + logger.debug(f"Read length: {read_length}") + + if len(buf) != read_length: + raise ValueError("Buffer is incomplete") + + response_as_bytes = buf[10:read_length] + + crc16_response = mkCrcFun(0x18005, rev=True, initCrc=0xFFFF, xorOut=0x0000)( + response_as_bytes + ) + + if crc16_response != crc16_target: + logger.debug( + f"CRC16 mismatch: {hex(crc16_response)} != {hex(crc16_target)}" + ) + raise ValueError("CRC16 mismatch") + + parsed = response_type.FromString(response_as_bytes) + + if not parsed: + raise ValueError("Parsing resulted in an empty or falsy value") + except Exception as e: + logger.debug(f"Failed to parse response: {e}") + self.set_state(NetworkState.Unknown) + return None + + self.set_state(NetworkState.Online) + return parsed diff --git a/hoymiles_wifi/hoymiles.py b/hoymiles_wifi/hoymiles.py new file mode 100644 index 0000000..6bbcffa --- /dev/null +++ b/hoymiles_wifi/hoymiles.py @@ -0,0 +1,304 @@ +"""Hoymiles quirks for inverters and DTU.""" + +import struct +from enum import Enum + +from hoymiles_wifi import logger + + +class InverterType(Enum): + """Inverter type.""" + + ONE = "1T" + TWO = "2T" + FOUR = "4T" + SIX = "6T" + + +class InverterSeries(Enum): + """Inverter series.""" + + HM = "HM" + HMS = "HMS" + HMT = "HMT" + + +class InverterPower(Enum): + """Inverter power.""" + + P_100 = "100" + P_250 = "250" + P_300_350_400 = "300/350/400" + P_400 = "400" + P_500 = "500" + P_600_700_800 = "600/700/800" + P_800W = "800W" + P_1000 = "1000" + P_1000W = "1000W" + P_800W_1000W = "800W/1000W" + P_1000_1200_1500 = "1000/1200/1500" + P_1200_1500 = "1200/1500" + P_1600 = "1600" + P_2000 = "2000" + P_2250 = "2250" + + +power_mapping = { + 0x1011: InverterPower.P_100, + 0x1020: InverterPower.P_250, + 0x1021: InverterPower.P_300_350_400, + 0x1121: InverterPower.P_300_350_400, + 0x1125: InverterPower.P_400, + 0x1040: InverterPower.P_500, + 0x1041: InverterPower.P_600_700_800, + 0x1042: InverterPower.P_600_700_800, + 0x1141: InverterPower.P_600_700_800, + 0x1060: InverterPower.P_1000, + 0x1061: InverterPower.P_1200_1500, + 0x1161: InverterPower.P_1000_1200_1500, + 0x1164: InverterPower.P_1600, + 0x1412: InverterPower.P_800W_1000W, + 0x1382: InverterPower.P_2250, +} + + +class DTUType(Enum): + """DTU type.""" + + DTU_G100 = "DTU-G100" + DTU_W100 = "DTU-W100" + DTU_LITE_S = "DTU-Lite-S" + DTU_LITE = "DTU-Lite" + DTU_PRO = "DTU-PRO" + DTU_PRO_S = "DTU-PRO-S" + DTUBI = "DTUBI" + DTU_W100_LITE_S = "DTU-W100/DTU-Lite-S" + DTU_W_LITE = "DTU-WLite" + + +type_mapping = { + 0x10F7: DTUType.DTU_PRO, + 0x10FB: DTUType.DTU_PRO, + 0x4101: DTUType.DTU_PRO, + 0x10FC: DTUType.DTU_PRO, + 0x4120: DTUType.DTU_PRO, + 0x10F8: DTUType.DTU_PRO, + 0x4100: DTUType.DTU_PRO, + 0x10FD: DTUType.DTU_PRO, + 0x4121: DTUType.DTU_PRO, + 0x10D3: DTUType.DTU_W100_LITE_S, + 0x4110: DTUType.DTU_W100_LITE_S, + 0x10D8: DTUType.DTU_W100_LITE_S, + 0x4130: DTUType.DTU_W100_LITE_S, + 0x4132: DTUType.DTU_W100_LITE_S, + 0x4133: DTUType.DTU_W100_LITE_S, + 0x10D9: DTUType.DTU_W100_LITE_S, + 0x4111: DTUType.DTU_W100_LITE_S, + 0x10D2: DTUType.DTU_G100, + 0x10D6: DTUType.DTU_LITE, + 0x10D7: DTUType.DTU_LITE, + 0x4131: DTUType.DTU_LITE, + 0x1124: DTUType.DTUBI, + 0x1125: DTUType.DTUBI, + 0x1403: DTUType.DTUBI, + 0x1144: DTUType.DTUBI, + 0x1143: DTUType.DTUBI, + 0x1145: DTUType.DTUBI, + 0x1412: DTUType.DTUBI, + 0x1164: DTUType.DTUBI, + 0x1165: DTUType.DTUBI, + 0x1166: DTUType.DTUBI, + 0x1167: DTUType.DTUBI, + 0x1222: DTUType.DTUBI, + 0x1422: DTUType.DTUBI, + 0x1423: DTUType.DTUBI, + 0x1361: DTUType.DTUBI, + 0x1362: DTUType.DTUBI, + 0x1381: DTUType.DTUBI, + 0x1382: DTUType.DTUBI, + 0x4143: DTUType.DTUBI, +} + + +def format_number(number: int) -> str: + """Format number to two digits.""" + + return f"{number:02d}" + + +def generate_version_string(version_number: int) -> str: + """Generate version string.""" + + version_string = ( + format_number(version_number // 2048) + + "." + + format_number((version_number // 64) % 32) + + "." + + format_number(version_number % 64) + ) + return version_string + + +def generate_sw_version_string(version_number: int) -> str: + """Generate software version string.""" + + version_number2 = version_number // 10000 + version_number3 = (version_number - (version_number2 * 10000)) // 100 + version_number4 = (version_number - (version_number2 * 10000)) - ( + version_number3 * 100 + ) + + version_string = ( + format_number(version_number2) + + "." + + format_number(version_number3) + + "." + + format_number(version_number4) + ) + return version_string + + +def generate_dtu_version_string(version_number: int, type: str = "") -> str: + """Generate DTU version string.""" + + version_string = "" + version_number2 = version_number % 256 + version_number3 = (version_number // 256) % 16 + + if type == "SRF": + version_string += f"{format_number(version_number // 1048576)}.{format_number((version_number % 65536) // 4096)}.{format_number(version_number3)}.{format_number(version_number2)}" + elif type == "HRF": + version_string += f"{format_number(version_number // 65536)}.{format_number((version_number % 65536) // 4096)}.{format_number(version_number3)}.{format_number(version_number2)}" + else: + version_string += f"{format_number(version_number // 4096)}.{format_number(version_number3)}.{format_number(version_number2)}" + + return version_string + + +def generate_inverter_serial_number(serial_number: int) -> str: + """Generate inverter serial number.""" + + return hex(serial_number)[2:] + + +def convert_inverter_serial_number(serial_number_str: str) -> int: + """Get inverter serial number from string.""" + + return int(serial_number_str, 16) + + +def get_inverter_type(serial_bytes: bytes) -> InverterType: + """Get inverter type.""" + + inverter_type = None + # Access individual bytes + if serial_bytes[0] == 0x11: + if serial_bytes[1] in [0x25, 0x24, 0x22, 0x21]: + inverter_type = InverterType.ONE + elif serial_bytes[1] in [0x44, 0x42, 0x41]: + inverter_type = InverterType.TWO + elif serial_bytes[1] in [0x64, 0x62, 0x61]: + inverter_type = InverterType.FOUR + elif serial_bytes[0] == 0x13: + inverter_type = InverterType.SIX + elif serial_bytes[0] == 0x14: + if serial_bytes[1] in [0x12]: + inverter_type = InverterType.TWO + + if inverter_type is None: + raise ValueError( + f"Unknown inverter type: {hex(serial_bytes[0])} {hex(serial_bytes[1])}" + ) + + return inverter_type + + +def get_inverter_series(serial_bytes: bytes) -> InverterSeries: + """Get inverter series.""" + + series = None + if serial_bytes[0] == 0x11: + if (serial_bytes[1] & 0x0F) == 0x04: + series = InverterSeries.HMS + else: + series = InverterSeries.HM + elif serial_bytes[0] == 0x10: + if serial_bytes[1] & 0x03 == 0x02: + series = InverterSeries.HM + else: + series = InverterSeries.HMS + elif serial_bytes[0] == 0x13: + series = InverterSeries.HMT + elif serial_bytes[0] == 0x14: + series = InverterSeries.HMS + + if series is None: + raise ValueError( + f"Unknown series: {hex(serial_bytes[0])} {hex(serial_bytes[1])}!" + ) + + return series + + +def get_inverter_power(serial_bytes: bytes) -> InverterPower: + """Get inverter power.""" + + inverter_type_bytes = struct.unpack(">H", serial_bytes[:2])[0] + power = power_mapping.get(inverter_type_bytes) + + if power is None: + raise ValueError( + f"Unknown power: {hex(serial_bytes[0])} {hex(serial_bytes[1])}!" + ) + + return power + + +def get_inverter_model_name(serial_number: str) -> str: + """Get hardware model name.""" + + serial_bytes = bytes.fromhex(serial_number) + + try: + inverter_type = get_inverter_type(serial_bytes) + inverter_series = get_inverter_series(serial_bytes) + inverter_power = get_inverter_power(serial_bytes) + except Exception as e: + logger.error(e) + return "Unknown" + else: + inverter_model_name = ( + inverter_series.value + + "-" + + inverter_power.value + + "-" + + inverter_type.value + ) + return inverter_model_name + + +def get_dtu_model_type(serial_bytes: bytes) -> DTUType: + """Get DTU model type.""" + + dtu_type_bytes = struct.unpack(">H", serial_bytes[:2])[0] + + dtu_type = type_mapping.get(dtu_type_bytes) + + if dtu_type is None: + raise ValueError(f"Unknown DTU: {serial_bytes[:2]}!") + + return dtu_type + + +def get_dtu_model_name(serial_number: str) -> str: + """Get DTU model name.""" + + serial_bytes = bytes.fromhex(serial_number) + + try: + dtu_type = get_dtu_model_type(serial_bytes) + except Exception as e: + logger.error(e) + return "Unknown" + else: + return dtu_type.value diff --git a/hoymiles_wifi/protobuf/APPHeartbeatPB.proto b/hoymiles_wifi/protobuf/APPHeartbeatPB.proto new file mode 100644 index 0000000..486af76 --- /dev/null +++ b/hoymiles_wifi/protobuf/APPHeartbeatPB.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +message HBReqDTO { + int32 offset = 1; // Offset value + int32 time = 2; // Timestamp of the request + int32 csq = 3; // Carrier Signal Quality (CSQ) + string dtu_serial_number = 4; // DTU serial number + string device_serial_number = 5; // Device serial number +} + +message HBResDTO { + int32 offset = 1; // Offset value + int32 time = 2; // Timestamp of the response + string time_ymd_hms = 3; // Timestamp in the format YMD_HMS +} diff --git a/hoymiles_wifi/protobuf/APPHeartbeatPB_pb2.py b/hoymiles_wifi/protobuf/APPHeartbeatPB_pb2.py new file mode 100644 index 0000000..92257ff --- /dev/null +++ b/hoymiles_wifi/protobuf/APPHeartbeatPB_pb2.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: APPHeartbeatPB.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x41PPHeartbeatPB.proto\"n\n\x08HBReqDTO\x12\x0e\n\x06offset\x18\x01 \x01(\x05\x12\x0c\n\x04time\x18\x02 \x01(\x05\x12\x0b\n\x03\x63sq\x18\x03 \x01(\x05\x12\x19\n\x11\x64tu_serial_number\x18\x04 \x01(\t\x12\x1c\n\x14\x64\x65vice_serial_number\x18\x05 \x01(\t\">\n\x08HBResDTO\x12\x0e\n\x06offset\x18\x01 \x01(\x05\x12\x0c\n\x04time\x18\x02 \x01(\x05\x12\x14\n\x0ctime_ymd_hms\x18\x03 \x01(\tb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'APPHeartbeatPB_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_HBREQDTO']._serialized_start=24 + _globals['_HBREQDTO']._serialized_end=134 + _globals['_HBRESDTO']._serialized_start=136 + _globals['_HBRESDTO']._serialized_end=198 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/APPInfomationData.proto b/hoymiles_wifi/protobuf/APPInfomationData.proto new file mode 100644 index 0000000..f6038bb --- /dev/null +++ b/hoymiles_wifi/protobuf/APPInfomationData.proto @@ -0,0 +1,89 @@ +syntax = "proto3"; + +message APPDtuInfoMO { + int32 device_kind = 1; // Type of the device + int32 dtu_sw_version = 2; // DTU software version + int32 dtu_hw_version = 3; // DTU hardware version + int32 dtu_step_time = 4; // DTU step time + int32 dtu_rf_hw_version = 5; // DTU RF hardware version + int32 dtu_rf_sw_version = 6; // DTU RF software version + int32 access_model = 7; // Access model + int32 communication_time = 8; // Communication time + int32 signal_strength = 9; // Signal strength + string gprs_version = 10; // GPRS version + string wifi_version = 11; // Wifi version + string ka_number = 12; // KA number + int32 dtu_rule_id = 13; // DTU rule ID + int32 dtu_error_code = 14; // DTU error code + int32 dtu485_mode = 15; // DTU485 mode + int32 dtu485_address = 16; // DTU485 address + int32 sub1g_frequency_band = 17; // Sub1G frequency band + int32 sub1g_channel_total_number = 18; // Sub1G channel total number + int32 sub1g_channel_number = 19; // Sub1G channel number + int32 sub1g_rp = 20; // Sub1G RP + int32 sub1g_channel_total = 21; // Sub1G channel total + string gprs_imei = 22; // GPRS IMEI +} + +message APPMeterInfoMO { + int32 device_kind = 1; // Type of the device + int64 meter_serial_number = 2; // Meter serial number + int32 meter_model = 3; // Meter model + int32 meter_ct = 4; // Meter current transformer + int32 communication_way = 5; // Communication way + int32 access_mode = 6; // Access mode + int32 sw_version = 7; // Software version + string meter_value = 8; // Meter value +} + +message APPRpInfoMO { + int32 device_kind = 1; // Type of the device + int64 rp_serial_number = 2; // RP serial number + int32 rp_sw_version = 3; // RP software version + int32 rp_hw_version = 4; // RP hardware version + int32 rp_rule_id = 5; // RP rule ID +} + +message APPPvInfoMO { + int32 device_kind = 1; // Type of the device + int64 pv_serial_number = 2; // PV serial number + int32 pv_usfw = 3; // PV US firmware + int32 pv_sw_version = 4; // PV software version + int32 pv_hw_part_number = 5; // PV hardware part number + int32 pv_hw_version = 6; // PV hardware version + int32 pv_grid_profile_code = 7; // PV grid profile code + int32 pv_grid_profile = 8; // PV grid profile + int32 pv_rf_hw_version = 9; // PV RF hardware version + int32 pv_rf_sw_version = 10; // PV RF software version + int32 mi_rule_id = 11; // MI rule ID +} + +message APPFeatureMO { + int32 key = 1; // Feature key + string value = 2; // Feature value +} + +message APPInfoDataReqDTO { + string dtu_serial_number = 1; // DTU serial number + uint32 timestamp = 2; // Timestamp + int32 device_number = 3; // Device number + int32 pv_number = 4; // PV number + int32 package_number = 5; // Package number + int32 current_package = 6; // Current package + int32 channel = 7; // Channel + APPDtuInfoMO dtu_info = 8; // DTU information + repeated APPMeterInfoMO meter_info = 9; // Meter information + repeated APPRpInfoMO rp_info = 10; // RP information + repeated APPPvInfoMO pv_info = 11; // PV information + uint32 unknown_field = 12; // Unknown field + repeated APPFeatureMO app_features = 13; // Application features +} + +message APPInfoDataResDTO { + string time_ymd_hms = 1; // Date and time + int32 offset = 2; // Offset + int32 current_package = 3; // Current package + int32 error_code = 4; // Error code + uint32 time = 5; // Timestamp +} + diff --git a/hoymiles_wifi/protobuf/APPInfomationData_pb2.py b/hoymiles_wifi/protobuf/APPInfomationData_pb2.py new file mode 100644 index 0000000..e546927 --- /dev/null +++ b/hoymiles_wifi/protobuf/APPInfomationData_pb2.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: APPInfomationData.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x41PPInfomationData.proto\"\xa6\x04\n\x0c\x41PPDtuInfoMO\x12\x13\n\x0b\x64\x65vice_kind\x18\x01 \x01(\x05\x12\x16\n\x0e\x64tu_sw_version\x18\x02 \x01(\x05\x12\x16\n\x0e\x64tu_hw_version\x18\x03 \x01(\x05\x12\x15\n\rdtu_step_time\x18\x04 \x01(\x05\x12\x19\n\x11\x64tu_rf_hw_version\x18\x05 \x01(\x05\x12\x19\n\x11\x64tu_rf_sw_version\x18\x06 \x01(\x05\x12\x14\n\x0c\x61\x63\x63\x65ss_model\x18\x07 \x01(\x05\x12\x1a\n\x12\x63ommunication_time\x18\x08 \x01(\x05\x12\x17\n\x0fsignal_strength\x18\t \x01(\x05\x12\x14\n\x0cgprs_version\x18\n \x01(\t\x12\x14\n\x0cwifi_version\x18\x0b \x01(\t\x12\x11\n\tka_number\x18\x0c \x01(\t\x12\x13\n\x0b\x64tu_rule_id\x18\r \x01(\x05\x12\x16\n\x0e\x64tu_error_code\x18\x0e \x01(\x05\x12\x13\n\x0b\x64tu485_mode\x18\x0f \x01(\x05\x12\x16\n\x0e\x64tu485_address\x18\x10 \x01(\x05\x12\x1c\n\x14sub1g_frequency_band\x18\x11 \x01(\x05\x12\"\n\x1asub1g_channel_total_number\x18\x12 \x01(\x05\x12\x1c\n\x14sub1g_channel_number\x18\x13 \x01(\x05\x12\x10\n\x08sub1g_rp\x18\x14 \x01(\x05\x12\x1b\n\x13sub1g_channel_total\x18\x15 \x01(\x05\x12\x11\n\tgprs_imei\x18\x16 \x01(\t\"\xc2\x01\n\x0e\x41PPMeterInfoMO\x12\x13\n\x0b\x64\x65vice_kind\x18\x01 \x01(\x05\x12\x1b\n\x13meter_serial_number\x18\x02 \x01(\x03\x12\x13\n\x0bmeter_model\x18\x03 \x01(\x05\x12\x10\n\x08meter_ct\x18\x04 \x01(\x05\x12\x19\n\x11\x63ommunication_way\x18\x05 \x01(\x05\x12\x13\n\x0b\x61\x63\x63\x65ss_mode\x18\x06 \x01(\x05\x12\x12\n\nsw_version\x18\x07 \x01(\x05\x12\x13\n\x0bmeter_value\x18\x08 \x01(\t\"~\n\x0b\x41PPRpInfoMO\x12\x13\n\x0b\x64\x65vice_kind\x18\x01 \x01(\x05\x12\x18\n\x10rp_serial_number\x18\x02 \x01(\x03\x12\x15\n\rrp_sw_version\x18\x03 \x01(\x05\x12\x15\n\rrp_hw_version\x18\x04 \x01(\x05\x12\x12\n\nrp_rule_id\x18\x05 \x01(\x05\"\x95\x02\n\x0b\x41PPPvInfoMO\x12\x13\n\x0b\x64\x65vice_kind\x18\x01 \x01(\x05\x12\x18\n\x10pv_serial_number\x18\x02 \x01(\x03\x12\x0f\n\x07pv_usfw\x18\x03 \x01(\x05\x12\x15\n\rpv_sw_version\x18\x04 \x01(\x05\x12\x19\n\x11pv_hw_part_number\x18\x05 \x01(\x05\x12\x15\n\rpv_hw_version\x18\x06 \x01(\x05\x12\x1c\n\x14pv_grid_profile_code\x18\x07 \x01(\x05\x12\x17\n\x0fpv_grid_profile\x18\x08 \x01(\x05\x12\x18\n\x10pv_rf_hw_version\x18\t \x01(\x05\x12\x18\n\x10pv_rf_sw_version\x18\n \x01(\x05\x12\x12\n\nmi_rule_id\x18\x0b \x01(\x05\"*\n\x0c\x41PPFeatureMO\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\t\"\xed\x02\n\x11\x41PPInfoDataReqDTO\x12\x19\n\x11\x64tu_serial_number\x18\x01 \x01(\t\x12\x11\n\ttimestamp\x18\x02 \x01(\r\x12\x15\n\rdevice_number\x18\x03 \x01(\x05\x12\x11\n\tpv_number\x18\x04 \x01(\x05\x12\x16\n\x0epackage_number\x18\x05 \x01(\x05\x12\x17\n\x0f\x63urrent_package\x18\x06 \x01(\x05\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\x05\x12\x1f\n\x08\x64tu_info\x18\x08 \x01(\x0b\x32\r.APPDtuInfoMO\x12#\n\nmeter_info\x18\t \x03(\x0b\x32\x0f.APPMeterInfoMO\x12\x1d\n\x07rp_info\x18\n \x03(\x0b\x32\x0c.APPRpInfoMO\x12\x1d\n\x07pv_info\x18\x0b \x03(\x0b\x32\x0c.APPPvInfoMO\x12\x15\n\runknown_field\x18\x0c \x01(\r\x12#\n\x0c\x61pp_features\x18\r \x03(\x0b\x32\r.APPFeatureMO\"t\n\x11\x41PPInfoDataResDTO\x12\x14\n\x0ctime_ymd_hms\x18\x01 \x01(\t\x12\x0e\n\x06offset\x18\x02 \x01(\x05\x12\x17\n\x0f\x63urrent_package\x18\x03 \x01(\x05\x12\x12\n\nerror_code\x18\x04 \x01(\x05\x12\x0c\n\x04time\x18\x05 \x01(\rb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'APPInfomationData_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_APPDTUINFOMO']._serialized_start=28 + _globals['_APPDTUINFOMO']._serialized_end=578 + _globals['_APPMETERINFOMO']._serialized_start=581 + _globals['_APPMETERINFOMO']._serialized_end=775 + _globals['_APPRPINFOMO']._serialized_start=777 + _globals['_APPRPINFOMO']._serialized_end=903 + _globals['_APPPVINFOMO']._serialized_start=906 + _globals['_APPPVINFOMO']._serialized_end=1183 + _globals['_APPFEATUREMO']._serialized_start=1185 + _globals['_APPFEATUREMO']._serialized_end=1227 + _globals['_APPINFODATAREQDTO']._serialized_start=1230 + _globals['_APPINFODATAREQDTO']._serialized_end=1595 + _globals['_APPINFODATARESDTO']._serialized_start=1597 + _globals['_APPINFODATARESDTO']._serialized_end=1713 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/AlarmData.proto b/hoymiles_wifi/protobuf/AlarmData.proto new file mode 100644 index 0000000..68d92f5 --- /dev/null +++ b/hoymiles_wifi/protobuf/AlarmData.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; + +message WInfoMO { + int64 pv_sn = 1; // PV serial number + int32 WCode = 2; // WCode identifier + int32 WNum = 3; // WNum value + int32 WTime1 = 4; // First timestamp value + int32 WTime2 = 5; // Second timestamp value + int32 WData1 = 6; // First data value + int32 WData2 = 7; // Second data value +} + +message WInfoReqDTO { + string dtu_sn = 1; // DTU serial number + int32 time = 2; // Timestamp of the request + repeated WInfoMO mWInfo = 3; // WInfoMO data array +} + +message WInfoResDTO { + string time_ymd_hms = 1; // Timestamp in the format YMD_HMS + int32 error_code = 2; // Error code indicator + int32 offset = 3; // Offset value + int32 time = 4; // Timestamp value +} + +message WWVDataReqDTO { + string dtu_sn = 1; // DTU serial number + int32 time = 2; // Timestamp of the request + int32 package_nub = 3; // Package number + int32 package_now = 4; // Current package number + int64 pv_sn = 5; // PV serial number + int32 WCode = 6; // WCode identifier + int32 WNum = 7; // WNum value + int32 WTime1 = 8; // First timestamp value + int32 WVDataL = 9; // Length of WWVData + int32 WPos = 10; // WPos value + string mWVData = 11; // WWVData string +} + +message WWVDataResDTO { + string time_ymd_hms = 1; // Timestamp in the format YMD_HMS + int32 package_now = 2; // Current package number + int32 error_code = 3; // Error code indicator + int32 offset = 4; // Offset value + int32 time = 5; // Timestamp value +} diff --git a/hoymiles_wifi/protobuf/AlarmData_pb2.py b/hoymiles_wifi/protobuf/AlarmData_pb2.py new file mode 100644 index 0000000..77f5f4e --- /dev/null +++ b/hoymiles_wifi/protobuf/AlarmData_pb2.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: AlarmData.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0f\x41larmData.proto\"u\n\x07WInfoMO\x12\r\n\x05pv_sn\x18\x01 \x01(\x03\x12\r\n\x05WCode\x18\x02 \x01(\x05\x12\x0c\n\x04WNum\x18\x03 \x01(\x05\x12\x0e\n\x06WTime1\x18\x04 \x01(\x05\x12\x0e\n\x06WTime2\x18\x05 \x01(\x05\x12\x0e\n\x06WData1\x18\x06 \x01(\x05\x12\x0e\n\x06WData2\x18\x07 \x01(\x05\"E\n\x0bWInfoReqDTO\x12\x0e\n\x06\x64tu_sn\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\x02 \x01(\x05\x12\x18\n\x06mWInfo\x18\x03 \x03(\x0b\x32\x08.WInfoMO\"U\n\x0bWInfoResDTO\x12\x14\n\x0ctime_ymd_hms\x18\x01 \x01(\t\x12\x12\n\nerror_code\x18\x02 \x01(\x05\x12\x0e\n\x06offset\x18\x03 \x01(\x05\x12\x0c\n\x04time\x18\x04 \x01(\x05\"\xc3\x01\n\rWWVDataReqDTO\x12\x0e\n\x06\x64tu_sn\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\x02 \x01(\x05\x12\x13\n\x0bpackage_nub\x18\x03 \x01(\x05\x12\x13\n\x0bpackage_now\x18\x04 \x01(\x05\x12\r\n\x05pv_sn\x18\x05 \x01(\x03\x12\r\n\x05WCode\x18\x06 \x01(\x05\x12\x0c\n\x04WNum\x18\x07 \x01(\x05\x12\x0e\n\x06WTime1\x18\x08 \x01(\x05\x12\x0f\n\x07WVDataL\x18\t \x01(\x05\x12\x0c\n\x04WPos\x18\n \x01(\x05\x12\x0f\n\x07mWVData\x18\x0b \x01(\t\"l\n\rWWVDataResDTO\x12\x14\n\x0ctime_ymd_hms\x18\x01 \x01(\t\x12\x13\n\x0bpackage_now\x18\x02 \x01(\x05\x12\x12\n\nerror_code\x18\x03 \x01(\x05\x12\x0e\n\x06offset\x18\x04 \x01(\x05\x12\x0c\n\x04time\x18\x05 \x01(\x05\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'AlarmData_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_WINFOMO']._serialized_start=19 + _globals['_WINFOMO']._serialized_end=136 + _globals['_WINFOREQDTO']._serialized_start=138 + _globals['_WINFOREQDTO']._serialized_end=207 + _globals['_WINFORESDTO']._serialized_start=209 + _globals['_WINFORESDTO']._serialized_end=294 + _globals['_WWVDATAREQDTO']._serialized_start=297 + _globals['_WWVDATAREQDTO']._serialized_end=492 + _globals['_WWVDATARESDTO']._serialized_start=494 + _globals['_WWVDATARESDTO']._serialized_end=602 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/AppGetHistED.proto b/hoymiles_wifi/protobuf/AppGetHistED.proto new file mode 100644 index 0000000..e6205af --- /dev/null +++ b/hoymiles_wifi/protobuf/AppGetHistED.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +message AppGetHistEDResDTO { + int32 cp = 1; // Control point identifier + int32 oft = 2; // Offset value + uint32 time = 3; // Timestamp of the response +} + +message APPEnergyInfoMO { + uint32 ed = 1; // Energy data + uint32 r_time = 2; // Relative timestamp +} + +message AppGetHistEDReqDTO { + int64 sn = 1; // Serial number + int32 oft = 2; // Offset value + uint32 time = 3; // Timestamp of the request + repeated APPEnergyInfoMO energ = 4; // APPEnergyInfoMO data array +} diff --git a/hoymiles_wifi/protobuf/AppGetHistED_pb2.py b/hoymiles_wifi/protobuf/AppGetHistED_pb2.py new file mode 100644 index 0000000..dde1d57 --- /dev/null +++ b/hoymiles_wifi/protobuf/AppGetHistED_pb2.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: AppGetHistED.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12\x41ppGetHistED.proto\";\n\x12\x41ppGetHistEDResDTO\x12\n\n\x02\x63p\x18\x01 \x01(\x05\x12\x0b\n\x03oft\x18\x02 \x01(\x05\x12\x0c\n\x04time\x18\x03 \x01(\r\"-\n\x0f\x41PPEnergyInfoMO\x12\n\n\x02\x65\x64\x18\x01 \x01(\r\x12\x0e\n\x06r_time\x18\x02 \x01(\r\"\\\n\x12\x41ppGetHistEDReqDTO\x12\n\n\x02sn\x18\x01 \x01(\x03\x12\x0b\n\x03oft\x18\x02 \x01(\x05\x12\x0c\n\x04time\x18\x03 \x01(\r\x12\x1f\n\x05\x65nerg\x18\x04 \x03(\x0b\x32\x10.APPEnergyInfoMOb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'AppGetHistED_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_APPGETHISTEDRESDTO']._serialized_start=22 + _globals['_APPGETHISTEDRESDTO']._serialized_end=81 + _globals['_APPENERGYINFOMO']._serialized_start=83 + _globals['_APPENERGYINFOMO']._serialized_end=128 + _globals['_APPGETHISTEDREQDTO']._serialized_start=130 + _globals['_APPGETHISTEDREQDTO']._serialized_end=222 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/AppGetHistPower.proto b/hoymiles_wifi/protobuf/AppGetHistPower.proto new file mode 100644 index 0000000..b41dc9c --- /dev/null +++ b/hoymiles_wifi/protobuf/AppGetHistPower.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +message AppGetHistPowerResDTO { + int32 control_point = 1; // Control point identifier + int32 offset = 2; // Offset value + uint32 requested_time = 3; // Time requested + uint32 requested_day = 4; // Day requested +} + +message AppGetHistPowerReqDTO { + int64 serial_number = 1; // Device serial number + int32 access_point = 2; // Access point identifier + int32 control_point = 3; // Control point identifier + int32 offset = 4; // Offset value + uint32 request_time = 5; // Timestamp of the request + uint32 start_time = 6; // Start timestamp + uint32 long_term_start = 7; // Long-term start timestamp + uint32 absolute_start = 8; // Absolute start timestamp + uint32 step_time = 9; // Step time + uint32 relative_power = 10; // Relative power + uint32 total_energy = 11; // Total energy + uint32 daily_energy = 12; // Daily energy + repeated int32 power_array = 13; // Array of power values + uint32 warning_number = 14; // Warning number +} diff --git a/hoymiles_wifi/protobuf/AppGetHistPower_pb2.py b/hoymiles_wifi/protobuf/AppGetHistPower_pb2.py new file mode 100644 index 0000000..99fb412 --- /dev/null +++ b/hoymiles_wifi/protobuf/AppGetHistPower_pb2.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: AppGetHistPower.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x41ppGetHistPower.proto\"m\n\x15\x41ppGetHistPowerResDTO\x12\x15\n\rcontrol_point\x18\x01 \x01(\x05\x12\x0e\n\x06offset\x18\x02 \x01(\x05\x12\x16\n\x0erequested_time\x18\x03 \x01(\r\x12\x15\n\rrequested_day\x18\x04 \x01(\r\"\xca\x02\n\x15\x41ppGetHistPowerReqDTO\x12\x15\n\rserial_number\x18\x01 \x01(\x03\x12\x14\n\x0c\x61\x63\x63\x65ss_point\x18\x02 \x01(\x05\x12\x15\n\rcontrol_point\x18\x03 \x01(\x05\x12\x0e\n\x06offset\x18\x04 \x01(\x05\x12\x14\n\x0crequest_time\x18\x05 \x01(\r\x12\x12\n\nstart_time\x18\x06 \x01(\r\x12\x17\n\x0flong_term_start\x18\x07 \x01(\r\x12\x16\n\x0e\x61\x62solute_start\x18\x08 \x01(\r\x12\x11\n\tstep_time\x18\t \x01(\r\x12\x16\n\x0erelative_power\x18\n \x01(\r\x12\x14\n\x0ctotal_energy\x18\x0b \x01(\r\x12\x14\n\x0c\x64\x61ily_energy\x18\x0c \x01(\r\x12\x13\n\x0bpower_array\x18\r \x03(\x05\x12\x16\n\x0ewarning_number\x18\x0e \x01(\rb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'AppGetHistPower_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_APPGETHISTPOWERRESDTO']._serialized_start=25 + _globals['_APPGETHISTPOWERRESDTO']._serialized_end=134 + _globals['_APPGETHISTPOWERREQDTO']._serialized_start=137 + _globals['_APPGETHISTPOWERREQDTO']._serialized_end=467 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/AutoSearch.proto b/hoymiles_wifi/protobuf/AutoSearch.proto new file mode 100644 index 0000000..b06cb29 --- /dev/null +++ b/hoymiles_wifi/protobuf/AutoSearch.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +message AutoSearchReqDTO { + string dtu_serial_number = 1; // DTU serial number + int32 time = 2; // Timestamp of the request + int32 package_number = 3; // Total number of packages + int32 current_package = 4; // Current package number + repeated int64 mi_serial_numbers = 5; // Array of MI (Meter Interface) serial numbers +} + +message AutoSearchResDTO { + string ymd_hms = 1; // Timestamp in the format YMD_HMS + int32 offset = 2; // Offset value + int32 current_package = 3; // Current package number + int32 error_code = 4; // Error code indicator + int32 time = 5; // Timestamp of the response +} diff --git a/hoymiles_wifi/protobuf/AutoSearch_pb2.py b/hoymiles_wifi/protobuf/AutoSearch_pb2.py new file mode 100644 index 0000000..fc37994 --- /dev/null +++ b/hoymiles_wifi/protobuf/AutoSearch_pb2.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: AutoSearch.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x41utoSearch.proto\"\x87\x01\n\x10\x41utoSearchReqDTO\x12\x19\n\x11\x64tu_serial_number\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\x02 \x01(\x05\x12\x16\n\x0epackage_number\x18\x03 \x01(\x05\x12\x17\n\x0f\x63urrent_package\x18\x04 \x01(\x05\x12\x19\n\x11mi_serial_numbers\x18\x05 \x03(\x03\"n\n\x10\x41utoSearchResDTO\x12\x0f\n\x07ymd_hms\x18\x01 \x01(\t\x12\x0e\n\x06offset\x18\x02 \x01(\x05\x12\x17\n\x0f\x63urrent_package\x18\x03 \x01(\x05\x12\x12\n\nerror_code\x18\x04 \x01(\x05\x12\x0c\n\x04time\x18\x05 \x01(\x05\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'AutoSearch_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_AUTOSEARCHREQDTO']._serialized_start=21 + _globals['_AUTOSEARCHREQDTO']._serialized_end=156 + _globals['_AUTOSEARCHRESDTO']._serialized_start=158 + _globals['_AUTOSEARCHRESDTO']._serialized_end=268 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/CommandPB.proto b/hoymiles_wifi/protobuf/CommandPB.proto new file mode 100644 index 0000000..4b77aa1 --- /dev/null +++ b/hoymiles_wifi/protobuf/CommandPB.proto @@ -0,0 +1,78 @@ +syntax = "proto3"; + +message CommandResDTO { + int32 time = 1; // Timestamp of the response + int32 action = 2; // Action code indicating the type of command + int32 dev_kind = 3; // Device kind information + int32 package_nub = 4; // Total number of packages expected + int32 package_now = 5; // Current package number + int64 tid = 6; // Transaction ID? + string data = 7; // Additional data payload + repeated string es_to_sn = 8; // List of ES serial numbers + repeated int64 mi_to_sn = 9; // List of MI serial numbers + int32 system_total_a = 10; // System total for category A + int32 system_total_b = 11; // System total for category B + int32 system_total_c = 12; // System total for category C + repeated int64 mi_sn_item_a = 13; // List of MI serial numbers for category A + repeated int64 mi_sn_item_b = 14; // List of MI serial numbers for category B + repeated int64 mi_sn_item_c = 15; // List of MI serial numbers for category C +} + +message CommandReqDTO { + string dtu_sn = 1; // Data Terminal Unit (DTU) serial number + int32 time = 2; // Timestamp of the request + int32 action = 3; // Action code indicating the type of command + int32 package_now = 4; // Current package number + int32 err_code = 5; // Error code (if any) + int64 tid = 6; // Transaction ID +} + +message ESOperatingStatusMO { + string es_sn = 1; // ES serial number + int32 progress_rate = 2; // Progress rate of the operation +} + +message MIOperatingStatusMO { + int64 mi_sn = 1; // MI serial number + int32 progress_rate = 2; // Progress rate of the operation +} + +message MIErrorStatusMO { + int64 mi_sn = 1; // MI serial number + int64 error_code = 2; // Error code associated with the MI +} + +message ESSucStatusMO { + string es_sn = 1; // ES serial number +} + +message ESErrorStatusMO { + string es_sn = 1; // ES serial number + int64 error_code = 2; // Error code associated with the ES +} + +message CommandStatusReqDTO { + string dtu_sn = 1; // Data Terminal Unit (DTU) serial number + int32 time = 2; // Timestamp of the request + int32 action = 3; // Action code indicating the type of command + int32 package_nub = 4; // Total number of packages expected + int32 package_now = 5; // Current package number + int64 tid = 6; // Transaction ID + repeated string es_sns_sucs = 7; // List of ES serial numbers with successful execution + repeated int64 mi_sns_sucs = 8; // List of MI serial numbers with successful execution + repeated string es_sns_failds = 9; // List of ES serial numbers with failed execution + repeated int64 mi_sns_failds = 10; // List of MI serial numbers with failed execution + repeated ESOperatingStatusMO es_mOperatingStatus = 11; // List of ES operating statuses + repeated MIOperatingStatusMO mi_mOperatingStatus = 12; // List of MI operating statuses + repeated MIErrorStatusMO mi_mErrorStatus = 13; // List of MI error statuses + repeated ESSucStatusMO es_mSucStatus = 14; // List of successful ES statuses + repeated ESErrorStatusMO es_mErrorStatus = 15; // List of error ES statuses +} + +message CommandStatusResDTO { + int32 time = 1; // Timestamp of the response + int32 action = 2; // Action code indicating the type of command + int32 package_now = 3; // Current package number + int64 tid = 4; // Transaction ID + int32 err_code = 5; // Error code (if any) +} diff --git a/hoymiles_wifi/protobuf/CommandPB_pb2.py b/hoymiles_wifi/protobuf/CommandPB_pb2.py new file mode 100644 index 0000000..3424881 --- /dev/null +++ b/hoymiles_wifi/protobuf/CommandPB_pb2.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: CommandPB.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0f\x43ommandPB.proto\"\xb2\x02\n\rCommandResDTO\x12\x0c\n\x04time\x18\x01 \x01(\x05\x12\x0e\n\x06\x61\x63tion\x18\x02 \x01(\x05\x12\x10\n\x08\x64\x65v_kind\x18\x03 \x01(\x05\x12\x13\n\x0bpackage_nub\x18\x04 \x01(\x05\x12\x13\n\x0bpackage_now\x18\x05 \x01(\x05\x12\x0b\n\x03tid\x18\x06 \x01(\x03\x12\x0c\n\x04\x64\x61ta\x18\x07 \x01(\t\x12\x10\n\x08\x65s_to_sn\x18\x08 \x03(\t\x12\x10\n\x08mi_to_sn\x18\t \x03(\x03\x12\x16\n\x0esystem_total_a\x18\n \x01(\x05\x12\x16\n\x0esystem_total_b\x18\x0b \x01(\x05\x12\x16\n\x0esystem_total_c\x18\x0c \x01(\x05\x12\x14\n\x0cmi_sn_item_a\x18\r \x03(\x03\x12\x14\n\x0cmi_sn_item_b\x18\x0e \x03(\x03\x12\x14\n\x0cmi_sn_item_c\x18\x0f \x03(\x03\"q\n\rCommandReqDTO\x12\x0e\n\x06\x64tu_sn\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\x02 \x01(\x05\x12\x0e\n\x06\x61\x63tion\x18\x03 \x01(\x05\x12\x13\n\x0bpackage_now\x18\x04 \x01(\x05\x12\x10\n\x08\x65rr_code\x18\x05 \x01(\x05\x12\x0b\n\x03tid\x18\x06 \x01(\x03\";\n\x13\x45SOperatingStatusMO\x12\r\n\x05\x65s_sn\x18\x01 \x01(\t\x12\x15\n\rprogress_rate\x18\x02 \x01(\x05\";\n\x13MIOperatingStatusMO\x12\r\n\x05mi_sn\x18\x01 \x01(\x03\x12\x15\n\rprogress_rate\x18\x02 \x01(\x05\"4\n\x0fMIErrorStatusMO\x12\r\n\x05mi_sn\x18\x01 \x01(\x03\x12\x12\n\nerror_code\x18\x02 \x01(\x03\"\x1e\n\rESSucStatusMO\x12\r\n\x05\x65s_sn\x18\x01 \x01(\t\"4\n\x0f\x45SErrorStatusMO\x12\r\n\x05\x65s_sn\x18\x01 \x01(\t\x12\x12\n\nerror_code\x18\x02 \x01(\x03\"\xb5\x03\n\x13\x43ommandStatusReqDTO\x12\x0e\n\x06\x64tu_sn\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\x02 \x01(\x05\x12\x0e\n\x06\x61\x63tion\x18\x03 \x01(\x05\x12\x13\n\x0bpackage_nub\x18\x04 \x01(\x05\x12\x13\n\x0bpackage_now\x18\x05 \x01(\x05\x12\x0b\n\x03tid\x18\x06 \x01(\x03\x12\x13\n\x0b\x65s_sns_sucs\x18\x07 \x03(\t\x12\x13\n\x0bmi_sns_sucs\x18\x08 \x03(\x03\x12\x15\n\res_sns_failds\x18\t \x03(\t\x12\x15\n\rmi_sns_failds\x18\n \x03(\x03\x12\x31\n\x13\x65s_mOperatingStatus\x18\x0b \x03(\x0b\x32\x14.ESOperatingStatusMO\x12\x31\n\x13mi_mOperatingStatus\x18\x0c \x03(\x0b\x32\x14.MIOperatingStatusMO\x12)\n\x0fmi_mErrorStatus\x18\r \x03(\x0b\x32\x10.MIErrorStatusMO\x12%\n\res_mSucStatus\x18\x0e \x03(\x0b\x32\x0e.ESSucStatusMO\x12)\n\x0f\x65s_mErrorStatus\x18\x0f \x03(\x0b\x32\x10.ESErrorStatusMO\"g\n\x13\x43ommandStatusResDTO\x12\x0c\n\x04time\x18\x01 \x01(\x05\x12\x0e\n\x06\x61\x63tion\x18\x02 \x01(\x05\x12\x13\n\x0bpackage_now\x18\x03 \x01(\x05\x12\x0b\n\x03tid\x18\x04 \x01(\x03\x12\x10\n\x08\x65rr_code\x18\x05 \x01(\x05\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'CommandPB_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_COMMANDRESDTO']._serialized_start=20 + _globals['_COMMANDRESDTO']._serialized_end=326 + _globals['_COMMANDREQDTO']._serialized_start=328 + _globals['_COMMANDREQDTO']._serialized_end=441 + _globals['_ESOPERATINGSTATUSMO']._serialized_start=443 + _globals['_ESOPERATINGSTATUSMO']._serialized_end=502 + _globals['_MIOPERATINGSTATUSMO']._serialized_start=504 + _globals['_MIOPERATINGSTATUSMO']._serialized_end=563 + _globals['_MIERRORSTATUSMO']._serialized_start=565 + _globals['_MIERRORSTATUSMO']._serialized_end=617 + _globals['_ESSUCSTATUSMO']._serialized_start=619 + _globals['_ESSUCSTATUSMO']._serialized_end=649 + _globals['_ESERRORSTATUSMO']._serialized_start=651 + _globals['_ESERRORSTATUSMO']._serialized_end=703 + _globals['_COMMANDSTATUSREQDTO']._serialized_start=706 + _globals['_COMMANDSTATUSREQDTO']._serialized_end=1143 + _globals['_COMMANDSTATUSRESDTO']._serialized_start=1145 + _globals['_COMMANDSTATUSRESDTO']._serialized_end=1248 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/DevConfig.proto b/hoymiles_wifi/protobuf/DevConfig.proto new file mode 100644 index 0000000..1efe666 --- /dev/null +++ b/hoymiles_wifi/protobuf/DevConfig.proto @@ -0,0 +1,76 @@ +syntax = "proto3"; + +message DevConfigFetchResDTO { + int32 response_time = 1; // Timestamp of the response + int64 transaction_id = 2; // Transaction ID + string dtu_sn = 3; // DTU serial number + string dev_sn = 4; // Device serial number + int32 current_package = 5; // Current package number + int32 rule_type = 6; // Rule type +} + +message DevConfigFetchReqDTO { + int32 request_time = 1; // Timestamp of the request + int64 transaction_id = 2; // Transaction ID + int32 rule_id = 3; // Rule ID + string data = 4; // Data + int32 crc = 5; // CRC value + string dtu_sn = 6; // DTU serial number + string dev_sn = 7; // Device serial number + string cfg_data = 8; // Configuration data + int32 cfg_crc = 9; // Configuration CRC value + int32 total_packages = 10; // Total number of packages + int32 current_package = 11; // Current package number + int32 rule_type = 12; // Rule type +} + +message DevConfigPutResDTO { + int32 response_time = 1; // Timestamp of the response + int64 transaction_id = 2; // Transaction ID + int32 rule_id = 3; // Rule ID + string data = 4; // Data + int32 crc = 5; // CRC value + string dtu_sn = 6; // DTU serial number + string dev_sn = 7; // Device serial number + string cfg_data = 8; // Configuration data + int32 cfg_crc = 9; // Configuration CRC value + int32 total_packages = 10; // Total number of packages + int32 current_package = 11; // Current package number + repeated int64 mi_to_sn = 12; // List of MI (Meter Interface) serial numbers + int32 rule_type = 13; // Rule type +} + +message DevConfigPutReqDTO { + int32 request_time = 1; // Timestamp of the request + int64 transaction_id = 2; // Transaction ID + string dtu_sn = 3; // DTU serial number + string dev_sn = 4; // Device serial number + int32 status = 5; // Status indicator + int32 current_package = 6; // Current package number + repeated int64 mi_to_sn = 7; // List of MI (Meter Interface) serial numbers + int32 rule_type = 8; // Rule type +} + +message DevConfigReportReqDTO { + int32 request_time = 1; // Timestamp of the request + int64 transaction_id = 2; // Transaction ID + int32 rule_id = 3; // Rule ID + string data = 4; // Data + int32 crc = 5; // CRC value + string dtu_sn = 6; // DTU serial number + string dev_sn = 7; // Device serial number + string cfg_data = 8; // Configuration data + int32 cfg_crc = 9; // Configuration CRC value + int32 total_packages = 10; // Total number of packages + int32 current_package = 11; // Current package number + int32 rule_type = 12; // Rule type +} + +message DevConfigReportResDTO { + int32 response_time = 1; // Timestamp of the response + int64 transaction_id = 2; // Transaction ID + string dtu_sn = 3; // DTU serial number + string dev_sn = 4; // Device serial number + int32 current_package = 5; // Current package number + int32 rule_type = 6; // Rule type +} diff --git a/hoymiles_wifi/protobuf/DevConfig_pb2.py b/hoymiles_wifi/protobuf/DevConfig_pb2.py new file mode 100644 index 0000000..e8f82b4 --- /dev/null +++ b/hoymiles_wifi/protobuf/DevConfig_pb2.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: DevConfig.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0f\x44\x65vConfig.proto\"\x91\x01\n\x14\x44\x65vConfigFetchResDTO\x12\x15\n\rresponse_time\x18\x01 \x01(\x05\x12\x16\n\x0etransaction_id\x18\x02 \x01(\x03\x12\x0e\n\x06\x64tu_sn\x18\x03 \x01(\t\x12\x0e\n\x06\x64\x65v_sn\x18\x04 \x01(\t\x12\x17\n\x0f\x63urrent_package\x18\x05 \x01(\x05\x12\x11\n\trule_type\x18\x06 \x01(\x05\"\xf7\x01\n\x14\x44\x65vConfigFetchReqDTO\x12\x14\n\x0crequest_time\x18\x01 \x01(\x05\x12\x16\n\x0etransaction_id\x18\x02 \x01(\x03\x12\x0f\n\x07rule_id\x18\x03 \x01(\x05\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\t\x12\x0b\n\x03\x63rc\x18\x05 \x01(\x05\x12\x0e\n\x06\x64tu_sn\x18\x06 \x01(\t\x12\x0e\n\x06\x64\x65v_sn\x18\x07 \x01(\t\x12\x10\n\x08\x63\x66g_data\x18\x08 \x01(\t\x12\x0f\n\x07\x63\x66g_crc\x18\t \x01(\x05\x12\x16\n\x0etotal_packages\x18\n \x01(\x05\x12\x17\n\x0f\x63urrent_package\x18\x0b \x01(\x05\x12\x11\n\trule_type\x18\x0c \x01(\x05\"\x88\x02\n\x12\x44\x65vConfigPutResDTO\x12\x15\n\rresponse_time\x18\x01 \x01(\x05\x12\x16\n\x0etransaction_id\x18\x02 \x01(\x03\x12\x0f\n\x07rule_id\x18\x03 \x01(\x05\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\t\x12\x0b\n\x03\x63rc\x18\x05 \x01(\x05\x12\x0e\n\x06\x64tu_sn\x18\x06 \x01(\t\x12\x0e\n\x06\x64\x65v_sn\x18\x07 \x01(\t\x12\x10\n\x08\x63\x66g_data\x18\x08 \x01(\t\x12\x0f\n\x07\x63\x66g_crc\x18\t \x01(\x05\x12\x16\n\x0etotal_packages\x18\n \x01(\x05\x12\x17\n\x0f\x63urrent_package\x18\x0b \x01(\x05\x12\x10\n\x08mi_to_sn\x18\x0c \x03(\x03\x12\x11\n\trule_type\x18\r \x01(\x05\"\xb0\x01\n\x12\x44\x65vConfigPutReqDTO\x12\x14\n\x0crequest_time\x18\x01 \x01(\x05\x12\x16\n\x0etransaction_id\x18\x02 \x01(\x03\x12\x0e\n\x06\x64tu_sn\x18\x03 \x01(\t\x12\x0e\n\x06\x64\x65v_sn\x18\x04 \x01(\t\x12\x0e\n\x06status\x18\x05 \x01(\x05\x12\x17\n\x0f\x63urrent_package\x18\x06 \x01(\x05\x12\x10\n\x08mi_to_sn\x18\x07 \x03(\x03\x12\x11\n\trule_type\x18\x08 \x01(\x05\"\xf8\x01\n\x15\x44\x65vConfigReportReqDTO\x12\x14\n\x0crequest_time\x18\x01 \x01(\x05\x12\x16\n\x0etransaction_id\x18\x02 \x01(\x03\x12\x0f\n\x07rule_id\x18\x03 \x01(\x05\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\t\x12\x0b\n\x03\x63rc\x18\x05 \x01(\x05\x12\x0e\n\x06\x64tu_sn\x18\x06 \x01(\t\x12\x0e\n\x06\x64\x65v_sn\x18\x07 \x01(\t\x12\x10\n\x08\x63\x66g_data\x18\x08 \x01(\t\x12\x0f\n\x07\x63\x66g_crc\x18\t \x01(\x05\x12\x16\n\x0etotal_packages\x18\n \x01(\x05\x12\x17\n\x0f\x63urrent_package\x18\x0b \x01(\x05\x12\x11\n\trule_type\x18\x0c \x01(\x05\"\x92\x01\n\x15\x44\x65vConfigReportResDTO\x12\x15\n\rresponse_time\x18\x01 \x01(\x05\x12\x16\n\x0etransaction_id\x18\x02 \x01(\x03\x12\x0e\n\x06\x64tu_sn\x18\x03 \x01(\t\x12\x0e\n\x06\x64\x65v_sn\x18\x04 \x01(\t\x12\x17\n\x0f\x63urrent_package\x18\x05 \x01(\x05\x12\x11\n\trule_type\x18\x06 \x01(\x05\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'DevConfig_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_DEVCONFIGFETCHRESDTO']._serialized_start=20 + _globals['_DEVCONFIGFETCHRESDTO']._serialized_end=165 + _globals['_DEVCONFIGFETCHREQDTO']._serialized_start=168 + _globals['_DEVCONFIGFETCHREQDTO']._serialized_end=415 + _globals['_DEVCONFIGPUTRESDTO']._serialized_start=418 + _globals['_DEVCONFIGPUTRESDTO']._serialized_end=682 + _globals['_DEVCONFIGPUTREQDTO']._serialized_start=685 + _globals['_DEVCONFIGPUTREQDTO']._serialized_end=861 + _globals['_DEVCONFIGREPORTREQDTO']._serialized_start=864 + _globals['_DEVCONFIGREPORTREQDTO']._serialized_end=1112 + _globals['_DEVCONFIGREPORTRESDTO']._serialized_start=1115 + _globals['_DEVCONFIGREPORTRESDTO']._serialized_end=1261 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/EventData.proto b/hoymiles_wifi/protobuf/EventData.proto new file mode 100644 index 0000000..38ee4c7 --- /dev/null +++ b/hoymiles_wifi/protobuf/EventData.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +message EventDataResDTO { + int32 offset = 1; // Offset in the response + int32 time = 2; // Timestamp of the response + string time_ymd_hms = 3; // Start timestamp in YYYY-MM-DD HH:MM:SS format +} + +message MIEvent { + int32 event_code = 1; // Event code + int32 event_status = 2; // Event status + int32 event_count = 3; // Event count + int32 pv_voltage = 4; // PV voltage + int32 grid_voltage = 5; // Grid voltage + int32 grid_frequency = 6; // Grid frequency + int32 grid_power = 7; // Grid power + int32 temperature = 8; // Temperature + int64 mi_id = 9; // Meter Interface (MI) ID + int32 start_timestamp = 10; // Start timestamp +} + +message EventDataReqDTO { + int32 offset = 1; // Offset in the request + int32 time = 2; // Timestamp of the request + repeated MIEvent mi_events = 3; // List of MI (Meter Interface) events +} diff --git a/hoymiles_wifi/protobuf/EventData_pb2.py b/hoymiles_wifi/protobuf/EventData_pb2.py new file mode 100644 index 0000000..70c0fd8 --- /dev/null +++ b/hoymiles_wifi/protobuf/EventData_pb2.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: EventData.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0f\x45ventData.proto\"E\n\x0f\x45ventDataResDTO\x12\x0e\n\x06offset\x18\x01 \x01(\x05\x12\x0c\n\x04time\x18\x02 \x01(\x05\x12\x14\n\x0ctime_ymd_hms\x18\x03 \x01(\t\"\xdb\x01\n\x07MIEvent\x12\x12\n\nevent_code\x18\x01 \x01(\x05\x12\x14\n\x0c\x65vent_status\x18\x02 \x01(\x05\x12\x13\n\x0b\x65vent_count\x18\x03 \x01(\x05\x12\x12\n\npv_voltage\x18\x04 \x01(\x05\x12\x14\n\x0cgrid_voltage\x18\x05 \x01(\x05\x12\x16\n\x0egrid_frequency\x18\x06 \x01(\x05\x12\x12\n\ngrid_power\x18\x07 \x01(\x05\x12\x13\n\x0btemperature\x18\x08 \x01(\x05\x12\r\n\x05mi_id\x18\t \x01(\x03\x12\x17\n\x0fstart_timestamp\x18\n \x01(\x05\"L\n\x0f\x45ventDataReqDTO\x12\x0e\n\x06offset\x18\x01 \x01(\x05\x12\x0c\n\x04time\x18\x02 \x01(\x05\x12\x1b\n\tmi_events\x18\x03 \x03(\x0b\x32\x08.MIEventb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'EventData_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_EVENTDATARESDTO']._serialized_start=19 + _globals['_EVENTDATARESDTO']._serialized_end=88 + _globals['_MIEVENT']._serialized_start=91 + _globals['_MIEVENT']._serialized_end=310 + _globals['_EVENTDATAREQDTO']._serialized_start=312 + _globals['_EVENTDATAREQDTO']._serialized_end=388 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/GPSTData.proto b/hoymiles_wifi/protobuf/GPSTData.proto new file mode 100644 index 0000000..fccdb8f --- /dev/null +++ b/hoymiles_wifi/protobuf/GPSTData.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +message STValMO { + int32 code = 1; + int32 dflt_val = 2; + int32 dflt_tim = 3; + int32 rslt_val = 4; + int32 rslt_tim = 5; +} + +message GPSTValMO { + int64 pv_sn = 1; + int32 ver = 2; + int32 st = 3; + int32 gpf = 4; + int32 gpf_ver = 5; + STValMO hv1_stval = 6; + STValMO lv1_stval = 7; + STValMO hv2_stval = 8; + STValMO lv2_stval = 9; + STValMO hf1_stval = 10; + STValMO lf1_stval = 11; + STValMO hf2_stval = 12; + STValMO lf2_stval = 13; +} + +message GPSTReqDTO { + string dtu_sn = 1; + int32 time = 2; + int32 package_nub = 3; + int32 package_now = 4; + repeated GPSTValMO mGPSTInfo = 5; +} + +message GPSTResDTO { + string ymd_hms = 1; + int32 offset = 2; + int32 package_now = 3; + int32 err_code = 4; + int32 time = 5; +} diff --git a/hoymiles_wifi/protobuf/GPSTData_pb2.py b/hoymiles_wifi/protobuf/GPSTData_pb2.py new file mode 100644 index 0000000..1d690e9 --- /dev/null +++ b/hoymiles_wifi/protobuf/GPSTData_pb2.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: GPSTData.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eGPSTData.proto\"_\n\x07STValMO\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x05\x12\x10\n\x08\x64\x66lt_val\x18\x02 \x01(\x05\x12\x10\n\x08\x64\x66lt_tim\x18\x03 \x01(\x05\x12\x10\n\x08rslt_val\x18\x04 \x01(\x05\x12\x10\n\x08rslt_tim\x18\x05 \x01(\x05\"\xb9\x02\n\tGPSTValMO\x12\r\n\x05pv_sn\x18\x01 \x01(\x03\x12\x0b\n\x03ver\x18\x02 \x01(\x05\x12\n\n\x02st\x18\x03 \x01(\x05\x12\x0b\n\x03gpf\x18\x04 \x01(\x05\x12\x0f\n\x07gpf_ver\x18\x05 \x01(\x05\x12\x1b\n\thv1_stval\x18\x06 \x01(\x0b\x32\x08.STValMO\x12\x1b\n\tlv1_stval\x18\x07 \x01(\x0b\x32\x08.STValMO\x12\x1b\n\thv2_stval\x18\x08 \x01(\x0b\x32\x08.STValMO\x12\x1b\n\tlv2_stval\x18\t \x01(\x0b\x32\x08.STValMO\x12\x1b\n\thf1_stval\x18\n \x01(\x0b\x32\x08.STValMO\x12\x1b\n\tlf1_stval\x18\x0b \x01(\x0b\x32\x08.STValMO\x12\x1b\n\thf2_stval\x18\x0c \x01(\x0b\x32\x08.STValMO\x12\x1b\n\tlf2_stval\x18\r \x01(\x0b\x32\x08.STValMO\"s\n\nGPSTReqDTO\x12\x0e\n\x06\x64tu_sn\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\x02 \x01(\x05\x12\x13\n\x0bpackage_nub\x18\x03 \x01(\x05\x12\x13\n\x0bpackage_now\x18\x04 \x01(\x05\x12\x1d\n\tmGPSTInfo\x18\x05 \x03(\x0b\x32\n.GPSTValMO\"b\n\nGPSTResDTO\x12\x0f\n\x07ymd_hms\x18\x01 \x01(\t\x12\x0e\n\x06offset\x18\x02 \x01(\x05\x12\x13\n\x0bpackage_now\x18\x03 \x01(\x05\x12\x10\n\x08\x65rr_code\x18\x04 \x01(\x05\x12\x0c\n\x04time\x18\x05 \x01(\x05\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'GPSTData_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_STVALMO']._serialized_start=18 + _globals['_STVALMO']._serialized_end=113 + _globals['_GPSTVALMO']._serialized_start=116 + _globals['_GPSTVALMO']._serialized_end=429 + _globals['_GPSTREQDTO']._serialized_start=431 + _globals['_GPSTREQDTO']._serialized_end=546 + _globals['_GPSTRESDTO']._serialized_start=548 + _globals['_GPSTRESDTO']._serialized_end=646 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/GetConfig.proto b/hoymiles_wifi/protobuf/GetConfig.proto new file mode 100644 index 0000000..295febe --- /dev/null +++ b/hoymiles_wifi/protobuf/GetConfig.proto @@ -0,0 +1,71 @@ +syntax = "proto3"; + +message GetConfigResDTO { + int32 offset = 1; // Offset in the response + uint32 time = 2; // Timestamp of the response +} + +message GetConfigReqDTO { + int32 request_offset = 1; // Offset in the request + uint32 request_time = 2; // Timestamp of the request + int32 lock_password = 3; // Lock password + int32 lock_time = 4; // Lock time + int32 limit_power_mypower = 5; // Limit power mypower + int32 zero_export_433_addr = 6; // Zero export 433 address + int32 zero_export_enable = 7; // Zero export enable + int32 netmode_select = 8; // Netmode select + int32 channel_select = 9; // Channel select + int32 server_send_time = 10; // Server send time + int32 wifi_rssi = 11; // Wifi RSSI + int32 serverport = 12; // Server port + string apn_set = 13; // Access Point Name (APN) set + string meter_kind = 14; // Meter kind + string meter_interface = 15; // Meter interface + string wifi_ssid = 16; // Wifi SSID + string wifi_password = 17; // Wifi password + string server_domain_name = 18; // Server domain name + int32 inv_type = 19; // Inverter type + string dtu_sn = 20; // DTU serial number + int32 access_model = 21; // Access model + int32 mac_0 = 22; // MAC address byte 0 + int32 mac_1 = 23; // MAC address byte 1 + int32 mac_2 = 24; // MAC address byte 2 + int32 mac_3 = 25; // MAC address byte 3 + int32 dhcp_switch = 26; // DHCP switch + int32 ip_addr_0 = 27; // IP address byte 0 + int32 ip_addr_1 = 28; // IP address byte 1 + int32 ip_addr_2 = 29; // IP address byte 2 + int32 ip_addr_3 = 30; // IP address byte 3 + int32 subnet_mask_0 = 31; // Subnet mask byte 0 + int32 subnet_mask_1 = 32; // Subnet mask byte 1 + int32 subnet_mask_2 = 33; // Subnet mask byte 2 + int32 subnet_mask_3 = 34; // Subnet mask byte 3 + int32 default_gateway_0 = 35; // Default gateway byte 0 + int32 default_gateway_1 = 36; // Default gateway byte 1 + int32 default_gateway_2 = 37; // Default gateway byte 2 + int32 default_gateway_3 = 38; // Default gateway byte 3 + string ka_nub = 39; // KA number + string apn_name = 40; // APN name + string apn_password = 41; // APN password + int32 sub1g_sweep_switch = 42; // Sub1G sweep switch + int32 sub1g_work_channel = 43; // Sub1G work channel + int32 cable_dns_0 = 44; // Cable DNS byte 0 + int32 cable_dns_1 = 45; // Cable DNS byte 1 + int32 cable_dns_2 = 46; // Cable DNS byte 2 + int32 cable_dns_3 = 47; // Cable DNS byte 3 + int32 wifi_ip_addr_0 = 48; // Wifi IP address byte 0 + int32 wifi_ip_addr_1 = 49; // Wifi IP address byte 1 + int32 wifi_ip_addr_2 = 50; // Wifi IP address byte 2 + int32 wifi_ip_addr_3 = 51; // Wifi IP address byte 3 + int32 mac_4 = 52; // MAC address byte 4 + int32 mac_5 = 53; // MAC address byte 5 + int32 wifi_mac_0 = 54; // Wifi MAC address byte 0 + int32 wifi_mac_1 = 55; // Wifi MAC address byte 1 + int32 wifi_mac_2 = 56; // Wifi MAC address byte 2 + int32 wifi_mac_3 = 57; // Wifi MAC address byte 3 + int32 wifi_mac_4 = 58; // Wifi MAC address byte 4 + int32 wifi_mac_5 = 59; // Wifi MAC address byte 5 + string gprs_imei = 60; // GPRS IMEI + string dtu_ap_ssid = 61; // DTU Access Point (AP) SSID + string dtu_ap_pass = 62; // DTU AP password +} diff --git a/hoymiles_wifi/protobuf/GetConfig_pb2.py b/hoymiles_wifi/protobuf/GetConfig_pb2.py new file mode 100644 index 0000000..6da7965 --- /dev/null +++ b/hoymiles_wifi/protobuf/GetConfig_pb2.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: GetConfig.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fGetConfig.proto\"/\n\x0fGetConfigResDTO\x12\x0e\n\x06offset\x18\x01 \x01(\x05\x12\x0c\n\x04time\x18\x02 \x01(\r\"\xc9\n\n\x0fGetConfigReqDTO\x12\x16\n\x0erequest_offset\x18\x01 \x01(\x05\x12\x14\n\x0crequest_time\x18\x02 \x01(\r\x12\x15\n\rlock_password\x18\x03 \x01(\x05\x12\x11\n\tlock_time\x18\x04 \x01(\x05\x12\x1b\n\x13limit_power_mypower\x18\x05 \x01(\x05\x12\x1c\n\x14zero_export_433_addr\x18\x06 \x01(\x05\x12\x1a\n\x12zero_export_enable\x18\x07 \x01(\x05\x12\x16\n\x0enetmode_select\x18\x08 \x01(\x05\x12\x16\n\x0e\x63hannel_select\x18\t \x01(\x05\x12\x18\n\x10server_send_time\x18\n \x01(\x05\x12\x11\n\twifi_rssi\x18\x0b \x01(\x05\x12\x12\n\nserverport\x18\x0c \x01(\x05\x12\x0f\n\x07\x61pn_set\x18\r \x01(\t\x12\x12\n\nmeter_kind\x18\x0e \x01(\t\x12\x17\n\x0fmeter_interface\x18\x0f \x01(\t\x12\x11\n\twifi_ssid\x18\x10 \x01(\t\x12\x15\n\rwifi_password\x18\x11 \x01(\t\x12\x1a\n\x12server_domain_name\x18\x12 \x01(\t\x12\x10\n\x08inv_type\x18\x13 \x01(\x05\x12\x0e\n\x06\x64tu_sn\x18\x14 \x01(\t\x12\x14\n\x0c\x61\x63\x63\x65ss_model\x18\x15 \x01(\x05\x12\r\n\x05mac_0\x18\x16 \x01(\x05\x12\r\n\x05mac_1\x18\x17 \x01(\x05\x12\r\n\x05mac_2\x18\x18 \x01(\x05\x12\r\n\x05mac_3\x18\x19 \x01(\x05\x12\x13\n\x0b\x64hcp_switch\x18\x1a \x01(\x05\x12\x11\n\tip_addr_0\x18\x1b \x01(\x05\x12\x11\n\tip_addr_1\x18\x1c \x01(\x05\x12\x11\n\tip_addr_2\x18\x1d \x01(\x05\x12\x11\n\tip_addr_3\x18\x1e \x01(\x05\x12\x15\n\rsubnet_mask_0\x18\x1f \x01(\x05\x12\x15\n\rsubnet_mask_1\x18 \x01(\x05\x12\x15\n\rsubnet_mask_2\x18! \x01(\x05\x12\x15\n\rsubnet_mask_3\x18\" \x01(\x05\x12\x19\n\x11\x64\x65\x66\x61ult_gateway_0\x18# \x01(\x05\x12\x19\n\x11\x64\x65\x66\x61ult_gateway_1\x18$ \x01(\x05\x12\x19\n\x11\x64\x65\x66\x61ult_gateway_2\x18% \x01(\x05\x12\x19\n\x11\x64\x65\x66\x61ult_gateway_3\x18& \x01(\x05\x12\x0e\n\x06ka_nub\x18\' \x01(\t\x12\x10\n\x08\x61pn_name\x18( \x01(\t\x12\x14\n\x0c\x61pn_password\x18) \x01(\t\x12\x1a\n\x12sub1g_sweep_switch\x18* \x01(\x05\x12\x1a\n\x12sub1g_work_channel\x18+ \x01(\x05\x12\x13\n\x0b\x63\x61\x62le_dns_0\x18, \x01(\x05\x12\x13\n\x0b\x63\x61\x62le_dns_1\x18- \x01(\x05\x12\x13\n\x0b\x63\x61\x62le_dns_2\x18. \x01(\x05\x12\x13\n\x0b\x63\x61\x62le_dns_3\x18/ \x01(\x05\x12\x16\n\x0ewifi_ip_addr_0\x18\x30 \x01(\x05\x12\x16\n\x0ewifi_ip_addr_1\x18\x31 \x01(\x05\x12\x16\n\x0ewifi_ip_addr_2\x18\x32 \x01(\x05\x12\x16\n\x0ewifi_ip_addr_3\x18\x33 \x01(\x05\x12\r\n\x05mac_4\x18\x34 \x01(\x05\x12\r\n\x05mac_5\x18\x35 \x01(\x05\x12\x12\n\nwifi_mac_0\x18\x36 \x01(\x05\x12\x12\n\nwifi_mac_1\x18\x37 \x01(\x05\x12\x12\n\nwifi_mac_2\x18\x38 \x01(\x05\x12\x12\n\nwifi_mac_3\x18\x39 \x01(\x05\x12\x12\n\nwifi_mac_4\x18: \x01(\x05\x12\x12\n\nwifi_mac_5\x18; \x01(\x05\x12\x11\n\tgprs_imei\x18< \x01(\t\x12\x13\n\x0b\x64tu_ap_ssid\x18= \x01(\t\x12\x13\n\x0b\x64tu_ap_pass\x18> \x01(\tb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'GetConfig_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_GETCONFIGRESDTO']._serialized_start=19 + _globals['_GETCONFIGRESDTO']._serialized_end=66 + _globals['_GETCONFIGREQDTO']._serialized_start=69 + _globals['_GETCONFIGREQDTO']._serialized_end=1422 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/InfomationData.proto b/hoymiles_wifi/protobuf/InfomationData.proto new file mode 100644 index 0000000..e99a8f2 --- /dev/null +++ b/hoymiles_wifi/protobuf/InfomationData.proto @@ -0,0 +1,83 @@ +syntax = "proto3"; + +message DtuInfoMO { + int32 device_kind = 1; + int32 dtu_sw = 2; + int32 dtu_hw = 3; + int32 dtu_step_time = 4; + int32 dtu_rf_hw = 5; + int32 dtu_rf_sw = 6; + int32 access_model = 7; + string gprs_vsn = 8; + string wifi_vsn = 9; + string ka_nub = 10; + int32 dtu_rule_id = 11; + int32 dtu_error_code = 12; + int32 grid_type = 13; + int32 zero_export_switch= 14; + int32 surplus_power_a = 15; + int32 surplus_power_b = 16; + int32 surplus_power_c = 17; + int32 zero_export_control= 18; + int32 phase_balance_switch = 19; + int32 tolerance_between_phases = 20; +} + +message MeterInfoMO { + int32 device_kind = 1; + int64 meter_sn = 2; + int32 meter_model = 3; + int32 meter_ct = 4; + int32 com_way = 5; + int32 access_mode = 6; +} + +message RpInfoMO { + int32 device_kind = 1; + int64 rp_sn = 2; + int32 rp_sw = 3; + int32 rp_hw = 4; + int32 rp_rule_id = 5; +} + +message PvInfoMO { + int32 device_kind = 1; + int64 pv_sn = 2; + int32 pv_usfw = 3; + int32 pv_sw = 4; + int32 pv_hw_pn = 5; + int32 pv_hw = 6; + int32 pv_gpf_code = 7; + int32 pv_gpf = 8; + int32 pv_rf_hw = 9; + int32 pv_rf_sw = 10; + int32 mi_rule_id = 11; +} + +message FeatureMO { + int32 key = 1; + string value = 2; +} + +message InfoDataReqDTO { + string dtu_sn = 1; + int32 time = 2; + int32 device_nub = 3; + int32 pv_nub = 4; + int32 package_nub = 5; + int32 package_now = 6; + int32 channel = 7; + DtuInfoMO mDtuInfo = 8; + repeated MeterInfoMO mMeterInfo = 9; + repeated RpInfoMO mRpInfo = 10; + repeated PvInfoMO mpvInfo = 11; + repeated FeatureMO m_feature = 12; +} + +message InfoDataResDTO { + string time_ymd_hms = 1; + int32 offset = 2; + int32 package_now = 3; + int32 error_code = 4; + int32 time = 5; +} diff --git a/hoymiles_wifi/protobuf/InfomationData_pb2.py b/hoymiles_wifi/protobuf/InfomationData_pb2.py new file mode 100644 index 0000000..4ce1775 --- /dev/null +++ b/hoymiles_wifi/protobuf/InfomationData_pb2.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: InfomationData.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14InfomationData.proto\"\xcb\x03\n\tDtuInfoMO\x12\x13\n\x0b\x64\x65vice_kind\x18\x01 \x01(\x05\x12\x0e\n\x06\x64tu_sw\x18\x02 \x01(\x05\x12\x0e\n\x06\x64tu_hw\x18\x03 \x01(\x05\x12\x15\n\rdtu_step_time\x18\x04 \x01(\x05\x12\x11\n\tdtu_rf_hw\x18\x05 \x01(\x05\x12\x11\n\tdtu_rf_sw\x18\x06 \x01(\x05\x12\x14\n\x0c\x61\x63\x63\x65ss_model\x18\x07 \x01(\x05\x12\x10\n\x08gprs_vsn\x18\x08 \x01(\t\x12\x10\n\x08wifi_vsn\x18\t \x01(\t\x12\x0e\n\x06ka_nub\x18\n \x01(\t\x12\x13\n\x0b\x64tu_rule_id\x18\x0b \x01(\x05\x12\x16\n\x0e\x64tu_error_code\x18\x0c \x01(\x05\x12\x11\n\tgrid_type\x18\r \x01(\x05\x12\x1a\n\x12zero_export_switch\x18\x0e \x01(\x05\x12\x17\n\x0fsurplus_power_a\x18\x0f \x01(\x05\x12\x17\n\x0fsurplus_power_b\x18\x10 \x01(\x05\x12\x17\n\x0fsurplus_power_c\x18\x11 \x01(\x05\x12\x1b\n\x13zero_export_control\x18\x12 \x01(\x05\x12\x1c\n\x14phase_balance_switch\x18\x13 \x01(\x05\x12 \n\x18tolerance_between_phases\x18\x14 \x01(\x05\"\x81\x01\n\x0bMeterInfoMO\x12\x13\n\x0b\x64\x65vice_kind\x18\x01 \x01(\x05\x12\x10\n\x08meter_sn\x18\x02 \x01(\x03\x12\x13\n\x0bmeter_model\x18\x03 \x01(\x05\x12\x10\n\x08meter_ct\x18\x04 \x01(\x05\x12\x0f\n\x07\x63om_way\x18\x05 \x01(\x05\x12\x13\n\x0b\x61\x63\x63\x65ss_mode\x18\x06 \x01(\x05\"`\n\x08RpInfoMO\x12\x13\n\x0b\x64\x65vice_kind\x18\x01 \x01(\x05\x12\r\n\x05rp_sn\x18\x02 \x01(\x03\x12\r\n\x05rp_sw\x18\x03 \x01(\x05\x12\r\n\x05rp_hw\x18\x04 \x01(\x05\x12\x12\n\nrp_rule_id\x18\x05 \x01(\x05\"\xcc\x01\n\x08PvInfoMO\x12\x13\n\x0b\x64\x65vice_kind\x18\x01 \x01(\x05\x12\r\n\x05pv_sn\x18\x02 \x01(\x03\x12\x0f\n\x07pv_usfw\x18\x03 \x01(\x05\x12\r\n\x05pv_sw\x18\x04 \x01(\x05\x12\x10\n\x08pv_hw_pn\x18\x05 \x01(\x05\x12\r\n\x05pv_hw\x18\x06 \x01(\x05\x12\x13\n\x0bpv_gpf_code\x18\x07 \x01(\x05\x12\x0e\n\x06pv_gpf\x18\x08 \x01(\x05\x12\x10\n\x08pv_rf_hw\x18\t \x01(\x05\x12\x10\n\x08pv_rf_sw\x18\n \x01(\x05\x12\x12\n\nmi_rule_id\x18\x0b \x01(\x05\"\'\n\tFeatureMO\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\t\"\xa4\x02\n\x0eInfoDataReqDTO\x12\x0e\n\x06\x64tu_sn\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\x02 \x01(\x05\x12\x12\n\ndevice_nub\x18\x03 \x01(\x05\x12\x0e\n\x06pv_nub\x18\x04 \x01(\x05\x12\x13\n\x0bpackage_nub\x18\x05 \x01(\x05\x12\x13\n\x0bpackage_now\x18\x06 \x01(\x05\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\x05\x12\x1c\n\x08mDtuInfo\x18\x08 \x01(\x0b\x32\n.DtuInfoMO\x12 \n\nmMeterInfo\x18\t \x03(\x0b\x32\x0c.MeterInfoMO\x12\x1a\n\x07mRpInfo\x18\n \x03(\x0b\x32\t.RpInfoMO\x12\x1a\n\x07mpvInfo\x18\x0b \x03(\x0b\x32\t.PvInfoMO\x12\x1d\n\tm_feature\x18\x0c \x03(\x0b\x32\n.FeatureMO\"m\n\x0eInfoDataResDTO\x12\x14\n\x0ctime_ymd_hms\x18\x01 \x01(\t\x12\x0e\n\x06offset\x18\x02 \x01(\x05\x12\x13\n\x0bpackage_now\x18\x03 \x01(\x05\x12\x12\n\nerror_code\x18\x04 \x01(\x05\x12\x0c\n\x04time\x18\x05 \x01(\x05\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'InfomationData_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_DTUINFOMO']._serialized_start=25 + _globals['_DTUINFOMO']._serialized_end=484 + _globals['_METERINFOMO']._serialized_start=487 + _globals['_METERINFOMO']._serialized_end=616 + _globals['_RPINFOMO']._serialized_start=618 + _globals['_RPINFOMO']._serialized_end=714 + _globals['_PVINFOMO']._serialized_start=717 + _globals['_PVINFOMO']._serialized_end=921 + _globals['_FEATUREMO']._serialized_start=923 + _globals['_FEATUREMO']._serialized_end=962 + _globals['_INFODATAREQDTO']._serialized_start=965 + _globals['_INFODATAREQDTO']._serialized_end=1257 + _globals['_INFODATARESDTO']._serialized_start=1259 + _globals['_INFODATARESDTO']._serialized_end=1368 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/NetworkInfo.proto b/hoymiles_wifi/protobuf/NetworkInfo.proto new file mode 100644 index 0000000..bba6980 --- /dev/null +++ b/hoymiles_wifi/protobuf/NetworkInfo.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +message NetworkInfoReqDTO { + string dtu_sn = 1; // DTU serial number + uint32 time = 2; // Timestamp of the request + int32 net_set_mod = 3; // Network setting mode + int32 net_set_time = 4; // Network setting time + int32 net_set_state = 5; // Network setting state + int32 net_work_mod = 6; // Network working mode + int32 net_work_time = 7; // Network working time + int32 csq = 8; // Carrier Signal Quality (CSQ) + int32 net_work_state = 9; // Network working state + int32 ap_set_state = 10; // Access Point (AP) setting state +} + +message NetworkInfoResDTO { + int32 offset = 1; // Offset value for response + uint32 time = 2; // Timestamp of the response +} diff --git a/hoymiles_wifi/protobuf/NetworkInfo_pb2.py b/hoymiles_wifi/protobuf/NetworkInfo_pb2.py new file mode 100644 index 0000000..5b3a1e0 --- /dev/null +++ b/hoymiles_wifi/protobuf/NetworkInfo_pb2.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: NetworkInfo.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11NetworkInfo.proto\"\xdb\x01\n\x11NetworkInfoReqDTO\x12\x0e\n\x06\x64tu_sn\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\x02 \x01(\r\x12\x13\n\x0bnet_set_mod\x18\x03 \x01(\x05\x12\x14\n\x0cnet_set_time\x18\x04 \x01(\x05\x12\x15\n\rnet_set_state\x18\x05 \x01(\x05\x12\x14\n\x0cnet_work_mod\x18\x06 \x01(\x05\x12\x15\n\rnet_work_time\x18\x07 \x01(\x05\x12\x0b\n\x03\x63sq\x18\x08 \x01(\x05\x12\x16\n\x0enet_work_state\x18\t \x01(\x05\x12\x14\n\x0c\x61p_set_state\x18\n \x01(\x05\"1\n\x11NetworkInfoResDTO\x12\x0e\n\x06offset\x18\x01 \x01(\x05\x12\x0c\n\x04time\x18\x02 \x01(\rb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'NetworkInfo_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_NETWORKINFOREQDTO']._serialized_start=22 + _globals['_NETWORKINFOREQDTO']._serialized_end=241 + _globals['_NETWORKINFORESDTO']._serialized_start=243 + _globals['_NETWORKINFORESDTO']._serialized_end=292 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/RealData.proto b/hoymiles_wifi/protobuf/RealData.proto new file mode 100644 index 0000000..89c41eb --- /dev/null +++ b/hoymiles_wifi/protobuf/RealData.proto @@ -0,0 +1,78 @@ +syntax = "proto3"; + +message MeterDataMO { + int32 device_kind = 1; // Device kind identifier + int64 meter_sn = 2; // Meter serial number + int32 meter_total_power = 3; // Total power by the meter (Watts) + int32 meter_phase_A_power = 4; // Power in phase A (Watts) + int32 meter_phase_B_power = 5; // Power in phase B (Watts) + int32 meter_phase_C_power = 6; // Power in phase C (Watts) + int32 meter_factor = 7; // Meter factor + int32 meter_total_energy = + 8; // Total energy produced by the meter (Watt-hours) + int32 meter_phase_A_energy = 9; // Energy produced in phase A (Watt-hours) + int32 meter_phase_B_energy = 10; // Energy produced in phase B (Watt-hours) + int32 meter_phase_C_energy = 11; // Energy produced in phase C (Watt-hours) + int32 meter_total_consumed = 12; // Total energy consumed (Watt-hours) + int32 meter_phase_A_consumed = 13; // Energy consumed in phase A (Watt-hours) + int32 meter_phase_B_consumed = 14; // Energy consumed in phase B (Watt-hours) + int32 meter_phase_C_consumed = 15; // Energy consumed in phase C (Watt-hours) + int32 meter_fault = 16; // Meter fault code +} + +message RpDataMO { + int64 rp_sn = 1; // RP serial number + int32 rp_signal = 2; // RP signal strength + int32 rp_channel = 3; // RP channel number + int32 rp_link_nub = 4; // RP link number + int32 rp_link_status = 5; // RP link status +} + +message PvDataMO { + int64 pv_sn = 1; // PV serial number + int32 pv_port = 2; // PV port number + int32 pv_vol = 3; // PV voltage (Volts) + int32 pv_cur = 4; // PV current (Amperes) + int32 pv_power = 5; // PV power (Watts) + int32 pv_energy_total = 6; // Total energy generated by PV (Watt-hours) + int32 grid_vol = 7; // Grid voltage (Volts) + int32 grid_vol_max = 8; // Maximum grid voltage (Volts) + int32 grid_freq = 9; // Grid frequency (Hertz) + int32 grid_p = 10; // Grid active power (Watts) + int32 grid_q = 11; // Grid reactive power (VAR) + int32 grid_i = 12; // Grid current (Amperes) + int32 grid_pf = 13; // Grid power factor + int32 pv_temp = 14; // PV temperature + int32 pv_run_status = 15; // PV running status + int32 pv_fault_num = 16; // PV fault number + int32 pv_fault_cnt = 17; // PV fault count + int32 pv_warning_cnt = 18; // PV warning count + int32 pv_link_status = 19; // PV link status + int32 pv_send_power = 20; // PV send power (Watts) + int32 pv_receive_power = 21; // PV received power (Watts) + int32 pv_time = 22; // PV time + int32 pv_energy = 23; // PV energy (Watt-hours) + int32 mi_signal = 24; // Modulation index signal +} + +message RealDataReqDTO { + string dtu_sn = 1; // DTU serial number + int32 timestamp = 2; // Timestamp of the data + int32 device_number = 3; // Device number + int32 pv_number = 4; // PV number + int32 package_number = 5; // Package number + int32 current_package = 6; // Current package number + int32 csq = 7; // Carrier Signal Quality (CSQ) + repeated MeterDataMO meter_data = 8; // Meter data array + repeated RpDataMO rp_data = 9; // RpDataMO data array + repeated PvDataMO pv_data = 10; // PvDataMO data array + int32 version = 11; // Version number +} + +message RealDataResDTO { + string time_ymd_hms = 1; // Timestamp in the format YMD_HMS + int32 package_now = 2; // Package now + int32 error_code = 3; // Error code indicator + int32 offset = 4; // Offset value + int32 time = 5; // Timestamp value +} diff --git a/hoymiles_wifi/protobuf/RealDataNew.proto b/hoymiles_wifi/protobuf/RealDataNew.proto new file mode 100644 index 0000000..69523e1 --- /dev/null +++ b/hoymiles_wifi/protobuf/RealDataNew.proto @@ -0,0 +1,123 @@ +syntax = "proto3"; + +message MeterMO { + int32 device_type = 1; // Device type identifier + int64 serial_number = 2; // Serial number of the meter + int32 phase_total_power = 3; // Total power across all phases (Watts) + int32 phase_A_power = 4; // Power in phase A (Watts) + int32 phase_B_power = 5; // Power in phase B (Watts) + int32 phase_C_power = 6; // Power in phase C (Watts) + int32 power_factor_total = 7; // Total power factor + int32 energy_total_power = 8; // Total energy generated (Watt-hours) + int32 energy_phase_A = 9; // Energy generated in phase A (Watt-hours) + int32 energy_phase_B = 10; // Energy generated in phase B (Watt-hours) + int32 energy_phase_C = 11; // Energy generated in phase C (Watt-hours) + int32 energy_total_consumed = 12; // Total energy consumed (Watt-hours) + int32 energy_phase_A_consumed = 13; // Energy consumed in phase A (Watt-hours) + int32 energy_phase_B_consumed = 14; // Energy consumed in phase B (Watt-hours) + int32 energy_phase_C_consumed = 15; // Energy consumed in phase C (Watt-hours) + int32 fault_code = 16; // Fault code indicator + int32 voltage_phase_A = 17; // Voltage in phase A (Volts) + int32 voltage_phase_B = 18; // Voltage in phase B (Volts) + int32 voltage_phase_C = 19; // Voltage in phase C (Volts) + int32 current_phase_A = 20; // Current in phase A (Amperes) + int32 current_phase_B = 21; // Current in phase B (Amperes) + int32 current_phase_C = 22; // Current in phase C (Amperes) + int32 power_factor_phase_A = 23; // Power factor in phase A + int32 power_factor_phase_B = 24; // Power factor in phase B + int32 power_factor_phase_C = 25; // Power factor in phase C +} + +message RpMO { + int64 serial_number = 1; // Serial number of the device + int32 signature = 2; // Signature value + int32 channel = 3; // Channel number + int32 pv_number = 4; // Photovoltaic (PV) number + int32 link_status = 5; // Link status indicator +} + +message RSDMO { + int64 serial_number = 1; // Serial number of the device + int32 firmware_version = 2; // Firmware version + int32 voltage = 3; // Voltage value + int32 power = 4; // Power value + int32 temperature = 5; // Temperature value + int32 warning_number = 6; // Warning number + int32 crc_checksum = 7; // CRC checksum + int32 link_status = 8; // Link status indicator +} + +message SGSMO { + int64 serial_number = 1; // Serial number of the device + int32 firmware_version = 2; // Firmware version + int32 voltage = 3; // Grid voltage (Volts) + int32 frequency = 4; // Grid frequency (Hertz) + int32 active_power = 5; // Active power (Watts) + int32 reactive_power = 6; // Reactive power (VAR) + int32 current = 7; // Current (Amperes) + int32 power_factor = 8; // Power factor + int32 temperature = 9; // Temperature value + int32 warning_number = 10; // Warning number + int32 crc_checksum = 11; // CRC checksum + int32 link_status = 12; // Link status indicator + int32 power_limit = 13; // Power limit (Watts) + int32 modulation_index_signal = 20; // Modulation index signal +} + +message TGSMO { + int64 serial_number = 1; // Serial number of the device + int32 firmware_version = 2; // Firmware version + int32 voltage_phase_A = 3; // Voltage in phase A (Volts) + int32 voltage_phase_B = 4; // Voltage in phase B (Volts) + int32 voltage_phase_C = 5; // Voltage in phase C (Volts) + int32 voltage_line_AB = 6; // Voltage between lines A and B (Volts) + int32 voltage_line_BC = 7; // Voltage between lines B and C (Volts) + int32 voltage_line_CA = 8; // Voltage between lines C and A (Volts) + int32 frequency = 9; // Frequency (Hertz) + int32 active_power = 10; // Active power (Watts) + int32 reactive_power = 11; // Reactive power (VAR) + int32 current_phase_A = 12; // Current in phase A (Amperes) + int32 current_phase_B = 13; // Current in phase B (Amperes) + int32 current_phase_C = 14; // Current in phase C (Amperes) + int32 power_factor = 15; // Power factor + int32 temperature = 16; // Temperature value + int32 warning_number = 17; // Warning number + int32 crc_checksum = 18; // CRC checksum + int32 link_status = 19; // Link status indicator + int32 modulation_index_signal = 20; // Modulation index signal +} + +message PvMO { + int64 serial_number = 1; // Serial number of the device + int32 port_number = 2; // Port number + int32 voltage = 3; // Voltage (Volts) + int32 current = 4; // Current (Amperes) + int32 power = 5; // Power (Watts) + int32 energy_total = 6; // Total energy generated (Watt-hours) + int32 energy_daily = 7; // Daily energy generated (Watt-hours) + int32 error_code = 8; // Error code indicator +} + +message RealDataNewReqDTO { + string device_serial_number = 1; // Serial number of the device + int32 timestamp = 2; // Timestamp of the data + int32 active_power = 3; // Active power (Watts) + int32 cp = 4; // Control parameter? + int32 firmware_version = 5; // Firmware version + repeated MeterMO meter_data = 6; // Meter data array + repeated RpMO rp_data = 7; // RpMO data array + repeated RSDMO rsd_data = 8; // RSDMO data array + repeated SGSMO sgs_data = 9; // SGSMO data array + repeated TGSMO tgs_data = 10; // TGSMO data array + repeated PvMO pv_data = 11; // PvMO data array + uint64 dtu_power = 12; // Power of the DTU (Watts) + uint64 dtu_daily_energy = 13; // Daily energy of the DTU (Watt-hours) +} + +message RealDataNewResDTO { + string time_ymd_hms = 1; // Timestamp in the format YMD_HMS + int32 cp = 2; // Control parameter? + int32 error_code = 3; // Error code indicator + int32 offset = 4; // Offset value + int32 time = 5; // Timestamp value +} diff --git a/hoymiles_wifi/protobuf/RealDataNew_pb2.py b/hoymiles_wifi/protobuf/RealDataNew_pb2.py new file mode 100644 index 0000000..b491c3e --- /dev/null +++ b/hoymiles_wifi/protobuf/RealDataNew_pb2.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: RealDataNew.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11RealDataNew.proto\"\x9b\x05\n\x07MeterMO\x12\x13\n\x0b\x64\x65vice_type\x18\x01 \x01(\x05\x12\x15\n\rserial_number\x18\x02 \x01(\x03\x12\x19\n\x11phase_total_power\x18\x03 \x01(\x05\x12\x15\n\rphase_A_power\x18\x04 \x01(\x05\x12\x15\n\rphase_B_power\x18\x05 \x01(\x05\x12\x15\n\rphase_C_power\x18\x06 \x01(\x05\x12\x1a\n\x12power_factor_total\x18\x07 \x01(\x05\x12\x1a\n\x12\x65nergy_total_power\x18\x08 \x01(\x05\x12\x16\n\x0e\x65nergy_phase_A\x18\t \x01(\x05\x12\x16\n\x0e\x65nergy_phase_B\x18\n \x01(\x05\x12\x16\n\x0e\x65nergy_phase_C\x18\x0b \x01(\x05\x12\x1d\n\x15\x65nergy_total_consumed\x18\x0c \x01(\x05\x12\x1f\n\x17\x65nergy_phase_A_consumed\x18\r \x01(\x05\x12\x1f\n\x17\x65nergy_phase_B_consumed\x18\x0e \x01(\x05\x12\x1f\n\x17\x65nergy_phase_C_consumed\x18\x0f \x01(\x05\x12\x12\n\nfault_code\x18\x10 \x01(\x05\x12\x17\n\x0fvoltage_phase_A\x18\x11 \x01(\x05\x12\x17\n\x0fvoltage_phase_B\x18\x12 \x01(\x05\x12\x17\n\x0fvoltage_phase_C\x18\x13 \x01(\x05\x12\x17\n\x0f\x63urrent_phase_A\x18\x14 \x01(\x05\x12\x17\n\x0f\x63urrent_phase_B\x18\x15 \x01(\x05\x12\x17\n\x0f\x63urrent_phase_C\x18\x16 \x01(\x05\x12\x1c\n\x14power_factor_phase_A\x18\x17 \x01(\x05\x12\x1c\n\x14power_factor_phase_B\x18\x18 \x01(\x05\x12\x1c\n\x14power_factor_phase_C\x18\x19 \x01(\x05\"i\n\x04RpMO\x12\x15\n\rserial_number\x18\x01 \x01(\x03\x12\x11\n\tsignature\x18\x02 \x01(\x05\x12\x0f\n\x07\x63hannel\x18\x03 \x01(\x05\x12\x11\n\tpv_number\x18\x04 \x01(\x05\x12\x13\n\x0blink_status\x18\x05 \x01(\x05\"\xb0\x01\n\x05RSDMO\x12\x15\n\rserial_number\x18\x01 \x01(\x03\x12\x18\n\x10\x66irmware_version\x18\x02 \x01(\x05\x12\x0f\n\x07voltage\x18\x03 \x01(\x05\x12\r\n\x05power\x18\x04 \x01(\x05\x12\x13\n\x0btemperature\x18\x05 \x01(\x05\x12\x16\n\x0ewarning_number\x18\x06 \x01(\x05\x12\x14\n\x0c\x63rc_checksum\x18\x07 \x01(\x05\x12\x13\n\x0blink_status\x18\x08 \x01(\x05\"\xbf\x02\n\x05SGSMO\x12\x15\n\rserial_number\x18\x01 \x01(\x03\x12\x18\n\x10\x66irmware_version\x18\x02 \x01(\x05\x12\x0f\n\x07voltage\x18\x03 \x01(\x05\x12\x11\n\tfrequency\x18\x04 \x01(\x05\x12\x14\n\x0c\x61\x63tive_power\x18\x05 \x01(\x05\x12\x16\n\x0ereactive_power\x18\x06 \x01(\x05\x12\x0f\n\x07\x63urrent\x18\x07 \x01(\x05\x12\x14\n\x0cpower_factor\x18\x08 \x01(\x05\x12\x13\n\x0btemperature\x18\t \x01(\x05\x12\x16\n\x0ewarning_number\x18\n \x01(\x05\x12\x14\n\x0c\x63rc_checksum\x18\x0b \x01(\x05\x12\x13\n\x0blink_status\x18\x0c \x01(\x05\x12\x13\n\x0bpower_limit\x18\r \x01(\x05\x12\x1f\n\x17modulation_index_signal\x18\x14 \x01(\x05\"\xe9\x03\n\x05TGSMO\x12\x15\n\rserial_number\x18\x01 \x01(\x03\x12\x18\n\x10\x66irmware_version\x18\x02 \x01(\x05\x12\x17\n\x0fvoltage_phase_A\x18\x03 \x01(\x05\x12\x17\n\x0fvoltage_phase_B\x18\x04 \x01(\x05\x12\x17\n\x0fvoltage_phase_C\x18\x05 \x01(\x05\x12\x17\n\x0fvoltage_line_AB\x18\x06 \x01(\x05\x12\x17\n\x0fvoltage_line_BC\x18\x07 \x01(\x05\x12\x17\n\x0fvoltage_line_CA\x18\x08 \x01(\x05\x12\x11\n\tfrequency\x18\t \x01(\x05\x12\x14\n\x0c\x61\x63tive_power\x18\n \x01(\x05\x12\x16\n\x0ereactive_power\x18\x0b \x01(\x05\x12\x17\n\x0f\x63urrent_phase_A\x18\x0c \x01(\x05\x12\x17\n\x0f\x63urrent_phase_B\x18\r \x01(\x05\x12\x17\n\x0f\x63urrent_phase_C\x18\x0e \x01(\x05\x12\x14\n\x0cpower_factor\x18\x0f \x01(\x05\x12\x13\n\x0btemperature\x18\x10 \x01(\x05\x12\x16\n\x0ewarning_number\x18\x11 \x01(\x05\x12\x14\n\x0c\x63rc_checksum\x18\x12 \x01(\x05\x12\x13\n\x0blink_status\x18\x13 \x01(\x05\x12\x1f\n\x17modulation_index_signal\x18\x14 \x01(\x05\"\xa3\x01\n\x04PvMO\x12\x15\n\rserial_number\x18\x01 \x01(\x03\x12\x13\n\x0bport_number\x18\x02 \x01(\x05\x12\x0f\n\x07voltage\x18\x03 \x01(\x05\x12\x0f\n\x07\x63urrent\x18\x04 \x01(\x05\x12\r\n\x05power\x18\x05 \x01(\x05\x12\x14\n\x0c\x65nergy_total\x18\x06 \x01(\x05\x12\x14\n\x0c\x65nergy_daily\x18\x07 \x01(\x05\x12\x12\n\nerror_code\x18\x08 \x01(\x05\"\xc9\x02\n\x11RealDataNewReqDTO\x12\x1c\n\x14\x64\x65vice_serial_number\x18\x01 \x01(\t\x12\x11\n\ttimestamp\x18\x02 \x01(\x05\x12\x14\n\x0c\x61\x63tive_power\x18\x03 \x01(\x05\x12\n\n\x02\x63p\x18\x04 \x01(\x05\x12\x18\n\x10\x66irmware_version\x18\x05 \x01(\x05\x12\x1c\n\nmeter_data\x18\x06 \x03(\x0b\x32\x08.MeterMO\x12\x16\n\x07rp_data\x18\x07 \x03(\x0b\x32\x05.RpMO\x12\x18\n\x08rsd_data\x18\x08 \x03(\x0b\x32\x06.RSDMO\x12\x18\n\x08sgs_data\x18\t \x03(\x0b\x32\x06.SGSMO\x12\x18\n\x08tgs_data\x18\n \x03(\x0b\x32\x06.TGSMO\x12\x16\n\x07pv_data\x18\x0b \x03(\x0b\x32\x05.PvMO\x12\x11\n\tdtu_power\x18\x0c \x01(\x04\x12\x18\n\x10\x64tu_daily_energy\x18\r \x01(\x04\"g\n\x11RealDataNewResDTO\x12\x14\n\x0ctime_ymd_hms\x18\x01 \x01(\t\x12\n\n\x02\x63p\x18\x02 \x01(\x05\x12\x12\n\nerror_code\x18\x03 \x01(\x05\x12\x0e\n\x06offset\x18\x04 \x01(\x05\x12\x0c\n\x04time\x18\x05 \x01(\x05\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'RealDataNew_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_METERMO']._serialized_start=22 + _globals['_METERMO']._serialized_end=689 + _globals['_RPMO']._serialized_start=691 + _globals['_RPMO']._serialized_end=796 + _globals['_RSDMO']._serialized_start=799 + _globals['_RSDMO']._serialized_end=975 + _globals['_SGSMO']._serialized_start=978 + _globals['_SGSMO']._serialized_end=1297 + _globals['_TGSMO']._serialized_start=1300 + _globals['_TGSMO']._serialized_end=1789 + _globals['_PVMO']._serialized_start=1792 + _globals['_PVMO']._serialized_end=1955 + _globals['_REALDATANEWREQDTO']._serialized_start=1958 + _globals['_REALDATANEWREQDTO']._serialized_end=2287 + _globals['_REALDATANEWRESDTO']._serialized_start=2289 + _globals['_REALDATANEWRESDTO']._serialized_end=2392 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/RealData_pb2.py b/hoymiles_wifi/protobuf/RealData_pb2.py new file mode 100644 index 0000000..cdbff51 --- /dev/null +++ b/hoymiles_wifi/protobuf/RealData_pb2.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: RealData.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eRealData.proto\"\xc5\x03\n\x0bMeterDataMO\x12\x13\n\x0b\x64\x65vice_kind\x18\x01 \x01(\x05\x12\x10\n\x08meter_sn\x18\x02 \x01(\x03\x12\x19\n\x11meter_total_power\x18\x03 \x01(\x05\x12\x1b\n\x13meter_phase_A_power\x18\x04 \x01(\x05\x12\x1b\n\x13meter_phase_B_power\x18\x05 \x01(\x05\x12\x1b\n\x13meter_phase_C_power\x18\x06 \x01(\x05\x12\x14\n\x0cmeter_factor\x18\x07 \x01(\x05\x12\x1a\n\x12meter_total_energy\x18\x08 \x01(\x05\x12\x1c\n\x14meter_phase_A_energy\x18\t \x01(\x05\x12\x1c\n\x14meter_phase_B_energy\x18\n \x01(\x05\x12\x1c\n\x14meter_phase_C_energy\x18\x0b \x01(\x05\x12\x1c\n\x14meter_total_consumed\x18\x0c \x01(\x05\x12\x1e\n\x16meter_phase_A_consumed\x18\r \x01(\x05\x12\x1e\n\x16meter_phase_B_consumed\x18\x0e \x01(\x05\x12\x1e\n\x16meter_phase_C_consumed\x18\x0f \x01(\x05\x12\x13\n\x0bmeter_fault\x18\x10 \x01(\x05\"m\n\x08RpDataMO\x12\r\n\x05rp_sn\x18\x01 \x01(\x03\x12\x11\n\trp_signal\x18\x02 \x01(\x05\x12\x12\n\nrp_channel\x18\x03 \x01(\x05\x12\x13\n\x0brp_link_nub\x18\x04 \x01(\x05\x12\x16\n\x0erp_link_status\x18\x05 \x01(\x05\"\xdd\x03\n\x08PvDataMO\x12\r\n\x05pv_sn\x18\x01 \x01(\x03\x12\x0f\n\x07pv_port\x18\x02 \x01(\x05\x12\x0e\n\x06pv_vol\x18\x03 \x01(\x05\x12\x0e\n\x06pv_cur\x18\x04 \x01(\x05\x12\x10\n\x08pv_power\x18\x05 \x01(\x05\x12\x17\n\x0fpv_energy_total\x18\x06 \x01(\x05\x12\x10\n\x08grid_vol\x18\x07 \x01(\x05\x12\x14\n\x0cgrid_vol_max\x18\x08 \x01(\x05\x12\x11\n\tgrid_freq\x18\t \x01(\x05\x12\x0e\n\x06grid_p\x18\n \x01(\x05\x12\x0e\n\x06grid_q\x18\x0b \x01(\x05\x12\x0e\n\x06grid_i\x18\x0c \x01(\x05\x12\x0f\n\x07grid_pf\x18\r \x01(\x05\x12\x0f\n\x07pv_temp\x18\x0e \x01(\x05\x12\x15\n\rpv_run_status\x18\x0f \x01(\x05\x12\x14\n\x0cpv_fault_num\x18\x10 \x01(\x05\x12\x14\n\x0cpv_fault_cnt\x18\x11 \x01(\x05\x12\x16\n\x0epv_warning_cnt\x18\x12 \x01(\x05\x12\x16\n\x0epv_link_status\x18\x13 \x01(\x05\x12\x15\n\rpv_send_power\x18\x14 \x01(\x05\x12\x18\n\x10pv_receive_power\x18\x15 \x01(\x05\x12\x0f\n\x07pv_time\x18\x16 \x01(\x05\x12\x11\n\tpv_energy\x18\x17 \x01(\x05\x12\x11\n\tmi_signal\x18\x18 \x01(\x05\"\x86\x02\n\x0eRealDataReqDTO\x12\x0e\n\x06\x64tu_sn\x18\x01 \x01(\t\x12\x11\n\ttimestamp\x18\x02 \x01(\x05\x12\x15\n\rdevice_number\x18\x03 \x01(\x05\x12\x11\n\tpv_number\x18\x04 \x01(\x05\x12\x16\n\x0epackage_number\x18\x05 \x01(\x05\x12\x17\n\x0f\x63urrent_package\x18\x06 \x01(\x05\x12\x0b\n\x03\x63sq\x18\x07 \x01(\x05\x12 \n\nmeter_data\x18\x08 \x03(\x0b\x32\x0c.MeterDataMO\x12\x1a\n\x07rp_data\x18\t \x03(\x0b\x32\t.RpDataMO\x12\x1a\n\x07pv_data\x18\n \x03(\x0b\x32\t.PvDataMO\x12\x0f\n\x07version\x18\x0b \x01(\x05\"d\n\x0eRealDataResDTO\x12\x14\n\x0ctime_ymd_hms\x18\x01 \x01(\t\x12\n\n\x02\x63p\x18\x02 \x01(\x05\x12\x12\n\nerror_code\x18\x03 \x01(\x05\x12\x0e\n\x06offset\x18\x04 \x01(\x05\x12\x0c\n\x04time\x18\x05 \x01(\x05\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'RealData_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_METERDATAMO']._serialized_start=19 + _globals['_METERDATAMO']._serialized_end=472 + _globals['_RPDATAMO']._serialized_start=474 + _globals['_RPDATAMO']._serialized_end=583 + _globals['_PVDATAMO']._serialized_start=586 + _globals['_PVDATAMO']._serialized_end=1063 + _globals['_REALDATAREQDTO']._serialized_start=1066 + _globals['_REALDATAREQDTO']._serialized_end=1328 + _globals['_REALDATARESDTO']._serialized_start=1330 + _globals['_REALDATARESDTO']._serialized_end=1430 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/SetConfig.proto b/hoymiles_wifi/protobuf/SetConfig.proto new file mode 100644 index 0000000..bc79133 --- /dev/null +++ b/hoymiles_wifi/protobuf/SetConfig.proto @@ -0,0 +1,62 @@ +syntax = "proto3"; + +// Definition of the response message for setting configuration. +message SetConfigResDTO { + int32 offset = 1; // Offset value + uint32 time = 2; // Time value + int32 lock_password = 3; // Lock password + int32 lock_time = 4; // Lock time + int32 limit_power_mypower = 5; // Limit power for mypower + int32 zero_export_433_addr = 6; // Zero export 433 address + int32 zero_export_enable = 7; // Zero export enable flag + int32 netmode_select = 8; // Network mode selection + int32 channel_select = 9; // Channel selection + int32 server_send_time = 10; // Server send time + int32 serverport = 11; // Server port + string apn_set = 12; // Access Point Name (APN) setting + string meter_kind = 13; // Meter kind + string meter_interface = 14; // Meter interface + string wifi_ssid = 15; // Wi-Fi SSID + string wifi_password = 16; // Wi-Fi password + string server_domain_name = 17; // Server domain name + int32 inv_type = 18; // Inverter type + string dtu_sn = 19; // Data Terminal Unit (DTU) serial number + int32 access_model = 20; // Access model + int32 mac_0 = 21; // MAC address octet 0 + int32 mac_1 = 22; // MAC address octet 1 + int32 mac_2 = 23; // MAC address octet 2 + int32 mac_3 = 24; // MAC address octet 3 + int32 dhcp_switch = 25; // DHCP switch + int32 ip_addr_0 = 26; // IP address octet 0 + int32 ip_addr_1 = 27; // IP address octet 1 + int32 ip_addr_2 = 28; // IP address octet 2 + int32 ip_addr_3 = 29; // IP address octet 3 + int32 subnet_mask_0 = 30; // Subnet mask octet 0 + int32 subnet_mask_1 = 31; // Subnet mask octet 1 + int32 subnet_mask_2 = 32; // Subnet mask octet 2 + int32 subnet_mask_3 = 33; // Subnet mask octet 3 + int32 default_gateway_0 = 34; // Default gateway octet 0 + int32 default_gateway_1 = 35; // Default gateway octet 1 + int32 default_gateway_2 = 36; // Default gateway octet 2 + int32 default_gateway_3 = 37; // Default gateway octet 3 + string apn_name = 38; // APN name + string apn_password = 39; // APN password + int32 sub1g_sweep_switch = 40; // Sub-1GHz sweep switch + int32 sub1g_work_channel = 41; // Sub-1GHz work channel + int32 cable_dns_0 = 42; // Cable DNS octet 0 + int32 cable_dns_1 = 43; // Cable DNS octet 1 + int32 cable_dns_2 = 44; // Cable DNS octet 2 + int32 cable_dns_3 = 45; // Cable DNS octet 3 + int32 mac_4 = 46; // MAC address octet 4 + int32 mac_5 = 47; // MAC address octet 5 + string dtu_ap_ssid = 48; // DTU AP SSID + string dtu_ap_pass = 49; // DTU AP password + int32 app_page = 50; // App page +} + +// Definition of the request message for setting configuration. +message SetConfigReqDTO { + int32 offset = 1; // Offset value + uint32 time = 2; // Time value + int32 error_code = 3; // Error code +} diff --git a/hoymiles_wifi/protobuf/SetConfig_pb2.py b/hoymiles_wifi/protobuf/SetConfig_pb2.py new file mode 100644 index 0000000..ef892dd --- /dev/null +++ b/hoymiles_wifi/protobuf/SetConfig_pb2.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: SetConfig.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fSetConfig.proto\"\xbd\x08\n\x0fSetConfigResDTO\x12\x0e\n\x06offset\x18\x01 \x01(\x05\x12\x0c\n\x04time\x18\x02 \x01(\r\x12\x15\n\rlock_password\x18\x03 \x01(\x05\x12\x11\n\tlock_time\x18\x04 \x01(\x05\x12\x1b\n\x13limit_power_mypower\x18\x05 \x01(\x05\x12\x1c\n\x14zero_export_433_addr\x18\x06 \x01(\x05\x12\x1a\n\x12zero_export_enable\x18\x07 \x01(\x05\x12\x16\n\x0enetmode_select\x18\x08 \x01(\x05\x12\x16\n\x0e\x63hannel_select\x18\t \x01(\x05\x12\x18\n\x10server_send_time\x18\n \x01(\x05\x12\x12\n\nserverport\x18\x0b \x01(\x05\x12\x0f\n\x07\x61pn_set\x18\x0c \x01(\t\x12\x12\n\nmeter_kind\x18\r \x01(\t\x12\x17\n\x0fmeter_interface\x18\x0e \x01(\t\x12\x11\n\twifi_ssid\x18\x0f \x01(\t\x12\x15\n\rwifi_password\x18\x10 \x01(\t\x12\x1a\n\x12server_domain_name\x18\x11 \x01(\t\x12\x10\n\x08inv_type\x18\x12 \x01(\x05\x12\x0e\n\x06\x64tu_sn\x18\x13 \x01(\t\x12\x14\n\x0c\x61\x63\x63\x65ss_model\x18\x14 \x01(\x05\x12\r\n\x05mac_0\x18\x15 \x01(\x05\x12\r\n\x05mac_1\x18\x16 \x01(\x05\x12\r\n\x05mac_2\x18\x17 \x01(\x05\x12\r\n\x05mac_3\x18\x18 \x01(\x05\x12\x13\n\x0b\x64hcp_switch\x18\x19 \x01(\x05\x12\x11\n\tip_addr_0\x18\x1a \x01(\x05\x12\x11\n\tip_addr_1\x18\x1b \x01(\x05\x12\x11\n\tip_addr_2\x18\x1c \x01(\x05\x12\x11\n\tip_addr_3\x18\x1d \x01(\x05\x12\x15\n\rsubnet_mask_0\x18\x1e \x01(\x05\x12\x15\n\rsubnet_mask_1\x18\x1f \x01(\x05\x12\x15\n\rsubnet_mask_2\x18 \x01(\x05\x12\x15\n\rsubnet_mask_3\x18! \x01(\x05\x12\x19\n\x11\x64\x65\x66\x61ult_gateway_0\x18\" \x01(\x05\x12\x19\n\x11\x64\x65\x66\x61ult_gateway_1\x18# \x01(\x05\x12\x19\n\x11\x64\x65\x66\x61ult_gateway_2\x18$ \x01(\x05\x12\x19\n\x11\x64\x65\x66\x61ult_gateway_3\x18% \x01(\x05\x12\x10\n\x08\x61pn_name\x18& \x01(\t\x12\x14\n\x0c\x61pn_password\x18\' \x01(\t\x12\x1a\n\x12sub1g_sweep_switch\x18( \x01(\x05\x12\x1a\n\x12sub1g_work_channel\x18) \x01(\x05\x12\x13\n\x0b\x63\x61\x62le_dns_0\x18* \x01(\x05\x12\x13\n\x0b\x63\x61\x62le_dns_1\x18+ \x01(\x05\x12\x13\n\x0b\x63\x61\x62le_dns_2\x18, \x01(\x05\x12\x13\n\x0b\x63\x61\x62le_dns_3\x18- \x01(\x05\x12\r\n\x05mac_4\x18. \x01(\x05\x12\r\n\x05mac_5\x18/ \x01(\x05\x12\x13\n\x0b\x64tu_ap_ssid\x18\x30 \x01(\t\x12\x13\n\x0b\x64tu_ap_pass\x18\x31 \x01(\t\x12\x10\n\x08\x61pp_page\x18\x32 \x01(\x05\"C\n\x0fSetConfigReqDTO\x12\x0e\n\x06offset\x18\x01 \x01(\x05\x12\x0c\n\x04time\x18\x02 \x01(\r\x12\x12\n\nerror_code\x18\x03 \x01(\x05\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'SetConfig_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_SETCONFIGRESDTO']._serialized_start=20 + _globals['_SETCONFIGRESDTO']._serialized_end=1105 + _globals['_SETCONFIGREQDTO']._serialized_start=1107 + _globals['_SETCONFIGREQDTO']._serialized_end=1174 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/WarnData.proto b/hoymiles_wifi/protobuf/WarnData.proto new file mode 100644 index 0000000..a02b8f6 --- /dev/null +++ b/hoymiles_wifi/protobuf/WarnData.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; + +message WarnReqDTO { + string dtu_sn = 1; + int32 time = 2; + int32 package_nub = 3; + int32 package_now = 4; + int32 warn_device = 5; + repeated WarnMO warns = 6; +} + +message WarnMO { + int64 pv_sn = 1; + int32 code = 2; + int32 num = 3; + int32 s_time = 4; + int32 e_time = 5; + int32 w_data1 = 6; + int32 w_data2 = 7; +} + +message WarnResDTO { + string ymd_hms = 1; + int32 package_now = 2; + int32 err_code = 3; + int32 offset = 4; + int32 time = 5; +} + +message WaveReqDTO { + string dtu_sn = 1; + int32 time = 2; + int32 package_nub = 3; + int32 package_now = 4; + int64 pv_sn = 5; + int32 code = 6; + int32 num = 7; + int32 warn_time = 8; + int32 data_len = 9; + int32 pos = 10; + string warn_data = 11; +} + +message WaveResDTO { + string ymd_hms = 1; + int32 package_now = 2; + int32 err_code = 3; + int32 offset = 4; + int32 time = 5; +} diff --git a/hoymiles_wifi/protobuf/WarnData_pb2.py b/hoymiles_wifi/protobuf/WarnData_pb2.py new file mode 100644 index 0000000..98afc0b --- /dev/null +++ b/hoymiles_wifi/protobuf/WarnData_pb2.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: WarnData.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eWarnData.proto\"\x81\x01\n\nWarnReqDTO\x12\x0e\n\x06\x64tu_sn\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\x02 \x01(\x05\x12\x13\n\x0bpackage_nub\x18\x03 \x01(\x05\x12\x13\n\x0bpackage_now\x18\x04 \x01(\x05\x12\x13\n\x0bwarn_device\x18\x05 \x01(\x05\x12\x16\n\x05warns\x18\x06 \x03(\x0b\x32\x07.WarnMO\"t\n\x06WarnMO\x12\r\n\x05pv_sn\x18\x01 \x01(\x03\x12\x0c\n\x04\x63ode\x18\x02 \x01(\x05\x12\x0b\n\x03num\x18\x03 \x01(\x05\x12\x0e\n\x06s_time\x18\x04 \x01(\x05\x12\x0e\n\x06\x65_time\x18\x05 \x01(\x05\x12\x0f\n\x07w_data1\x18\x06 \x01(\x05\x12\x0f\n\x07w_data2\x18\x07 \x01(\x05\"b\n\nWarnResDTO\x12\x0f\n\x07ymd_hms\x18\x01 \x01(\t\x12\x13\n\x0bpackage_now\x18\x02 \x01(\x05\x12\x10\n\x08\x65rr_code\x18\x03 \x01(\x05\x12\x0e\n\x06offset\x18\x04 \x01(\x05\x12\x0c\n\x04time\x18\x05 \x01(\x05\"\xc3\x01\n\nWaveReqDTO\x12\x0e\n\x06\x64tu_sn\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\x02 \x01(\x05\x12\x13\n\x0bpackage_nub\x18\x03 \x01(\x05\x12\x13\n\x0bpackage_now\x18\x04 \x01(\x05\x12\r\n\x05pv_sn\x18\x05 \x01(\x03\x12\x0c\n\x04\x63ode\x18\x06 \x01(\x05\x12\x0b\n\x03num\x18\x07 \x01(\x05\x12\x11\n\twarn_time\x18\x08 \x01(\x05\x12\x10\n\x08\x64\x61ta_len\x18\t \x01(\x05\x12\x0b\n\x03pos\x18\n \x01(\x05\x12\x11\n\twarn_data\x18\x0b \x01(\t\"b\n\nWaveResDTO\x12\x0f\n\x07ymd_hms\x18\x01 \x01(\t\x12\x13\n\x0bpackage_now\x18\x02 \x01(\x05\x12\x10\n\x08\x65rr_code\x18\x03 \x01(\x05\x12\x0e\n\x06offset\x18\x04 \x01(\x05\x12\x0c\n\x04time\x18\x05 \x01(\x05\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'WarnData_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_WARNREQDTO']._serialized_start=19 + _globals['_WARNREQDTO']._serialized_end=148 + _globals['_WARNMO']._serialized_start=150 + _globals['_WARNMO']._serialized_end=266 + _globals['_WARNRESDTO']._serialized_start=268 + _globals['_WARNRESDTO']._serialized_end=366 + _globals['_WAVEREQDTO']._serialized_start=369 + _globals['_WAVEREQDTO']._serialized_end=564 + _globals['_WAVERESDTO']._serialized_start=566 + _globals['_WAVERESDTO']._serialized_end=664 +# @@protoc_insertion_point(module_scope) diff --git a/hoymiles_wifi/protobuf/__init__.py b/hoymiles_wifi/protobuf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hoymiles_wifi/protobuf/compile_proto.sh b/hoymiles_wifi/protobuf/compile_proto.sh new file mode 100644 index 0000000..fc92142 --- /dev/null +++ b/hoymiles_wifi/protobuf/compile_proto.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +for file in $(ls *.proto) +do + #protoc --pyi_out=. $file + protoc --python_out=. $file +done diff --git a/hoymiles_wifi/utils.py b/hoymiles_wifi/utils.py new file mode 100644 index 0000000..52c3dd3 --- /dev/null +++ b/hoymiles_wifi/utils.py @@ -0,0 +1,61 @@ +"""Utils for interacting with Hoymiles WiFi API.""" + +from hoymiles_wifi.protobuf import ( + GetConfig_pb2, + SetConfig_pb2, +) + + +def initialize_set_config(get_config_req: GetConfig_pb2.GetConfigReqDTO): + """Initialize set config response with get config request.""" + + set_config_res = SetConfig_pb2.SetConfigResDTO() + set_config_res.lock_password = get_config_req.lock_password + set_config_res.lock_time = get_config_req.lock_time + set_config_res.limit_power_mypower = get_config_req.limit_power_mypower + set_config_res.zero_export_433_addr = get_config_req.zero_export_433_addr + set_config_res.zero_export_enable = get_config_req.zero_export_enable + set_config_res.netmode_select = get_config_req.netmode_select + set_config_res.channel_select = get_config_req.channel_select + set_config_res.server_send_time = get_config_req.server_send_time + set_config_res.serverport = get_config_req.serverport + set_config_res.apn_set = get_config_req.apn_set + set_config_res.meter_kind = get_config_req.meter_kind + set_config_res.meter_interface = get_config_req.meter_interface + set_config_res.wifi_ssid = get_config_req.wifi_ssid + set_config_res.wifi_password = get_config_req.wifi_password + set_config_res.server_domain_name = get_config_req.server_domain_name + set_config_res.inv_type = get_config_req.inv_type + set_config_res.dtu_sn = get_config_req.dtu_sn + set_config_res.access_model = get_config_req.access_model + set_config_res.mac_0 = get_config_req.mac_0 + set_config_res.mac_1 = get_config_req.mac_1 + set_config_res.mac_2 = get_config_req.mac_2 + set_config_res.mac_3 = get_config_req.mac_3 + set_config_res.mac_4 = get_config_req.mac_4 + set_config_res.mac_5 = get_config_req.mac_5 + set_config_res.dhcp_switch = get_config_req.dhcp_switch + set_config_res.ip_addr_0 = get_config_req.ip_addr_0 + set_config_res.ip_addr_1 = get_config_req.ip_addr_1 + set_config_res.ip_addr_2 = get_config_req.ip_addr_2 + set_config_res.ip_addr_3 = get_config_req.ip_addr_3 + set_config_res.subnet_mask_0 = get_config_req.subnet_mask_0 + set_config_res.subnet_mask_1 = get_config_req.subnet_mask_1 + set_config_res.subnet_mask_2 = get_config_req.subnet_mask_2 + set_config_res.subnet_mask_3 = get_config_req.subnet_mask_3 + set_config_res.default_gateway_0 = get_config_req.default_gateway_0 + set_config_res.default_gateway_1 = get_config_req.default_gateway_1 + set_config_res.default_gateway_2 = get_config_req.default_gateway_2 + set_config_res.default_gateway_3 = get_config_req.default_gateway_3 + set_config_res.apn_name = get_config_req.apn_name + set_config_res.apn_password = get_config_req.apn_password + set_config_res.sub1g_sweep_switch = get_config_req.sub1g_sweep_switch + set_config_res.sub1g_work_channel = get_config_req.sub1g_work_channel + set_config_res.cable_dns_0 = get_config_req.cable_dns_0 + set_config_res.cable_dns_1 = get_config_req.cable_dns_1 + set_config_res.cable_dns_2 = get_config_req.cable_dns_2 + set_config_res.cable_dns_3 = get_config_req.cable_dns_3 + set_config_res.dtu_ap_ssid = get_config_req.dtu_ap_ssid + set_config_res.dtu_ap_pass = get_config_req.dtu_ap_pass + + return set_config_res diff --git a/idna/__init__.py b/idna/__init__.py new file mode 100644 index 0000000..a40eeaf --- /dev/null +++ b/idna/__init__.py @@ -0,0 +1,44 @@ +from .package_data import __version__ +from .core import ( + IDNABidiError, + IDNAError, + InvalidCodepoint, + InvalidCodepointContext, + alabel, + check_bidi, + check_hyphen_ok, + check_initial_combiner, + check_label, + check_nfc, + decode, + encode, + ulabel, + uts46_remap, + valid_contextj, + valid_contexto, + valid_label_length, + valid_string_length, +) +from .intranges import intranges_contain + +__all__ = [ + "IDNABidiError", + "IDNAError", + "InvalidCodepoint", + "InvalidCodepointContext", + "alabel", + "check_bidi", + "check_hyphen_ok", + "check_initial_combiner", + "check_label", + "check_nfc", + "decode", + "encode", + "intranges_contain", + "ulabel", + "uts46_remap", + "valid_contextj", + "valid_contexto", + "valid_label_length", + "valid_string_length", +] diff --git a/idna/codec.py b/idna/codec.py new file mode 100644 index 0000000..eaeada5 --- /dev/null +++ b/idna/codec.py @@ -0,0 +1,118 @@ +from .core import encode, decode, alabel, ulabel, IDNAError +import codecs +import re +from typing import Any, Tuple, Optional + +_unicode_dots_re = re.compile('[\u002e\u3002\uff0e\uff61]') + +class Codec(codecs.Codec): + + def encode(self, data: str, errors: str = 'strict') -> Tuple[bytes, int]: + if errors != 'strict': + raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) + + if not data: + return b"", 0 + + return encode(data), len(data) + + def decode(self, data: bytes, errors: str = 'strict') -> Tuple[str, int]: + if errors != 'strict': + raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) + + if not data: + return '', 0 + + return decode(data), len(data) + +class IncrementalEncoder(codecs.BufferedIncrementalEncoder): + def _buffer_encode(self, data: str, errors: str, final: bool) -> Tuple[bytes, int]: + if errors != 'strict': + raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) + + if not data: + return b'', 0 + + labels = _unicode_dots_re.split(data) + trailing_dot = b'' + if labels: + if not labels[-1]: + trailing_dot = b'.' + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = b'.' + + result = [] + size = 0 + for label in labels: + result.append(alabel(label)) + if size: + size += 1 + size += len(label) + + # Join with U+002E + result_bytes = b'.'.join(result) + trailing_dot + size += len(trailing_dot) + return result_bytes, size + +class IncrementalDecoder(codecs.BufferedIncrementalDecoder): + def _buffer_decode(self, data: Any, errors: str, final: bool) -> Tuple[str, int]: + if errors != 'strict': + raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) + + if not data: + return ('', 0) + + if not isinstance(data, str): + data = str(data, 'ascii') + + labels = _unicode_dots_re.split(data) + trailing_dot = '' + if labels: + if not labels[-1]: + trailing_dot = '.' + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = '.' + + result = [] + size = 0 + for label in labels: + result.append(ulabel(label)) + if size: + size += 1 + size += len(label) + + result_str = '.'.join(result) + trailing_dot + size += len(trailing_dot) + return (result_str, size) + + +class StreamWriter(Codec, codecs.StreamWriter): + pass + + +class StreamReader(Codec, codecs.StreamReader): + pass + + +def search_function(name: str) -> Optional[codecs.CodecInfo]: + if name != 'idna2008': + return None + return codecs.CodecInfo( + name=name, + encode=Codec().encode, # type: ignore + decode=Codec().decode, # type: ignore + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamwriter=StreamWriter, + streamreader=StreamReader, + ) + +codecs.register(search_function) diff --git a/idna/compat.py b/idna/compat.py new file mode 100644 index 0000000..786e6bd --- /dev/null +++ b/idna/compat.py @@ -0,0 +1,13 @@ +from .core import * +from .codec import * +from typing import Any, Union + +def ToASCII(label: str) -> bytes: + return encode(label) + +def ToUnicode(label: Union[bytes, bytearray]) -> str: + return decode(label) + +def nameprep(s: Any) -> None: + raise NotImplementedError('IDNA 2008 does not utilise nameprep protocol') + diff --git a/idna/core.py b/idna/core.py new file mode 100644 index 0000000..0bd89a3 --- /dev/null +++ b/idna/core.py @@ -0,0 +1,400 @@ +from . import idnadata +import bisect +import unicodedata +import re +from typing import Union, Optional +from .intranges import intranges_contain + +_virama_combining_class = 9 +_alabel_prefix = b'xn--' +_unicode_dots_re = re.compile('[\u002e\u3002\uff0e\uff61]') + +class IDNAError(UnicodeError): + """ Base exception for all IDNA-encoding related problems """ + pass + + +class IDNABidiError(IDNAError): + """ Exception when bidirectional requirements are not satisfied """ + pass + + +class InvalidCodepoint(IDNAError): + """ Exception when a disallowed or unallocated codepoint is used """ + pass + + +class InvalidCodepointContext(IDNAError): + """ Exception when the codepoint is not valid in the context it is used """ + pass + + +def _combining_class(cp: int) -> int: + v = unicodedata.combining(chr(cp)) + if v == 0: + if not unicodedata.name(chr(cp)): + raise ValueError('Unknown character in unicodedata') + return v + +def _is_script(cp: str, script: str) -> bool: + return intranges_contain(ord(cp), idnadata.scripts[script]) + +def _punycode(s: str) -> bytes: + return s.encode('punycode') + +def _unot(s: int) -> str: + return 'U+{:04X}'.format(s) + + +def valid_label_length(label: Union[bytes, str]) -> bool: + if len(label) > 63: + return False + return True + + +def valid_string_length(label: Union[bytes, str], trailing_dot: bool) -> bool: + if len(label) > (254 if trailing_dot else 253): + return False + return True + + +def check_bidi(label: str, check_ltr: bool = False) -> bool: + # Bidi rules should only be applied if string contains RTL characters + bidi_label = False + for (idx, cp) in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + if direction == '': + # String likely comes from a newer version of Unicode + raise IDNABidiError('Unknown directionality in label {} at position {}'.format(repr(label), idx)) + if direction in ['R', 'AL', 'AN']: + bidi_label = True + if not bidi_label and not check_ltr: + return True + + # Bidi rule 1 + direction = unicodedata.bidirectional(label[0]) + if direction in ['R', 'AL']: + rtl = True + elif direction == 'L': + rtl = False + else: + raise IDNABidiError('First codepoint in label {} must be directionality L, R or AL'.format(repr(label))) + + valid_ending = False + number_type = None # type: Optional[str] + for (idx, cp) in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + + if rtl: + # Bidi rule 2 + if not direction in ['R', 'AL', 'AN', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: + raise IDNABidiError('Invalid direction for codepoint at position {} in a right-to-left label'.format(idx)) + # Bidi rule 3 + if direction in ['R', 'AL', 'EN', 'AN']: + valid_ending = True + elif direction != 'NSM': + valid_ending = False + # Bidi rule 4 + if direction in ['AN', 'EN']: + if not number_type: + number_type = direction + else: + if number_type != direction: + raise IDNABidiError('Can not mix numeral types in a right-to-left label') + else: + # Bidi rule 5 + if not direction in ['L', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: + raise IDNABidiError('Invalid direction for codepoint at position {} in a left-to-right label'.format(idx)) + # Bidi rule 6 + if direction in ['L', 'EN']: + valid_ending = True + elif direction != 'NSM': + valid_ending = False + + if not valid_ending: + raise IDNABidiError('Label ends with illegal codepoint directionality') + + return True + + +def check_initial_combiner(label: str) -> bool: + if unicodedata.category(label[0])[0] == 'M': + raise IDNAError('Label begins with an illegal combining character') + return True + + +def check_hyphen_ok(label: str) -> bool: + if label[2:4] == '--': + raise IDNAError('Label has disallowed hyphens in 3rd and 4th position') + if label[0] == '-' or label[-1] == '-': + raise IDNAError('Label must not start or end with a hyphen') + return True + + +def check_nfc(label: str) -> None: + if unicodedata.normalize('NFC', label) != label: + raise IDNAError('Label must be in Normalization Form C') + + +def valid_contextj(label: str, pos: int) -> bool: + cp_value = ord(label[pos]) + + if cp_value == 0x200c: + + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + + ok = False + for i in range(pos-1, -1, -1): + joining_type = idnadata.joining_types.get(ord(label[i])) + if joining_type == ord('T'): + continue + if joining_type in [ord('L'), ord('D')]: + ok = True + break + + if not ok: + return False + + ok = False + for i in range(pos+1, len(label)): + joining_type = idnadata.joining_types.get(ord(label[i])) + if joining_type == ord('T'): + continue + if joining_type in [ord('R'), ord('D')]: + ok = True + break + return ok + + if cp_value == 0x200d: + + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + return False + + else: + + return False + + +def valid_contexto(label: str, pos: int, exception: bool = False) -> bool: + cp_value = ord(label[pos]) + + if cp_value == 0x00b7: + if 0 < pos < len(label)-1: + if ord(label[pos - 1]) == 0x006c and ord(label[pos + 1]) == 0x006c: + return True + return False + + elif cp_value == 0x0375: + if pos < len(label)-1 and len(label) > 1: + return _is_script(label[pos + 1], 'Greek') + return False + + elif cp_value == 0x05f3 or cp_value == 0x05f4: + if pos > 0: + return _is_script(label[pos - 1], 'Hebrew') + return False + + elif cp_value == 0x30fb: + for cp in label: + if cp == '\u30fb': + continue + if _is_script(cp, 'Hiragana') or _is_script(cp, 'Katakana') or _is_script(cp, 'Han'): + return True + return False + + elif 0x660 <= cp_value <= 0x669: + for cp in label: + if 0x6f0 <= ord(cp) <= 0x06f9: + return False + return True + + elif 0x6f0 <= cp_value <= 0x6f9: + for cp in label: + if 0x660 <= ord(cp) <= 0x0669: + return False + return True + + return False + + +def check_label(label: Union[str, bytes, bytearray]) -> None: + if isinstance(label, (bytes, bytearray)): + label = label.decode('utf-8') + if len(label) == 0: + raise IDNAError('Empty Label') + + check_nfc(label) + check_hyphen_ok(label) + check_initial_combiner(label) + + for (pos, cp) in enumerate(label): + cp_value = ord(cp) + if intranges_contain(cp_value, idnadata.codepoint_classes['PVALID']): + continue + elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTJ']): + try: + if not valid_contextj(label, pos): + raise InvalidCodepointContext('Joiner {} not allowed at position {} in {}'.format( + _unot(cp_value), pos+1, repr(label))) + except ValueError: + raise IDNAError('Unknown codepoint adjacent to joiner {} at position {} in {}'.format( + _unot(cp_value), pos+1, repr(label))) + elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTO']): + if not valid_contexto(label, pos): + raise InvalidCodepointContext('Codepoint {} not allowed at position {} in {}'.format(_unot(cp_value), pos+1, repr(label))) + else: + raise InvalidCodepoint('Codepoint {} at position {} of {} not allowed'.format(_unot(cp_value), pos+1, repr(label))) + + check_bidi(label) + + +def alabel(label: str) -> bytes: + try: + label_bytes = label.encode('ascii') + ulabel(label_bytes) + if not valid_label_length(label_bytes): + raise IDNAError('Label too long') + return label_bytes + except UnicodeEncodeError: + pass + + if not label: + raise IDNAError('No Input') + + label = str(label) + check_label(label) + label_bytes = _punycode(label) + label_bytes = _alabel_prefix + label_bytes + + if not valid_label_length(label_bytes): + raise IDNAError('Label too long') + + return label_bytes + + +def ulabel(label: Union[str, bytes, bytearray]) -> str: + if not isinstance(label, (bytes, bytearray)): + try: + label_bytes = label.encode('ascii') + except UnicodeEncodeError: + check_label(label) + return label + else: + label_bytes = label + + label_bytes = label_bytes.lower() + if label_bytes.startswith(_alabel_prefix): + label_bytes = label_bytes[len(_alabel_prefix):] + if not label_bytes: + raise IDNAError('Malformed A-label, no Punycode eligible content found') + if label_bytes.decode('ascii')[-1] == '-': + raise IDNAError('A-label must not end with a hyphen') + else: + check_label(label_bytes) + return label_bytes.decode('ascii') + + try: + label = label_bytes.decode('punycode') + except UnicodeError: + raise IDNAError('Invalid A-label') + check_label(label) + return label + + +def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False) -> str: + """Re-map the characters in the string according to UTS46 processing.""" + from .uts46data import uts46data + output = '' + + for pos, char in enumerate(domain): + code_point = ord(char) + try: + uts46row = uts46data[code_point if code_point < 256 else + bisect.bisect_left(uts46data, (code_point, 'Z')) - 1] + status = uts46row[1] + replacement = None # type: Optional[str] + if len(uts46row) == 3: + replacement = uts46row[2] # type: ignore + if (status == 'V' or + (status == 'D' and not transitional) or + (status == '3' and not std3_rules and replacement is None)): + output += char + elif replacement is not None and (status == 'M' or + (status == '3' and not std3_rules) or + (status == 'D' and transitional)): + output += replacement + elif status != 'I': + raise IndexError() + except IndexError: + raise InvalidCodepoint( + 'Codepoint {} not allowed at position {} in {}'.format( + _unot(code_point), pos + 1, repr(domain))) + + return unicodedata.normalize('NFC', output) + + +def encode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = False, std3_rules: bool = False, transitional: bool = False) -> bytes: + if not isinstance(s, str): + try: + s = str(s, 'ascii') + except UnicodeDecodeError: + raise IDNAError('should pass a unicode string to the function rather than a byte string.') + if uts46: + s = uts46_remap(s, std3_rules, transitional) + trailing_dot = False + result = [] + if strict: + labels = s.split('.') + else: + labels = _unicode_dots_re.split(s) + if not labels or labels == ['']: + raise IDNAError('Empty domain') + if labels[-1] == '': + del labels[-1] + trailing_dot = True + for label in labels: + s = alabel(label) + if s: + result.append(s) + else: + raise IDNAError('Empty label') + if trailing_dot: + result.append(b'') + s = b'.'.join(result) + if not valid_string_length(s, trailing_dot): + raise IDNAError('Domain too long') + return s + + +def decode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = False, std3_rules: bool = False) -> str: + try: + if not isinstance(s, str): + s = str(s, 'ascii') + except UnicodeDecodeError: + raise IDNAError('Invalid ASCII in A-label') + if uts46: + s = uts46_remap(s, std3_rules, False) + trailing_dot = False + result = [] + if not strict: + labels = _unicode_dots_re.split(s) + else: + labels = s.split('.') + if not labels or labels == ['']: + raise IDNAError('Empty domain') + if not labels[-1]: + del labels[-1] + trailing_dot = True + for label in labels: + s = ulabel(label) + if s: + result.append(s) + else: + raise IDNAError('Empty label') + if trailing_dot: + result.append('') + return '.'.join(result) diff --git a/idna/idnadata.py b/idna/idnadata.py new file mode 100644 index 0000000..f9bc0d8 --- /dev/null +++ b/idna/idnadata.py @@ -0,0 +1,2148 @@ +# This file is automatically generated by tools/idna-data + +__version__ = '15.0.0' +scripts = { + 'Greek': ( + 0x37000000374, + 0x37500000378, + 0x37a0000037e, + 0x37f00000380, + 0x38400000385, + 0x38600000387, + 0x3880000038b, + 0x38c0000038d, + 0x38e000003a2, + 0x3a3000003e2, + 0x3f000000400, + 0x1d2600001d2b, + 0x1d5d00001d62, + 0x1d6600001d6b, + 0x1dbf00001dc0, + 0x1f0000001f16, + 0x1f1800001f1e, + 0x1f2000001f46, + 0x1f4800001f4e, + 0x1f5000001f58, + 0x1f5900001f5a, + 0x1f5b00001f5c, + 0x1f5d00001f5e, + 0x1f5f00001f7e, + 0x1f8000001fb5, + 0x1fb600001fc5, + 0x1fc600001fd4, + 0x1fd600001fdc, + 0x1fdd00001ff0, + 0x1ff200001ff5, + 0x1ff600001fff, + 0x212600002127, + 0xab650000ab66, + 0x101400001018f, + 0x101a0000101a1, + 0x1d2000001d246, + ), + 'Han': ( + 0x2e8000002e9a, + 0x2e9b00002ef4, + 0x2f0000002fd6, + 0x300500003006, + 0x300700003008, + 0x30210000302a, + 0x30380000303c, + 0x340000004dc0, + 0x4e000000a000, + 0xf9000000fa6e, + 0xfa700000fada, + 0x16fe200016fe4, + 0x16ff000016ff2, + 0x200000002a6e0, + 0x2a7000002b73a, + 0x2b7400002b81e, + 0x2b8200002cea2, + 0x2ceb00002ebe1, + 0x2f8000002fa1e, + 0x300000003134b, + 0x31350000323b0, + ), + 'Hebrew': ( + 0x591000005c8, + 0x5d0000005eb, + 0x5ef000005f5, + 0xfb1d0000fb37, + 0xfb380000fb3d, + 0xfb3e0000fb3f, + 0xfb400000fb42, + 0xfb430000fb45, + 0xfb460000fb50, + ), + 'Hiragana': ( + 0x304100003097, + 0x309d000030a0, + 0x1b0010001b120, + 0x1b1320001b133, + 0x1b1500001b153, + 0x1f2000001f201, + ), + 'Katakana': ( + 0x30a1000030fb, + 0x30fd00003100, + 0x31f000003200, + 0x32d0000032ff, + 0x330000003358, + 0xff660000ff70, + 0xff710000ff9e, + 0x1aff00001aff4, + 0x1aff50001affc, + 0x1affd0001afff, + 0x1b0000001b001, + 0x1b1200001b123, + 0x1b1550001b156, + 0x1b1640001b168, + ), +} +joining_types = { + 0x600: 85, + 0x601: 85, + 0x602: 85, + 0x603: 85, + 0x604: 85, + 0x605: 85, + 0x608: 85, + 0x60b: 85, + 0x620: 68, + 0x621: 85, + 0x622: 82, + 0x623: 82, + 0x624: 82, + 0x625: 82, + 0x626: 68, + 0x627: 82, + 0x628: 68, + 0x629: 82, + 0x62a: 68, + 0x62b: 68, + 0x62c: 68, + 0x62d: 68, + 0x62e: 68, + 0x62f: 82, + 0x630: 82, + 0x631: 82, + 0x632: 82, + 0x633: 68, + 0x634: 68, + 0x635: 68, + 0x636: 68, + 0x637: 68, + 0x638: 68, + 0x639: 68, + 0x63a: 68, + 0x63b: 68, + 0x63c: 68, + 0x63d: 68, + 0x63e: 68, + 0x63f: 68, + 0x640: 67, + 0x641: 68, + 0x642: 68, + 0x643: 68, + 0x644: 68, + 0x645: 68, + 0x646: 68, + 0x647: 68, + 0x648: 82, + 0x649: 68, + 0x64a: 68, + 0x66e: 68, + 0x66f: 68, + 0x671: 82, + 0x672: 82, + 0x673: 82, + 0x674: 85, + 0x675: 82, + 0x676: 82, + 0x677: 82, + 0x678: 68, + 0x679: 68, + 0x67a: 68, + 0x67b: 68, + 0x67c: 68, + 0x67d: 68, + 0x67e: 68, + 0x67f: 68, + 0x680: 68, + 0x681: 68, + 0x682: 68, + 0x683: 68, + 0x684: 68, + 0x685: 68, + 0x686: 68, + 0x687: 68, + 0x688: 82, + 0x689: 82, + 0x68a: 82, + 0x68b: 82, + 0x68c: 82, + 0x68d: 82, + 0x68e: 82, + 0x68f: 82, + 0x690: 82, + 0x691: 82, + 0x692: 82, + 0x693: 82, + 0x694: 82, + 0x695: 82, + 0x696: 82, + 0x697: 82, + 0x698: 82, + 0x699: 82, + 0x69a: 68, + 0x69b: 68, + 0x69c: 68, + 0x69d: 68, + 0x69e: 68, + 0x69f: 68, + 0x6a0: 68, + 0x6a1: 68, + 0x6a2: 68, + 0x6a3: 68, + 0x6a4: 68, + 0x6a5: 68, + 0x6a6: 68, + 0x6a7: 68, + 0x6a8: 68, + 0x6a9: 68, + 0x6aa: 68, + 0x6ab: 68, + 0x6ac: 68, + 0x6ad: 68, + 0x6ae: 68, + 0x6af: 68, + 0x6b0: 68, + 0x6b1: 68, + 0x6b2: 68, + 0x6b3: 68, + 0x6b4: 68, + 0x6b5: 68, + 0x6b6: 68, + 0x6b7: 68, + 0x6b8: 68, + 0x6b9: 68, + 0x6ba: 68, + 0x6bb: 68, + 0x6bc: 68, + 0x6bd: 68, + 0x6be: 68, + 0x6bf: 68, + 0x6c0: 82, + 0x6c1: 68, + 0x6c2: 68, + 0x6c3: 82, + 0x6c4: 82, + 0x6c5: 82, + 0x6c6: 82, + 0x6c7: 82, + 0x6c8: 82, + 0x6c9: 82, + 0x6ca: 82, + 0x6cb: 82, + 0x6cc: 68, + 0x6cd: 82, + 0x6ce: 68, + 0x6cf: 82, + 0x6d0: 68, + 0x6d1: 68, + 0x6d2: 82, + 0x6d3: 82, + 0x6d5: 82, + 0x6dd: 85, + 0x6ee: 82, + 0x6ef: 82, + 0x6fa: 68, + 0x6fb: 68, + 0x6fc: 68, + 0x6ff: 68, + 0x70f: 84, + 0x710: 82, + 0x712: 68, + 0x713: 68, + 0x714: 68, + 0x715: 82, + 0x716: 82, + 0x717: 82, + 0x718: 82, + 0x719: 82, + 0x71a: 68, + 0x71b: 68, + 0x71c: 68, + 0x71d: 68, + 0x71e: 82, + 0x71f: 68, + 0x720: 68, + 0x721: 68, + 0x722: 68, + 0x723: 68, + 0x724: 68, + 0x725: 68, + 0x726: 68, + 0x727: 68, + 0x728: 82, + 0x729: 68, + 0x72a: 82, + 0x72b: 68, + 0x72c: 82, + 0x72d: 68, + 0x72e: 68, + 0x72f: 82, + 0x74d: 82, + 0x74e: 68, + 0x74f: 68, + 0x750: 68, + 0x751: 68, + 0x752: 68, + 0x753: 68, + 0x754: 68, + 0x755: 68, + 0x756: 68, + 0x757: 68, + 0x758: 68, + 0x759: 82, + 0x75a: 82, + 0x75b: 82, + 0x75c: 68, + 0x75d: 68, + 0x75e: 68, + 0x75f: 68, + 0x760: 68, + 0x761: 68, + 0x762: 68, + 0x763: 68, + 0x764: 68, + 0x765: 68, + 0x766: 68, + 0x767: 68, + 0x768: 68, + 0x769: 68, + 0x76a: 68, + 0x76b: 82, + 0x76c: 82, + 0x76d: 68, + 0x76e: 68, + 0x76f: 68, + 0x770: 68, + 0x771: 82, + 0x772: 68, + 0x773: 82, + 0x774: 82, + 0x775: 68, + 0x776: 68, + 0x777: 68, + 0x778: 82, + 0x779: 82, + 0x77a: 68, + 0x77b: 68, + 0x77c: 68, + 0x77d: 68, + 0x77e: 68, + 0x77f: 68, + 0x7ca: 68, + 0x7cb: 68, + 0x7cc: 68, + 0x7cd: 68, + 0x7ce: 68, + 0x7cf: 68, + 0x7d0: 68, + 0x7d1: 68, + 0x7d2: 68, + 0x7d3: 68, + 0x7d4: 68, + 0x7d5: 68, + 0x7d6: 68, + 0x7d7: 68, + 0x7d8: 68, + 0x7d9: 68, + 0x7da: 68, + 0x7db: 68, + 0x7dc: 68, + 0x7dd: 68, + 0x7de: 68, + 0x7df: 68, + 0x7e0: 68, + 0x7e1: 68, + 0x7e2: 68, + 0x7e3: 68, + 0x7e4: 68, + 0x7e5: 68, + 0x7e6: 68, + 0x7e7: 68, + 0x7e8: 68, + 0x7e9: 68, + 0x7ea: 68, + 0x7fa: 67, + 0x840: 82, + 0x841: 68, + 0x842: 68, + 0x843: 68, + 0x844: 68, + 0x845: 68, + 0x846: 82, + 0x847: 82, + 0x848: 68, + 0x849: 82, + 0x84a: 68, + 0x84b: 68, + 0x84c: 68, + 0x84d: 68, + 0x84e: 68, + 0x84f: 68, + 0x850: 68, + 0x851: 68, + 0x852: 68, + 0x853: 68, + 0x854: 82, + 0x855: 68, + 0x856: 82, + 0x857: 82, + 0x858: 82, + 0x860: 68, + 0x861: 85, + 0x862: 68, + 0x863: 68, + 0x864: 68, + 0x865: 68, + 0x866: 85, + 0x867: 82, + 0x868: 68, + 0x869: 82, + 0x86a: 82, + 0x870: 82, + 0x871: 82, + 0x872: 82, + 0x873: 82, + 0x874: 82, + 0x875: 82, + 0x876: 82, + 0x877: 82, + 0x878: 82, + 0x879: 82, + 0x87a: 82, + 0x87b: 82, + 0x87c: 82, + 0x87d: 82, + 0x87e: 82, + 0x87f: 82, + 0x880: 82, + 0x881: 82, + 0x882: 82, + 0x883: 67, + 0x884: 67, + 0x885: 67, + 0x886: 68, + 0x887: 85, + 0x888: 85, + 0x889: 68, + 0x88a: 68, + 0x88b: 68, + 0x88c: 68, + 0x88d: 68, + 0x88e: 82, + 0x890: 85, + 0x891: 85, + 0x8a0: 68, + 0x8a1: 68, + 0x8a2: 68, + 0x8a3: 68, + 0x8a4: 68, + 0x8a5: 68, + 0x8a6: 68, + 0x8a7: 68, + 0x8a8: 68, + 0x8a9: 68, + 0x8aa: 82, + 0x8ab: 82, + 0x8ac: 82, + 0x8ad: 85, + 0x8ae: 82, + 0x8af: 68, + 0x8b0: 68, + 0x8b1: 82, + 0x8b2: 82, + 0x8b3: 68, + 0x8b4: 68, + 0x8b5: 68, + 0x8b6: 68, + 0x8b7: 68, + 0x8b8: 68, + 0x8b9: 82, + 0x8ba: 68, + 0x8bb: 68, + 0x8bc: 68, + 0x8bd: 68, + 0x8be: 68, + 0x8bf: 68, + 0x8c0: 68, + 0x8c1: 68, + 0x8c2: 68, + 0x8c3: 68, + 0x8c4: 68, + 0x8c5: 68, + 0x8c6: 68, + 0x8c7: 68, + 0x8c8: 68, + 0x8e2: 85, + 0x1806: 85, + 0x1807: 68, + 0x180a: 67, + 0x180e: 85, + 0x1820: 68, + 0x1821: 68, + 0x1822: 68, + 0x1823: 68, + 0x1824: 68, + 0x1825: 68, + 0x1826: 68, + 0x1827: 68, + 0x1828: 68, + 0x1829: 68, + 0x182a: 68, + 0x182b: 68, + 0x182c: 68, + 0x182d: 68, + 0x182e: 68, + 0x182f: 68, + 0x1830: 68, + 0x1831: 68, + 0x1832: 68, + 0x1833: 68, + 0x1834: 68, + 0x1835: 68, + 0x1836: 68, + 0x1837: 68, + 0x1838: 68, + 0x1839: 68, + 0x183a: 68, + 0x183b: 68, + 0x183c: 68, + 0x183d: 68, + 0x183e: 68, + 0x183f: 68, + 0x1840: 68, + 0x1841: 68, + 0x1842: 68, + 0x1843: 68, + 0x1844: 68, + 0x1845: 68, + 0x1846: 68, + 0x1847: 68, + 0x1848: 68, + 0x1849: 68, + 0x184a: 68, + 0x184b: 68, + 0x184c: 68, + 0x184d: 68, + 0x184e: 68, + 0x184f: 68, + 0x1850: 68, + 0x1851: 68, + 0x1852: 68, + 0x1853: 68, + 0x1854: 68, + 0x1855: 68, + 0x1856: 68, + 0x1857: 68, + 0x1858: 68, + 0x1859: 68, + 0x185a: 68, + 0x185b: 68, + 0x185c: 68, + 0x185d: 68, + 0x185e: 68, + 0x185f: 68, + 0x1860: 68, + 0x1861: 68, + 0x1862: 68, + 0x1863: 68, + 0x1864: 68, + 0x1865: 68, + 0x1866: 68, + 0x1867: 68, + 0x1868: 68, + 0x1869: 68, + 0x186a: 68, + 0x186b: 68, + 0x186c: 68, + 0x186d: 68, + 0x186e: 68, + 0x186f: 68, + 0x1870: 68, + 0x1871: 68, + 0x1872: 68, + 0x1873: 68, + 0x1874: 68, + 0x1875: 68, + 0x1876: 68, + 0x1877: 68, + 0x1878: 68, + 0x1880: 85, + 0x1881: 85, + 0x1882: 85, + 0x1883: 85, + 0x1884: 85, + 0x1885: 84, + 0x1886: 84, + 0x1887: 68, + 0x1888: 68, + 0x1889: 68, + 0x188a: 68, + 0x188b: 68, + 0x188c: 68, + 0x188d: 68, + 0x188e: 68, + 0x188f: 68, + 0x1890: 68, + 0x1891: 68, + 0x1892: 68, + 0x1893: 68, + 0x1894: 68, + 0x1895: 68, + 0x1896: 68, + 0x1897: 68, + 0x1898: 68, + 0x1899: 68, + 0x189a: 68, + 0x189b: 68, + 0x189c: 68, + 0x189d: 68, + 0x189e: 68, + 0x189f: 68, + 0x18a0: 68, + 0x18a1: 68, + 0x18a2: 68, + 0x18a3: 68, + 0x18a4: 68, + 0x18a5: 68, + 0x18a6: 68, + 0x18a7: 68, + 0x18a8: 68, + 0x18aa: 68, + 0x200c: 85, + 0x200d: 67, + 0x202f: 85, + 0x2066: 85, + 0x2067: 85, + 0x2068: 85, + 0x2069: 85, + 0xa840: 68, + 0xa841: 68, + 0xa842: 68, + 0xa843: 68, + 0xa844: 68, + 0xa845: 68, + 0xa846: 68, + 0xa847: 68, + 0xa848: 68, + 0xa849: 68, + 0xa84a: 68, + 0xa84b: 68, + 0xa84c: 68, + 0xa84d: 68, + 0xa84e: 68, + 0xa84f: 68, + 0xa850: 68, + 0xa851: 68, + 0xa852: 68, + 0xa853: 68, + 0xa854: 68, + 0xa855: 68, + 0xa856: 68, + 0xa857: 68, + 0xa858: 68, + 0xa859: 68, + 0xa85a: 68, + 0xa85b: 68, + 0xa85c: 68, + 0xa85d: 68, + 0xa85e: 68, + 0xa85f: 68, + 0xa860: 68, + 0xa861: 68, + 0xa862: 68, + 0xa863: 68, + 0xa864: 68, + 0xa865: 68, + 0xa866: 68, + 0xa867: 68, + 0xa868: 68, + 0xa869: 68, + 0xa86a: 68, + 0xa86b: 68, + 0xa86c: 68, + 0xa86d: 68, + 0xa86e: 68, + 0xa86f: 68, + 0xa870: 68, + 0xa871: 68, + 0xa872: 76, + 0xa873: 85, + 0x10ac0: 68, + 0x10ac1: 68, + 0x10ac2: 68, + 0x10ac3: 68, + 0x10ac4: 68, + 0x10ac5: 82, + 0x10ac6: 85, + 0x10ac7: 82, + 0x10ac8: 85, + 0x10ac9: 82, + 0x10aca: 82, + 0x10acb: 85, + 0x10acc: 85, + 0x10acd: 76, + 0x10ace: 82, + 0x10acf: 82, + 0x10ad0: 82, + 0x10ad1: 82, + 0x10ad2: 82, + 0x10ad3: 68, + 0x10ad4: 68, + 0x10ad5: 68, + 0x10ad6: 68, + 0x10ad7: 76, + 0x10ad8: 68, + 0x10ad9: 68, + 0x10ada: 68, + 0x10adb: 68, + 0x10adc: 68, + 0x10add: 82, + 0x10ade: 68, + 0x10adf: 68, + 0x10ae0: 68, + 0x10ae1: 82, + 0x10ae2: 85, + 0x10ae3: 85, + 0x10ae4: 82, + 0x10aeb: 68, + 0x10aec: 68, + 0x10aed: 68, + 0x10aee: 68, + 0x10aef: 82, + 0x10b80: 68, + 0x10b81: 82, + 0x10b82: 68, + 0x10b83: 82, + 0x10b84: 82, + 0x10b85: 82, + 0x10b86: 68, + 0x10b87: 68, + 0x10b88: 68, + 0x10b89: 82, + 0x10b8a: 68, + 0x10b8b: 68, + 0x10b8c: 82, + 0x10b8d: 68, + 0x10b8e: 82, + 0x10b8f: 82, + 0x10b90: 68, + 0x10b91: 82, + 0x10ba9: 82, + 0x10baa: 82, + 0x10bab: 82, + 0x10bac: 82, + 0x10bad: 68, + 0x10bae: 68, + 0x10baf: 85, + 0x10d00: 76, + 0x10d01: 68, + 0x10d02: 68, + 0x10d03: 68, + 0x10d04: 68, + 0x10d05: 68, + 0x10d06: 68, + 0x10d07: 68, + 0x10d08: 68, + 0x10d09: 68, + 0x10d0a: 68, + 0x10d0b: 68, + 0x10d0c: 68, + 0x10d0d: 68, + 0x10d0e: 68, + 0x10d0f: 68, + 0x10d10: 68, + 0x10d11: 68, + 0x10d12: 68, + 0x10d13: 68, + 0x10d14: 68, + 0x10d15: 68, + 0x10d16: 68, + 0x10d17: 68, + 0x10d18: 68, + 0x10d19: 68, + 0x10d1a: 68, + 0x10d1b: 68, + 0x10d1c: 68, + 0x10d1d: 68, + 0x10d1e: 68, + 0x10d1f: 68, + 0x10d20: 68, + 0x10d21: 68, + 0x10d22: 82, + 0x10d23: 68, + 0x10f30: 68, + 0x10f31: 68, + 0x10f32: 68, + 0x10f33: 82, + 0x10f34: 68, + 0x10f35: 68, + 0x10f36: 68, + 0x10f37: 68, + 0x10f38: 68, + 0x10f39: 68, + 0x10f3a: 68, + 0x10f3b: 68, + 0x10f3c: 68, + 0x10f3d: 68, + 0x10f3e: 68, + 0x10f3f: 68, + 0x10f40: 68, + 0x10f41: 68, + 0x10f42: 68, + 0x10f43: 68, + 0x10f44: 68, + 0x10f45: 85, + 0x10f51: 68, + 0x10f52: 68, + 0x10f53: 68, + 0x10f54: 82, + 0x10f70: 68, + 0x10f71: 68, + 0x10f72: 68, + 0x10f73: 68, + 0x10f74: 82, + 0x10f75: 82, + 0x10f76: 68, + 0x10f77: 68, + 0x10f78: 68, + 0x10f79: 68, + 0x10f7a: 68, + 0x10f7b: 68, + 0x10f7c: 68, + 0x10f7d: 68, + 0x10f7e: 68, + 0x10f7f: 68, + 0x10f80: 68, + 0x10f81: 68, + 0x10fb0: 68, + 0x10fb1: 85, + 0x10fb2: 68, + 0x10fb3: 68, + 0x10fb4: 82, + 0x10fb5: 82, + 0x10fb6: 82, + 0x10fb7: 85, + 0x10fb8: 68, + 0x10fb9: 82, + 0x10fba: 82, + 0x10fbb: 68, + 0x10fbc: 68, + 0x10fbd: 82, + 0x10fbe: 68, + 0x10fbf: 68, + 0x10fc0: 85, + 0x10fc1: 68, + 0x10fc2: 82, + 0x10fc3: 82, + 0x10fc4: 68, + 0x10fc5: 85, + 0x10fc6: 85, + 0x10fc7: 85, + 0x10fc8: 85, + 0x10fc9: 82, + 0x10fca: 68, + 0x10fcb: 76, + 0x110bd: 85, + 0x110cd: 85, + 0x1e900: 68, + 0x1e901: 68, + 0x1e902: 68, + 0x1e903: 68, + 0x1e904: 68, + 0x1e905: 68, + 0x1e906: 68, + 0x1e907: 68, + 0x1e908: 68, + 0x1e909: 68, + 0x1e90a: 68, + 0x1e90b: 68, + 0x1e90c: 68, + 0x1e90d: 68, + 0x1e90e: 68, + 0x1e90f: 68, + 0x1e910: 68, + 0x1e911: 68, + 0x1e912: 68, + 0x1e913: 68, + 0x1e914: 68, + 0x1e915: 68, + 0x1e916: 68, + 0x1e917: 68, + 0x1e918: 68, + 0x1e919: 68, + 0x1e91a: 68, + 0x1e91b: 68, + 0x1e91c: 68, + 0x1e91d: 68, + 0x1e91e: 68, + 0x1e91f: 68, + 0x1e920: 68, + 0x1e921: 68, + 0x1e922: 68, + 0x1e923: 68, + 0x1e924: 68, + 0x1e925: 68, + 0x1e926: 68, + 0x1e927: 68, + 0x1e928: 68, + 0x1e929: 68, + 0x1e92a: 68, + 0x1e92b: 68, + 0x1e92c: 68, + 0x1e92d: 68, + 0x1e92e: 68, + 0x1e92f: 68, + 0x1e930: 68, + 0x1e931: 68, + 0x1e932: 68, + 0x1e933: 68, + 0x1e934: 68, + 0x1e935: 68, + 0x1e936: 68, + 0x1e937: 68, + 0x1e938: 68, + 0x1e939: 68, + 0x1e93a: 68, + 0x1e93b: 68, + 0x1e93c: 68, + 0x1e93d: 68, + 0x1e93e: 68, + 0x1e93f: 68, + 0x1e940: 68, + 0x1e941: 68, + 0x1e942: 68, + 0x1e943: 68, + 0x1e94b: 84, +} +codepoint_classes = { + 'PVALID': ( + 0x2d0000002e, + 0x300000003a, + 0x610000007b, + 0xdf000000f7, + 0xf800000100, + 0x10100000102, + 0x10300000104, + 0x10500000106, + 0x10700000108, + 0x1090000010a, + 0x10b0000010c, + 0x10d0000010e, + 0x10f00000110, + 0x11100000112, + 0x11300000114, + 0x11500000116, + 0x11700000118, + 0x1190000011a, + 0x11b0000011c, + 0x11d0000011e, + 0x11f00000120, + 0x12100000122, + 0x12300000124, + 0x12500000126, + 0x12700000128, + 0x1290000012a, + 0x12b0000012c, + 0x12d0000012e, + 0x12f00000130, + 0x13100000132, + 0x13500000136, + 0x13700000139, + 0x13a0000013b, + 0x13c0000013d, + 0x13e0000013f, + 0x14200000143, + 0x14400000145, + 0x14600000147, + 0x14800000149, + 0x14b0000014c, + 0x14d0000014e, + 0x14f00000150, + 0x15100000152, + 0x15300000154, + 0x15500000156, + 0x15700000158, + 0x1590000015a, + 0x15b0000015c, + 0x15d0000015e, + 0x15f00000160, + 0x16100000162, + 0x16300000164, + 0x16500000166, + 0x16700000168, + 0x1690000016a, + 0x16b0000016c, + 0x16d0000016e, + 0x16f00000170, + 0x17100000172, + 0x17300000174, + 0x17500000176, + 0x17700000178, + 0x17a0000017b, + 0x17c0000017d, + 0x17e0000017f, + 0x18000000181, + 0x18300000184, + 0x18500000186, + 0x18800000189, + 0x18c0000018e, + 0x19200000193, + 0x19500000196, + 0x1990000019c, + 0x19e0000019f, + 0x1a1000001a2, + 0x1a3000001a4, + 0x1a5000001a6, + 0x1a8000001a9, + 0x1aa000001ac, + 0x1ad000001ae, + 0x1b0000001b1, + 0x1b4000001b5, + 0x1b6000001b7, + 0x1b9000001bc, + 0x1bd000001c4, + 0x1ce000001cf, + 0x1d0000001d1, + 0x1d2000001d3, + 0x1d4000001d5, + 0x1d6000001d7, + 0x1d8000001d9, + 0x1da000001db, + 0x1dc000001de, + 0x1df000001e0, + 0x1e1000001e2, + 0x1e3000001e4, + 0x1e5000001e6, + 0x1e7000001e8, + 0x1e9000001ea, + 0x1eb000001ec, + 0x1ed000001ee, + 0x1ef000001f1, + 0x1f5000001f6, + 0x1f9000001fa, + 0x1fb000001fc, + 0x1fd000001fe, + 0x1ff00000200, + 0x20100000202, + 0x20300000204, + 0x20500000206, + 0x20700000208, + 0x2090000020a, + 0x20b0000020c, + 0x20d0000020e, + 0x20f00000210, + 0x21100000212, + 0x21300000214, + 0x21500000216, + 0x21700000218, + 0x2190000021a, + 0x21b0000021c, + 0x21d0000021e, + 0x21f00000220, + 0x22100000222, + 0x22300000224, + 0x22500000226, + 0x22700000228, + 0x2290000022a, + 0x22b0000022c, + 0x22d0000022e, + 0x22f00000230, + 0x23100000232, + 0x2330000023a, + 0x23c0000023d, + 0x23f00000241, + 0x24200000243, + 0x24700000248, + 0x2490000024a, + 0x24b0000024c, + 0x24d0000024e, + 0x24f000002b0, + 0x2b9000002c2, + 0x2c6000002d2, + 0x2ec000002ed, + 0x2ee000002ef, + 0x30000000340, + 0x34200000343, + 0x3460000034f, + 0x35000000370, + 0x37100000372, + 0x37300000374, + 0x37700000378, + 0x37b0000037e, + 0x39000000391, + 0x3ac000003cf, + 0x3d7000003d8, + 0x3d9000003da, + 0x3db000003dc, + 0x3dd000003de, + 0x3df000003e0, + 0x3e1000003e2, + 0x3e3000003e4, + 0x3e5000003e6, + 0x3e7000003e8, + 0x3e9000003ea, + 0x3eb000003ec, + 0x3ed000003ee, + 0x3ef000003f0, + 0x3f3000003f4, + 0x3f8000003f9, + 0x3fb000003fd, + 0x43000000460, + 0x46100000462, + 0x46300000464, + 0x46500000466, + 0x46700000468, + 0x4690000046a, + 0x46b0000046c, + 0x46d0000046e, + 0x46f00000470, + 0x47100000472, + 0x47300000474, + 0x47500000476, + 0x47700000478, + 0x4790000047a, + 0x47b0000047c, + 0x47d0000047e, + 0x47f00000480, + 0x48100000482, + 0x48300000488, + 0x48b0000048c, + 0x48d0000048e, + 0x48f00000490, + 0x49100000492, + 0x49300000494, + 0x49500000496, + 0x49700000498, + 0x4990000049a, + 0x49b0000049c, + 0x49d0000049e, + 0x49f000004a0, + 0x4a1000004a2, + 0x4a3000004a4, + 0x4a5000004a6, + 0x4a7000004a8, + 0x4a9000004aa, + 0x4ab000004ac, + 0x4ad000004ae, + 0x4af000004b0, + 0x4b1000004b2, + 0x4b3000004b4, + 0x4b5000004b6, + 0x4b7000004b8, + 0x4b9000004ba, + 0x4bb000004bc, + 0x4bd000004be, + 0x4bf000004c0, + 0x4c2000004c3, + 0x4c4000004c5, + 0x4c6000004c7, + 0x4c8000004c9, + 0x4ca000004cb, + 0x4cc000004cd, + 0x4ce000004d0, + 0x4d1000004d2, + 0x4d3000004d4, + 0x4d5000004d6, + 0x4d7000004d8, + 0x4d9000004da, + 0x4db000004dc, + 0x4dd000004de, + 0x4df000004e0, + 0x4e1000004e2, + 0x4e3000004e4, + 0x4e5000004e6, + 0x4e7000004e8, + 0x4e9000004ea, + 0x4eb000004ec, + 0x4ed000004ee, + 0x4ef000004f0, + 0x4f1000004f2, + 0x4f3000004f4, + 0x4f5000004f6, + 0x4f7000004f8, + 0x4f9000004fa, + 0x4fb000004fc, + 0x4fd000004fe, + 0x4ff00000500, + 0x50100000502, + 0x50300000504, + 0x50500000506, + 0x50700000508, + 0x5090000050a, + 0x50b0000050c, + 0x50d0000050e, + 0x50f00000510, + 0x51100000512, + 0x51300000514, + 0x51500000516, + 0x51700000518, + 0x5190000051a, + 0x51b0000051c, + 0x51d0000051e, + 0x51f00000520, + 0x52100000522, + 0x52300000524, + 0x52500000526, + 0x52700000528, + 0x5290000052a, + 0x52b0000052c, + 0x52d0000052e, + 0x52f00000530, + 0x5590000055a, + 0x56000000587, + 0x58800000589, + 0x591000005be, + 0x5bf000005c0, + 0x5c1000005c3, + 0x5c4000005c6, + 0x5c7000005c8, + 0x5d0000005eb, + 0x5ef000005f3, + 0x6100000061b, + 0x62000000640, + 0x64100000660, + 0x66e00000675, + 0x679000006d4, + 0x6d5000006dd, + 0x6df000006e9, + 0x6ea000006f0, + 0x6fa00000700, + 0x7100000074b, + 0x74d000007b2, + 0x7c0000007f6, + 0x7fd000007fe, + 0x8000000082e, + 0x8400000085c, + 0x8600000086b, + 0x87000000888, + 0x8890000088f, + 0x898000008e2, + 0x8e300000958, + 0x96000000964, + 0x96600000970, + 0x97100000984, + 0x9850000098d, + 0x98f00000991, + 0x993000009a9, + 0x9aa000009b1, + 0x9b2000009b3, + 0x9b6000009ba, + 0x9bc000009c5, + 0x9c7000009c9, + 0x9cb000009cf, + 0x9d7000009d8, + 0x9e0000009e4, + 0x9e6000009f2, + 0x9fc000009fd, + 0x9fe000009ff, + 0xa0100000a04, + 0xa0500000a0b, + 0xa0f00000a11, + 0xa1300000a29, + 0xa2a00000a31, + 0xa3200000a33, + 0xa3500000a36, + 0xa3800000a3a, + 0xa3c00000a3d, + 0xa3e00000a43, + 0xa4700000a49, + 0xa4b00000a4e, + 0xa5100000a52, + 0xa5c00000a5d, + 0xa6600000a76, + 0xa8100000a84, + 0xa8500000a8e, + 0xa8f00000a92, + 0xa9300000aa9, + 0xaaa00000ab1, + 0xab200000ab4, + 0xab500000aba, + 0xabc00000ac6, + 0xac700000aca, + 0xacb00000ace, + 0xad000000ad1, + 0xae000000ae4, + 0xae600000af0, + 0xaf900000b00, + 0xb0100000b04, + 0xb0500000b0d, + 0xb0f00000b11, + 0xb1300000b29, + 0xb2a00000b31, + 0xb3200000b34, + 0xb3500000b3a, + 0xb3c00000b45, + 0xb4700000b49, + 0xb4b00000b4e, + 0xb5500000b58, + 0xb5f00000b64, + 0xb6600000b70, + 0xb7100000b72, + 0xb8200000b84, + 0xb8500000b8b, + 0xb8e00000b91, + 0xb9200000b96, + 0xb9900000b9b, + 0xb9c00000b9d, + 0xb9e00000ba0, + 0xba300000ba5, + 0xba800000bab, + 0xbae00000bba, + 0xbbe00000bc3, + 0xbc600000bc9, + 0xbca00000bce, + 0xbd000000bd1, + 0xbd700000bd8, + 0xbe600000bf0, + 0xc0000000c0d, + 0xc0e00000c11, + 0xc1200000c29, + 0xc2a00000c3a, + 0xc3c00000c45, + 0xc4600000c49, + 0xc4a00000c4e, + 0xc5500000c57, + 0xc5800000c5b, + 0xc5d00000c5e, + 0xc6000000c64, + 0xc6600000c70, + 0xc8000000c84, + 0xc8500000c8d, + 0xc8e00000c91, + 0xc9200000ca9, + 0xcaa00000cb4, + 0xcb500000cba, + 0xcbc00000cc5, + 0xcc600000cc9, + 0xcca00000cce, + 0xcd500000cd7, + 0xcdd00000cdf, + 0xce000000ce4, + 0xce600000cf0, + 0xcf100000cf4, + 0xd0000000d0d, + 0xd0e00000d11, + 0xd1200000d45, + 0xd4600000d49, + 0xd4a00000d4f, + 0xd5400000d58, + 0xd5f00000d64, + 0xd6600000d70, + 0xd7a00000d80, + 0xd8100000d84, + 0xd8500000d97, + 0xd9a00000db2, + 0xdb300000dbc, + 0xdbd00000dbe, + 0xdc000000dc7, + 0xdca00000dcb, + 0xdcf00000dd5, + 0xdd600000dd7, + 0xdd800000de0, + 0xde600000df0, + 0xdf200000df4, + 0xe0100000e33, + 0xe3400000e3b, + 0xe4000000e4f, + 0xe5000000e5a, + 0xe8100000e83, + 0xe8400000e85, + 0xe8600000e8b, + 0xe8c00000ea4, + 0xea500000ea6, + 0xea700000eb3, + 0xeb400000ebe, + 0xec000000ec5, + 0xec600000ec7, + 0xec800000ecf, + 0xed000000eda, + 0xede00000ee0, + 0xf0000000f01, + 0xf0b00000f0c, + 0xf1800000f1a, + 0xf2000000f2a, + 0xf3500000f36, + 0xf3700000f38, + 0xf3900000f3a, + 0xf3e00000f43, + 0xf4400000f48, + 0xf4900000f4d, + 0xf4e00000f52, + 0xf5300000f57, + 0xf5800000f5c, + 0xf5d00000f69, + 0xf6a00000f6d, + 0xf7100000f73, + 0xf7400000f75, + 0xf7a00000f81, + 0xf8200000f85, + 0xf8600000f93, + 0xf9400000f98, + 0xf9900000f9d, + 0xf9e00000fa2, + 0xfa300000fa7, + 0xfa800000fac, + 0xfad00000fb9, + 0xfba00000fbd, + 0xfc600000fc7, + 0x10000000104a, + 0x10500000109e, + 0x10d0000010fb, + 0x10fd00001100, + 0x120000001249, + 0x124a0000124e, + 0x125000001257, + 0x125800001259, + 0x125a0000125e, + 0x126000001289, + 0x128a0000128e, + 0x1290000012b1, + 0x12b2000012b6, + 0x12b8000012bf, + 0x12c0000012c1, + 0x12c2000012c6, + 0x12c8000012d7, + 0x12d800001311, + 0x131200001316, + 0x13180000135b, + 0x135d00001360, + 0x138000001390, + 0x13a0000013f6, + 0x14010000166d, + 0x166f00001680, + 0x16810000169b, + 0x16a0000016eb, + 0x16f1000016f9, + 0x170000001716, + 0x171f00001735, + 0x174000001754, + 0x17600000176d, + 0x176e00001771, + 0x177200001774, + 0x1780000017b4, + 0x17b6000017d4, + 0x17d7000017d8, + 0x17dc000017de, + 0x17e0000017ea, + 0x18100000181a, + 0x182000001879, + 0x1880000018ab, + 0x18b0000018f6, + 0x19000000191f, + 0x19200000192c, + 0x19300000193c, + 0x19460000196e, + 0x197000001975, + 0x1980000019ac, + 0x19b0000019ca, + 0x19d0000019da, + 0x1a0000001a1c, + 0x1a2000001a5f, + 0x1a6000001a7d, + 0x1a7f00001a8a, + 0x1a9000001a9a, + 0x1aa700001aa8, + 0x1ab000001abe, + 0x1abf00001acf, + 0x1b0000001b4d, + 0x1b5000001b5a, + 0x1b6b00001b74, + 0x1b8000001bf4, + 0x1c0000001c38, + 0x1c4000001c4a, + 0x1c4d00001c7e, + 0x1cd000001cd3, + 0x1cd400001cfb, + 0x1d0000001d2c, + 0x1d2f00001d30, + 0x1d3b00001d3c, + 0x1d4e00001d4f, + 0x1d6b00001d78, + 0x1d7900001d9b, + 0x1dc000001e00, + 0x1e0100001e02, + 0x1e0300001e04, + 0x1e0500001e06, + 0x1e0700001e08, + 0x1e0900001e0a, + 0x1e0b00001e0c, + 0x1e0d00001e0e, + 0x1e0f00001e10, + 0x1e1100001e12, + 0x1e1300001e14, + 0x1e1500001e16, + 0x1e1700001e18, + 0x1e1900001e1a, + 0x1e1b00001e1c, + 0x1e1d00001e1e, + 0x1e1f00001e20, + 0x1e2100001e22, + 0x1e2300001e24, + 0x1e2500001e26, + 0x1e2700001e28, + 0x1e2900001e2a, + 0x1e2b00001e2c, + 0x1e2d00001e2e, + 0x1e2f00001e30, + 0x1e3100001e32, + 0x1e3300001e34, + 0x1e3500001e36, + 0x1e3700001e38, + 0x1e3900001e3a, + 0x1e3b00001e3c, + 0x1e3d00001e3e, + 0x1e3f00001e40, + 0x1e4100001e42, + 0x1e4300001e44, + 0x1e4500001e46, + 0x1e4700001e48, + 0x1e4900001e4a, + 0x1e4b00001e4c, + 0x1e4d00001e4e, + 0x1e4f00001e50, + 0x1e5100001e52, + 0x1e5300001e54, + 0x1e5500001e56, + 0x1e5700001e58, + 0x1e5900001e5a, + 0x1e5b00001e5c, + 0x1e5d00001e5e, + 0x1e5f00001e60, + 0x1e6100001e62, + 0x1e6300001e64, + 0x1e6500001e66, + 0x1e6700001e68, + 0x1e6900001e6a, + 0x1e6b00001e6c, + 0x1e6d00001e6e, + 0x1e6f00001e70, + 0x1e7100001e72, + 0x1e7300001e74, + 0x1e7500001e76, + 0x1e7700001e78, + 0x1e7900001e7a, + 0x1e7b00001e7c, + 0x1e7d00001e7e, + 0x1e7f00001e80, + 0x1e8100001e82, + 0x1e8300001e84, + 0x1e8500001e86, + 0x1e8700001e88, + 0x1e8900001e8a, + 0x1e8b00001e8c, + 0x1e8d00001e8e, + 0x1e8f00001e90, + 0x1e9100001e92, + 0x1e9300001e94, + 0x1e9500001e9a, + 0x1e9c00001e9e, + 0x1e9f00001ea0, + 0x1ea100001ea2, + 0x1ea300001ea4, + 0x1ea500001ea6, + 0x1ea700001ea8, + 0x1ea900001eaa, + 0x1eab00001eac, + 0x1ead00001eae, + 0x1eaf00001eb0, + 0x1eb100001eb2, + 0x1eb300001eb4, + 0x1eb500001eb6, + 0x1eb700001eb8, + 0x1eb900001eba, + 0x1ebb00001ebc, + 0x1ebd00001ebe, + 0x1ebf00001ec0, + 0x1ec100001ec2, + 0x1ec300001ec4, + 0x1ec500001ec6, + 0x1ec700001ec8, + 0x1ec900001eca, + 0x1ecb00001ecc, + 0x1ecd00001ece, + 0x1ecf00001ed0, + 0x1ed100001ed2, + 0x1ed300001ed4, + 0x1ed500001ed6, + 0x1ed700001ed8, + 0x1ed900001eda, + 0x1edb00001edc, + 0x1edd00001ede, + 0x1edf00001ee0, + 0x1ee100001ee2, + 0x1ee300001ee4, + 0x1ee500001ee6, + 0x1ee700001ee8, + 0x1ee900001eea, + 0x1eeb00001eec, + 0x1eed00001eee, + 0x1eef00001ef0, + 0x1ef100001ef2, + 0x1ef300001ef4, + 0x1ef500001ef6, + 0x1ef700001ef8, + 0x1ef900001efa, + 0x1efb00001efc, + 0x1efd00001efe, + 0x1eff00001f08, + 0x1f1000001f16, + 0x1f2000001f28, + 0x1f3000001f38, + 0x1f4000001f46, + 0x1f5000001f58, + 0x1f6000001f68, + 0x1f7000001f71, + 0x1f7200001f73, + 0x1f7400001f75, + 0x1f7600001f77, + 0x1f7800001f79, + 0x1f7a00001f7b, + 0x1f7c00001f7d, + 0x1fb000001fb2, + 0x1fb600001fb7, + 0x1fc600001fc7, + 0x1fd000001fd3, + 0x1fd600001fd8, + 0x1fe000001fe3, + 0x1fe400001fe8, + 0x1ff600001ff7, + 0x214e0000214f, + 0x218400002185, + 0x2c3000002c60, + 0x2c6100002c62, + 0x2c6500002c67, + 0x2c6800002c69, + 0x2c6a00002c6b, + 0x2c6c00002c6d, + 0x2c7100002c72, + 0x2c7300002c75, + 0x2c7600002c7c, + 0x2c8100002c82, + 0x2c8300002c84, + 0x2c8500002c86, + 0x2c8700002c88, + 0x2c8900002c8a, + 0x2c8b00002c8c, + 0x2c8d00002c8e, + 0x2c8f00002c90, + 0x2c9100002c92, + 0x2c9300002c94, + 0x2c9500002c96, + 0x2c9700002c98, + 0x2c9900002c9a, + 0x2c9b00002c9c, + 0x2c9d00002c9e, + 0x2c9f00002ca0, + 0x2ca100002ca2, + 0x2ca300002ca4, + 0x2ca500002ca6, + 0x2ca700002ca8, + 0x2ca900002caa, + 0x2cab00002cac, + 0x2cad00002cae, + 0x2caf00002cb0, + 0x2cb100002cb2, + 0x2cb300002cb4, + 0x2cb500002cb6, + 0x2cb700002cb8, + 0x2cb900002cba, + 0x2cbb00002cbc, + 0x2cbd00002cbe, + 0x2cbf00002cc0, + 0x2cc100002cc2, + 0x2cc300002cc4, + 0x2cc500002cc6, + 0x2cc700002cc8, + 0x2cc900002cca, + 0x2ccb00002ccc, + 0x2ccd00002cce, + 0x2ccf00002cd0, + 0x2cd100002cd2, + 0x2cd300002cd4, + 0x2cd500002cd6, + 0x2cd700002cd8, + 0x2cd900002cda, + 0x2cdb00002cdc, + 0x2cdd00002cde, + 0x2cdf00002ce0, + 0x2ce100002ce2, + 0x2ce300002ce5, + 0x2cec00002ced, + 0x2cee00002cf2, + 0x2cf300002cf4, + 0x2d0000002d26, + 0x2d2700002d28, + 0x2d2d00002d2e, + 0x2d3000002d68, + 0x2d7f00002d97, + 0x2da000002da7, + 0x2da800002daf, + 0x2db000002db7, + 0x2db800002dbf, + 0x2dc000002dc7, + 0x2dc800002dcf, + 0x2dd000002dd7, + 0x2dd800002ddf, + 0x2de000002e00, + 0x2e2f00002e30, + 0x300500003008, + 0x302a0000302e, + 0x303c0000303d, + 0x304100003097, + 0x30990000309b, + 0x309d0000309f, + 0x30a1000030fb, + 0x30fc000030ff, + 0x310500003130, + 0x31a0000031c0, + 0x31f000003200, + 0x340000004dc0, + 0x4e000000a48d, + 0xa4d00000a4fe, + 0xa5000000a60d, + 0xa6100000a62c, + 0xa6410000a642, + 0xa6430000a644, + 0xa6450000a646, + 0xa6470000a648, + 0xa6490000a64a, + 0xa64b0000a64c, + 0xa64d0000a64e, + 0xa64f0000a650, + 0xa6510000a652, + 0xa6530000a654, + 0xa6550000a656, + 0xa6570000a658, + 0xa6590000a65a, + 0xa65b0000a65c, + 0xa65d0000a65e, + 0xa65f0000a660, + 0xa6610000a662, + 0xa6630000a664, + 0xa6650000a666, + 0xa6670000a668, + 0xa6690000a66a, + 0xa66b0000a66c, + 0xa66d0000a670, + 0xa6740000a67e, + 0xa67f0000a680, + 0xa6810000a682, + 0xa6830000a684, + 0xa6850000a686, + 0xa6870000a688, + 0xa6890000a68a, + 0xa68b0000a68c, + 0xa68d0000a68e, + 0xa68f0000a690, + 0xa6910000a692, + 0xa6930000a694, + 0xa6950000a696, + 0xa6970000a698, + 0xa6990000a69a, + 0xa69b0000a69c, + 0xa69e0000a6e6, + 0xa6f00000a6f2, + 0xa7170000a720, + 0xa7230000a724, + 0xa7250000a726, + 0xa7270000a728, + 0xa7290000a72a, + 0xa72b0000a72c, + 0xa72d0000a72e, + 0xa72f0000a732, + 0xa7330000a734, + 0xa7350000a736, + 0xa7370000a738, + 0xa7390000a73a, + 0xa73b0000a73c, + 0xa73d0000a73e, + 0xa73f0000a740, + 0xa7410000a742, + 0xa7430000a744, + 0xa7450000a746, + 0xa7470000a748, + 0xa7490000a74a, + 0xa74b0000a74c, + 0xa74d0000a74e, + 0xa74f0000a750, + 0xa7510000a752, + 0xa7530000a754, + 0xa7550000a756, + 0xa7570000a758, + 0xa7590000a75a, + 0xa75b0000a75c, + 0xa75d0000a75e, + 0xa75f0000a760, + 0xa7610000a762, + 0xa7630000a764, + 0xa7650000a766, + 0xa7670000a768, + 0xa7690000a76a, + 0xa76b0000a76c, + 0xa76d0000a76e, + 0xa76f0000a770, + 0xa7710000a779, + 0xa77a0000a77b, + 0xa77c0000a77d, + 0xa77f0000a780, + 0xa7810000a782, + 0xa7830000a784, + 0xa7850000a786, + 0xa7870000a789, + 0xa78c0000a78d, + 0xa78e0000a790, + 0xa7910000a792, + 0xa7930000a796, + 0xa7970000a798, + 0xa7990000a79a, + 0xa79b0000a79c, + 0xa79d0000a79e, + 0xa79f0000a7a0, + 0xa7a10000a7a2, + 0xa7a30000a7a4, + 0xa7a50000a7a6, + 0xa7a70000a7a8, + 0xa7a90000a7aa, + 0xa7af0000a7b0, + 0xa7b50000a7b6, + 0xa7b70000a7b8, + 0xa7b90000a7ba, + 0xa7bb0000a7bc, + 0xa7bd0000a7be, + 0xa7bf0000a7c0, + 0xa7c10000a7c2, + 0xa7c30000a7c4, + 0xa7c80000a7c9, + 0xa7ca0000a7cb, + 0xa7d10000a7d2, + 0xa7d30000a7d4, + 0xa7d50000a7d6, + 0xa7d70000a7d8, + 0xa7d90000a7da, + 0xa7f60000a7f8, + 0xa7fa0000a828, + 0xa82c0000a82d, + 0xa8400000a874, + 0xa8800000a8c6, + 0xa8d00000a8da, + 0xa8e00000a8f8, + 0xa8fb0000a8fc, + 0xa8fd0000a92e, + 0xa9300000a954, + 0xa9800000a9c1, + 0xa9cf0000a9da, + 0xa9e00000a9ff, + 0xaa000000aa37, + 0xaa400000aa4e, + 0xaa500000aa5a, + 0xaa600000aa77, + 0xaa7a0000aac3, + 0xaadb0000aade, + 0xaae00000aaf0, + 0xaaf20000aaf7, + 0xab010000ab07, + 0xab090000ab0f, + 0xab110000ab17, + 0xab200000ab27, + 0xab280000ab2f, + 0xab300000ab5b, + 0xab600000ab69, + 0xabc00000abeb, + 0xabec0000abee, + 0xabf00000abfa, + 0xac000000d7a4, + 0xfa0e0000fa10, + 0xfa110000fa12, + 0xfa130000fa15, + 0xfa1f0000fa20, + 0xfa210000fa22, + 0xfa230000fa25, + 0xfa270000fa2a, + 0xfb1e0000fb1f, + 0xfe200000fe30, + 0xfe730000fe74, + 0x100000001000c, + 0x1000d00010027, + 0x100280001003b, + 0x1003c0001003e, + 0x1003f0001004e, + 0x100500001005e, + 0x10080000100fb, + 0x101fd000101fe, + 0x102800001029d, + 0x102a0000102d1, + 0x102e0000102e1, + 0x1030000010320, + 0x1032d00010341, + 0x103420001034a, + 0x103500001037b, + 0x103800001039e, + 0x103a0000103c4, + 0x103c8000103d0, + 0x104280001049e, + 0x104a0000104aa, + 0x104d8000104fc, + 0x1050000010528, + 0x1053000010564, + 0x10597000105a2, + 0x105a3000105b2, + 0x105b3000105ba, + 0x105bb000105bd, + 0x1060000010737, + 0x1074000010756, + 0x1076000010768, + 0x1078000010781, + 0x1080000010806, + 0x1080800010809, + 0x1080a00010836, + 0x1083700010839, + 0x1083c0001083d, + 0x1083f00010856, + 0x1086000010877, + 0x108800001089f, + 0x108e0000108f3, + 0x108f4000108f6, + 0x1090000010916, + 0x109200001093a, + 0x10980000109b8, + 0x109be000109c0, + 0x10a0000010a04, + 0x10a0500010a07, + 0x10a0c00010a14, + 0x10a1500010a18, + 0x10a1900010a36, + 0x10a3800010a3b, + 0x10a3f00010a40, + 0x10a6000010a7d, + 0x10a8000010a9d, + 0x10ac000010ac8, + 0x10ac900010ae7, + 0x10b0000010b36, + 0x10b4000010b56, + 0x10b6000010b73, + 0x10b8000010b92, + 0x10c0000010c49, + 0x10cc000010cf3, + 0x10d0000010d28, + 0x10d3000010d3a, + 0x10e8000010eaa, + 0x10eab00010ead, + 0x10eb000010eb2, + 0x10efd00010f1d, + 0x10f2700010f28, + 0x10f3000010f51, + 0x10f7000010f86, + 0x10fb000010fc5, + 0x10fe000010ff7, + 0x1100000011047, + 0x1106600011076, + 0x1107f000110bb, + 0x110c2000110c3, + 0x110d0000110e9, + 0x110f0000110fa, + 0x1110000011135, + 0x1113600011140, + 0x1114400011148, + 0x1115000011174, + 0x1117600011177, + 0x11180000111c5, + 0x111c9000111cd, + 0x111ce000111db, + 0x111dc000111dd, + 0x1120000011212, + 0x1121300011238, + 0x1123e00011242, + 0x1128000011287, + 0x1128800011289, + 0x1128a0001128e, + 0x1128f0001129e, + 0x1129f000112a9, + 0x112b0000112eb, + 0x112f0000112fa, + 0x1130000011304, + 0x113050001130d, + 0x1130f00011311, + 0x1131300011329, + 0x1132a00011331, + 0x1133200011334, + 0x113350001133a, + 0x1133b00011345, + 0x1134700011349, + 0x1134b0001134e, + 0x1135000011351, + 0x1135700011358, + 0x1135d00011364, + 0x113660001136d, + 0x1137000011375, + 0x114000001144b, + 0x114500001145a, + 0x1145e00011462, + 0x11480000114c6, + 0x114c7000114c8, + 0x114d0000114da, + 0x11580000115b6, + 0x115b8000115c1, + 0x115d8000115de, + 0x1160000011641, + 0x1164400011645, + 0x116500001165a, + 0x11680000116b9, + 0x116c0000116ca, + 0x117000001171b, + 0x1171d0001172c, + 0x117300001173a, + 0x1174000011747, + 0x118000001183b, + 0x118c0000118ea, + 0x118ff00011907, + 0x119090001190a, + 0x1190c00011914, + 0x1191500011917, + 0x1191800011936, + 0x1193700011939, + 0x1193b00011944, + 0x119500001195a, + 0x119a0000119a8, + 0x119aa000119d8, + 0x119da000119e2, + 0x119e3000119e5, + 0x11a0000011a3f, + 0x11a4700011a48, + 0x11a5000011a9a, + 0x11a9d00011a9e, + 0x11ab000011af9, + 0x11c0000011c09, + 0x11c0a00011c37, + 0x11c3800011c41, + 0x11c5000011c5a, + 0x11c7200011c90, + 0x11c9200011ca8, + 0x11ca900011cb7, + 0x11d0000011d07, + 0x11d0800011d0a, + 0x11d0b00011d37, + 0x11d3a00011d3b, + 0x11d3c00011d3e, + 0x11d3f00011d48, + 0x11d5000011d5a, + 0x11d6000011d66, + 0x11d6700011d69, + 0x11d6a00011d8f, + 0x11d9000011d92, + 0x11d9300011d99, + 0x11da000011daa, + 0x11ee000011ef7, + 0x11f0000011f11, + 0x11f1200011f3b, + 0x11f3e00011f43, + 0x11f5000011f5a, + 0x11fb000011fb1, + 0x120000001239a, + 0x1248000012544, + 0x12f9000012ff1, + 0x1300000013430, + 0x1344000013456, + 0x1440000014647, + 0x1680000016a39, + 0x16a4000016a5f, + 0x16a6000016a6a, + 0x16a7000016abf, + 0x16ac000016aca, + 0x16ad000016aee, + 0x16af000016af5, + 0x16b0000016b37, + 0x16b4000016b44, + 0x16b5000016b5a, + 0x16b6300016b78, + 0x16b7d00016b90, + 0x16e6000016e80, + 0x16f0000016f4b, + 0x16f4f00016f88, + 0x16f8f00016fa0, + 0x16fe000016fe2, + 0x16fe300016fe5, + 0x16ff000016ff2, + 0x17000000187f8, + 0x1880000018cd6, + 0x18d0000018d09, + 0x1aff00001aff4, + 0x1aff50001affc, + 0x1affd0001afff, + 0x1b0000001b123, + 0x1b1320001b133, + 0x1b1500001b153, + 0x1b1550001b156, + 0x1b1640001b168, + 0x1b1700001b2fc, + 0x1bc000001bc6b, + 0x1bc700001bc7d, + 0x1bc800001bc89, + 0x1bc900001bc9a, + 0x1bc9d0001bc9f, + 0x1cf000001cf2e, + 0x1cf300001cf47, + 0x1da000001da37, + 0x1da3b0001da6d, + 0x1da750001da76, + 0x1da840001da85, + 0x1da9b0001daa0, + 0x1daa10001dab0, + 0x1df000001df1f, + 0x1df250001df2b, + 0x1e0000001e007, + 0x1e0080001e019, + 0x1e01b0001e022, + 0x1e0230001e025, + 0x1e0260001e02b, + 0x1e0300001e06e, + 0x1e08f0001e090, + 0x1e1000001e12d, + 0x1e1300001e13e, + 0x1e1400001e14a, + 0x1e14e0001e14f, + 0x1e2900001e2af, + 0x1e2c00001e2fa, + 0x1e4d00001e4fa, + 0x1e7e00001e7e7, + 0x1e7e80001e7ec, + 0x1e7ed0001e7ef, + 0x1e7f00001e7ff, + 0x1e8000001e8c5, + 0x1e8d00001e8d7, + 0x1e9220001e94c, + 0x1e9500001e95a, + 0x200000002a6e0, + 0x2a7000002b73a, + 0x2b7400002b81e, + 0x2b8200002cea2, + 0x2ceb00002ebe1, + 0x300000003134b, + 0x31350000323b0, + ), + 'CONTEXTJ': ( + 0x200c0000200e, + ), + 'CONTEXTO': ( + 0xb7000000b8, + 0x37500000376, + 0x5f3000005f5, + 0x6600000066a, + 0x6f0000006fa, + 0x30fb000030fc, + ), +} diff --git a/idna/intranges.py b/idna/intranges.py new file mode 100644 index 0000000..6a43b04 --- /dev/null +++ b/idna/intranges.py @@ -0,0 +1,54 @@ +""" +Given a list of integers, made up of (hopefully) a small number of long runs +of consecutive integers, compute a representation of the form +((start1, end1), (start2, end2) ...). Then answer the question "was x present +in the original list?" in time O(log(# runs)). +""" + +import bisect +from typing import List, Tuple + +def intranges_from_list(list_: List[int]) -> Tuple[int, ...]: + """Represent a list of integers as a sequence of ranges: + ((start_0, end_0), (start_1, end_1), ...), such that the original + integers are exactly those x such that start_i <= x < end_i for some i. + + Ranges are encoded as single integers (start << 32 | end), not as tuples. + """ + + sorted_list = sorted(list_) + ranges = [] + last_write = -1 + for i in range(len(sorted_list)): + if i+1 < len(sorted_list): + if sorted_list[i] == sorted_list[i+1]-1: + continue + current_range = sorted_list[last_write+1:i+1] + ranges.append(_encode_range(current_range[0], current_range[-1] + 1)) + last_write = i + + return tuple(ranges) + +def _encode_range(start: int, end: int) -> int: + return (start << 32) | end + +def _decode_range(r: int) -> Tuple[int, int]: + return (r >> 32), (r & ((1 << 32) - 1)) + + +def intranges_contain(int_: int, ranges: Tuple[int, ...]) -> bool: + """Determine if `int_` falls into one of the ranges in `ranges`.""" + tuple_ = _encode_range(int_, 0) + pos = bisect.bisect_left(ranges, tuple_) + # we could be immediately ahead of a tuple (start, end) + # with start < int_ <= end + if pos > 0: + left, right = _decode_range(ranges[pos-1]) + if left <= int_ < right: + return True + # or we could be immediately behind a tuple (int_, end) + if pos < len(ranges): + left, _ = _decode_range(ranges[pos]) + if left == int_: + return True + return False diff --git a/idna/package_data.py b/idna/package_data.py new file mode 100644 index 0000000..8501893 --- /dev/null +++ b/idna/package_data.py @@ -0,0 +1,2 @@ +__version__ = '3.4' + diff --git a/idna/py.typed b/idna/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/idna/uts46data.py b/idna/uts46data.py new file mode 100644 index 0000000..186796c --- /dev/null +++ b/idna/uts46data.py @@ -0,0 +1,8600 @@ +# This file is automatically generated by tools/idna-data +# vim: set fileencoding=utf-8 : + +from typing import List, Tuple, Union + + +"""IDNA Mapping Table from UTS46.""" + + +__version__ = '15.0.0' +def _seg_0() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x0, '3'), + (0x1, '3'), + (0x2, '3'), + (0x3, '3'), + (0x4, '3'), + (0x5, '3'), + (0x6, '3'), + (0x7, '3'), + (0x8, '3'), + (0x9, '3'), + (0xA, '3'), + (0xB, '3'), + (0xC, '3'), + (0xD, '3'), + (0xE, '3'), + (0xF, '3'), + (0x10, '3'), + (0x11, '3'), + (0x12, '3'), + (0x13, '3'), + (0x14, '3'), + (0x15, '3'), + (0x16, '3'), + (0x17, '3'), + (0x18, '3'), + (0x19, '3'), + (0x1A, '3'), + (0x1B, '3'), + (0x1C, '3'), + (0x1D, '3'), + (0x1E, '3'), + (0x1F, '3'), + (0x20, '3'), + (0x21, '3'), + (0x22, '3'), + (0x23, '3'), + (0x24, '3'), + (0x25, '3'), + (0x26, '3'), + (0x27, '3'), + (0x28, '3'), + (0x29, '3'), + (0x2A, '3'), + (0x2B, '3'), + (0x2C, '3'), + (0x2D, 'V'), + (0x2E, 'V'), + (0x2F, '3'), + (0x30, 'V'), + (0x31, 'V'), + (0x32, 'V'), + (0x33, 'V'), + (0x34, 'V'), + (0x35, 'V'), + (0x36, 'V'), + (0x37, 'V'), + (0x38, 'V'), + (0x39, 'V'), + (0x3A, '3'), + (0x3B, '3'), + (0x3C, '3'), + (0x3D, '3'), + (0x3E, '3'), + (0x3F, '3'), + (0x40, '3'), + (0x41, 'M', 'a'), + (0x42, 'M', 'b'), + (0x43, 'M', 'c'), + (0x44, 'M', 'd'), + (0x45, 'M', 'e'), + (0x46, 'M', 'f'), + (0x47, 'M', 'g'), + (0x48, 'M', 'h'), + (0x49, 'M', 'i'), + (0x4A, 'M', 'j'), + (0x4B, 'M', 'k'), + (0x4C, 'M', 'l'), + (0x4D, 'M', 'm'), + (0x4E, 'M', 'n'), + (0x4F, 'M', 'o'), + (0x50, 'M', 'p'), + (0x51, 'M', 'q'), + (0x52, 'M', 'r'), + (0x53, 'M', 's'), + (0x54, 'M', 't'), + (0x55, 'M', 'u'), + (0x56, 'M', 'v'), + (0x57, 'M', 'w'), + (0x58, 'M', 'x'), + (0x59, 'M', 'y'), + (0x5A, 'M', 'z'), + (0x5B, '3'), + (0x5C, '3'), + (0x5D, '3'), + (0x5E, '3'), + (0x5F, '3'), + (0x60, '3'), + (0x61, 'V'), + (0x62, 'V'), + (0x63, 'V'), + ] + +def _seg_1() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x64, 'V'), + (0x65, 'V'), + (0x66, 'V'), + (0x67, 'V'), + (0x68, 'V'), + (0x69, 'V'), + (0x6A, 'V'), + (0x6B, 'V'), + (0x6C, 'V'), + (0x6D, 'V'), + (0x6E, 'V'), + (0x6F, 'V'), + (0x70, 'V'), + (0x71, 'V'), + (0x72, 'V'), + (0x73, 'V'), + (0x74, 'V'), + (0x75, 'V'), + (0x76, 'V'), + (0x77, 'V'), + (0x78, 'V'), + (0x79, 'V'), + (0x7A, 'V'), + (0x7B, '3'), + (0x7C, '3'), + (0x7D, '3'), + (0x7E, '3'), + (0x7F, '3'), + (0x80, 'X'), + (0x81, 'X'), + (0x82, 'X'), + (0x83, 'X'), + (0x84, 'X'), + (0x85, 'X'), + (0x86, 'X'), + (0x87, 'X'), + (0x88, 'X'), + (0x89, 'X'), + (0x8A, 'X'), + (0x8B, 'X'), + (0x8C, 'X'), + (0x8D, 'X'), + (0x8E, 'X'), + (0x8F, 'X'), + (0x90, 'X'), + (0x91, 'X'), + (0x92, 'X'), + (0x93, 'X'), + (0x94, 'X'), + (0x95, 'X'), + (0x96, 'X'), + (0x97, 'X'), + (0x98, 'X'), + (0x99, 'X'), + (0x9A, 'X'), + (0x9B, 'X'), + (0x9C, 'X'), + (0x9D, 'X'), + (0x9E, 'X'), + (0x9F, 'X'), + (0xA0, '3', ' '), + (0xA1, 'V'), + (0xA2, 'V'), + (0xA3, 'V'), + (0xA4, 'V'), + (0xA5, 'V'), + (0xA6, 'V'), + (0xA7, 'V'), + (0xA8, '3', ' ̈'), + (0xA9, 'V'), + (0xAA, 'M', 'a'), + (0xAB, 'V'), + (0xAC, 'V'), + (0xAD, 'I'), + (0xAE, 'V'), + (0xAF, '3', ' ̄'), + (0xB0, 'V'), + (0xB1, 'V'), + (0xB2, 'M', '2'), + (0xB3, 'M', '3'), + (0xB4, '3', ' ́'), + (0xB5, 'M', 'μ'), + (0xB6, 'V'), + (0xB7, 'V'), + (0xB8, '3', ' ̧'), + (0xB9, 'M', '1'), + (0xBA, 'M', 'o'), + (0xBB, 'V'), + (0xBC, 'M', '1⁄4'), + (0xBD, 'M', '1⁄2'), + (0xBE, 'M', '3⁄4'), + (0xBF, 'V'), + (0xC0, 'M', 'à'), + (0xC1, 'M', 'á'), + (0xC2, 'M', 'â'), + (0xC3, 'M', 'ã'), + (0xC4, 'M', 'ä'), + (0xC5, 'M', 'å'), + (0xC6, 'M', 'æ'), + (0xC7, 'M', 'ç'), + ] + +def _seg_2() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xC8, 'M', 'è'), + (0xC9, 'M', 'é'), + (0xCA, 'M', 'ê'), + (0xCB, 'M', 'ë'), + (0xCC, 'M', 'ì'), + (0xCD, 'M', 'í'), + (0xCE, 'M', 'î'), + (0xCF, 'M', 'ï'), + (0xD0, 'M', 'ð'), + (0xD1, 'M', 'ñ'), + (0xD2, 'M', 'ò'), + (0xD3, 'M', 'ó'), + (0xD4, 'M', 'ô'), + (0xD5, 'M', 'õ'), + (0xD6, 'M', 'ö'), + (0xD7, 'V'), + (0xD8, 'M', 'ø'), + (0xD9, 'M', 'ù'), + (0xDA, 'M', 'ú'), + (0xDB, 'M', 'û'), + (0xDC, 'M', 'ü'), + (0xDD, 'M', 'ý'), + (0xDE, 'M', 'þ'), + (0xDF, 'D', 'ss'), + (0xE0, 'V'), + (0xE1, 'V'), + (0xE2, 'V'), + (0xE3, 'V'), + (0xE4, 'V'), + (0xE5, 'V'), + (0xE6, 'V'), + (0xE7, 'V'), + (0xE8, 'V'), + (0xE9, 'V'), + (0xEA, 'V'), + (0xEB, 'V'), + (0xEC, 'V'), + (0xED, 'V'), + (0xEE, 'V'), + (0xEF, 'V'), + (0xF0, 'V'), + (0xF1, 'V'), + (0xF2, 'V'), + (0xF3, 'V'), + (0xF4, 'V'), + (0xF5, 'V'), + (0xF6, 'V'), + (0xF7, 'V'), + (0xF8, 'V'), + (0xF9, 'V'), + (0xFA, 'V'), + (0xFB, 'V'), + (0xFC, 'V'), + (0xFD, 'V'), + (0xFE, 'V'), + (0xFF, 'V'), + (0x100, 'M', 'ā'), + (0x101, 'V'), + (0x102, 'M', 'ă'), + (0x103, 'V'), + (0x104, 'M', 'ą'), + (0x105, 'V'), + (0x106, 'M', 'ć'), + (0x107, 'V'), + (0x108, 'M', 'ĉ'), + (0x109, 'V'), + (0x10A, 'M', 'ċ'), + (0x10B, 'V'), + (0x10C, 'M', 'č'), + (0x10D, 'V'), + (0x10E, 'M', 'ď'), + (0x10F, 'V'), + (0x110, 'M', 'đ'), + (0x111, 'V'), + (0x112, 'M', 'ē'), + (0x113, 'V'), + (0x114, 'M', 'ĕ'), + (0x115, 'V'), + (0x116, 'M', 'ė'), + (0x117, 'V'), + (0x118, 'M', 'ę'), + (0x119, 'V'), + (0x11A, 'M', 'ě'), + (0x11B, 'V'), + (0x11C, 'M', 'ĝ'), + (0x11D, 'V'), + (0x11E, 'M', 'ğ'), + (0x11F, 'V'), + (0x120, 'M', 'ġ'), + (0x121, 'V'), + (0x122, 'M', 'ģ'), + (0x123, 'V'), + (0x124, 'M', 'ĥ'), + (0x125, 'V'), + (0x126, 'M', 'ħ'), + (0x127, 'V'), + (0x128, 'M', 'ĩ'), + (0x129, 'V'), + (0x12A, 'M', 'ī'), + (0x12B, 'V'), + ] + +def _seg_3() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x12C, 'M', 'ĭ'), + (0x12D, 'V'), + (0x12E, 'M', 'į'), + (0x12F, 'V'), + (0x130, 'M', 'i̇'), + (0x131, 'V'), + (0x132, 'M', 'ij'), + (0x134, 'M', 'ĵ'), + (0x135, 'V'), + (0x136, 'M', 'ķ'), + (0x137, 'V'), + (0x139, 'M', 'ĺ'), + (0x13A, 'V'), + (0x13B, 'M', 'ļ'), + (0x13C, 'V'), + (0x13D, 'M', 'ľ'), + (0x13E, 'V'), + (0x13F, 'M', 'l·'), + (0x141, 'M', 'ł'), + (0x142, 'V'), + (0x143, 'M', 'ń'), + (0x144, 'V'), + (0x145, 'M', 'ņ'), + (0x146, 'V'), + (0x147, 'M', 'ň'), + (0x148, 'V'), + (0x149, 'M', 'ʼn'), + (0x14A, 'M', 'ŋ'), + (0x14B, 'V'), + (0x14C, 'M', 'ō'), + (0x14D, 'V'), + (0x14E, 'M', 'ŏ'), + (0x14F, 'V'), + (0x150, 'M', 'ő'), + (0x151, 'V'), + (0x152, 'M', 'œ'), + (0x153, 'V'), + (0x154, 'M', 'ŕ'), + (0x155, 'V'), + (0x156, 'M', 'ŗ'), + (0x157, 'V'), + (0x158, 'M', 'ř'), + (0x159, 'V'), + (0x15A, 'M', 'ś'), + (0x15B, 'V'), + (0x15C, 'M', 'ŝ'), + (0x15D, 'V'), + (0x15E, 'M', 'ş'), + (0x15F, 'V'), + (0x160, 'M', 'š'), + (0x161, 'V'), + (0x162, 'M', 'ţ'), + (0x163, 'V'), + (0x164, 'M', 'ť'), + (0x165, 'V'), + (0x166, 'M', 'ŧ'), + (0x167, 'V'), + (0x168, 'M', 'ũ'), + (0x169, 'V'), + (0x16A, 'M', 'ū'), + (0x16B, 'V'), + (0x16C, 'M', 'ŭ'), + (0x16D, 'V'), + (0x16E, 'M', 'ů'), + (0x16F, 'V'), + (0x170, 'M', 'ű'), + (0x171, 'V'), + (0x172, 'M', 'ų'), + (0x173, 'V'), + (0x174, 'M', 'ŵ'), + (0x175, 'V'), + (0x176, 'M', 'ŷ'), + (0x177, 'V'), + (0x178, 'M', 'ÿ'), + (0x179, 'M', 'ź'), + (0x17A, 'V'), + (0x17B, 'M', 'ż'), + (0x17C, 'V'), + (0x17D, 'M', 'ž'), + (0x17E, 'V'), + (0x17F, 'M', 's'), + (0x180, 'V'), + (0x181, 'M', 'ɓ'), + (0x182, 'M', 'ƃ'), + (0x183, 'V'), + (0x184, 'M', 'ƅ'), + (0x185, 'V'), + (0x186, 'M', 'ɔ'), + (0x187, 'M', 'ƈ'), + (0x188, 'V'), + (0x189, 'M', 'ɖ'), + (0x18A, 'M', 'ɗ'), + (0x18B, 'M', 'ƌ'), + (0x18C, 'V'), + (0x18E, 'M', 'ǝ'), + (0x18F, 'M', 'ə'), + (0x190, 'M', 'ɛ'), + (0x191, 'M', 'ƒ'), + (0x192, 'V'), + (0x193, 'M', 'ɠ'), + ] + +def _seg_4() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x194, 'M', 'ɣ'), + (0x195, 'V'), + (0x196, 'M', 'ɩ'), + (0x197, 'M', 'ɨ'), + (0x198, 'M', 'ƙ'), + (0x199, 'V'), + (0x19C, 'M', 'ɯ'), + (0x19D, 'M', 'ɲ'), + (0x19E, 'V'), + (0x19F, 'M', 'ɵ'), + (0x1A0, 'M', 'ơ'), + (0x1A1, 'V'), + (0x1A2, 'M', 'ƣ'), + (0x1A3, 'V'), + (0x1A4, 'M', 'ƥ'), + (0x1A5, 'V'), + (0x1A6, 'M', 'ʀ'), + (0x1A7, 'M', 'ƨ'), + (0x1A8, 'V'), + (0x1A9, 'M', 'ʃ'), + (0x1AA, 'V'), + (0x1AC, 'M', 'ƭ'), + (0x1AD, 'V'), + (0x1AE, 'M', 'ʈ'), + (0x1AF, 'M', 'ư'), + (0x1B0, 'V'), + (0x1B1, 'M', 'ʊ'), + (0x1B2, 'M', 'ʋ'), + (0x1B3, 'M', 'ƴ'), + (0x1B4, 'V'), + (0x1B5, 'M', 'ƶ'), + (0x1B6, 'V'), + (0x1B7, 'M', 'ʒ'), + (0x1B8, 'M', 'ƹ'), + (0x1B9, 'V'), + (0x1BC, 'M', 'ƽ'), + (0x1BD, 'V'), + (0x1C4, 'M', 'dž'), + (0x1C7, 'M', 'lj'), + (0x1CA, 'M', 'nj'), + (0x1CD, 'M', 'ǎ'), + (0x1CE, 'V'), + (0x1CF, 'M', 'ǐ'), + (0x1D0, 'V'), + (0x1D1, 'M', 'ǒ'), + (0x1D2, 'V'), + (0x1D3, 'M', 'ǔ'), + (0x1D4, 'V'), + (0x1D5, 'M', 'ǖ'), + (0x1D6, 'V'), + (0x1D7, 'M', 'ǘ'), + (0x1D8, 'V'), + (0x1D9, 'M', 'ǚ'), + (0x1DA, 'V'), + (0x1DB, 'M', 'ǜ'), + (0x1DC, 'V'), + (0x1DE, 'M', 'ǟ'), + (0x1DF, 'V'), + (0x1E0, 'M', 'ǡ'), + (0x1E1, 'V'), + (0x1E2, 'M', 'ǣ'), + (0x1E3, 'V'), + (0x1E4, 'M', 'ǥ'), + (0x1E5, 'V'), + (0x1E6, 'M', 'ǧ'), + (0x1E7, 'V'), + (0x1E8, 'M', 'ǩ'), + (0x1E9, 'V'), + (0x1EA, 'M', 'ǫ'), + (0x1EB, 'V'), + (0x1EC, 'M', 'ǭ'), + (0x1ED, 'V'), + (0x1EE, 'M', 'ǯ'), + (0x1EF, 'V'), + (0x1F1, 'M', 'dz'), + (0x1F4, 'M', 'ǵ'), + (0x1F5, 'V'), + (0x1F6, 'M', 'ƕ'), + (0x1F7, 'M', 'ƿ'), + (0x1F8, 'M', 'ǹ'), + (0x1F9, 'V'), + (0x1FA, 'M', 'ǻ'), + (0x1FB, 'V'), + (0x1FC, 'M', 'ǽ'), + (0x1FD, 'V'), + (0x1FE, 'M', 'ǿ'), + (0x1FF, 'V'), + (0x200, 'M', 'ȁ'), + (0x201, 'V'), + (0x202, 'M', 'ȃ'), + (0x203, 'V'), + (0x204, 'M', 'ȅ'), + (0x205, 'V'), + (0x206, 'M', 'ȇ'), + (0x207, 'V'), + (0x208, 'M', 'ȉ'), + (0x209, 'V'), + (0x20A, 'M', 'ȋ'), + (0x20B, 'V'), + (0x20C, 'M', 'ȍ'), + ] + +def _seg_5() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x20D, 'V'), + (0x20E, 'M', 'ȏ'), + (0x20F, 'V'), + (0x210, 'M', 'ȑ'), + (0x211, 'V'), + (0x212, 'M', 'ȓ'), + (0x213, 'V'), + (0x214, 'M', 'ȕ'), + (0x215, 'V'), + (0x216, 'M', 'ȗ'), + (0x217, 'V'), + (0x218, 'M', 'ș'), + (0x219, 'V'), + (0x21A, 'M', 'ț'), + (0x21B, 'V'), + (0x21C, 'M', 'ȝ'), + (0x21D, 'V'), + (0x21E, 'M', 'ȟ'), + (0x21F, 'V'), + (0x220, 'M', 'ƞ'), + (0x221, 'V'), + (0x222, 'M', 'ȣ'), + (0x223, 'V'), + (0x224, 'M', 'ȥ'), + (0x225, 'V'), + (0x226, 'M', 'ȧ'), + (0x227, 'V'), + (0x228, 'M', 'ȩ'), + (0x229, 'V'), + (0x22A, 'M', 'ȫ'), + (0x22B, 'V'), + (0x22C, 'M', 'ȭ'), + (0x22D, 'V'), + (0x22E, 'M', 'ȯ'), + (0x22F, 'V'), + (0x230, 'M', 'ȱ'), + (0x231, 'V'), + (0x232, 'M', 'ȳ'), + (0x233, 'V'), + (0x23A, 'M', 'ⱥ'), + (0x23B, 'M', 'ȼ'), + (0x23C, 'V'), + (0x23D, 'M', 'ƚ'), + (0x23E, 'M', 'ⱦ'), + (0x23F, 'V'), + (0x241, 'M', 'ɂ'), + (0x242, 'V'), + (0x243, 'M', 'ƀ'), + (0x244, 'M', 'ʉ'), + (0x245, 'M', 'ʌ'), + (0x246, 'M', 'ɇ'), + (0x247, 'V'), + (0x248, 'M', 'ɉ'), + (0x249, 'V'), + (0x24A, 'M', 'ɋ'), + (0x24B, 'V'), + (0x24C, 'M', 'ɍ'), + (0x24D, 'V'), + (0x24E, 'M', 'ɏ'), + (0x24F, 'V'), + (0x2B0, 'M', 'h'), + (0x2B1, 'M', 'ɦ'), + (0x2B2, 'M', 'j'), + (0x2B3, 'M', 'r'), + (0x2B4, 'M', 'ɹ'), + (0x2B5, 'M', 'ɻ'), + (0x2B6, 'M', 'ʁ'), + (0x2B7, 'M', 'w'), + (0x2B8, 'M', 'y'), + (0x2B9, 'V'), + (0x2D8, '3', ' ̆'), + (0x2D9, '3', ' ̇'), + (0x2DA, '3', ' ̊'), + (0x2DB, '3', ' ̨'), + (0x2DC, '3', ' ̃'), + (0x2DD, '3', ' ̋'), + (0x2DE, 'V'), + (0x2E0, 'M', 'ɣ'), + (0x2E1, 'M', 'l'), + (0x2E2, 'M', 's'), + (0x2E3, 'M', 'x'), + (0x2E4, 'M', 'ʕ'), + (0x2E5, 'V'), + (0x340, 'M', '̀'), + (0x341, 'M', '́'), + (0x342, 'V'), + (0x343, 'M', '̓'), + (0x344, 'M', '̈́'), + (0x345, 'M', 'ι'), + (0x346, 'V'), + (0x34F, 'I'), + (0x350, 'V'), + (0x370, 'M', 'ͱ'), + (0x371, 'V'), + (0x372, 'M', 'ͳ'), + (0x373, 'V'), + (0x374, 'M', 'ʹ'), + (0x375, 'V'), + (0x376, 'M', 'ͷ'), + (0x377, 'V'), + ] + +def _seg_6() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x378, 'X'), + (0x37A, '3', ' ι'), + (0x37B, 'V'), + (0x37E, '3', ';'), + (0x37F, 'M', 'ϳ'), + (0x380, 'X'), + (0x384, '3', ' ́'), + (0x385, '3', ' ̈́'), + (0x386, 'M', 'ά'), + (0x387, 'M', '·'), + (0x388, 'M', 'έ'), + (0x389, 'M', 'ή'), + (0x38A, 'M', 'ί'), + (0x38B, 'X'), + (0x38C, 'M', 'ό'), + (0x38D, 'X'), + (0x38E, 'M', 'ύ'), + (0x38F, 'M', 'ώ'), + (0x390, 'V'), + (0x391, 'M', 'α'), + (0x392, 'M', 'β'), + (0x393, 'M', 'γ'), + (0x394, 'M', 'δ'), + (0x395, 'M', 'ε'), + (0x396, 'M', 'ζ'), + (0x397, 'M', 'η'), + (0x398, 'M', 'θ'), + (0x399, 'M', 'ι'), + (0x39A, 'M', 'κ'), + (0x39B, 'M', 'λ'), + (0x39C, 'M', 'μ'), + (0x39D, 'M', 'ν'), + (0x39E, 'M', 'ξ'), + (0x39F, 'M', 'ο'), + (0x3A0, 'M', 'π'), + (0x3A1, 'M', 'ρ'), + (0x3A2, 'X'), + (0x3A3, 'M', 'σ'), + (0x3A4, 'M', 'τ'), + (0x3A5, 'M', 'υ'), + (0x3A6, 'M', 'φ'), + (0x3A7, 'M', 'χ'), + (0x3A8, 'M', 'ψ'), + (0x3A9, 'M', 'ω'), + (0x3AA, 'M', 'ϊ'), + (0x3AB, 'M', 'ϋ'), + (0x3AC, 'V'), + (0x3C2, 'D', 'σ'), + (0x3C3, 'V'), + (0x3CF, 'M', 'ϗ'), + (0x3D0, 'M', 'β'), + (0x3D1, 'M', 'θ'), + (0x3D2, 'M', 'υ'), + (0x3D3, 'M', 'ύ'), + (0x3D4, 'M', 'ϋ'), + (0x3D5, 'M', 'φ'), + (0x3D6, 'M', 'π'), + (0x3D7, 'V'), + (0x3D8, 'M', 'ϙ'), + (0x3D9, 'V'), + (0x3DA, 'M', 'ϛ'), + (0x3DB, 'V'), + (0x3DC, 'M', 'ϝ'), + (0x3DD, 'V'), + (0x3DE, 'M', 'ϟ'), + (0x3DF, 'V'), + (0x3E0, 'M', 'ϡ'), + (0x3E1, 'V'), + (0x3E2, 'M', 'ϣ'), + (0x3E3, 'V'), + (0x3E4, 'M', 'ϥ'), + (0x3E5, 'V'), + (0x3E6, 'M', 'ϧ'), + (0x3E7, 'V'), + (0x3E8, 'M', 'ϩ'), + (0x3E9, 'V'), + (0x3EA, 'M', 'ϫ'), + (0x3EB, 'V'), + (0x3EC, 'M', 'ϭ'), + (0x3ED, 'V'), + (0x3EE, 'M', 'ϯ'), + (0x3EF, 'V'), + (0x3F0, 'M', 'κ'), + (0x3F1, 'M', 'ρ'), + (0x3F2, 'M', 'σ'), + (0x3F3, 'V'), + (0x3F4, 'M', 'θ'), + (0x3F5, 'M', 'ε'), + (0x3F6, 'V'), + (0x3F7, 'M', 'ϸ'), + (0x3F8, 'V'), + (0x3F9, 'M', 'σ'), + (0x3FA, 'M', 'ϻ'), + (0x3FB, 'V'), + (0x3FD, 'M', 'ͻ'), + (0x3FE, 'M', 'ͼ'), + (0x3FF, 'M', 'ͽ'), + (0x400, 'M', 'ѐ'), + (0x401, 'M', 'ё'), + (0x402, 'M', 'ђ'), + ] + +def _seg_7() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x403, 'M', 'ѓ'), + (0x404, 'M', 'є'), + (0x405, 'M', 'ѕ'), + (0x406, 'M', 'і'), + (0x407, 'M', 'ї'), + (0x408, 'M', 'ј'), + (0x409, 'M', 'љ'), + (0x40A, 'M', 'њ'), + (0x40B, 'M', 'ћ'), + (0x40C, 'M', 'ќ'), + (0x40D, 'M', 'ѝ'), + (0x40E, 'M', 'ў'), + (0x40F, 'M', 'џ'), + (0x410, 'M', 'а'), + (0x411, 'M', 'б'), + (0x412, 'M', 'в'), + (0x413, 'M', 'г'), + (0x414, 'M', 'д'), + (0x415, 'M', 'е'), + (0x416, 'M', 'ж'), + (0x417, 'M', 'з'), + (0x418, 'M', 'и'), + (0x419, 'M', 'й'), + (0x41A, 'M', 'к'), + (0x41B, 'M', 'л'), + (0x41C, 'M', 'м'), + (0x41D, 'M', 'н'), + (0x41E, 'M', 'о'), + (0x41F, 'M', 'п'), + (0x420, 'M', 'р'), + (0x421, 'M', 'с'), + (0x422, 'M', 'т'), + (0x423, 'M', 'у'), + (0x424, 'M', 'ф'), + (0x425, 'M', 'х'), + (0x426, 'M', 'ц'), + (0x427, 'M', 'ч'), + (0x428, 'M', 'ш'), + (0x429, 'M', 'щ'), + (0x42A, 'M', 'ъ'), + (0x42B, 'M', 'ы'), + (0x42C, 'M', 'ь'), + (0x42D, 'M', 'э'), + (0x42E, 'M', 'ю'), + (0x42F, 'M', 'я'), + (0x430, 'V'), + (0x460, 'M', 'ѡ'), + (0x461, 'V'), + (0x462, 'M', 'ѣ'), + (0x463, 'V'), + (0x464, 'M', 'ѥ'), + (0x465, 'V'), + (0x466, 'M', 'ѧ'), + (0x467, 'V'), + (0x468, 'M', 'ѩ'), + (0x469, 'V'), + (0x46A, 'M', 'ѫ'), + (0x46B, 'V'), + (0x46C, 'M', 'ѭ'), + (0x46D, 'V'), + (0x46E, 'M', 'ѯ'), + (0x46F, 'V'), + (0x470, 'M', 'ѱ'), + (0x471, 'V'), + (0x472, 'M', 'ѳ'), + (0x473, 'V'), + (0x474, 'M', 'ѵ'), + (0x475, 'V'), + (0x476, 'M', 'ѷ'), + (0x477, 'V'), + (0x478, 'M', 'ѹ'), + (0x479, 'V'), + (0x47A, 'M', 'ѻ'), + (0x47B, 'V'), + (0x47C, 'M', 'ѽ'), + (0x47D, 'V'), + (0x47E, 'M', 'ѿ'), + (0x47F, 'V'), + (0x480, 'M', 'ҁ'), + (0x481, 'V'), + (0x48A, 'M', 'ҋ'), + (0x48B, 'V'), + (0x48C, 'M', 'ҍ'), + (0x48D, 'V'), + (0x48E, 'M', 'ҏ'), + (0x48F, 'V'), + (0x490, 'M', 'ґ'), + (0x491, 'V'), + (0x492, 'M', 'ғ'), + (0x493, 'V'), + (0x494, 'M', 'ҕ'), + (0x495, 'V'), + (0x496, 'M', 'җ'), + (0x497, 'V'), + (0x498, 'M', 'ҙ'), + (0x499, 'V'), + (0x49A, 'M', 'қ'), + (0x49B, 'V'), + (0x49C, 'M', 'ҝ'), + (0x49D, 'V'), + ] + +def _seg_8() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x49E, 'M', 'ҟ'), + (0x49F, 'V'), + (0x4A0, 'M', 'ҡ'), + (0x4A1, 'V'), + (0x4A2, 'M', 'ң'), + (0x4A3, 'V'), + (0x4A4, 'M', 'ҥ'), + (0x4A5, 'V'), + (0x4A6, 'M', 'ҧ'), + (0x4A7, 'V'), + (0x4A8, 'M', 'ҩ'), + (0x4A9, 'V'), + (0x4AA, 'M', 'ҫ'), + (0x4AB, 'V'), + (0x4AC, 'M', 'ҭ'), + (0x4AD, 'V'), + (0x4AE, 'M', 'ү'), + (0x4AF, 'V'), + (0x4B0, 'M', 'ұ'), + (0x4B1, 'V'), + (0x4B2, 'M', 'ҳ'), + (0x4B3, 'V'), + (0x4B4, 'M', 'ҵ'), + (0x4B5, 'V'), + (0x4B6, 'M', 'ҷ'), + (0x4B7, 'V'), + (0x4B8, 'M', 'ҹ'), + (0x4B9, 'V'), + (0x4BA, 'M', 'һ'), + (0x4BB, 'V'), + (0x4BC, 'M', 'ҽ'), + (0x4BD, 'V'), + (0x4BE, 'M', 'ҿ'), + (0x4BF, 'V'), + (0x4C0, 'X'), + (0x4C1, 'M', 'ӂ'), + (0x4C2, 'V'), + (0x4C3, 'M', 'ӄ'), + (0x4C4, 'V'), + (0x4C5, 'M', 'ӆ'), + (0x4C6, 'V'), + (0x4C7, 'M', 'ӈ'), + (0x4C8, 'V'), + (0x4C9, 'M', 'ӊ'), + (0x4CA, 'V'), + (0x4CB, 'M', 'ӌ'), + (0x4CC, 'V'), + (0x4CD, 'M', 'ӎ'), + (0x4CE, 'V'), + (0x4D0, 'M', 'ӑ'), + (0x4D1, 'V'), + (0x4D2, 'M', 'ӓ'), + (0x4D3, 'V'), + (0x4D4, 'M', 'ӕ'), + (0x4D5, 'V'), + (0x4D6, 'M', 'ӗ'), + (0x4D7, 'V'), + (0x4D8, 'M', 'ә'), + (0x4D9, 'V'), + (0x4DA, 'M', 'ӛ'), + (0x4DB, 'V'), + (0x4DC, 'M', 'ӝ'), + (0x4DD, 'V'), + (0x4DE, 'M', 'ӟ'), + (0x4DF, 'V'), + (0x4E0, 'M', 'ӡ'), + (0x4E1, 'V'), + (0x4E2, 'M', 'ӣ'), + (0x4E3, 'V'), + (0x4E4, 'M', 'ӥ'), + (0x4E5, 'V'), + (0x4E6, 'M', 'ӧ'), + (0x4E7, 'V'), + (0x4E8, 'M', 'ө'), + (0x4E9, 'V'), + (0x4EA, 'M', 'ӫ'), + (0x4EB, 'V'), + (0x4EC, 'M', 'ӭ'), + (0x4ED, 'V'), + (0x4EE, 'M', 'ӯ'), + (0x4EF, 'V'), + (0x4F0, 'M', 'ӱ'), + (0x4F1, 'V'), + (0x4F2, 'M', 'ӳ'), + (0x4F3, 'V'), + (0x4F4, 'M', 'ӵ'), + (0x4F5, 'V'), + (0x4F6, 'M', 'ӷ'), + (0x4F7, 'V'), + (0x4F8, 'M', 'ӹ'), + (0x4F9, 'V'), + (0x4FA, 'M', 'ӻ'), + (0x4FB, 'V'), + (0x4FC, 'M', 'ӽ'), + (0x4FD, 'V'), + (0x4FE, 'M', 'ӿ'), + (0x4FF, 'V'), + (0x500, 'M', 'ԁ'), + (0x501, 'V'), + (0x502, 'M', 'ԃ'), + ] + +def _seg_9() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x503, 'V'), + (0x504, 'M', 'ԅ'), + (0x505, 'V'), + (0x506, 'M', 'ԇ'), + (0x507, 'V'), + (0x508, 'M', 'ԉ'), + (0x509, 'V'), + (0x50A, 'M', 'ԋ'), + (0x50B, 'V'), + (0x50C, 'M', 'ԍ'), + (0x50D, 'V'), + (0x50E, 'M', 'ԏ'), + (0x50F, 'V'), + (0x510, 'M', 'ԑ'), + (0x511, 'V'), + (0x512, 'M', 'ԓ'), + (0x513, 'V'), + (0x514, 'M', 'ԕ'), + (0x515, 'V'), + (0x516, 'M', 'ԗ'), + (0x517, 'V'), + (0x518, 'M', 'ԙ'), + (0x519, 'V'), + (0x51A, 'M', 'ԛ'), + (0x51B, 'V'), + (0x51C, 'M', 'ԝ'), + (0x51D, 'V'), + (0x51E, 'M', 'ԟ'), + (0x51F, 'V'), + (0x520, 'M', 'ԡ'), + (0x521, 'V'), + (0x522, 'M', 'ԣ'), + (0x523, 'V'), + (0x524, 'M', 'ԥ'), + (0x525, 'V'), + (0x526, 'M', 'ԧ'), + (0x527, 'V'), + (0x528, 'M', 'ԩ'), + (0x529, 'V'), + (0x52A, 'M', 'ԫ'), + (0x52B, 'V'), + (0x52C, 'M', 'ԭ'), + (0x52D, 'V'), + (0x52E, 'M', 'ԯ'), + (0x52F, 'V'), + (0x530, 'X'), + (0x531, 'M', 'ա'), + (0x532, 'M', 'բ'), + (0x533, 'M', 'գ'), + (0x534, 'M', 'դ'), + (0x535, 'M', 'ե'), + (0x536, 'M', 'զ'), + (0x537, 'M', 'է'), + (0x538, 'M', 'ը'), + (0x539, 'M', 'թ'), + (0x53A, 'M', 'ժ'), + (0x53B, 'M', 'ի'), + (0x53C, 'M', 'լ'), + (0x53D, 'M', 'խ'), + (0x53E, 'M', 'ծ'), + (0x53F, 'M', 'կ'), + (0x540, 'M', 'հ'), + (0x541, 'M', 'ձ'), + (0x542, 'M', 'ղ'), + (0x543, 'M', 'ճ'), + (0x544, 'M', 'մ'), + (0x545, 'M', 'յ'), + (0x546, 'M', 'ն'), + (0x547, 'M', 'շ'), + (0x548, 'M', 'ո'), + (0x549, 'M', 'չ'), + (0x54A, 'M', 'պ'), + (0x54B, 'M', 'ջ'), + (0x54C, 'M', 'ռ'), + (0x54D, 'M', 'ս'), + (0x54E, 'M', 'վ'), + (0x54F, 'M', 'տ'), + (0x550, 'M', 'ր'), + (0x551, 'M', 'ց'), + (0x552, 'M', 'ւ'), + (0x553, 'M', 'փ'), + (0x554, 'M', 'ք'), + (0x555, 'M', 'օ'), + (0x556, 'M', 'ֆ'), + (0x557, 'X'), + (0x559, 'V'), + (0x587, 'M', 'եւ'), + (0x588, 'V'), + (0x58B, 'X'), + (0x58D, 'V'), + (0x590, 'X'), + (0x591, 'V'), + (0x5C8, 'X'), + (0x5D0, 'V'), + (0x5EB, 'X'), + (0x5EF, 'V'), + (0x5F5, 'X'), + (0x606, 'V'), + (0x61C, 'X'), + (0x61D, 'V'), + ] + +def _seg_10() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x675, 'M', 'اٴ'), + (0x676, 'M', 'وٴ'), + (0x677, 'M', 'ۇٴ'), + (0x678, 'M', 'يٴ'), + (0x679, 'V'), + (0x6DD, 'X'), + (0x6DE, 'V'), + (0x70E, 'X'), + (0x710, 'V'), + (0x74B, 'X'), + (0x74D, 'V'), + (0x7B2, 'X'), + (0x7C0, 'V'), + (0x7FB, 'X'), + (0x7FD, 'V'), + (0x82E, 'X'), + (0x830, 'V'), + (0x83F, 'X'), + (0x840, 'V'), + (0x85C, 'X'), + (0x85E, 'V'), + (0x85F, 'X'), + (0x860, 'V'), + (0x86B, 'X'), + (0x870, 'V'), + (0x88F, 'X'), + (0x898, 'V'), + (0x8E2, 'X'), + (0x8E3, 'V'), + (0x958, 'M', 'क़'), + (0x959, 'M', 'ख़'), + (0x95A, 'M', 'ग़'), + (0x95B, 'M', 'ज़'), + (0x95C, 'M', 'ड़'), + (0x95D, 'M', 'ढ़'), + (0x95E, 'M', 'फ़'), + (0x95F, 'M', 'य़'), + (0x960, 'V'), + (0x984, 'X'), + (0x985, 'V'), + (0x98D, 'X'), + (0x98F, 'V'), + (0x991, 'X'), + (0x993, 'V'), + (0x9A9, 'X'), + (0x9AA, 'V'), + (0x9B1, 'X'), + (0x9B2, 'V'), + (0x9B3, 'X'), + (0x9B6, 'V'), + (0x9BA, 'X'), + (0x9BC, 'V'), + (0x9C5, 'X'), + (0x9C7, 'V'), + (0x9C9, 'X'), + (0x9CB, 'V'), + (0x9CF, 'X'), + (0x9D7, 'V'), + (0x9D8, 'X'), + (0x9DC, 'M', 'ড়'), + (0x9DD, 'M', 'ঢ়'), + (0x9DE, 'X'), + (0x9DF, 'M', 'য়'), + (0x9E0, 'V'), + (0x9E4, 'X'), + (0x9E6, 'V'), + (0x9FF, 'X'), + (0xA01, 'V'), + (0xA04, 'X'), + (0xA05, 'V'), + (0xA0B, 'X'), + (0xA0F, 'V'), + (0xA11, 'X'), + (0xA13, 'V'), + (0xA29, 'X'), + (0xA2A, 'V'), + (0xA31, 'X'), + (0xA32, 'V'), + (0xA33, 'M', 'ਲ਼'), + (0xA34, 'X'), + (0xA35, 'V'), + (0xA36, 'M', 'ਸ਼'), + (0xA37, 'X'), + (0xA38, 'V'), + (0xA3A, 'X'), + (0xA3C, 'V'), + (0xA3D, 'X'), + (0xA3E, 'V'), + (0xA43, 'X'), + (0xA47, 'V'), + (0xA49, 'X'), + (0xA4B, 'V'), + (0xA4E, 'X'), + (0xA51, 'V'), + (0xA52, 'X'), + (0xA59, 'M', 'ਖ਼'), + (0xA5A, 'M', 'ਗ਼'), + (0xA5B, 'M', 'ਜ਼'), + (0xA5C, 'V'), + (0xA5D, 'X'), + ] + +def _seg_11() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xA5E, 'M', 'ਫ਼'), + (0xA5F, 'X'), + (0xA66, 'V'), + (0xA77, 'X'), + (0xA81, 'V'), + (0xA84, 'X'), + (0xA85, 'V'), + (0xA8E, 'X'), + (0xA8F, 'V'), + (0xA92, 'X'), + (0xA93, 'V'), + (0xAA9, 'X'), + (0xAAA, 'V'), + (0xAB1, 'X'), + (0xAB2, 'V'), + (0xAB4, 'X'), + (0xAB5, 'V'), + (0xABA, 'X'), + (0xABC, 'V'), + (0xAC6, 'X'), + (0xAC7, 'V'), + (0xACA, 'X'), + (0xACB, 'V'), + (0xACE, 'X'), + (0xAD0, 'V'), + (0xAD1, 'X'), + (0xAE0, 'V'), + (0xAE4, 'X'), + (0xAE6, 'V'), + (0xAF2, 'X'), + (0xAF9, 'V'), + (0xB00, 'X'), + (0xB01, 'V'), + (0xB04, 'X'), + (0xB05, 'V'), + (0xB0D, 'X'), + (0xB0F, 'V'), + (0xB11, 'X'), + (0xB13, 'V'), + (0xB29, 'X'), + (0xB2A, 'V'), + (0xB31, 'X'), + (0xB32, 'V'), + (0xB34, 'X'), + (0xB35, 'V'), + (0xB3A, 'X'), + (0xB3C, 'V'), + (0xB45, 'X'), + (0xB47, 'V'), + (0xB49, 'X'), + (0xB4B, 'V'), + (0xB4E, 'X'), + (0xB55, 'V'), + (0xB58, 'X'), + (0xB5C, 'M', 'ଡ଼'), + (0xB5D, 'M', 'ଢ଼'), + (0xB5E, 'X'), + (0xB5F, 'V'), + (0xB64, 'X'), + (0xB66, 'V'), + (0xB78, 'X'), + (0xB82, 'V'), + (0xB84, 'X'), + (0xB85, 'V'), + (0xB8B, 'X'), + (0xB8E, 'V'), + (0xB91, 'X'), + (0xB92, 'V'), + (0xB96, 'X'), + (0xB99, 'V'), + (0xB9B, 'X'), + (0xB9C, 'V'), + (0xB9D, 'X'), + (0xB9E, 'V'), + (0xBA0, 'X'), + (0xBA3, 'V'), + (0xBA5, 'X'), + (0xBA8, 'V'), + (0xBAB, 'X'), + (0xBAE, 'V'), + (0xBBA, 'X'), + (0xBBE, 'V'), + (0xBC3, 'X'), + (0xBC6, 'V'), + (0xBC9, 'X'), + (0xBCA, 'V'), + (0xBCE, 'X'), + (0xBD0, 'V'), + (0xBD1, 'X'), + (0xBD7, 'V'), + (0xBD8, 'X'), + (0xBE6, 'V'), + (0xBFB, 'X'), + (0xC00, 'V'), + (0xC0D, 'X'), + (0xC0E, 'V'), + (0xC11, 'X'), + (0xC12, 'V'), + (0xC29, 'X'), + (0xC2A, 'V'), + ] + +def _seg_12() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xC3A, 'X'), + (0xC3C, 'V'), + (0xC45, 'X'), + (0xC46, 'V'), + (0xC49, 'X'), + (0xC4A, 'V'), + (0xC4E, 'X'), + (0xC55, 'V'), + (0xC57, 'X'), + (0xC58, 'V'), + (0xC5B, 'X'), + (0xC5D, 'V'), + (0xC5E, 'X'), + (0xC60, 'V'), + (0xC64, 'X'), + (0xC66, 'V'), + (0xC70, 'X'), + (0xC77, 'V'), + (0xC8D, 'X'), + (0xC8E, 'V'), + (0xC91, 'X'), + (0xC92, 'V'), + (0xCA9, 'X'), + (0xCAA, 'V'), + (0xCB4, 'X'), + (0xCB5, 'V'), + (0xCBA, 'X'), + (0xCBC, 'V'), + (0xCC5, 'X'), + (0xCC6, 'V'), + (0xCC9, 'X'), + (0xCCA, 'V'), + (0xCCE, 'X'), + (0xCD5, 'V'), + (0xCD7, 'X'), + (0xCDD, 'V'), + (0xCDF, 'X'), + (0xCE0, 'V'), + (0xCE4, 'X'), + (0xCE6, 'V'), + (0xCF0, 'X'), + (0xCF1, 'V'), + (0xCF4, 'X'), + (0xD00, 'V'), + (0xD0D, 'X'), + (0xD0E, 'V'), + (0xD11, 'X'), + (0xD12, 'V'), + (0xD45, 'X'), + (0xD46, 'V'), + (0xD49, 'X'), + (0xD4A, 'V'), + (0xD50, 'X'), + (0xD54, 'V'), + (0xD64, 'X'), + (0xD66, 'V'), + (0xD80, 'X'), + (0xD81, 'V'), + (0xD84, 'X'), + (0xD85, 'V'), + (0xD97, 'X'), + (0xD9A, 'V'), + (0xDB2, 'X'), + (0xDB3, 'V'), + (0xDBC, 'X'), + (0xDBD, 'V'), + (0xDBE, 'X'), + (0xDC0, 'V'), + (0xDC7, 'X'), + (0xDCA, 'V'), + (0xDCB, 'X'), + (0xDCF, 'V'), + (0xDD5, 'X'), + (0xDD6, 'V'), + (0xDD7, 'X'), + (0xDD8, 'V'), + (0xDE0, 'X'), + (0xDE6, 'V'), + (0xDF0, 'X'), + (0xDF2, 'V'), + (0xDF5, 'X'), + (0xE01, 'V'), + (0xE33, 'M', 'ํา'), + (0xE34, 'V'), + (0xE3B, 'X'), + (0xE3F, 'V'), + (0xE5C, 'X'), + (0xE81, 'V'), + (0xE83, 'X'), + (0xE84, 'V'), + (0xE85, 'X'), + (0xE86, 'V'), + (0xE8B, 'X'), + (0xE8C, 'V'), + (0xEA4, 'X'), + (0xEA5, 'V'), + (0xEA6, 'X'), + (0xEA7, 'V'), + (0xEB3, 'M', 'ໍາ'), + (0xEB4, 'V'), + ] + +def _seg_13() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xEBE, 'X'), + (0xEC0, 'V'), + (0xEC5, 'X'), + (0xEC6, 'V'), + (0xEC7, 'X'), + (0xEC8, 'V'), + (0xECF, 'X'), + (0xED0, 'V'), + (0xEDA, 'X'), + (0xEDC, 'M', 'ຫນ'), + (0xEDD, 'M', 'ຫມ'), + (0xEDE, 'V'), + (0xEE0, 'X'), + (0xF00, 'V'), + (0xF0C, 'M', '་'), + (0xF0D, 'V'), + (0xF43, 'M', 'གྷ'), + (0xF44, 'V'), + (0xF48, 'X'), + (0xF49, 'V'), + (0xF4D, 'M', 'ཌྷ'), + (0xF4E, 'V'), + (0xF52, 'M', 'དྷ'), + (0xF53, 'V'), + (0xF57, 'M', 'བྷ'), + (0xF58, 'V'), + (0xF5C, 'M', 'ཛྷ'), + (0xF5D, 'V'), + (0xF69, 'M', 'ཀྵ'), + (0xF6A, 'V'), + (0xF6D, 'X'), + (0xF71, 'V'), + (0xF73, 'M', 'ཱི'), + (0xF74, 'V'), + (0xF75, 'M', 'ཱུ'), + (0xF76, 'M', 'ྲྀ'), + (0xF77, 'M', 'ྲཱྀ'), + (0xF78, 'M', 'ླྀ'), + (0xF79, 'M', 'ླཱྀ'), + (0xF7A, 'V'), + (0xF81, 'M', 'ཱྀ'), + (0xF82, 'V'), + (0xF93, 'M', 'ྒྷ'), + (0xF94, 'V'), + (0xF98, 'X'), + (0xF99, 'V'), + (0xF9D, 'M', 'ྜྷ'), + (0xF9E, 'V'), + (0xFA2, 'M', 'ྡྷ'), + (0xFA3, 'V'), + (0xFA7, 'M', 'ྦྷ'), + (0xFA8, 'V'), + (0xFAC, 'M', 'ྫྷ'), + (0xFAD, 'V'), + (0xFB9, 'M', 'ྐྵ'), + (0xFBA, 'V'), + (0xFBD, 'X'), + (0xFBE, 'V'), + (0xFCD, 'X'), + (0xFCE, 'V'), + (0xFDB, 'X'), + (0x1000, 'V'), + (0x10A0, 'X'), + (0x10C7, 'M', 'ⴧ'), + (0x10C8, 'X'), + (0x10CD, 'M', 'ⴭ'), + (0x10CE, 'X'), + (0x10D0, 'V'), + (0x10FC, 'M', 'ნ'), + (0x10FD, 'V'), + (0x115F, 'X'), + (0x1161, 'V'), + (0x1249, 'X'), + (0x124A, 'V'), + (0x124E, 'X'), + (0x1250, 'V'), + (0x1257, 'X'), + (0x1258, 'V'), + (0x1259, 'X'), + (0x125A, 'V'), + (0x125E, 'X'), + (0x1260, 'V'), + (0x1289, 'X'), + (0x128A, 'V'), + (0x128E, 'X'), + (0x1290, 'V'), + (0x12B1, 'X'), + (0x12B2, 'V'), + (0x12B6, 'X'), + (0x12B8, 'V'), + (0x12BF, 'X'), + (0x12C0, 'V'), + (0x12C1, 'X'), + (0x12C2, 'V'), + (0x12C6, 'X'), + (0x12C8, 'V'), + (0x12D7, 'X'), + (0x12D8, 'V'), + (0x1311, 'X'), + (0x1312, 'V'), + ] + +def _seg_14() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1316, 'X'), + (0x1318, 'V'), + (0x135B, 'X'), + (0x135D, 'V'), + (0x137D, 'X'), + (0x1380, 'V'), + (0x139A, 'X'), + (0x13A0, 'V'), + (0x13F6, 'X'), + (0x13F8, 'M', 'Ᏸ'), + (0x13F9, 'M', 'Ᏹ'), + (0x13FA, 'M', 'Ᏺ'), + (0x13FB, 'M', 'Ᏻ'), + (0x13FC, 'M', 'Ᏼ'), + (0x13FD, 'M', 'Ᏽ'), + (0x13FE, 'X'), + (0x1400, 'V'), + (0x1680, 'X'), + (0x1681, 'V'), + (0x169D, 'X'), + (0x16A0, 'V'), + (0x16F9, 'X'), + (0x1700, 'V'), + (0x1716, 'X'), + (0x171F, 'V'), + (0x1737, 'X'), + (0x1740, 'V'), + (0x1754, 'X'), + (0x1760, 'V'), + (0x176D, 'X'), + (0x176E, 'V'), + (0x1771, 'X'), + (0x1772, 'V'), + (0x1774, 'X'), + (0x1780, 'V'), + (0x17B4, 'X'), + (0x17B6, 'V'), + (0x17DE, 'X'), + (0x17E0, 'V'), + (0x17EA, 'X'), + (0x17F0, 'V'), + (0x17FA, 'X'), + (0x1800, 'V'), + (0x1806, 'X'), + (0x1807, 'V'), + (0x180B, 'I'), + (0x180E, 'X'), + (0x180F, 'I'), + (0x1810, 'V'), + (0x181A, 'X'), + (0x1820, 'V'), + (0x1879, 'X'), + (0x1880, 'V'), + (0x18AB, 'X'), + (0x18B0, 'V'), + (0x18F6, 'X'), + (0x1900, 'V'), + (0x191F, 'X'), + (0x1920, 'V'), + (0x192C, 'X'), + (0x1930, 'V'), + (0x193C, 'X'), + (0x1940, 'V'), + (0x1941, 'X'), + (0x1944, 'V'), + (0x196E, 'X'), + (0x1970, 'V'), + (0x1975, 'X'), + (0x1980, 'V'), + (0x19AC, 'X'), + (0x19B0, 'V'), + (0x19CA, 'X'), + (0x19D0, 'V'), + (0x19DB, 'X'), + (0x19DE, 'V'), + (0x1A1C, 'X'), + (0x1A1E, 'V'), + (0x1A5F, 'X'), + (0x1A60, 'V'), + (0x1A7D, 'X'), + (0x1A7F, 'V'), + (0x1A8A, 'X'), + (0x1A90, 'V'), + (0x1A9A, 'X'), + (0x1AA0, 'V'), + (0x1AAE, 'X'), + (0x1AB0, 'V'), + (0x1ACF, 'X'), + (0x1B00, 'V'), + (0x1B4D, 'X'), + (0x1B50, 'V'), + (0x1B7F, 'X'), + (0x1B80, 'V'), + (0x1BF4, 'X'), + (0x1BFC, 'V'), + (0x1C38, 'X'), + (0x1C3B, 'V'), + (0x1C4A, 'X'), + (0x1C4D, 'V'), + (0x1C80, 'M', 'в'), + ] + +def _seg_15() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1C81, 'M', 'д'), + (0x1C82, 'M', 'о'), + (0x1C83, 'M', 'с'), + (0x1C84, 'M', 'т'), + (0x1C86, 'M', 'ъ'), + (0x1C87, 'M', 'ѣ'), + (0x1C88, 'M', 'ꙋ'), + (0x1C89, 'X'), + (0x1C90, 'M', 'ა'), + (0x1C91, 'M', 'ბ'), + (0x1C92, 'M', 'გ'), + (0x1C93, 'M', 'დ'), + (0x1C94, 'M', 'ე'), + (0x1C95, 'M', 'ვ'), + (0x1C96, 'M', 'ზ'), + (0x1C97, 'M', 'თ'), + (0x1C98, 'M', 'ი'), + (0x1C99, 'M', 'კ'), + (0x1C9A, 'M', 'ლ'), + (0x1C9B, 'M', 'მ'), + (0x1C9C, 'M', 'ნ'), + (0x1C9D, 'M', 'ო'), + (0x1C9E, 'M', 'პ'), + (0x1C9F, 'M', 'ჟ'), + (0x1CA0, 'M', 'რ'), + (0x1CA1, 'M', 'ს'), + (0x1CA2, 'M', 'ტ'), + (0x1CA3, 'M', 'უ'), + (0x1CA4, 'M', 'ფ'), + (0x1CA5, 'M', 'ქ'), + (0x1CA6, 'M', 'ღ'), + (0x1CA7, 'M', 'ყ'), + (0x1CA8, 'M', 'შ'), + (0x1CA9, 'M', 'ჩ'), + (0x1CAA, 'M', 'ც'), + (0x1CAB, 'M', 'ძ'), + (0x1CAC, 'M', 'წ'), + (0x1CAD, 'M', 'ჭ'), + (0x1CAE, 'M', 'ხ'), + (0x1CAF, 'M', 'ჯ'), + (0x1CB0, 'M', 'ჰ'), + (0x1CB1, 'M', 'ჱ'), + (0x1CB2, 'M', 'ჲ'), + (0x1CB3, 'M', 'ჳ'), + (0x1CB4, 'M', 'ჴ'), + (0x1CB5, 'M', 'ჵ'), + (0x1CB6, 'M', 'ჶ'), + (0x1CB7, 'M', 'ჷ'), + (0x1CB8, 'M', 'ჸ'), + (0x1CB9, 'M', 'ჹ'), + (0x1CBA, 'M', 'ჺ'), + (0x1CBB, 'X'), + (0x1CBD, 'M', 'ჽ'), + (0x1CBE, 'M', 'ჾ'), + (0x1CBF, 'M', 'ჿ'), + (0x1CC0, 'V'), + (0x1CC8, 'X'), + (0x1CD0, 'V'), + (0x1CFB, 'X'), + (0x1D00, 'V'), + (0x1D2C, 'M', 'a'), + (0x1D2D, 'M', 'æ'), + (0x1D2E, 'M', 'b'), + (0x1D2F, 'V'), + (0x1D30, 'M', 'd'), + (0x1D31, 'M', 'e'), + (0x1D32, 'M', 'ǝ'), + (0x1D33, 'M', 'g'), + (0x1D34, 'M', 'h'), + (0x1D35, 'M', 'i'), + (0x1D36, 'M', 'j'), + (0x1D37, 'M', 'k'), + (0x1D38, 'M', 'l'), + (0x1D39, 'M', 'm'), + (0x1D3A, 'M', 'n'), + (0x1D3B, 'V'), + (0x1D3C, 'M', 'o'), + (0x1D3D, 'M', 'ȣ'), + (0x1D3E, 'M', 'p'), + (0x1D3F, 'M', 'r'), + (0x1D40, 'M', 't'), + (0x1D41, 'M', 'u'), + (0x1D42, 'M', 'w'), + (0x1D43, 'M', 'a'), + (0x1D44, 'M', 'ɐ'), + (0x1D45, 'M', 'ɑ'), + (0x1D46, 'M', 'ᴂ'), + (0x1D47, 'M', 'b'), + (0x1D48, 'M', 'd'), + (0x1D49, 'M', 'e'), + (0x1D4A, 'M', 'ə'), + (0x1D4B, 'M', 'ɛ'), + (0x1D4C, 'M', 'ɜ'), + (0x1D4D, 'M', 'g'), + (0x1D4E, 'V'), + (0x1D4F, 'M', 'k'), + (0x1D50, 'M', 'm'), + (0x1D51, 'M', 'ŋ'), + (0x1D52, 'M', 'o'), + (0x1D53, 'M', 'ɔ'), + ] + +def _seg_16() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D54, 'M', 'ᴖ'), + (0x1D55, 'M', 'ᴗ'), + (0x1D56, 'M', 'p'), + (0x1D57, 'M', 't'), + (0x1D58, 'M', 'u'), + (0x1D59, 'M', 'ᴝ'), + (0x1D5A, 'M', 'ɯ'), + (0x1D5B, 'M', 'v'), + (0x1D5C, 'M', 'ᴥ'), + (0x1D5D, 'M', 'β'), + (0x1D5E, 'M', 'γ'), + (0x1D5F, 'M', 'δ'), + (0x1D60, 'M', 'φ'), + (0x1D61, 'M', 'χ'), + (0x1D62, 'M', 'i'), + (0x1D63, 'M', 'r'), + (0x1D64, 'M', 'u'), + (0x1D65, 'M', 'v'), + (0x1D66, 'M', 'β'), + (0x1D67, 'M', 'γ'), + (0x1D68, 'M', 'ρ'), + (0x1D69, 'M', 'φ'), + (0x1D6A, 'M', 'χ'), + (0x1D6B, 'V'), + (0x1D78, 'M', 'н'), + (0x1D79, 'V'), + (0x1D9B, 'M', 'ɒ'), + (0x1D9C, 'M', 'c'), + (0x1D9D, 'M', 'ɕ'), + (0x1D9E, 'M', 'ð'), + (0x1D9F, 'M', 'ɜ'), + (0x1DA0, 'M', 'f'), + (0x1DA1, 'M', 'ɟ'), + (0x1DA2, 'M', 'ɡ'), + (0x1DA3, 'M', 'ɥ'), + (0x1DA4, 'M', 'ɨ'), + (0x1DA5, 'M', 'ɩ'), + (0x1DA6, 'M', 'ɪ'), + (0x1DA7, 'M', 'ᵻ'), + (0x1DA8, 'M', 'ʝ'), + (0x1DA9, 'M', 'ɭ'), + (0x1DAA, 'M', 'ᶅ'), + (0x1DAB, 'M', 'ʟ'), + (0x1DAC, 'M', 'ɱ'), + (0x1DAD, 'M', 'ɰ'), + (0x1DAE, 'M', 'ɲ'), + (0x1DAF, 'M', 'ɳ'), + (0x1DB0, 'M', 'ɴ'), + (0x1DB1, 'M', 'ɵ'), + (0x1DB2, 'M', 'ɸ'), + (0x1DB3, 'M', 'ʂ'), + (0x1DB4, 'M', 'ʃ'), + (0x1DB5, 'M', 'ƫ'), + (0x1DB6, 'M', 'ʉ'), + (0x1DB7, 'M', 'ʊ'), + (0x1DB8, 'M', 'ᴜ'), + (0x1DB9, 'M', 'ʋ'), + (0x1DBA, 'M', 'ʌ'), + (0x1DBB, 'M', 'z'), + (0x1DBC, 'M', 'ʐ'), + (0x1DBD, 'M', 'ʑ'), + (0x1DBE, 'M', 'ʒ'), + (0x1DBF, 'M', 'θ'), + (0x1DC0, 'V'), + (0x1E00, 'M', 'ḁ'), + (0x1E01, 'V'), + (0x1E02, 'M', 'ḃ'), + (0x1E03, 'V'), + (0x1E04, 'M', 'ḅ'), + (0x1E05, 'V'), + (0x1E06, 'M', 'ḇ'), + (0x1E07, 'V'), + (0x1E08, 'M', 'ḉ'), + (0x1E09, 'V'), + (0x1E0A, 'M', 'ḋ'), + (0x1E0B, 'V'), + (0x1E0C, 'M', 'ḍ'), + (0x1E0D, 'V'), + (0x1E0E, 'M', 'ḏ'), + (0x1E0F, 'V'), + (0x1E10, 'M', 'ḑ'), + (0x1E11, 'V'), + (0x1E12, 'M', 'ḓ'), + (0x1E13, 'V'), + (0x1E14, 'M', 'ḕ'), + (0x1E15, 'V'), + (0x1E16, 'M', 'ḗ'), + (0x1E17, 'V'), + (0x1E18, 'M', 'ḙ'), + (0x1E19, 'V'), + (0x1E1A, 'M', 'ḛ'), + (0x1E1B, 'V'), + (0x1E1C, 'M', 'ḝ'), + (0x1E1D, 'V'), + (0x1E1E, 'M', 'ḟ'), + (0x1E1F, 'V'), + (0x1E20, 'M', 'ḡ'), + (0x1E21, 'V'), + (0x1E22, 'M', 'ḣ'), + (0x1E23, 'V'), + ] + +def _seg_17() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1E24, 'M', 'ḥ'), + (0x1E25, 'V'), + (0x1E26, 'M', 'ḧ'), + (0x1E27, 'V'), + (0x1E28, 'M', 'ḩ'), + (0x1E29, 'V'), + (0x1E2A, 'M', 'ḫ'), + (0x1E2B, 'V'), + (0x1E2C, 'M', 'ḭ'), + (0x1E2D, 'V'), + (0x1E2E, 'M', 'ḯ'), + (0x1E2F, 'V'), + (0x1E30, 'M', 'ḱ'), + (0x1E31, 'V'), + (0x1E32, 'M', 'ḳ'), + (0x1E33, 'V'), + (0x1E34, 'M', 'ḵ'), + (0x1E35, 'V'), + (0x1E36, 'M', 'ḷ'), + (0x1E37, 'V'), + (0x1E38, 'M', 'ḹ'), + (0x1E39, 'V'), + (0x1E3A, 'M', 'ḻ'), + (0x1E3B, 'V'), + (0x1E3C, 'M', 'ḽ'), + (0x1E3D, 'V'), + (0x1E3E, 'M', 'ḿ'), + (0x1E3F, 'V'), + (0x1E40, 'M', 'ṁ'), + (0x1E41, 'V'), + (0x1E42, 'M', 'ṃ'), + (0x1E43, 'V'), + (0x1E44, 'M', 'ṅ'), + (0x1E45, 'V'), + (0x1E46, 'M', 'ṇ'), + (0x1E47, 'V'), + (0x1E48, 'M', 'ṉ'), + (0x1E49, 'V'), + (0x1E4A, 'M', 'ṋ'), + (0x1E4B, 'V'), + (0x1E4C, 'M', 'ṍ'), + (0x1E4D, 'V'), + (0x1E4E, 'M', 'ṏ'), + (0x1E4F, 'V'), + (0x1E50, 'M', 'ṑ'), + (0x1E51, 'V'), + (0x1E52, 'M', 'ṓ'), + (0x1E53, 'V'), + (0x1E54, 'M', 'ṕ'), + (0x1E55, 'V'), + (0x1E56, 'M', 'ṗ'), + (0x1E57, 'V'), + (0x1E58, 'M', 'ṙ'), + (0x1E59, 'V'), + (0x1E5A, 'M', 'ṛ'), + (0x1E5B, 'V'), + (0x1E5C, 'M', 'ṝ'), + (0x1E5D, 'V'), + (0x1E5E, 'M', 'ṟ'), + (0x1E5F, 'V'), + (0x1E60, 'M', 'ṡ'), + (0x1E61, 'V'), + (0x1E62, 'M', 'ṣ'), + (0x1E63, 'V'), + (0x1E64, 'M', 'ṥ'), + (0x1E65, 'V'), + (0x1E66, 'M', 'ṧ'), + (0x1E67, 'V'), + (0x1E68, 'M', 'ṩ'), + (0x1E69, 'V'), + (0x1E6A, 'M', 'ṫ'), + (0x1E6B, 'V'), + (0x1E6C, 'M', 'ṭ'), + (0x1E6D, 'V'), + (0x1E6E, 'M', 'ṯ'), + (0x1E6F, 'V'), + (0x1E70, 'M', 'ṱ'), + (0x1E71, 'V'), + (0x1E72, 'M', 'ṳ'), + (0x1E73, 'V'), + (0x1E74, 'M', 'ṵ'), + (0x1E75, 'V'), + (0x1E76, 'M', 'ṷ'), + (0x1E77, 'V'), + (0x1E78, 'M', 'ṹ'), + (0x1E79, 'V'), + (0x1E7A, 'M', 'ṻ'), + (0x1E7B, 'V'), + (0x1E7C, 'M', 'ṽ'), + (0x1E7D, 'V'), + (0x1E7E, 'M', 'ṿ'), + (0x1E7F, 'V'), + (0x1E80, 'M', 'ẁ'), + (0x1E81, 'V'), + (0x1E82, 'M', 'ẃ'), + (0x1E83, 'V'), + (0x1E84, 'M', 'ẅ'), + (0x1E85, 'V'), + (0x1E86, 'M', 'ẇ'), + (0x1E87, 'V'), + ] + +def _seg_18() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1E88, 'M', 'ẉ'), + (0x1E89, 'V'), + (0x1E8A, 'M', 'ẋ'), + (0x1E8B, 'V'), + (0x1E8C, 'M', 'ẍ'), + (0x1E8D, 'V'), + (0x1E8E, 'M', 'ẏ'), + (0x1E8F, 'V'), + (0x1E90, 'M', 'ẑ'), + (0x1E91, 'V'), + (0x1E92, 'M', 'ẓ'), + (0x1E93, 'V'), + (0x1E94, 'M', 'ẕ'), + (0x1E95, 'V'), + (0x1E9A, 'M', 'aʾ'), + (0x1E9B, 'M', 'ṡ'), + (0x1E9C, 'V'), + (0x1E9E, 'M', 'ss'), + (0x1E9F, 'V'), + (0x1EA0, 'M', 'ạ'), + (0x1EA1, 'V'), + (0x1EA2, 'M', 'ả'), + (0x1EA3, 'V'), + (0x1EA4, 'M', 'ấ'), + (0x1EA5, 'V'), + (0x1EA6, 'M', 'ầ'), + (0x1EA7, 'V'), + (0x1EA8, 'M', 'ẩ'), + (0x1EA9, 'V'), + (0x1EAA, 'M', 'ẫ'), + (0x1EAB, 'V'), + (0x1EAC, 'M', 'ậ'), + (0x1EAD, 'V'), + (0x1EAE, 'M', 'ắ'), + (0x1EAF, 'V'), + (0x1EB0, 'M', 'ằ'), + (0x1EB1, 'V'), + (0x1EB2, 'M', 'ẳ'), + (0x1EB3, 'V'), + (0x1EB4, 'M', 'ẵ'), + (0x1EB5, 'V'), + (0x1EB6, 'M', 'ặ'), + (0x1EB7, 'V'), + (0x1EB8, 'M', 'ẹ'), + (0x1EB9, 'V'), + (0x1EBA, 'M', 'ẻ'), + (0x1EBB, 'V'), + (0x1EBC, 'M', 'ẽ'), + (0x1EBD, 'V'), + (0x1EBE, 'M', 'ế'), + (0x1EBF, 'V'), + (0x1EC0, 'M', 'ề'), + (0x1EC1, 'V'), + (0x1EC2, 'M', 'ể'), + (0x1EC3, 'V'), + (0x1EC4, 'M', 'ễ'), + (0x1EC5, 'V'), + (0x1EC6, 'M', 'ệ'), + (0x1EC7, 'V'), + (0x1EC8, 'M', 'ỉ'), + (0x1EC9, 'V'), + (0x1ECA, 'M', 'ị'), + (0x1ECB, 'V'), + (0x1ECC, 'M', 'ọ'), + (0x1ECD, 'V'), + (0x1ECE, 'M', 'ỏ'), + (0x1ECF, 'V'), + (0x1ED0, 'M', 'ố'), + (0x1ED1, 'V'), + (0x1ED2, 'M', 'ồ'), + (0x1ED3, 'V'), + (0x1ED4, 'M', 'ổ'), + (0x1ED5, 'V'), + (0x1ED6, 'M', 'ỗ'), + (0x1ED7, 'V'), + (0x1ED8, 'M', 'ộ'), + (0x1ED9, 'V'), + (0x1EDA, 'M', 'ớ'), + (0x1EDB, 'V'), + (0x1EDC, 'M', 'ờ'), + (0x1EDD, 'V'), + (0x1EDE, 'M', 'ở'), + (0x1EDF, 'V'), + (0x1EE0, 'M', 'ỡ'), + (0x1EE1, 'V'), + (0x1EE2, 'M', 'ợ'), + (0x1EE3, 'V'), + (0x1EE4, 'M', 'ụ'), + (0x1EE5, 'V'), + (0x1EE6, 'M', 'ủ'), + (0x1EE7, 'V'), + (0x1EE8, 'M', 'ứ'), + (0x1EE9, 'V'), + (0x1EEA, 'M', 'ừ'), + (0x1EEB, 'V'), + (0x1EEC, 'M', 'ử'), + (0x1EED, 'V'), + (0x1EEE, 'M', 'ữ'), + (0x1EEF, 'V'), + (0x1EF0, 'M', 'ự'), + ] + +def _seg_19() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1EF1, 'V'), + (0x1EF2, 'M', 'ỳ'), + (0x1EF3, 'V'), + (0x1EF4, 'M', 'ỵ'), + (0x1EF5, 'V'), + (0x1EF6, 'M', 'ỷ'), + (0x1EF7, 'V'), + (0x1EF8, 'M', 'ỹ'), + (0x1EF9, 'V'), + (0x1EFA, 'M', 'ỻ'), + (0x1EFB, 'V'), + (0x1EFC, 'M', 'ỽ'), + (0x1EFD, 'V'), + (0x1EFE, 'M', 'ỿ'), + (0x1EFF, 'V'), + (0x1F08, 'M', 'ἀ'), + (0x1F09, 'M', 'ἁ'), + (0x1F0A, 'M', 'ἂ'), + (0x1F0B, 'M', 'ἃ'), + (0x1F0C, 'M', 'ἄ'), + (0x1F0D, 'M', 'ἅ'), + (0x1F0E, 'M', 'ἆ'), + (0x1F0F, 'M', 'ἇ'), + (0x1F10, 'V'), + (0x1F16, 'X'), + (0x1F18, 'M', 'ἐ'), + (0x1F19, 'M', 'ἑ'), + (0x1F1A, 'M', 'ἒ'), + (0x1F1B, 'M', 'ἓ'), + (0x1F1C, 'M', 'ἔ'), + (0x1F1D, 'M', 'ἕ'), + (0x1F1E, 'X'), + (0x1F20, 'V'), + (0x1F28, 'M', 'ἠ'), + (0x1F29, 'M', 'ἡ'), + (0x1F2A, 'M', 'ἢ'), + (0x1F2B, 'M', 'ἣ'), + (0x1F2C, 'M', 'ἤ'), + (0x1F2D, 'M', 'ἥ'), + (0x1F2E, 'M', 'ἦ'), + (0x1F2F, 'M', 'ἧ'), + (0x1F30, 'V'), + (0x1F38, 'M', 'ἰ'), + (0x1F39, 'M', 'ἱ'), + (0x1F3A, 'M', 'ἲ'), + (0x1F3B, 'M', 'ἳ'), + (0x1F3C, 'M', 'ἴ'), + (0x1F3D, 'M', 'ἵ'), + (0x1F3E, 'M', 'ἶ'), + (0x1F3F, 'M', 'ἷ'), + (0x1F40, 'V'), + (0x1F46, 'X'), + (0x1F48, 'M', 'ὀ'), + (0x1F49, 'M', 'ὁ'), + (0x1F4A, 'M', 'ὂ'), + (0x1F4B, 'M', 'ὃ'), + (0x1F4C, 'M', 'ὄ'), + (0x1F4D, 'M', 'ὅ'), + (0x1F4E, 'X'), + (0x1F50, 'V'), + (0x1F58, 'X'), + (0x1F59, 'M', 'ὑ'), + (0x1F5A, 'X'), + (0x1F5B, 'M', 'ὓ'), + (0x1F5C, 'X'), + (0x1F5D, 'M', 'ὕ'), + (0x1F5E, 'X'), + (0x1F5F, 'M', 'ὗ'), + (0x1F60, 'V'), + (0x1F68, 'M', 'ὠ'), + (0x1F69, 'M', 'ὡ'), + (0x1F6A, 'M', 'ὢ'), + (0x1F6B, 'M', 'ὣ'), + (0x1F6C, 'M', 'ὤ'), + (0x1F6D, 'M', 'ὥ'), + (0x1F6E, 'M', 'ὦ'), + (0x1F6F, 'M', 'ὧ'), + (0x1F70, 'V'), + (0x1F71, 'M', 'ά'), + (0x1F72, 'V'), + (0x1F73, 'M', 'έ'), + (0x1F74, 'V'), + (0x1F75, 'M', 'ή'), + (0x1F76, 'V'), + (0x1F77, 'M', 'ί'), + (0x1F78, 'V'), + (0x1F79, 'M', 'ό'), + (0x1F7A, 'V'), + (0x1F7B, 'M', 'ύ'), + (0x1F7C, 'V'), + (0x1F7D, 'M', 'ώ'), + (0x1F7E, 'X'), + (0x1F80, 'M', 'ἀι'), + (0x1F81, 'M', 'ἁι'), + (0x1F82, 'M', 'ἂι'), + (0x1F83, 'M', 'ἃι'), + (0x1F84, 'M', 'ἄι'), + (0x1F85, 'M', 'ἅι'), + (0x1F86, 'M', 'ἆι'), + (0x1F87, 'M', 'ἇι'), + ] + +def _seg_20() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1F88, 'M', 'ἀι'), + (0x1F89, 'M', 'ἁι'), + (0x1F8A, 'M', 'ἂι'), + (0x1F8B, 'M', 'ἃι'), + (0x1F8C, 'M', 'ἄι'), + (0x1F8D, 'M', 'ἅι'), + (0x1F8E, 'M', 'ἆι'), + (0x1F8F, 'M', 'ἇι'), + (0x1F90, 'M', 'ἠι'), + (0x1F91, 'M', 'ἡι'), + (0x1F92, 'M', 'ἢι'), + (0x1F93, 'M', 'ἣι'), + (0x1F94, 'M', 'ἤι'), + (0x1F95, 'M', 'ἥι'), + (0x1F96, 'M', 'ἦι'), + (0x1F97, 'M', 'ἧι'), + (0x1F98, 'M', 'ἠι'), + (0x1F99, 'M', 'ἡι'), + (0x1F9A, 'M', 'ἢι'), + (0x1F9B, 'M', 'ἣι'), + (0x1F9C, 'M', 'ἤι'), + (0x1F9D, 'M', 'ἥι'), + (0x1F9E, 'M', 'ἦι'), + (0x1F9F, 'M', 'ἧι'), + (0x1FA0, 'M', 'ὠι'), + (0x1FA1, 'M', 'ὡι'), + (0x1FA2, 'M', 'ὢι'), + (0x1FA3, 'M', 'ὣι'), + (0x1FA4, 'M', 'ὤι'), + (0x1FA5, 'M', 'ὥι'), + (0x1FA6, 'M', 'ὦι'), + (0x1FA7, 'M', 'ὧι'), + (0x1FA8, 'M', 'ὠι'), + (0x1FA9, 'M', 'ὡι'), + (0x1FAA, 'M', 'ὢι'), + (0x1FAB, 'M', 'ὣι'), + (0x1FAC, 'M', 'ὤι'), + (0x1FAD, 'M', 'ὥι'), + (0x1FAE, 'M', 'ὦι'), + (0x1FAF, 'M', 'ὧι'), + (0x1FB0, 'V'), + (0x1FB2, 'M', 'ὰι'), + (0x1FB3, 'M', 'αι'), + (0x1FB4, 'M', 'άι'), + (0x1FB5, 'X'), + (0x1FB6, 'V'), + (0x1FB7, 'M', 'ᾶι'), + (0x1FB8, 'M', 'ᾰ'), + (0x1FB9, 'M', 'ᾱ'), + (0x1FBA, 'M', 'ὰ'), + (0x1FBB, 'M', 'ά'), + (0x1FBC, 'M', 'αι'), + (0x1FBD, '3', ' ̓'), + (0x1FBE, 'M', 'ι'), + (0x1FBF, '3', ' ̓'), + (0x1FC0, '3', ' ͂'), + (0x1FC1, '3', ' ̈͂'), + (0x1FC2, 'M', 'ὴι'), + (0x1FC3, 'M', 'ηι'), + (0x1FC4, 'M', 'ήι'), + (0x1FC5, 'X'), + (0x1FC6, 'V'), + (0x1FC7, 'M', 'ῆι'), + (0x1FC8, 'M', 'ὲ'), + (0x1FC9, 'M', 'έ'), + (0x1FCA, 'M', 'ὴ'), + (0x1FCB, 'M', 'ή'), + (0x1FCC, 'M', 'ηι'), + (0x1FCD, '3', ' ̓̀'), + (0x1FCE, '3', ' ̓́'), + (0x1FCF, '3', ' ̓͂'), + (0x1FD0, 'V'), + (0x1FD3, 'M', 'ΐ'), + (0x1FD4, 'X'), + (0x1FD6, 'V'), + (0x1FD8, 'M', 'ῐ'), + (0x1FD9, 'M', 'ῑ'), + (0x1FDA, 'M', 'ὶ'), + (0x1FDB, 'M', 'ί'), + (0x1FDC, 'X'), + (0x1FDD, '3', ' ̔̀'), + (0x1FDE, '3', ' ̔́'), + (0x1FDF, '3', ' ̔͂'), + (0x1FE0, 'V'), + (0x1FE3, 'M', 'ΰ'), + (0x1FE4, 'V'), + (0x1FE8, 'M', 'ῠ'), + (0x1FE9, 'M', 'ῡ'), + (0x1FEA, 'M', 'ὺ'), + (0x1FEB, 'M', 'ύ'), + (0x1FEC, 'M', 'ῥ'), + (0x1FED, '3', ' ̈̀'), + (0x1FEE, '3', ' ̈́'), + (0x1FEF, '3', '`'), + (0x1FF0, 'X'), + (0x1FF2, 'M', 'ὼι'), + (0x1FF3, 'M', 'ωι'), + (0x1FF4, 'M', 'ώι'), + (0x1FF5, 'X'), + (0x1FF6, 'V'), + ] + +def _seg_21() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1FF7, 'M', 'ῶι'), + (0x1FF8, 'M', 'ὸ'), + (0x1FF9, 'M', 'ό'), + (0x1FFA, 'M', 'ὼ'), + (0x1FFB, 'M', 'ώ'), + (0x1FFC, 'M', 'ωι'), + (0x1FFD, '3', ' ́'), + (0x1FFE, '3', ' ̔'), + (0x1FFF, 'X'), + (0x2000, '3', ' '), + (0x200B, 'I'), + (0x200C, 'D', ''), + (0x200E, 'X'), + (0x2010, 'V'), + (0x2011, 'M', '‐'), + (0x2012, 'V'), + (0x2017, '3', ' ̳'), + (0x2018, 'V'), + (0x2024, 'X'), + (0x2027, 'V'), + (0x2028, 'X'), + (0x202F, '3', ' '), + (0x2030, 'V'), + (0x2033, 'M', '′′'), + (0x2034, 'M', '′′′'), + (0x2035, 'V'), + (0x2036, 'M', '‵‵'), + (0x2037, 'M', '‵‵‵'), + (0x2038, 'V'), + (0x203C, '3', '!!'), + (0x203D, 'V'), + (0x203E, '3', ' ̅'), + (0x203F, 'V'), + (0x2047, '3', '??'), + (0x2048, '3', '?!'), + (0x2049, '3', '!?'), + (0x204A, 'V'), + (0x2057, 'M', '′′′′'), + (0x2058, 'V'), + (0x205F, '3', ' '), + (0x2060, 'I'), + (0x2061, 'X'), + (0x2064, 'I'), + (0x2065, 'X'), + (0x2070, 'M', '0'), + (0x2071, 'M', 'i'), + (0x2072, 'X'), + (0x2074, 'M', '4'), + (0x2075, 'M', '5'), + (0x2076, 'M', '6'), + (0x2077, 'M', '7'), + (0x2078, 'M', '8'), + (0x2079, 'M', '9'), + (0x207A, '3', '+'), + (0x207B, 'M', '−'), + (0x207C, '3', '='), + (0x207D, '3', '('), + (0x207E, '3', ')'), + (0x207F, 'M', 'n'), + (0x2080, 'M', '0'), + (0x2081, 'M', '1'), + (0x2082, 'M', '2'), + (0x2083, 'M', '3'), + (0x2084, 'M', '4'), + (0x2085, 'M', '5'), + (0x2086, 'M', '6'), + (0x2087, 'M', '7'), + (0x2088, 'M', '8'), + (0x2089, 'M', '9'), + (0x208A, '3', '+'), + (0x208B, 'M', '−'), + (0x208C, '3', '='), + (0x208D, '3', '('), + (0x208E, '3', ')'), + (0x208F, 'X'), + (0x2090, 'M', 'a'), + (0x2091, 'M', 'e'), + (0x2092, 'M', 'o'), + (0x2093, 'M', 'x'), + (0x2094, 'M', 'ə'), + (0x2095, 'M', 'h'), + (0x2096, 'M', 'k'), + (0x2097, 'M', 'l'), + (0x2098, 'M', 'm'), + (0x2099, 'M', 'n'), + (0x209A, 'M', 'p'), + (0x209B, 'M', 's'), + (0x209C, 'M', 't'), + (0x209D, 'X'), + (0x20A0, 'V'), + (0x20A8, 'M', 'rs'), + (0x20A9, 'V'), + (0x20C1, 'X'), + (0x20D0, 'V'), + (0x20F1, 'X'), + (0x2100, '3', 'a/c'), + (0x2101, '3', 'a/s'), + (0x2102, 'M', 'c'), + (0x2103, 'M', '°c'), + (0x2104, 'V'), + ] + +def _seg_22() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2105, '3', 'c/o'), + (0x2106, '3', 'c/u'), + (0x2107, 'M', 'ɛ'), + (0x2108, 'V'), + (0x2109, 'M', '°f'), + (0x210A, 'M', 'g'), + (0x210B, 'M', 'h'), + (0x210F, 'M', 'ħ'), + (0x2110, 'M', 'i'), + (0x2112, 'M', 'l'), + (0x2114, 'V'), + (0x2115, 'M', 'n'), + (0x2116, 'M', 'no'), + (0x2117, 'V'), + (0x2119, 'M', 'p'), + (0x211A, 'M', 'q'), + (0x211B, 'M', 'r'), + (0x211E, 'V'), + (0x2120, 'M', 'sm'), + (0x2121, 'M', 'tel'), + (0x2122, 'M', 'tm'), + (0x2123, 'V'), + (0x2124, 'M', 'z'), + (0x2125, 'V'), + (0x2126, 'M', 'ω'), + (0x2127, 'V'), + (0x2128, 'M', 'z'), + (0x2129, 'V'), + (0x212A, 'M', 'k'), + (0x212B, 'M', 'å'), + (0x212C, 'M', 'b'), + (0x212D, 'M', 'c'), + (0x212E, 'V'), + (0x212F, 'M', 'e'), + (0x2131, 'M', 'f'), + (0x2132, 'X'), + (0x2133, 'M', 'm'), + (0x2134, 'M', 'o'), + (0x2135, 'M', 'א'), + (0x2136, 'M', 'ב'), + (0x2137, 'M', 'ג'), + (0x2138, 'M', 'ד'), + (0x2139, 'M', 'i'), + (0x213A, 'V'), + (0x213B, 'M', 'fax'), + (0x213C, 'M', 'π'), + (0x213D, 'M', 'γ'), + (0x213F, 'M', 'π'), + (0x2140, 'M', '∑'), + (0x2141, 'V'), + (0x2145, 'M', 'd'), + (0x2147, 'M', 'e'), + (0x2148, 'M', 'i'), + (0x2149, 'M', 'j'), + (0x214A, 'V'), + (0x2150, 'M', '1⁄7'), + (0x2151, 'M', '1⁄9'), + (0x2152, 'M', '1⁄10'), + (0x2153, 'M', '1⁄3'), + (0x2154, 'M', '2⁄3'), + (0x2155, 'M', '1⁄5'), + (0x2156, 'M', '2⁄5'), + (0x2157, 'M', '3⁄5'), + (0x2158, 'M', '4⁄5'), + (0x2159, 'M', '1⁄6'), + (0x215A, 'M', '5⁄6'), + (0x215B, 'M', '1⁄8'), + (0x215C, 'M', '3⁄8'), + (0x215D, 'M', '5⁄8'), + (0x215E, 'M', '7⁄8'), + (0x215F, 'M', '1⁄'), + (0x2160, 'M', 'i'), + (0x2161, 'M', 'ii'), + (0x2162, 'M', 'iii'), + (0x2163, 'M', 'iv'), + (0x2164, 'M', 'v'), + (0x2165, 'M', 'vi'), + (0x2166, 'M', 'vii'), + (0x2167, 'M', 'viii'), + (0x2168, 'M', 'ix'), + (0x2169, 'M', 'x'), + (0x216A, 'M', 'xi'), + (0x216B, 'M', 'xii'), + (0x216C, 'M', 'l'), + (0x216D, 'M', 'c'), + (0x216E, 'M', 'd'), + (0x216F, 'M', 'm'), + (0x2170, 'M', 'i'), + (0x2171, 'M', 'ii'), + (0x2172, 'M', 'iii'), + (0x2173, 'M', 'iv'), + (0x2174, 'M', 'v'), + (0x2175, 'M', 'vi'), + (0x2176, 'M', 'vii'), + (0x2177, 'M', 'viii'), + (0x2178, 'M', 'ix'), + (0x2179, 'M', 'x'), + (0x217A, 'M', 'xi'), + (0x217B, 'M', 'xii'), + (0x217C, 'M', 'l'), + ] + +def _seg_23() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x217D, 'M', 'c'), + (0x217E, 'M', 'd'), + (0x217F, 'M', 'm'), + (0x2180, 'V'), + (0x2183, 'X'), + (0x2184, 'V'), + (0x2189, 'M', '0⁄3'), + (0x218A, 'V'), + (0x218C, 'X'), + (0x2190, 'V'), + (0x222C, 'M', '∫∫'), + (0x222D, 'M', '∫∫∫'), + (0x222E, 'V'), + (0x222F, 'M', '∮∮'), + (0x2230, 'M', '∮∮∮'), + (0x2231, 'V'), + (0x2260, '3'), + (0x2261, 'V'), + (0x226E, '3'), + (0x2270, 'V'), + (0x2329, 'M', '〈'), + (0x232A, 'M', '〉'), + (0x232B, 'V'), + (0x2427, 'X'), + (0x2440, 'V'), + (0x244B, 'X'), + (0x2460, 'M', '1'), + (0x2461, 'M', '2'), + (0x2462, 'M', '3'), + (0x2463, 'M', '4'), + (0x2464, 'M', '5'), + (0x2465, 'M', '6'), + (0x2466, 'M', '7'), + (0x2467, 'M', '8'), + (0x2468, 'M', '9'), + (0x2469, 'M', '10'), + (0x246A, 'M', '11'), + (0x246B, 'M', '12'), + (0x246C, 'M', '13'), + (0x246D, 'M', '14'), + (0x246E, 'M', '15'), + (0x246F, 'M', '16'), + (0x2470, 'M', '17'), + (0x2471, 'M', '18'), + (0x2472, 'M', '19'), + (0x2473, 'M', '20'), + (0x2474, '3', '(1)'), + (0x2475, '3', '(2)'), + (0x2476, '3', '(3)'), + (0x2477, '3', '(4)'), + (0x2478, '3', '(5)'), + (0x2479, '3', '(6)'), + (0x247A, '3', '(7)'), + (0x247B, '3', '(8)'), + (0x247C, '3', '(9)'), + (0x247D, '3', '(10)'), + (0x247E, '3', '(11)'), + (0x247F, '3', '(12)'), + (0x2480, '3', '(13)'), + (0x2481, '3', '(14)'), + (0x2482, '3', '(15)'), + (0x2483, '3', '(16)'), + (0x2484, '3', '(17)'), + (0x2485, '3', '(18)'), + (0x2486, '3', '(19)'), + (0x2487, '3', '(20)'), + (0x2488, 'X'), + (0x249C, '3', '(a)'), + (0x249D, '3', '(b)'), + (0x249E, '3', '(c)'), + (0x249F, '3', '(d)'), + (0x24A0, '3', '(e)'), + (0x24A1, '3', '(f)'), + (0x24A2, '3', '(g)'), + (0x24A3, '3', '(h)'), + (0x24A4, '3', '(i)'), + (0x24A5, '3', '(j)'), + (0x24A6, '3', '(k)'), + (0x24A7, '3', '(l)'), + (0x24A8, '3', '(m)'), + (0x24A9, '3', '(n)'), + (0x24AA, '3', '(o)'), + (0x24AB, '3', '(p)'), + (0x24AC, '3', '(q)'), + (0x24AD, '3', '(r)'), + (0x24AE, '3', '(s)'), + (0x24AF, '3', '(t)'), + (0x24B0, '3', '(u)'), + (0x24B1, '3', '(v)'), + (0x24B2, '3', '(w)'), + (0x24B3, '3', '(x)'), + (0x24B4, '3', '(y)'), + (0x24B5, '3', '(z)'), + (0x24B6, 'M', 'a'), + (0x24B7, 'M', 'b'), + (0x24B8, 'M', 'c'), + (0x24B9, 'M', 'd'), + (0x24BA, 'M', 'e'), + (0x24BB, 'M', 'f'), + (0x24BC, 'M', 'g'), + ] + +def _seg_24() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x24BD, 'M', 'h'), + (0x24BE, 'M', 'i'), + (0x24BF, 'M', 'j'), + (0x24C0, 'M', 'k'), + (0x24C1, 'M', 'l'), + (0x24C2, 'M', 'm'), + (0x24C3, 'M', 'n'), + (0x24C4, 'M', 'o'), + (0x24C5, 'M', 'p'), + (0x24C6, 'M', 'q'), + (0x24C7, 'M', 'r'), + (0x24C8, 'M', 's'), + (0x24C9, 'M', 't'), + (0x24CA, 'M', 'u'), + (0x24CB, 'M', 'v'), + (0x24CC, 'M', 'w'), + (0x24CD, 'M', 'x'), + (0x24CE, 'M', 'y'), + (0x24CF, 'M', 'z'), + (0x24D0, 'M', 'a'), + (0x24D1, 'M', 'b'), + (0x24D2, 'M', 'c'), + (0x24D3, 'M', 'd'), + (0x24D4, 'M', 'e'), + (0x24D5, 'M', 'f'), + (0x24D6, 'M', 'g'), + (0x24D7, 'M', 'h'), + (0x24D8, 'M', 'i'), + (0x24D9, 'M', 'j'), + (0x24DA, 'M', 'k'), + (0x24DB, 'M', 'l'), + (0x24DC, 'M', 'm'), + (0x24DD, 'M', 'n'), + (0x24DE, 'M', 'o'), + (0x24DF, 'M', 'p'), + (0x24E0, 'M', 'q'), + (0x24E1, 'M', 'r'), + (0x24E2, 'M', 's'), + (0x24E3, 'M', 't'), + (0x24E4, 'M', 'u'), + (0x24E5, 'M', 'v'), + (0x24E6, 'M', 'w'), + (0x24E7, 'M', 'x'), + (0x24E8, 'M', 'y'), + (0x24E9, 'M', 'z'), + (0x24EA, 'M', '0'), + (0x24EB, 'V'), + (0x2A0C, 'M', '∫∫∫∫'), + (0x2A0D, 'V'), + (0x2A74, '3', '::='), + (0x2A75, '3', '=='), + (0x2A76, '3', '==='), + (0x2A77, 'V'), + (0x2ADC, 'M', '⫝̸'), + (0x2ADD, 'V'), + (0x2B74, 'X'), + (0x2B76, 'V'), + (0x2B96, 'X'), + (0x2B97, 'V'), + (0x2C00, 'M', 'ⰰ'), + (0x2C01, 'M', 'ⰱ'), + (0x2C02, 'M', 'ⰲ'), + (0x2C03, 'M', 'ⰳ'), + (0x2C04, 'M', 'ⰴ'), + (0x2C05, 'M', 'ⰵ'), + (0x2C06, 'M', 'ⰶ'), + (0x2C07, 'M', 'ⰷ'), + (0x2C08, 'M', 'ⰸ'), + (0x2C09, 'M', 'ⰹ'), + (0x2C0A, 'M', 'ⰺ'), + (0x2C0B, 'M', 'ⰻ'), + (0x2C0C, 'M', 'ⰼ'), + (0x2C0D, 'M', 'ⰽ'), + (0x2C0E, 'M', 'ⰾ'), + (0x2C0F, 'M', 'ⰿ'), + (0x2C10, 'M', 'ⱀ'), + (0x2C11, 'M', 'ⱁ'), + (0x2C12, 'M', 'ⱂ'), + (0x2C13, 'M', 'ⱃ'), + (0x2C14, 'M', 'ⱄ'), + (0x2C15, 'M', 'ⱅ'), + (0x2C16, 'M', 'ⱆ'), + (0x2C17, 'M', 'ⱇ'), + (0x2C18, 'M', 'ⱈ'), + (0x2C19, 'M', 'ⱉ'), + (0x2C1A, 'M', 'ⱊ'), + (0x2C1B, 'M', 'ⱋ'), + (0x2C1C, 'M', 'ⱌ'), + (0x2C1D, 'M', 'ⱍ'), + (0x2C1E, 'M', 'ⱎ'), + (0x2C1F, 'M', 'ⱏ'), + (0x2C20, 'M', 'ⱐ'), + (0x2C21, 'M', 'ⱑ'), + (0x2C22, 'M', 'ⱒ'), + (0x2C23, 'M', 'ⱓ'), + (0x2C24, 'M', 'ⱔ'), + (0x2C25, 'M', 'ⱕ'), + (0x2C26, 'M', 'ⱖ'), + (0x2C27, 'M', 'ⱗ'), + (0x2C28, 'M', 'ⱘ'), + ] + +def _seg_25() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2C29, 'M', 'ⱙ'), + (0x2C2A, 'M', 'ⱚ'), + (0x2C2B, 'M', 'ⱛ'), + (0x2C2C, 'M', 'ⱜ'), + (0x2C2D, 'M', 'ⱝ'), + (0x2C2E, 'M', 'ⱞ'), + (0x2C2F, 'M', 'ⱟ'), + (0x2C30, 'V'), + (0x2C60, 'M', 'ⱡ'), + (0x2C61, 'V'), + (0x2C62, 'M', 'ɫ'), + (0x2C63, 'M', 'ᵽ'), + (0x2C64, 'M', 'ɽ'), + (0x2C65, 'V'), + (0x2C67, 'M', 'ⱨ'), + (0x2C68, 'V'), + (0x2C69, 'M', 'ⱪ'), + (0x2C6A, 'V'), + (0x2C6B, 'M', 'ⱬ'), + (0x2C6C, 'V'), + (0x2C6D, 'M', 'ɑ'), + (0x2C6E, 'M', 'ɱ'), + (0x2C6F, 'M', 'ɐ'), + (0x2C70, 'M', 'ɒ'), + (0x2C71, 'V'), + (0x2C72, 'M', 'ⱳ'), + (0x2C73, 'V'), + (0x2C75, 'M', 'ⱶ'), + (0x2C76, 'V'), + (0x2C7C, 'M', 'j'), + (0x2C7D, 'M', 'v'), + (0x2C7E, 'M', 'ȿ'), + (0x2C7F, 'M', 'ɀ'), + (0x2C80, 'M', 'ⲁ'), + (0x2C81, 'V'), + (0x2C82, 'M', 'ⲃ'), + (0x2C83, 'V'), + (0x2C84, 'M', 'ⲅ'), + (0x2C85, 'V'), + (0x2C86, 'M', 'ⲇ'), + (0x2C87, 'V'), + (0x2C88, 'M', 'ⲉ'), + (0x2C89, 'V'), + (0x2C8A, 'M', 'ⲋ'), + (0x2C8B, 'V'), + (0x2C8C, 'M', 'ⲍ'), + (0x2C8D, 'V'), + (0x2C8E, 'M', 'ⲏ'), + (0x2C8F, 'V'), + (0x2C90, 'M', 'ⲑ'), + (0x2C91, 'V'), + (0x2C92, 'M', 'ⲓ'), + (0x2C93, 'V'), + (0x2C94, 'M', 'ⲕ'), + (0x2C95, 'V'), + (0x2C96, 'M', 'ⲗ'), + (0x2C97, 'V'), + (0x2C98, 'M', 'ⲙ'), + (0x2C99, 'V'), + (0x2C9A, 'M', 'ⲛ'), + (0x2C9B, 'V'), + (0x2C9C, 'M', 'ⲝ'), + (0x2C9D, 'V'), + (0x2C9E, 'M', 'ⲟ'), + (0x2C9F, 'V'), + (0x2CA0, 'M', 'ⲡ'), + (0x2CA1, 'V'), + (0x2CA2, 'M', 'ⲣ'), + (0x2CA3, 'V'), + (0x2CA4, 'M', 'ⲥ'), + (0x2CA5, 'V'), + (0x2CA6, 'M', 'ⲧ'), + (0x2CA7, 'V'), + (0x2CA8, 'M', 'ⲩ'), + (0x2CA9, 'V'), + (0x2CAA, 'M', 'ⲫ'), + (0x2CAB, 'V'), + (0x2CAC, 'M', 'ⲭ'), + (0x2CAD, 'V'), + (0x2CAE, 'M', 'ⲯ'), + (0x2CAF, 'V'), + (0x2CB0, 'M', 'ⲱ'), + (0x2CB1, 'V'), + (0x2CB2, 'M', 'ⲳ'), + (0x2CB3, 'V'), + (0x2CB4, 'M', 'ⲵ'), + (0x2CB5, 'V'), + (0x2CB6, 'M', 'ⲷ'), + (0x2CB7, 'V'), + (0x2CB8, 'M', 'ⲹ'), + (0x2CB9, 'V'), + (0x2CBA, 'M', 'ⲻ'), + (0x2CBB, 'V'), + (0x2CBC, 'M', 'ⲽ'), + (0x2CBD, 'V'), + (0x2CBE, 'M', 'ⲿ'), + (0x2CBF, 'V'), + (0x2CC0, 'M', 'ⳁ'), + (0x2CC1, 'V'), + (0x2CC2, 'M', 'ⳃ'), + ] + +def _seg_26() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2CC3, 'V'), + (0x2CC4, 'M', 'ⳅ'), + (0x2CC5, 'V'), + (0x2CC6, 'M', 'ⳇ'), + (0x2CC7, 'V'), + (0x2CC8, 'M', 'ⳉ'), + (0x2CC9, 'V'), + (0x2CCA, 'M', 'ⳋ'), + (0x2CCB, 'V'), + (0x2CCC, 'M', 'ⳍ'), + (0x2CCD, 'V'), + (0x2CCE, 'M', 'ⳏ'), + (0x2CCF, 'V'), + (0x2CD0, 'M', 'ⳑ'), + (0x2CD1, 'V'), + (0x2CD2, 'M', 'ⳓ'), + (0x2CD3, 'V'), + (0x2CD4, 'M', 'ⳕ'), + (0x2CD5, 'V'), + (0x2CD6, 'M', 'ⳗ'), + (0x2CD7, 'V'), + (0x2CD8, 'M', 'ⳙ'), + (0x2CD9, 'V'), + (0x2CDA, 'M', 'ⳛ'), + (0x2CDB, 'V'), + (0x2CDC, 'M', 'ⳝ'), + (0x2CDD, 'V'), + (0x2CDE, 'M', 'ⳟ'), + (0x2CDF, 'V'), + (0x2CE0, 'M', 'ⳡ'), + (0x2CE1, 'V'), + (0x2CE2, 'M', 'ⳣ'), + (0x2CE3, 'V'), + (0x2CEB, 'M', 'ⳬ'), + (0x2CEC, 'V'), + (0x2CED, 'M', 'ⳮ'), + (0x2CEE, 'V'), + (0x2CF2, 'M', 'ⳳ'), + (0x2CF3, 'V'), + (0x2CF4, 'X'), + (0x2CF9, 'V'), + (0x2D26, 'X'), + (0x2D27, 'V'), + (0x2D28, 'X'), + (0x2D2D, 'V'), + (0x2D2E, 'X'), + (0x2D30, 'V'), + (0x2D68, 'X'), + (0x2D6F, 'M', 'ⵡ'), + (0x2D70, 'V'), + (0x2D71, 'X'), + (0x2D7F, 'V'), + (0x2D97, 'X'), + (0x2DA0, 'V'), + (0x2DA7, 'X'), + (0x2DA8, 'V'), + (0x2DAF, 'X'), + (0x2DB0, 'V'), + (0x2DB7, 'X'), + (0x2DB8, 'V'), + (0x2DBF, 'X'), + (0x2DC0, 'V'), + (0x2DC7, 'X'), + (0x2DC8, 'V'), + (0x2DCF, 'X'), + (0x2DD0, 'V'), + (0x2DD7, 'X'), + (0x2DD8, 'V'), + (0x2DDF, 'X'), + (0x2DE0, 'V'), + (0x2E5E, 'X'), + (0x2E80, 'V'), + (0x2E9A, 'X'), + (0x2E9B, 'V'), + (0x2E9F, 'M', '母'), + (0x2EA0, 'V'), + (0x2EF3, 'M', '龟'), + (0x2EF4, 'X'), + (0x2F00, 'M', '一'), + (0x2F01, 'M', '丨'), + (0x2F02, 'M', '丶'), + (0x2F03, 'M', '丿'), + (0x2F04, 'M', '乙'), + (0x2F05, 'M', '亅'), + (0x2F06, 'M', '二'), + (0x2F07, 'M', '亠'), + (0x2F08, 'M', '人'), + (0x2F09, 'M', '儿'), + (0x2F0A, 'M', '入'), + (0x2F0B, 'M', '八'), + (0x2F0C, 'M', '冂'), + (0x2F0D, 'M', '冖'), + (0x2F0E, 'M', '冫'), + (0x2F0F, 'M', '几'), + (0x2F10, 'M', '凵'), + (0x2F11, 'M', '刀'), + (0x2F12, 'M', '力'), + (0x2F13, 'M', '勹'), + (0x2F14, 'M', '匕'), + (0x2F15, 'M', '匚'), + ] + +def _seg_27() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F16, 'M', '匸'), + (0x2F17, 'M', '十'), + (0x2F18, 'M', '卜'), + (0x2F19, 'M', '卩'), + (0x2F1A, 'M', '厂'), + (0x2F1B, 'M', '厶'), + (0x2F1C, 'M', '又'), + (0x2F1D, 'M', '口'), + (0x2F1E, 'M', '囗'), + (0x2F1F, 'M', '土'), + (0x2F20, 'M', '士'), + (0x2F21, 'M', '夂'), + (0x2F22, 'M', '夊'), + (0x2F23, 'M', '夕'), + (0x2F24, 'M', '大'), + (0x2F25, 'M', '女'), + (0x2F26, 'M', '子'), + (0x2F27, 'M', '宀'), + (0x2F28, 'M', '寸'), + (0x2F29, 'M', '小'), + (0x2F2A, 'M', '尢'), + (0x2F2B, 'M', '尸'), + (0x2F2C, 'M', '屮'), + (0x2F2D, 'M', '山'), + (0x2F2E, 'M', '巛'), + (0x2F2F, 'M', '工'), + (0x2F30, 'M', '己'), + (0x2F31, 'M', '巾'), + (0x2F32, 'M', '干'), + (0x2F33, 'M', '幺'), + (0x2F34, 'M', '广'), + (0x2F35, 'M', '廴'), + (0x2F36, 'M', '廾'), + (0x2F37, 'M', '弋'), + (0x2F38, 'M', '弓'), + (0x2F39, 'M', '彐'), + (0x2F3A, 'M', '彡'), + (0x2F3B, 'M', '彳'), + (0x2F3C, 'M', '心'), + (0x2F3D, 'M', '戈'), + (0x2F3E, 'M', '戶'), + (0x2F3F, 'M', '手'), + (0x2F40, 'M', '支'), + (0x2F41, 'M', '攴'), + (0x2F42, 'M', '文'), + (0x2F43, 'M', '斗'), + (0x2F44, 'M', '斤'), + (0x2F45, 'M', '方'), + (0x2F46, 'M', '无'), + (0x2F47, 'M', '日'), + (0x2F48, 'M', '曰'), + (0x2F49, 'M', '月'), + (0x2F4A, 'M', '木'), + (0x2F4B, 'M', '欠'), + (0x2F4C, 'M', '止'), + (0x2F4D, 'M', '歹'), + (0x2F4E, 'M', '殳'), + (0x2F4F, 'M', '毋'), + (0x2F50, 'M', '比'), + (0x2F51, 'M', '毛'), + (0x2F52, 'M', '氏'), + (0x2F53, 'M', '气'), + (0x2F54, 'M', '水'), + (0x2F55, 'M', '火'), + (0x2F56, 'M', '爪'), + (0x2F57, 'M', '父'), + (0x2F58, 'M', '爻'), + (0x2F59, 'M', '爿'), + (0x2F5A, 'M', '片'), + (0x2F5B, 'M', '牙'), + (0x2F5C, 'M', '牛'), + (0x2F5D, 'M', '犬'), + (0x2F5E, 'M', '玄'), + (0x2F5F, 'M', '玉'), + (0x2F60, 'M', '瓜'), + (0x2F61, 'M', '瓦'), + (0x2F62, 'M', '甘'), + (0x2F63, 'M', '生'), + (0x2F64, 'M', '用'), + (0x2F65, 'M', '田'), + (0x2F66, 'M', '疋'), + (0x2F67, 'M', '疒'), + (0x2F68, 'M', '癶'), + (0x2F69, 'M', '白'), + (0x2F6A, 'M', '皮'), + (0x2F6B, 'M', '皿'), + (0x2F6C, 'M', '目'), + (0x2F6D, 'M', '矛'), + (0x2F6E, 'M', '矢'), + (0x2F6F, 'M', '石'), + (0x2F70, 'M', '示'), + (0x2F71, 'M', '禸'), + (0x2F72, 'M', '禾'), + (0x2F73, 'M', '穴'), + (0x2F74, 'M', '立'), + (0x2F75, 'M', '竹'), + (0x2F76, 'M', '米'), + (0x2F77, 'M', '糸'), + (0x2F78, 'M', '缶'), + (0x2F79, 'M', '网'), + ] + +def _seg_28() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F7A, 'M', '羊'), + (0x2F7B, 'M', '羽'), + (0x2F7C, 'M', '老'), + (0x2F7D, 'M', '而'), + (0x2F7E, 'M', '耒'), + (0x2F7F, 'M', '耳'), + (0x2F80, 'M', '聿'), + (0x2F81, 'M', '肉'), + (0x2F82, 'M', '臣'), + (0x2F83, 'M', '自'), + (0x2F84, 'M', '至'), + (0x2F85, 'M', '臼'), + (0x2F86, 'M', '舌'), + (0x2F87, 'M', '舛'), + (0x2F88, 'M', '舟'), + (0x2F89, 'M', '艮'), + (0x2F8A, 'M', '色'), + (0x2F8B, 'M', '艸'), + (0x2F8C, 'M', '虍'), + (0x2F8D, 'M', '虫'), + (0x2F8E, 'M', '血'), + (0x2F8F, 'M', '行'), + (0x2F90, 'M', '衣'), + (0x2F91, 'M', '襾'), + (0x2F92, 'M', '見'), + (0x2F93, 'M', '角'), + (0x2F94, 'M', '言'), + (0x2F95, 'M', '谷'), + (0x2F96, 'M', '豆'), + (0x2F97, 'M', '豕'), + (0x2F98, 'M', '豸'), + (0x2F99, 'M', '貝'), + (0x2F9A, 'M', '赤'), + (0x2F9B, 'M', '走'), + (0x2F9C, 'M', '足'), + (0x2F9D, 'M', '身'), + (0x2F9E, 'M', '車'), + (0x2F9F, 'M', '辛'), + (0x2FA0, 'M', '辰'), + (0x2FA1, 'M', '辵'), + (0x2FA2, 'M', '邑'), + (0x2FA3, 'M', '酉'), + (0x2FA4, 'M', '釆'), + (0x2FA5, 'M', '里'), + (0x2FA6, 'M', '金'), + (0x2FA7, 'M', '長'), + (0x2FA8, 'M', '門'), + (0x2FA9, 'M', '阜'), + (0x2FAA, 'M', '隶'), + (0x2FAB, 'M', '隹'), + (0x2FAC, 'M', '雨'), + (0x2FAD, 'M', '靑'), + (0x2FAE, 'M', '非'), + (0x2FAF, 'M', '面'), + (0x2FB0, 'M', '革'), + (0x2FB1, 'M', '韋'), + (0x2FB2, 'M', '韭'), + (0x2FB3, 'M', '音'), + (0x2FB4, 'M', '頁'), + (0x2FB5, 'M', '風'), + (0x2FB6, 'M', '飛'), + (0x2FB7, 'M', '食'), + (0x2FB8, 'M', '首'), + (0x2FB9, 'M', '香'), + (0x2FBA, 'M', '馬'), + (0x2FBB, 'M', '骨'), + (0x2FBC, 'M', '高'), + (0x2FBD, 'M', '髟'), + (0x2FBE, 'M', '鬥'), + (0x2FBF, 'M', '鬯'), + (0x2FC0, 'M', '鬲'), + (0x2FC1, 'M', '鬼'), + (0x2FC2, 'M', '魚'), + (0x2FC3, 'M', '鳥'), + (0x2FC4, 'M', '鹵'), + (0x2FC5, 'M', '鹿'), + (0x2FC6, 'M', '麥'), + (0x2FC7, 'M', '麻'), + (0x2FC8, 'M', '黃'), + (0x2FC9, 'M', '黍'), + (0x2FCA, 'M', '黑'), + (0x2FCB, 'M', '黹'), + (0x2FCC, 'M', '黽'), + (0x2FCD, 'M', '鼎'), + (0x2FCE, 'M', '鼓'), + (0x2FCF, 'M', '鼠'), + (0x2FD0, 'M', '鼻'), + (0x2FD1, 'M', '齊'), + (0x2FD2, 'M', '齒'), + (0x2FD3, 'M', '龍'), + (0x2FD4, 'M', '龜'), + (0x2FD5, 'M', '龠'), + (0x2FD6, 'X'), + (0x3000, '3', ' '), + (0x3001, 'V'), + (0x3002, 'M', '.'), + (0x3003, 'V'), + (0x3036, 'M', '〒'), + (0x3037, 'V'), + (0x3038, 'M', '十'), + ] + +def _seg_29() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x3039, 'M', '卄'), + (0x303A, 'M', '卅'), + (0x303B, 'V'), + (0x3040, 'X'), + (0x3041, 'V'), + (0x3097, 'X'), + (0x3099, 'V'), + (0x309B, '3', ' ゙'), + (0x309C, '3', ' ゚'), + (0x309D, 'V'), + (0x309F, 'M', 'より'), + (0x30A0, 'V'), + (0x30FF, 'M', 'コト'), + (0x3100, 'X'), + (0x3105, 'V'), + (0x3130, 'X'), + (0x3131, 'M', 'ᄀ'), + (0x3132, 'M', 'ᄁ'), + (0x3133, 'M', 'ᆪ'), + (0x3134, 'M', 'ᄂ'), + (0x3135, 'M', 'ᆬ'), + (0x3136, 'M', 'ᆭ'), + (0x3137, 'M', 'ᄃ'), + (0x3138, 'M', 'ᄄ'), + (0x3139, 'M', 'ᄅ'), + (0x313A, 'M', 'ᆰ'), + (0x313B, 'M', 'ᆱ'), + (0x313C, 'M', 'ᆲ'), + (0x313D, 'M', 'ᆳ'), + (0x313E, 'M', 'ᆴ'), + (0x313F, 'M', 'ᆵ'), + (0x3140, 'M', 'ᄚ'), + (0x3141, 'M', 'ᄆ'), + (0x3142, 'M', 'ᄇ'), + (0x3143, 'M', 'ᄈ'), + (0x3144, 'M', 'ᄡ'), + (0x3145, 'M', 'ᄉ'), + (0x3146, 'M', 'ᄊ'), + (0x3147, 'M', 'ᄋ'), + (0x3148, 'M', 'ᄌ'), + (0x3149, 'M', 'ᄍ'), + (0x314A, 'M', 'ᄎ'), + (0x314B, 'M', 'ᄏ'), + (0x314C, 'M', 'ᄐ'), + (0x314D, 'M', 'ᄑ'), + (0x314E, 'M', 'ᄒ'), + (0x314F, 'M', 'ᅡ'), + (0x3150, 'M', 'ᅢ'), + (0x3151, 'M', 'ᅣ'), + (0x3152, 'M', 'ᅤ'), + (0x3153, 'M', 'ᅥ'), + (0x3154, 'M', 'ᅦ'), + (0x3155, 'M', 'ᅧ'), + (0x3156, 'M', 'ᅨ'), + (0x3157, 'M', 'ᅩ'), + (0x3158, 'M', 'ᅪ'), + (0x3159, 'M', 'ᅫ'), + (0x315A, 'M', 'ᅬ'), + (0x315B, 'M', 'ᅭ'), + (0x315C, 'M', 'ᅮ'), + (0x315D, 'M', 'ᅯ'), + (0x315E, 'M', 'ᅰ'), + (0x315F, 'M', 'ᅱ'), + (0x3160, 'M', 'ᅲ'), + (0x3161, 'M', 'ᅳ'), + (0x3162, 'M', 'ᅴ'), + (0x3163, 'M', 'ᅵ'), + (0x3164, 'X'), + (0x3165, 'M', 'ᄔ'), + (0x3166, 'M', 'ᄕ'), + (0x3167, 'M', 'ᇇ'), + (0x3168, 'M', 'ᇈ'), + (0x3169, 'M', 'ᇌ'), + (0x316A, 'M', 'ᇎ'), + (0x316B, 'M', 'ᇓ'), + (0x316C, 'M', 'ᇗ'), + (0x316D, 'M', 'ᇙ'), + (0x316E, 'M', 'ᄜ'), + (0x316F, 'M', 'ᇝ'), + (0x3170, 'M', 'ᇟ'), + (0x3171, 'M', 'ᄝ'), + (0x3172, 'M', 'ᄞ'), + (0x3173, 'M', 'ᄠ'), + (0x3174, 'M', 'ᄢ'), + (0x3175, 'M', 'ᄣ'), + (0x3176, 'M', 'ᄧ'), + (0x3177, 'M', 'ᄩ'), + (0x3178, 'M', 'ᄫ'), + (0x3179, 'M', 'ᄬ'), + (0x317A, 'M', 'ᄭ'), + (0x317B, 'M', 'ᄮ'), + (0x317C, 'M', 'ᄯ'), + (0x317D, 'M', 'ᄲ'), + (0x317E, 'M', 'ᄶ'), + (0x317F, 'M', 'ᅀ'), + (0x3180, 'M', 'ᅇ'), + (0x3181, 'M', 'ᅌ'), + (0x3182, 'M', 'ᇱ'), + (0x3183, 'M', 'ᇲ'), + (0x3184, 'M', 'ᅗ'), + ] + +def _seg_30() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x3185, 'M', 'ᅘ'), + (0x3186, 'M', 'ᅙ'), + (0x3187, 'M', 'ᆄ'), + (0x3188, 'M', 'ᆅ'), + (0x3189, 'M', 'ᆈ'), + (0x318A, 'M', 'ᆑ'), + (0x318B, 'M', 'ᆒ'), + (0x318C, 'M', 'ᆔ'), + (0x318D, 'M', 'ᆞ'), + (0x318E, 'M', 'ᆡ'), + (0x318F, 'X'), + (0x3190, 'V'), + (0x3192, 'M', '一'), + (0x3193, 'M', '二'), + (0x3194, 'M', '三'), + (0x3195, 'M', '四'), + (0x3196, 'M', '上'), + (0x3197, 'M', '中'), + (0x3198, 'M', '下'), + (0x3199, 'M', '甲'), + (0x319A, 'M', '乙'), + (0x319B, 'M', '丙'), + (0x319C, 'M', '丁'), + (0x319D, 'M', '天'), + (0x319E, 'M', '地'), + (0x319F, 'M', '人'), + (0x31A0, 'V'), + (0x31E4, 'X'), + (0x31F0, 'V'), + (0x3200, '3', '(ᄀ)'), + (0x3201, '3', '(ᄂ)'), + (0x3202, '3', '(ᄃ)'), + (0x3203, '3', '(ᄅ)'), + (0x3204, '3', '(ᄆ)'), + (0x3205, '3', '(ᄇ)'), + (0x3206, '3', '(ᄉ)'), + (0x3207, '3', '(ᄋ)'), + (0x3208, '3', '(ᄌ)'), + (0x3209, '3', '(ᄎ)'), + (0x320A, '3', '(ᄏ)'), + (0x320B, '3', '(ᄐ)'), + (0x320C, '3', '(ᄑ)'), + (0x320D, '3', '(ᄒ)'), + (0x320E, '3', '(가)'), + (0x320F, '3', '(나)'), + (0x3210, '3', '(다)'), + (0x3211, '3', '(라)'), + (0x3212, '3', '(마)'), + (0x3213, '3', '(바)'), + (0x3214, '3', '(사)'), + (0x3215, '3', '(아)'), + (0x3216, '3', '(자)'), + (0x3217, '3', '(차)'), + (0x3218, '3', '(카)'), + (0x3219, '3', '(타)'), + (0x321A, '3', '(파)'), + (0x321B, '3', '(하)'), + (0x321C, '3', '(주)'), + (0x321D, '3', '(오전)'), + (0x321E, '3', '(오후)'), + (0x321F, 'X'), + (0x3220, '3', '(一)'), + (0x3221, '3', '(二)'), + (0x3222, '3', '(三)'), + (0x3223, '3', '(四)'), + (0x3224, '3', '(五)'), + (0x3225, '3', '(六)'), + (0x3226, '3', '(七)'), + (0x3227, '3', '(八)'), + (0x3228, '3', '(九)'), + (0x3229, '3', '(十)'), + (0x322A, '3', '(月)'), + (0x322B, '3', '(火)'), + (0x322C, '3', '(水)'), + (0x322D, '3', '(木)'), + (0x322E, '3', '(金)'), + (0x322F, '3', '(土)'), + (0x3230, '3', '(日)'), + (0x3231, '3', '(株)'), + (0x3232, '3', '(有)'), + (0x3233, '3', '(社)'), + (0x3234, '3', '(名)'), + (0x3235, '3', '(特)'), + (0x3236, '3', '(財)'), + (0x3237, '3', '(祝)'), + (0x3238, '3', '(労)'), + (0x3239, '3', '(代)'), + (0x323A, '3', '(呼)'), + (0x323B, '3', '(学)'), + (0x323C, '3', '(監)'), + (0x323D, '3', '(企)'), + (0x323E, '3', '(資)'), + (0x323F, '3', '(協)'), + (0x3240, '3', '(祭)'), + (0x3241, '3', '(休)'), + (0x3242, '3', '(自)'), + (0x3243, '3', '(至)'), + (0x3244, 'M', '問'), + (0x3245, 'M', '幼'), + (0x3246, 'M', '文'), + ] + +def _seg_31() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x3247, 'M', '箏'), + (0x3248, 'V'), + (0x3250, 'M', 'pte'), + (0x3251, 'M', '21'), + (0x3252, 'M', '22'), + (0x3253, 'M', '23'), + (0x3254, 'M', '24'), + (0x3255, 'M', '25'), + (0x3256, 'M', '26'), + (0x3257, 'M', '27'), + (0x3258, 'M', '28'), + (0x3259, 'M', '29'), + (0x325A, 'M', '30'), + (0x325B, 'M', '31'), + (0x325C, 'M', '32'), + (0x325D, 'M', '33'), + (0x325E, 'M', '34'), + (0x325F, 'M', '35'), + (0x3260, 'M', 'ᄀ'), + (0x3261, 'M', 'ᄂ'), + (0x3262, 'M', 'ᄃ'), + (0x3263, 'M', 'ᄅ'), + (0x3264, 'M', 'ᄆ'), + (0x3265, 'M', 'ᄇ'), + (0x3266, 'M', 'ᄉ'), + (0x3267, 'M', 'ᄋ'), + (0x3268, 'M', 'ᄌ'), + (0x3269, 'M', 'ᄎ'), + (0x326A, 'M', 'ᄏ'), + (0x326B, 'M', 'ᄐ'), + (0x326C, 'M', 'ᄑ'), + (0x326D, 'M', 'ᄒ'), + (0x326E, 'M', '가'), + (0x326F, 'M', '나'), + (0x3270, 'M', '다'), + (0x3271, 'M', '라'), + (0x3272, 'M', '마'), + (0x3273, 'M', '바'), + (0x3274, 'M', '사'), + (0x3275, 'M', '아'), + (0x3276, 'M', '자'), + (0x3277, 'M', '차'), + (0x3278, 'M', '카'), + (0x3279, 'M', '타'), + (0x327A, 'M', '파'), + (0x327B, 'M', '하'), + (0x327C, 'M', '참고'), + (0x327D, 'M', '주의'), + (0x327E, 'M', '우'), + (0x327F, 'V'), + (0x3280, 'M', '一'), + (0x3281, 'M', '二'), + (0x3282, 'M', '三'), + (0x3283, 'M', '四'), + (0x3284, 'M', '五'), + (0x3285, 'M', '六'), + (0x3286, 'M', '七'), + (0x3287, 'M', '八'), + (0x3288, 'M', '九'), + (0x3289, 'M', '十'), + (0x328A, 'M', '月'), + (0x328B, 'M', '火'), + (0x328C, 'M', '水'), + (0x328D, 'M', '木'), + (0x328E, 'M', '金'), + (0x328F, 'M', '土'), + (0x3290, 'M', '日'), + (0x3291, 'M', '株'), + (0x3292, 'M', '有'), + (0x3293, 'M', '社'), + (0x3294, 'M', '名'), + (0x3295, 'M', '特'), + (0x3296, 'M', '財'), + (0x3297, 'M', '祝'), + (0x3298, 'M', '労'), + (0x3299, 'M', '秘'), + (0x329A, 'M', '男'), + (0x329B, 'M', '女'), + (0x329C, 'M', '適'), + (0x329D, 'M', '優'), + (0x329E, 'M', '印'), + (0x329F, 'M', '注'), + (0x32A0, 'M', '項'), + (0x32A1, 'M', '休'), + (0x32A2, 'M', '写'), + (0x32A3, 'M', '正'), + (0x32A4, 'M', '上'), + (0x32A5, 'M', '中'), + (0x32A6, 'M', '下'), + (0x32A7, 'M', '左'), + (0x32A8, 'M', '右'), + (0x32A9, 'M', '医'), + (0x32AA, 'M', '宗'), + (0x32AB, 'M', '学'), + (0x32AC, 'M', '監'), + (0x32AD, 'M', '企'), + (0x32AE, 'M', '資'), + (0x32AF, 'M', '協'), + (0x32B0, 'M', '夜'), + (0x32B1, 'M', '36'), + ] + +def _seg_32() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x32B2, 'M', '37'), + (0x32B3, 'M', '38'), + (0x32B4, 'M', '39'), + (0x32B5, 'M', '40'), + (0x32B6, 'M', '41'), + (0x32B7, 'M', '42'), + (0x32B8, 'M', '43'), + (0x32B9, 'M', '44'), + (0x32BA, 'M', '45'), + (0x32BB, 'M', '46'), + (0x32BC, 'M', '47'), + (0x32BD, 'M', '48'), + (0x32BE, 'M', '49'), + (0x32BF, 'M', '50'), + (0x32C0, 'M', '1月'), + (0x32C1, 'M', '2月'), + (0x32C2, 'M', '3月'), + (0x32C3, 'M', '4月'), + (0x32C4, 'M', '5月'), + (0x32C5, 'M', '6月'), + (0x32C6, 'M', '7月'), + (0x32C7, 'M', '8月'), + (0x32C8, 'M', '9月'), + (0x32C9, 'M', '10月'), + (0x32CA, 'M', '11月'), + (0x32CB, 'M', '12月'), + (0x32CC, 'M', 'hg'), + (0x32CD, 'M', 'erg'), + (0x32CE, 'M', 'ev'), + (0x32CF, 'M', 'ltd'), + (0x32D0, 'M', 'ア'), + (0x32D1, 'M', 'イ'), + (0x32D2, 'M', 'ウ'), + (0x32D3, 'M', 'エ'), + (0x32D4, 'M', 'オ'), + (0x32D5, 'M', 'カ'), + (0x32D6, 'M', 'キ'), + (0x32D7, 'M', 'ク'), + (0x32D8, 'M', 'ケ'), + (0x32D9, 'M', 'コ'), + (0x32DA, 'M', 'サ'), + (0x32DB, 'M', 'シ'), + (0x32DC, 'M', 'ス'), + (0x32DD, 'M', 'セ'), + (0x32DE, 'M', 'ソ'), + (0x32DF, 'M', 'タ'), + (0x32E0, 'M', 'チ'), + (0x32E1, 'M', 'ツ'), + (0x32E2, 'M', 'テ'), + (0x32E3, 'M', 'ト'), + (0x32E4, 'M', 'ナ'), + (0x32E5, 'M', 'ニ'), + (0x32E6, 'M', 'ヌ'), + (0x32E7, 'M', 'ネ'), + (0x32E8, 'M', 'ノ'), + (0x32E9, 'M', 'ハ'), + (0x32EA, 'M', 'ヒ'), + (0x32EB, 'M', 'フ'), + (0x32EC, 'M', 'ヘ'), + (0x32ED, 'M', 'ホ'), + (0x32EE, 'M', 'マ'), + (0x32EF, 'M', 'ミ'), + (0x32F0, 'M', 'ム'), + (0x32F1, 'M', 'メ'), + (0x32F2, 'M', 'モ'), + (0x32F3, 'M', 'ヤ'), + (0x32F4, 'M', 'ユ'), + (0x32F5, 'M', 'ヨ'), + (0x32F6, 'M', 'ラ'), + (0x32F7, 'M', 'リ'), + (0x32F8, 'M', 'ル'), + (0x32F9, 'M', 'レ'), + (0x32FA, 'M', 'ロ'), + (0x32FB, 'M', 'ワ'), + (0x32FC, 'M', 'ヰ'), + (0x32FD, 'M', 'ヱ'), + (0x32FE, 'M', 'ヲ'), + (0x32FF, 'M', '令和'), + (0x3300, 'M', 'アパート'), + (0x3301, 'M', 'アルファ'), + (0x3302, 'M', 'アンペア'), + (0x3303, 'M', 'アール'), + (0x3304, 'M', 'イニング'), + (0x3305, 'M', 'インチ'), + (0x3306, 'M', 'ウォン'), + (0x3307, 'M', 'エスクード'), + (0x3308, 'M', 'エーカー'), + (0x3309, 'M', 'オンス'), + (0x330A, 'M', 'オーム'), + (0x330B, 'M', 'カイリ'), + (0x330C, 'M', 'カラット'), + (0x330D, 'M', 'カロリー'), + (0x330E, 'M', 'ガロン'), + (0x330F, 'M', 'ガンマ'), + (0x3310, 'M', 'ギガ'), + (0x3311, 'M', 'ギニー'), + (0x3312, 'M', 'キュリー'), + (0x3313, 'M', 'ギルダー'), + (0x3314, 'M', 'キロ'), + (0x3315, 'M', 'キログラム'), + ] + +def _seg_33() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x3316, 'M', 'キロメートル'), + (0x3317, 'M', 'キロワット'), + (0x3318, 'M', 'グラム'), + (0x3319, 'M', 'グラムトン'), + (0x331A, 'M', 'クルゼイロ'), + (0x331B, 'M', 'クローネ'), + (0x331C, 'M', 'ケース'), + (0x331D, 'M', 'コルナ'), + (0x331E, 'M', 'コーポ'), + (0x331F, 'M', 'サイクル'), + (0x3320, 'M', 'サンチーム'), + (0x3321, 'M', 'シリング'), + (0x3322, 'M', 'センチ'), + (0x3323, 'M', 'セント'), + (0x3324, 'M', 'ダース'), + (0x3325, 'M', 'デシ'), + (0x3326, 'M', 'ドル'), + (0x3327, 'M', 'トン'), + (0x3328, 'M', 'ナノ'), + (0x3329, 'M', 'ノット'), + (0x332A, 'M', 'ハイツ'), + (0x332B, 'M', 'パーセント'), + (0x332C, 'M', 'パーツ'), + (0x332D, 'M', 'バーレル'), + (0x332E, 'M', 'ピアストル'), + (0x332F, 'M', 'ピクル'), + (0x3330, 'M', 'ピコ'), + (0x3331, 'M', 'ビル'), + (0x3332, 'M', 'ファラッド'), + (0x3333, 'M', 'フィート'), + (0x3334, 'M', 'ブッシェル'), + (0x3335, 'M', 'フラン'), + (0x3336, 'M', 'ヘクタール'), + (0x3337, 'M', 'ペソ'), + (0x3338, 'M', 'ペニヒ'), + (0x3339, 'M', 'ヘルツ'), + (0x333A, 'M', 'ペンス'), + (0x333B, 'M', 'ページ'), + (0x333C, 'M', 'ベータ'), + (0x333D, 'M', 'ポイント'), + (0x333E, 'M', 'ボルト'), + (0x333F, 'M', 'ホン'), + (0x3340, 'M', 'ポンド'), + (0x3341, 'M', 'ホール'), + (0x3342, 'M', 'ホーン'), + (0x3343, 'M', 'マイクロ'), + (0x3344, 'M', 'マイル'), + (0x3345, 'M', 'マッハ'), + (0x3346, 'M', 'マルク'), + (0x3347, 'M', 'マンション'), + (0x3348, 'M', 'ミクロン'), + (0x3349, 'M', 'ミリ'), + (0x334A, 'M', 'ミリバール'), + (0x334B, 'M', 'メガ'), + (0x334C, 'M', 'メガトン'), + (0x334D, 'M', 'メートル'), + (0x334E, 'M', 'ヤード'), + (0x334F, 'M', 'ヤール'), + (0x3350, 'M', 'ユアン'), + (0x3351, 'M', 'リットル'), + (0x3352, 'M', 'リラ'), + (0x3353, 'M', 'ルピー'), + (0x3354, 'M', 'ルーブル'), + (0x3355, 'M', 'レム'), + (0x3356, 'M', 'レントゲン'), + (0x3357, 'M', 'ワット'), + (0x3358, 'M', '0点'), + (0x3359, 'M', '1点'), + (0x335A, 'M', '2点'), + (0x335B, 'M', '3点'), + (0x335C, 'M', '4点'), + (0x335D, 'M', '5点'), + (0x335E, 'M', '6点'), + (0x335F, 'M', '7点'), + (0x3360, 'M', '8点'), + (0x3361, 'M', '9点'), + (0x3362, 'M', '10点'), + (0x3363, 'M', '11点'), + (0x3364, 'M', '12点'), + (0x3365, 'M', '13点'), + (0x3366, 'M', '14点'), + (0x3367, 'M', '15点'), + (0x3368, 'M', '16点'), + (0x3369, 'M', '17点'), + (0x336A, 'M', '18点'), + (0x336B, 'M', '19点'), + (0x336C, 'M', '20点'), + (0x336D, 'M', '21点'), + (0x336E, 'M', '22点'), + (0x336F, 'M', '23点'), + (0x3370, 'M', '24点'), + (0x3371, 'M', 'hpa'), + (0x3372, 'M', 'da'), + (0x3373, 'M', 'au'), + (0x3374, 'M', 'bar'), + (0x3375, 'M', 'ov'), + (0x3376, 'M', 'pc'), + (0x3377, 'M', 'dm'), + (0x3378, 'M', 'dm2'), + (0x3379, 'M', 'dm3'), + ] + +def _seg_34() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x337A, 'M', 'iu'), + (0x337B, 'M', '平成'), + (0x337C, 'M', '昭和'), + (0x337D, 'M', '大正'), + (0x337E, 'M', '明治'), + (0x337F, 'M', '株式会社'), + (0x3380, 'M', 'pa'), + (0x3381, 'M', 'na'), + (0x3382, 'M', 'μa'), + (0x3383, 'M', 'ma'), + (0x3384, 'M', 'ka'), + (0x3385, 'M', 'kb'), + (0x3386, 'M', 'mb'), + (0x3387, 'M', 'gb'), + (0x3388, 'M', 'cal'), + (0x3389, 'M', 'kcal'), + (0x338A, 'M', 'pf'), + (0x338B, 'M', 'nf'), + (0x338C, 'M', 'μf'), + (0x338D, 'M', 'μg'), + (0x338E, 'M', 'mg'), + (0x338F, 'M', 'kg'), + (0x3390, 'M', 'hz'), + (0x3391, 'M', 'khz'), + (0x3392, 'M', 'mhz'), + (0x3393, 'M', 'ghz'), + (0x3394, 'M', 'thz'), + (0x3395, 'M', 'μl'), + (0x3396, 'M', 'ml'), + (0x3397, 'M', 'dl'), + (0x3398, 'M', 'kl'), + (0x3399, 'M', 'fm'), + (0x339A, 'M', 'nm'), + (0x339B, 'M', 'μm'), + (0x339C, 'M', 'mm'), + (0x339D, 'M', 'cm'), + (0x339E, 'M', 'km'), + (0x339F, 'M', 'mm2'), + (0x33A0, 'M', 'cm2'), + (0x33A1, 'M', 'm2'), + (0x33A2, 'M', 'km2'), + (0x33A3, 'M', 'mm3'), + (0x33A4, 'M', 'cm3'), + (0x33A5, 'M', 'm3'), + (0x33A6, 'M', 'km3'), + (0x33A7, 'M', 'm∕s'), + (0x33A8, 'M', 'm∕s2'), + (0x33A9, 'M', 'pa'), + (0x33AA, 'M', 'kpa'), + (0x33AB, 'M', 'mpa'), + (0x33AC, 'M', 'gpa'), + (0x33AD, 'M', 'rad'), + (0x33AE, 'M', 'rad∕s'), + (0x33AF, 'M', 'rad∕s2'), + (0x33B0, 'M', 'ps'), + (0x33B1, 'M', 'ns'), + (0x33B2, 'M', 'μs'), + (0x33B3, 'M', 'ms'), + (0x33B4, 'M', 'pv'), + (0x33B5, 'M', 'nv'), + (0x33B6, 'M', 'μv'), + (0x33B7, 'M', 'mv'), + (0x33B8, 'M', 'kv'), + (0x33B9, 'M', 'mv'), + (0x33BA, 'M', 'pw'), + (0x33BB, 'M', 'nw'), + (0x33BC, 'M', 'μw'), + (0x33BD, 'M', 'mw'), + (0x33BE, 'M', 'kw'), + (0x33BF, 'M', 'mw'), + (0x33C0, 'M', 'kω'), + (0x33C1, 'M', 'mω'), + (0x33C2, 'X'), + (0x33C3, 'M', 'bq'), + (0x33C4, 'M', 'cc'), + (0x33C5, 'M', 'cd'), + (0x33C6, 'M', 'c∕kg'), + (0x33C7, 'X'), + (0x33C8, 'M', 'db'), + (0x33C9, 'M', 'gy'), + (0x33CA, 'M', 'ha'), + (0x33CB, 'M', 'hp'), + (0x33CC, 'M', 'in'), + (0x33CD, 'M', 'kk'), + (0x33CE, 'M', 'km'), + (0x33CF, 'M', 'kt'), + (0x33D0, 'M', 'lm'), + (0x33D1, 'M', 'ln'), + (0x33D2, 'M', 'log'), + (0x33D3, 'M', 'lx'), + (0x33D4, 'M', 'mb'), + (0x33D5, 'M', 'mil'), + (0x33D6, 'M', 'mol'), + (0x33D7, 'M', 'ph'), + (0x33D8, 'X'), + (0x33D9, 'M', 'ppm'), + (0x33DA, 'M', 'pr'), + (0x33DB, 'M', 'sr'), + (0x33DC, 'M', 'sv'), + (0x33DD, 'M', 'wb'), + ] + +def _seg_35() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x33DE, 'M', 'v∕m'), + (0x33DF, 'M', 'a∕m'), + (0x33E0, 'M', '1日'), + (0x33E1, 'M', '2日'), + (0x33E2, 'M', '3日'), + (0x33E3, 'M', '4日'), + (0x33E4, 'M', '5日'), + (0x33E5, 'M', '6日'), + (0x33E6, 'M', '7日'), + (0x33E7, 'M', '8日'), + (0x33E8, 'M', '9日'), + (0x33E9, 'M', '10日'), + (0x33EA, 'M', '11日'), + (0x33EB, 'M', '12日'), + (0x33EC, 'M', '13日'), + (0x33ED, 'M', '14日'), + (0x33EE, 'M', '15日'), + (0x33EF, 'M', '16日'), + (0x33F0, 'M', '17日'), + (0x33F1, 'M', '18日'), + (0x33F2, 'M', '19日'), + (0x33F3, 'M', '20日'), + (0x33F4, 'M', '21日'), + (0x33F5, 'M', '22日'), + (0x33F6, 'M', '23日'), + (0x33F7, 'M', '24日'), + (0x33F8, 'M', '25日'), + (0x33F9, 'M', '26日'), + (0x33FA, 'M', '27日'), + (0x33FB, 'M', '28日'), + (0x33FC, 'M', '29日'), + (0x33FD, 'M', '30日'), + (0x33FE, 'M', '31日'), + (0x33FF, 'M', 'gal'), + (0x3400, 'V'), + (0xA48D, 'X'), + (0xA490, 'V'), + (0xA4C7, 'X'), + (0xA4D0, 'V'), + (0xA62C, 'X'), + (0xA640, 'M', 'ꙁ'), + (0xA641, 'V'), + (0xA642, 'M', 'ꙃ'), + (0xA643, 'V'), + (0xA644, 'M', 'ꙅ'), + (0xA645, 'V'), + (0xA646, 'M', 'ꙇ'), + (0xA647, 'V'), + (0xA648, 'M', 'ꙉ'), + (0xA649, 'V'), + (0xA64A, 'M', 'ꙋ'), + (0xA64B, 'V'), + (0xA64C, 'M', 'ꙍ'), + (0xA64D, 'V'), + (0xA64E, 'M', 'ꙏ'), + (0xA64F, 'V'), + (0xA650, 'M', 'ꙑ'), + (0xA651, 'V'), + (0xA652, 'M', 'ꙓ'), + (0xA653, 'V'), + (0xA654, 'M', 'ꙕ'), + (0xA655, 'V'), + (0xA656, 'M', 'ꙗ'), + (0xA657, 'V'), + (0xA658, 'M', 'ꙙ'), + (0xA659, 'V'), + (0xA65A, 'M', 'ꙛ'), + (0xA65B, 'V'), + (0xA65C, 'M', 'ꙝ'), + (0xA65D, 'V'), + (0xA65E, 'M', 'ꙟ'), + (0xA65F, 'V'), + (0xA660, 'M', 'ꙡ'), + (0xA661, 'V'), + (0xA662, 'M', 'ꙣ'), + (0xA663, 'V'), + (0xA664, 'M', 'ꙥ'), + (0xA665, 'V'), + (0xA666, 'M', 'ꙧ'), + (0xA667, 'V'), + (0xA668, 'M', 'ꙩ'), + (0xA669, 'V'), + (0xA66A, 'M', 'ꙫ'), + (0xA66B, 'V'), + (0xA66C, 'M', 'ꙭ'), + (0xA66D, 'V'), + (0xA680, 'M', 'ꚁ'), + (0xA681, 'V'), + (0xA682, 'M', 'ꚃ'), + (0xA683, 'V'), + (0xA684, 'M', 'ꚅ'), + (0xA685, 'V'), + (0xA686, 'M', 'ꚇ'), + (0xA687, 'V'), + (0xA688, 'M', 'ꚉ'), + (0xA689, 'V'), + (0xA68A, 'M', 'ꚋ'), + (0xA68B, 'V'), + (0xA68C, 'M', 'ꚍ'), + (0xA68D, 'V'), + ] + +def _seg_36() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xA68E, 'M', 'ꚏ'), + (0xA68F, 'V'), + (0xA690, 'M', 'ꚑ'), + (0xA691, 'V'), + (0xA692, 'M', 'ꚓ'), + (0xA693, 'V'), + (0xA694, 'M', 'ꚕ'), + (0xA695, 'V'), + (0xA696, 'M', 'ꚗ'), + (0xA697, 'V'), + (0xA698, 'M', 'ꚙ'), + (0xA699, 'V'), + (0xA69A, 'M', 'ꚛ'), + (0xA69B, 'V'), + (0xA69C, 'M', 'ъ'), + (0xA69D, 'M', 'ь'), + (0xA69E, 'V'), + (0xA6F8, 'X'), + (0xA700, 'V'), + (0xA722, 'M', 'ꜣ'), + (0xA723, 'V'), + (0xA724, 'M', 'ꜥ'), + (0xA725, 'V'), + (0xA726, 'M', 'ꜧ'), + (0xA727, 'V'), + (0xA728, 'M', 'ꜩ'), + (0xA729, 'V'), + (0xA72A, 'M', 'ꜫ'), + (0xA72B, 'V'), + (0xA72C, 'M', 'ꜭ'), + (0xA72D, 'V'), + (0xA72E, 'M', 'ꜯ'), + (0xA72F, 'V'), + (0xA732, 'M', 'ꜳ'), + (0xA733, 'V'), + (0xA734, 'M', 'ꜵ'), + (0xA735, 'V'), + (0xA736, 'M', 'ꜷ'), + (0xA737, 'V'), + (0xA738, 'M', 'ꜹ'), + (0xA739, 'V'), + (0xA73A, 'M', 'ꜻ'), + (0xA73B, 'V'), + (0xA73C, 'M', 'ꜽ'), + (0xA73D, 'V'), + (0xA73E, 'M', 'ꜿ'), + (0xA73F, 'V'), + (0xA740, 'M', 'ꝁ'), + (0xA741, 'V'), + (0xA742, 'M', 'ꝃ'), + (0xA743, 'V'), + (0xA744, 'M', 'ꝅ'), + (0xA745, 'V'), + (0xA746, 'M', 'ꝇ'), + (0xA747, 'V'), + (0xA748, 'M', 'ꝉ'), + (0xA749, 'V'), + (0xA74A, 'M', 'ꝋ'), + (0xA74B, 'V'), + (0xA74C, 'M', 'ꝍ'), + (0xA74D, 'V'), + (0xA74E, 'M', 'ꝏ'), + (0xA74F, 'V'), + (0xA750, 'M', 'ꝑ'), + (0xA751, 'V'), + (0xA752, 'M', 'ꝓ'), + (0xA753, 'V'), + (0xA754, 'M', 'ꝕ'), + (0xA755, 'V'), + (0xA756, 'M', 'ꝗ'), + (0xA757, 'V'), + (0xA758, 'M', 'ꝙ'), + (0xA759, 'V'), + (0xA75A, 'M', 'ꝛ'), + (0xA75B, 'V'), + (0xA75C, 'M', 'ꝝ'), + (0xA75D, 'V'), + (0xA75E, 'M', 'ꝟ'), + (0xA75F, 'V'), + (0xA760, 'M', 'ꝡ'), + (0xA761, 'V'), + (0xA762, 'M', 'ꝣ'), + (0xA763, 'V'), + (0xA764, 'M', 'ꝥ'), + (0xA765, 'V'), + (0xA766, 'M', 'ꝧ'), + (0xA767, 'V'), + (0xA768, 'M', 'ꝩ'), + (0xA769, 'V'), + (0xA76A, 'M', 'ꝫ'), + (0xA76B, 'V'), + (0xA76C, 'M', 'ꝭ'), + (0xA76D, 'V'), + (0xA76E, 'M', 'ꝯ'), + (0xA76F, 'V'), + (0xA770, 'M', 'ꝯ'), + (0xA771, 'V'), + (0xA779, 'M', 'ꝺ'), + (0xA77A, 'V'), + (0xA77B, 'M', 'ꝼ'), + ] + +def _seg_37() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xA77C, 'V'), + (0xA77D, 'M', 'ᵹ'), + (0xA77E, 'M', 'ꝿ'), + (0xA77F, 'V'), + (0xA780, 'M', 'ꞁ'), + (0xA781, 'V'), + (0xA782, 'M', 'ꞃ'), + (0xA783, 'V'), + (0xA784, 'M', 'ꞅ'), + (0xA785, 'V'), + (0xA786, 'M', 'ꞇ'), + (0xA787, 'V'), + (0xA78B, 'M', 'ꞌ'), + (0xA78C, 'V'), + (0xA78D, 'M', 'ɥ'), + (0xA78E, 'V'), + (0xA790, 'M', 'ꞑ'), + (0xA791, 'V'), + (0xA792, 'M', 'ꞓ'), + (0xA793, 'V'), + (0xA796, 'M', 'ꞗ'), + (0xA797, 'V'), + (0xA798, 'M', 'ꞙ'), + (0xA799, 'V'), + (0xA79A, 'M', 'ꞛ'), + (0xA79B, 'V'), + (0xA79C, 'M', 'ꞝ'), + (0xA79D, 'V'), + (0xA79E, 'M', 'ꞟ'), + (0xA79F, 'V'), + (0xA7A0, 'M', 'ꞡ'), + (0xA7A1, 'V'), + (0xA7A2, 'M', 'ꞣ'), + (0xA7A3, 'V'), + (0xA7A4, 'M', 'ꞥ'), + (0xA7A5, 'V'), + (0xA7A6, 'M', 'ꞧ'), + (0xA7A7, 'V'), + (0xA7A8, 'M', 'ꞩ'), + (0xA7A9, 'V'), + (0xA7AA, 'M', 'ɦ'), + (0xA7AB, 'M', 'ɜ'), + (0xA7AC, 'M', 'ɡ'), + (0xA7AD, 'M', 'ɬ'), + (0xA7AE, 'M', 'ɪ'), + (0xA7AF, 'V'), + (0xA7B0, 'M', 'ʞ'), + (0xA7B1, 'M', 'ʇ'), + (0xA7B2, 'M', 'ʝ'), + (0xA7B3, 'M', 'ꭓ'), + (0xA7B4, 'M', 'ꞵ'), + (0xA7B5, 'V'), + (0xA7B6, 'M', 'ꞷ'), + (0xA7B7, 'V'), + (0xA7B8, 'M', 'ꞹ'), + (0xA7B9, 'V'), + (0xA7BA, 'M', 'ꞻ'), + (0xA7BB, 'V'), + (0xA7BC, 'M', 'ꞽ'), + (0xA7BD, 'V'), + (0xA7BE, 'M', 'ꞿ'), + (0xA7BF, 'V'), + (0xA7C0, 'M', 'ꟁ'), + (0xA7C1, 'V'), + (0xA7C2, 'M', 'ꟃ'), + (0xA7C3, 'V'), + (0xA7C4, 'M', 'ꞔ'), + (0xA7C5, 'M', 'ʂ'), + (0xA7C6, 'M', 'ᶎ'), + (0xA7C7, 'M', 'ꟈ'), + (0xA7C8, 'V'), + (0xA7C9, 'M', 'ꟊ'), + (0xA7CA, 'V'), + (0xA7CB, 'X'), + (0xA7D0, 'M', 'ꟑ'), + (0xA7D1, 'V'), + (0xA7D2, 'X'), + (0xA7D3, 'V'), + (0xA7D4, 'X'), + (0xA7D5, 'V'), + (0xA7D6, 'M', 'ꟗ'), + (0xA7D7, 'V'), + (0xA7D8, 'M', 'ꟙ'), + (0xA7D9, 'V'), + (0xA7DA, 'X'), + (0xA7F2, 'M', 'c'), + (0xA7F3, 'M', 'f'), + (0xA7F4, 'M', 'q'), + (0xA7F5, 'M', 'ꟶ'), + (0xA7F6, 'V'), + (0xA7F8, 'M', 'ħ'), + (0xA7F9, 'M', 'œ'), + (0xA7FA, 'V'), + (0xA82D, 'X'), + (0xA830, 'V'), + (0xA83A, 'X'), + (0xA840, 'V'), + (0xA878, 'X'), + (0xA880, 'V'), + (0xA8C6, 'X'), + ] + +def _seg_38() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xA8CE, 'V'), + (0xA8DA, 'X'), + (0xA8E0, 'V'), + (0xA954, 'X'), + (0xA95F, 'V'), + (0xA97D, 'X'), + (0xA980, 'V'), + (0xA9CE, 'X'), + (0xA9CF, 'V'), + (0xA9DA, 'X'), + (0xA9DE, 'V'), + (0xA9FF, 'X'), + (0xAA00, 'V'), + (0xAA37, 'X'), + (0xAA40, 'V'), + (0xAA4E, 'X'), + (0xAA50, 'V'), + (0xAA5A, 'X'), + (0xAA5C, 'V'), + (0xAAC3, 'X'), + (0xAADB, 'V'), + (0xAAF7, 'X'), + (0xAB01, 'V'), + (0xAB07, 'X'), + (0xAB09, 'V'), + (0xAB0F, 'X'), + (0xAB11, 'V'), + (0xAB17, 'X'), + (0xAB20, 'V'), + (0xAB27, 'X'), + (0xAB28, 'V'), + (0xAB2F, 'X'), + (0xAB30, 'V'), + (0xAB5C, 'M', 'ꜧ'), + (0xAB5D, 'M', 'ꬷ'), + (0xAB5E, 'M', 'ɫ'), + (0xAB5F, 'M', 'ꭒ'), + (0xAB60, 'V'), + (0xAB69, 'M', 'ʍ'), + (0xAB6A, 'V'), + (0xAB6C, 'X'), + (0xAB70, 'M', 'Ꭰ'), + (0xAB71, 'M', 'Ꭱ'), + (0xAB72, 'M', 'Ꭲ'), + (0xAB73, 'M', 'Ꭳ'), + (0xAB74, 'M', 'Ꭴ'), + (0xAB75, 'M', 'Ꭵ'), + (0xAB76, 'M', 'Ꭶ'), + (0xAB77, 'M', 'Ꭷ'), + (0xAB78, 'M', 'Ꭸ'), + (0xAB79, 'M', 'Ꭹ'), + (0xAB7A, 'M', 'Ꭺ'), + (0xAB7B, 'M', 'Ꭻ'), + (0xAB7C, 'M', 'Ꭼ'), + (0xAB7D, 'M', 'Ꭽ'), + (0xAB7E, 'M', 'Ꭾ'), + (0xAB7F, 'M', 'Ꭿ'), + (0xAB80, 'M', 'Ꮀ'), + (0xAB81, 'M', 'Ꮁ'), + (0xAB82, 'M', 'Ꮂ'), + (0xAB83, 'M', 'Ꮃ'), + (0xAB84, 'M', 'Ꮄ'), + (0xAB85, 'M', 'Ꮅ'), + (0xAB86, 'M', 'Ꮆ'), + (0xAB87, 'M', 'Ꮇ'), + (0xAB88, 'M', 'Ꮈ'), + (0xAB89, 'M', 'Ꮉ'), + (0xAB8A, 'M', 'Ꮊ'), + (0xAB8B, 'M', 'Ꮋ'), + (0xAB8C, 'M', 'Ꮌ'), + (0xAB8D, 'M', 'Ꮍ'), + (0xAB8E, 'M', 'Ꮎ'), + (0xAB8F, 'M', 'Ꮏ'), + (0xAB90, 'M', 'Ꮐ'), + (0xAB91, 'M', 'Ꮑ'), + (0xAB92, 'M', 'Ꮒ'), + (0xAB93, 'M', 'Ꮓ'), + (0xAB94, 'M', 'Ꮔ'), + (0xAB95, 'M', 'Ꮕ'), + (0xAB96, 'M', 'Ꮖ'), + (0xAB97, 'M', 'Ꮗ'), + (0xAB98, 'M', 'Ꮘ'), + (0xAB99, 'M', 'Ꮙ'), + (0xAB9A, 'M', 'Ꮚ'), + (0xAB9B, 'M', 'Ꮛ'), + (0xAB9C, 'M', 'Ꮜ'), + (0xAB9D, 'M', 'Ꮝ'), + (0xAB9E, 'M', 'Ꮞ'), + (0xAB9F, 'M', 'Ꮟ'), + (0xABA0, 'M', 'Ꮠ'), + (0xABA1, 'M', 'Ꮡ'), + (0xABA2, 'M', 'Ꮢ'), + (0xABA3, 'M', 'Ꮣ'), + (0xABA4, 'M', 'Ꮤ'), + (0xABA5, 'M', 'Ꮥ'), + (0xABA6, 'M', 'Ꮦ'), + (0xABA7, 'M', 'Ꮧ'), + (0xABA8, 'M', 'Ꮨ'), + (0xABA9, 'M', 'Ꮩ'), + (0xABAA, 'M', 'Ꮪ'), + ] + +def _seg_39() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xABAB, 'M', 'Ꮫ'), + (0xABAC, 'M', 'Ꮬ'), + (0xABAD, 'M', 'Ꮭ'), + (0xABAE, 'M', 'Ꮮ'), + (0xABAF, 'M', 'Ꮯ'), + (0xABB0, 'M', 'Ꮰ'), + (0xABB1, 'M', 'Ꮱ'), + (0xABB2, 'M', 'Ꮲ'), + (0xABB3, 'M', 'Ꮳ'), + (0xABB4, 'M', 'Ꮴ'), + (0xABB5, 'M', 'Ꮵ'), + (0xABB6, 'M', 'Ꮶ'), + (0xABB7, 'M', 'Ꮷ'), + (0xABB8, 'M', 'Ꮸ'), + (0xABB9, 'M', 'Ꮹ'), + (0xABBA, 'M', 'Ꮺ'), + (0xABBB, 'M', 'Ꮻ'), + (0xABBC, 'M', 'Ꮼ'), + (0xABBD, 'M', 'Ꮽ'), + (0xABBE, 'M', 'Ꮾ'), + (0xABBF, 'M', 'Ꮿ'), + (0xABC0, 'V'), + (0xABEE, 'X'), + (0xABF0, 'V'), + (0xABFA, 'X'), + (0xAC00, 'V'), + (0xD7A4, 'X'), + (0xD7B0, 'V'), + (0xD7C7, 'X'), + (0xD7CB, 'V'), + (0xD7FC, 'X'), + (0xF900, 'M', '豈'), + (0xF901, 'M', '更'), + (0xF902, 'M', '車'), + (0xF903, 'M', '賈'), + (0xF904, 'M', '滑'), + (0xF905, 'M', '串'), + (0xF906, 'M', '句'), + (0xF907, 'M', '龜'), + (0xF909, 'M', '契'), + (0xF90A, 'M', '金'), + (0xF90B, 'M', '喇'), + (0xF90C, 'M', '奈'), + (0xF90D, 'M', '懶'), + (0xF90E, 'M', '癩'), + (0xF90F, 'M', '羅'), + (0xF910, 'M', '蘿'), + (0xF911, 'M', '螺'), + (0xF912, 'M', '裸'), + (0xF913, 'M', '邏'), + (0xF914, 'M', '樂'), + (0xF915, 'M', '洛'), + (0xF916, 'M', '烙'), + (0xF917, 'M', '珞'), + (0xF918, 'M', '落'), + (0xF919, 'M', '酪'), + (0xF91A, 'M', '駱'), + (0xF91B, 'M', '亂'), + (0xF91C, 'M', '卵'), + (0xF91D, 'M', '欄'), + (0xF91E, 'M', '爛'), + (0xF91F, 'M', '蘭'), + (0xF920, 'M', '鸞'), + (0xF921, 'M', '嵐'), + (0xF922, 'M', '濫'), + (0xF923, 'M', '藍'), + (0xF924, 'M', '襤'), + (0xF925, 'M', '拉'), + (0xF926, 'M', '臘'), + (0xF927, 'M', '蠟'), + (0xF928, 'M', '廊'), + (0xF929, 'M', '朗'), + (0xF92A, 'M', '浪'), + (0xF92B, 'M', '狼'), + (0xF92C, 'M', '郎'), + (0xF92D, 'M', '來'), + (0xF92E, 'M', '冷'), + (0xF92F, 'M', '勞'), + (0xF930, 'M', '擄'), + (0xF931, 'M', '櫓'), + (0xF932, 'M', '爐'), + (0xF933, 'M', '盧'), + (0xF934, 'M', '老'), + (0xF935, 'M', '蘆'), + (0xF936, 'M', '虜'), + (0xF937, 'M', '路'), + (0xF938, 'M', '露'), + (0xF939, 'M', '魯'), + (0xF93A, 'M', '鷺'), + (0xF93B, 'M', '碌'), + (0xF93C, 'M', '祿'), + (0xF93D, 'M', '綠'), + (0xF93E, 'M', '菉'), + (0xF93F, 'M', '錄'), + (0xF940, 'M', '鹿'), + (0xF941, 'M', '論'), + (0xF942, 'M', '壟'), + (0xF943, 'M', '弄'), + (0xF944, 'M', '籠'), + (0xF945, 'M', '聾'), + ] + +def _seg_40() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xF946, 'M', '牢'), + (0xF947, 'M', '磊'), + (0xF948, 'M', '賂'), + (0xF949, 'M', '雷'), + (0xF94A, 'M', '壘'), + (0xF94B, 'M', '屢'), + (0xF94C, 'M', '樓'), + (0xF94D, 'M', '淚'), + (0xF94E, 'M', '漏'), + (0xF94F, 'M', '累'), + (0xF950, 'M', '縷'), + (0xF951, 'M', '陋'), + (0xF952, 'M', '勒'), + (0xF953, 'M', '肋'), + (0xF954, 'M', '凜'), + (0xF955, 'M', '凌'), + (0xF956, 'M', '稜'), + (0xF957, 'M', '綾'), + (0xF958, 'M', '菱'), + (0xF959, 'M', '陵'), + (0xF95A, 'M', '讀'), + (0xF95B, 'M', '拏'), + (0xF95C, 'M', '樂'), + (0xF95D, 'M', '諾'), + (0xF95E, 'M', '丹'), + (0xF95F, 'M', '寧'), + (0xF960, 'M', '怒'), + (0xF961, 'M', '率'), + (0xF962, 'M', '異'), + (0xF963, 'M', '北'), + (0xF964, 'M', '磻'), + (0xF965, 'M', '便'), + (0xF966, 'M', '復'), + (0xF967, 'M', '不'), + (0xF968, 'M', '泌'), + (0xF969, 'M', '數'), + (0xF96A, 'M', '索'), + (0xF96B, 'M', '參'), + (0xF96C, 'M', '塞'), + (0xF96D, 'M', '省'), + (0xF96E, 'M', '葉'), + (0xF96F, 'M', '說'), + (0xF970, 'M', '殺'), + (0xF971, 'M', '辰'), + (0xF972, 'M', '沈'), + (0xF973, 'M', '拾'), + (0xF974, 'M', '若'), + (0xF975, 'M', '掠'), + (0xF976, 'M', '略'), + (0xF977, 'M', '亮'), + (0xF978, 'M', '兩'), + (0xF979, 'M', '凉'), + (0xF97A, 'M', '梁'), + (0xF97B, 'M', '糧'), + (0xF97C, 'M', '良'), + (0xF97D, 'M', '諒'), + (0xF97E, 'M', '量'), + (0xF97F, 'M', '勵'), + (0xF980, 'M', '呂'), + (0xF981, 'M', '女'), + (0xF982, 'M', '廬'), + (0xF983, 'M', '旅'), + (0xF984, 'M', '濾'), + (0xF985, 'M', '礪'), + (0xF986, 'M', '閭'), + (0xF987, 'M', '驪'), + (0xF988, 'M', '麗'), + (0xF989, 'M', '黎'), + (0xF98A, 'M', '力'), + (0xF98B, 'M', '曆'), + (0xF98C, 'M', '歷'), + (0xF98D, 'M', '轢'), + (0xF98E, 'M', '年'), + (0xF98F, 'M', '憐'), + (0xF990, 'M', '戀'), + (0xF991, 'M', '撚'), + (0xF992, 'M', '漣'), + (0xF993, 'M', '煉'), + (0xF994, 'M', '璉'), + (0xF995, 'M', '秊'), + (0xF996, 'M', '練'), + (0xF997, 'M', '聯'), + (0xF998, 'M', '輦'), + (0xF999, 'M', '蓮'), + (0xF99A, 'M', '連'), + (0xF99B, 'M', '鍊'), + (0xF99C, 'M', '列'), + (0xF99D, 'M', '劣'), + (0xF99E, 'M', '咽'), + (0xF99F, 'M', '烈'), + (0xF9A0, 'M', '裂'), + (0xF9A1, 'M', '說'), + (0xF9A2, 'M', '廉'), + (0xF9A3, 'M', '念'), + (0xF9A4, 'M', '捻'), + (0xF9A5, 'M', '殮'), + (0xF9A6, 'M', '簾'), + (0xF9A7, 'M', '獵'), + (0xF9A8, 'M', '令'), + (0xF9A9, 'M', '囹'), + ] + +def _seg_41() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xF9AA, 'M', '寧'), + (0xF9AB, 'M', '嶺'), + (0xF9AC, 'M', '怜'), + (0xF9AD, 'M', '玲'), + (0xF9AE, 'M', '瑩'), + (0xF9AF, 'M', '羚'), + (0xF9B0, 'M', '聆'), + (0xF9B1, 'M', '鈴'), + (0xF9B2, 'M', '零'), + (0xF9B3, 'M', '靈'), + (0xF9B4, 'M', '領'), + (0xF9B5, 'M', '例'), + (0xF9B6, 'M', '禮'), + (0xF9B7, 'M', '醴'), + (0xF9B8, 'M', '隸'), + (0xF9B9, 'M', '惡'), + (0xF9BA, 'M', '了'), + (0xF9BB, 'M', '僚'), + (0xF9BC, 'M', '寮'), + (0xF9BD, 'M', '尿'), + (0xF9BE, 'M', '料'), + (0xF9BF, 'M', '樂'), + (0xF9C0, 'M', '燎'), + (0xF9C1, 'M', '療'), + (0xF9C2, 'M', '蓼'), + (0xF9C3, 'M', '遼'), + (0xF9C4, 'M', '龍'), + (0xF9C5, 'M', '暈'), + (0xF9C6, 'M', '阮'), + (0xF9C7, 'M', '劉'), + (0xF9C8, 'M', '杻'), + (0xF9C9, 'M', '柳'), + (0xF9CA, 'M', '流'), + (0xF9CB, 'M', '溜'), + (0xF9CC, 'M', '琉'), + (0xF9CD, 'M', '留'), + (0xF9CE, 'M', '硫'), + (0xF9CF, 'M', '紐'), + (0xF9D0, 'M', '類'), + (0xF9D1, 'M', '六'), + (0xF9D2, 'M', '戮'), + (0xF9D3, 'M', '陸'), + (0xF9D4, 'M', '倫'), + (0xF9D5, 'M', '崙'), + (0xF9D6, 'M', '淪'), + (0xF9D7, 'M', '輪'), + (0xF9D8, 'M', '律'), + (0xF9D9, 'M', '慄'), + (0xF9DA, 'M', '栗'), + (0xF9DB, 'M', '率'), + (0xF9DC, 'M', '隆'), + (0xF9DD, 'M', '利'), + (0xF9DE, 'M', '吏'), + (0xF9DF, 'M', '履'), + (0xF9E0, 'M', '易'), + (0xF9E1, 'M', '李'), + (0xF9E2, 'M', '梨'), + (0xF9E3, 'M', '泥'), + (0xF9E4, 'M', '理'), + (0xF9E5, 'M', '痢'), + (0xF9E6, 'M', '罹'), + (0xF9E7, 'M', '裏'), + (0xF9E8, 'M', '裡'), + (0xF9E9, 'M', '里'), + (0xF9EA, 'M', '離'), + (0xF9EB, 'M', '匿'), + (0xF9EC, 'M', '溺'), + (0xF9ED, 'M', '吝'), + (0xF9EE, 'M', '燐'), + (0xF9EF, 'M', '璘'), + (0xF9F0, 'M', '藺'), + (0xF9F1, 'M', '隣'), + (0xF9F2, 'M', '鱗'), + (0xF9F3, 'M', '麟'), + (0xF9F4, 'M', '林'), + (0xF9F5, 'M', '淋'), + (0xF9F6, 'M', '臨'), + (0xF9F7, 'M', '立'), + (0xF9F8, 'M', '笠'), + (0xF9F9, 'M', '粒'), + (0xF9FA, 'M', '狀'), + (0xF9FB, 'M', '炙'), + (0xF9FC, 'M', '識'), + (0xF9FD, 'M', '什'), + (0xF9FE, 'M', '茶'), + (0xF9FF, 'M', '刺'), + (0xFA00, 'M', '切'), + (0xFA01, 'M', '度'), + (0xFA02, 'M', '拓'), + (0xFA03, 'M', '糖'), + (0xFA04, 'M', '宅'), + (0xFA05, 'M', '洞'), + (0xFA06, 'M', '暴'), + (0xFA07, 'M', '輻'), + (0xFA08, 'M', '行'), + (0xFA09, 'M', '降'), + (0xFA0A, 'M', '見'), + (0xFA0B, 'M', '廓'), + (0xFA0C, 'M', '兀'), + (0xFA0D, 'M', '嗀'), + ] + +def _seg_42() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFA0E, 'V'), + (0xFA10, 'M', '塚'), + (0xFA11, 'V'), + (0xFA12, 'M', '晴'), + (0xFA13, 'V'), + (0xFA15, 'M', '凞'), + (0xFA16, 'M', '猪'), + (0xFA17, 'M', '益'), + (0xFA18, 'M', '礼'), + (0xFA19, 'M', '神'), + (0xFA1A, 'M', '祥'), + (0xFA1B, 'M', '福'), + (0xFA1C, 'M', '靖'), + (0xFA1D, 'M', '精'), + (0xFA1E, 'M', '羽'), + (0xFA1F, 'V'), + (0xFA20, 'M', '蘒'), + (0xFA21, 'V'), + (0xFA22, 'M', '諸'), + (0xFA23, 'V'), + (0xFA25, 'M', '逸'), + (0xFA26, 'M', '都'), + (0xFA27, 'V'), + (0xFA2A, 'M', '飯'), + (0xFA2B, 'M', '飼'), + (0xFA2C, 'M', '館'), + (0xFA2D, 'M', '鶴'), + (0xFA2E, 'M', '郞'), + (0xFA2F, 'M', '隷'), + (0xFA30, 'M', '侮'), + (0xFA31, 'M', '僧'), + (0xFA32, 'M', '免'), + (0xFA33, 'M', '勉'), + (0xFA34, 'M', '勤'), + (0xFA35, 'M', '卑'), + (0xFA36, 'M', '喝'), + (0xFA37, 'M', '嘆'), + (0xFA38, 'M', '器'), + (0xFA39, 'M', '塀'), + (0xFA3A, 'M', '墨'), + (0xFA3B, 'M', '層'), + (0xFA3C, 'M', '屮'), + (0xFA3D, 'M', '悔'), + (0xFA3E, 'M', '慨'), + (0xFA3F, 'M', '憎'), + (0xFA40, 'M', '懲'), + (0xFA41, 'M', '敏'), + (0xFA42, 'M', '既'), + (0xFA43, 'M', '暑'), + (0xFA44, 'M', '梅'), + (0xFA45, 'M', '海'), + (0xFA46, 'M', '渚'), + (0xFA47, 'M', '漢'), + (0xFA48, 'M', '煮'), + (0xFA49, 'M', '爫'), + (0xFA4A, 'M', '琢'), + (0xFA4B, 'M', '碑'), + (0xFA4C, 'M', '社'), + (0xFA4D, 'M', '祉'), + (0xFA4E, 'M', '祈'), + (0xFA4F, 'M', '祐'), + (0xFA50, 'M', '祖'), + (0xFA51, 'M', '祝'), + (0xFA52, 'M', '禍'), + (0xFA53, 'M', '禎'), + (0xFA54, 'M', '穀'), + (0xFA55, 'M', '突'), + (0xFA56, 'M', '節'), + (0xFA57, 'M', '練'), + (0xFA58, 'M', '縉'), + (0xFA59, 'M', '繁'), + (0xFA5A, 'M', '署'), + (0xFA5B, 'M', '者'), + (0xFA5C, 'M', '臭'), + (0xFA5D, 'M', '艹'), + (0xFA5F, 'M', '著'), + (0xFA60, 'M', '褐'), + (0xFA61, 'M', '視'), + (0xFA62, 'M', '謁'), + (0xFA63, 'M', '謹'), + (0xFA64, 'M', '賓'), + (0xFA65, 'M', '贈'), + (0xFA66, 'M', '辶'), + (0xFA67, 'M', '逸'), + (0xFA68, 'M', '難'), + (0xFA69, 'M', '響'), + (0xFA6A, 'M', '頻'), + (0xFA6B, 'M', '恵'), + (0xFA6C, 'M', '𤋮'), + (0xFA6D, 'M', '舘'), + (0xFA6E, 'X'), + (0xFA70, 'M', '並'), + (0xFA71, 'M', '况'), + (0xFA72, 'M', '全'), + (0xFA73, 'M', '侀'), + (0xFA74, 'M', '充'), + (0xFA75, 'M', '冀'), + (0xFA76, 'M', '勇'), + (0xFA77, 'M', '勺'), + (0xFA78, 'M', '喝'), + ] + +def _seg_43() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFA79, 'M', '啕'), + (0xFA7A, 'M', '喙'), + (0xFA7B, 'M', '嗢'), + (0xFA7C, 'M', '塚'), + (0xFA7D, 'M', '墳'), + (0xFA7E, 'M', '奄'), + (0xFA7F, 'M', '奔'), + (0xFA80, 'M', '婢'), + (0xFA81, 'M', '嬨'), + (0xFA82, 'M', '廒'), + (0xFA83, 'M', '廙'), + (0xFA84, 'M', '彩'), + (0xFA85, 'M', '徭'), + (0xFA86, 'M', '惘'), + (0xFA87, 'M', '慎'), + (0xFA88, 'M', '愈'), + (0xFA89, 'M', '憎'), + (0xFA8A, 'M', '慠'), + (0xFA8B, 'M', '懲'), + (0xFA8C, 'M', '戴'), + (0xFA8D, 'M', '揄'), + (0xFA8E, 'M', '搜'), + (0xFA8F, 'M', '摒'), + (0xFA90, 'M', '敖'), + (0xFA91, 'M', '晴'), + (0xFA92, 'M', '朗'), + (0xFA93, 'M', '望'), + (0xFA94, 'M', '杖'), + (0xFA95, 'M', '歹'), + (0xFA96, 'M', '殺'), + (0xFA97, 'M', '流'), + (0xFA98, 'M', '滛'), + (0xFA99, 'M', '滋'), + (0xFA9A, 'M', '漢'), + (0xFA9B, 'M', '瀞'), + (0xFA9C, 'M', '煮'), + (0xFA9D, 'M', '瞧'), + (0xFA9E, 'M', '爵'), + (0xFA9F, 'M', '犯'), + (0xFAA0, 'M', '猪'), + (0xFAA1, 'M', '瑱'), + (0xFAA2, 'M', '甆'), + (0xFAA3, 'M', '画'), + (0xFAA4, 'M', '瘝'), + (0xFAA5, 'M', '瘟'), + (0xFAA6, 'M', '益'), + (0xFAA7, 'M', '盛'), + (0xFAA8, 'M', '直'), + (0xFAA9, 'M', '睊'), + (0xFAAA, 'M', '着'), + (0xFAAB, 'M', '磌'), + (0xFAAC, 'M', '窱'), + (0xFAAD, 'M', '節'), + (0xFAAE, 'M', '类'), + (0xFAAF, 'M', '絛'), + (0xFAB0, 'M', '練'), + (0xFAB1, 'M', '缾'), + (0xFAB2, 'M', '者'), + (0xFAB3, 'M', '荒'), + (0xFAB4, 'M', '華'), + (0xFAB5, 'M', '蝹'), + (0xFAB6, 'M', '襁'), + (0xFAB7, 'M', '覆'), + (0xFAB8, 'M', '視'), + (0xFAB9, 'M', '調'), + (0xFABA, 'M', '諸'), + (0xFABB, 'M', '請'), + (0xFABC, 'M', '謁'), + (0xFABD, 'M', '諾'), + (0xFABE, 'M', '諭'), + (0xFABF, 'M', '謹'), + (0xFAC0, 'M', '變'), + (0xFAC1, 'M', '贈'), + (0xFAC2, 'M', '輸'), + (0xFAC3, 'M', '遲'), + (0xFAC4, 'M', '醙'), + (0xFAC5, 'M', '鉶'), + (0xFAC6, 'M', '陼'), + (0xFAC7, 'M', '難'), + (0xFAC8, 'M', '靖'), + (0xFAC9, 'M', '韛'), + (0xFACA, 'M', '響'), + (0xFACB, 'M', '頋'), + (0xFACC, 'M', '頻'), + (0xFACD, 'M', '鬒'), + (0xFACE, 'M', '龜'), + (0xFACF, 'M', '𢡊'), + (0xFAD0, 'M', '𢡄'), + (0xFAD1, 'M', '𣏕'), + (0xFAD2, 'M', '㮝'), + (0xFAD3, 'M', '䀘'), + (0xFAD4, 'M', '䀹'), + (0xFAD5, 'M', '𥉉'), + (0xFAD6, 'M', '𥳐'), + (0xFAD7, 'M', '𧻓'), + (0xFAD8, 'M', '齃'), + (0xFAD9, 'M', '龎'), + (0xFADA, 'X'), + (0xFB00, 'M', 'ff'), + (0xFB01, 'M', 'fi'), + ] + +def _seg_44() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFB02, 'M', 'fl'), + (0xFB03, 'M', 'ffi'), + (0xFB04, 'M', 'ffl'), + (0xFB05, 'M', 'st'), + (0xFB07, 'X'), + (0xFB13, 'M', 'մն'), + (0xFB14, 'M', 'մե'), + (0xFB15, 'M', 'մի'), + (0xFB16, 'M', 'վն'), + (0xFB17, 'M', 'մխ'), + (0xFB18, 'X'), + (0xFB1D, 'M', 'יִ'), + (0xFB1E, 'V'), + (0xFB1F, 'M', 'ײַ'), + (0xFB20, 'M', 'ע'), + (0xFB21, 'M', 'א'), + (0xFB22, 'M', 'ד'), + (0xFB23, 'M', 'ה'), + (0xFB24, 'M', 'כ'), + (0xFB25, 'M', 'ל'), + (0xFB26, 'M', 'ם'), + (0xFB27, 'M', 'ר'), + (0xFB28, 'M', 'ת'), + (0xFB29, '3', '+'), + (0xFB2A, 'M', 'שׁ'), + (0xFB2B, 'M', 'שׂ'), + (0xFB2C, 'M', 'שּׁ'), + (0xFB2D, 'M', 'שּׂ'), + (0xFB2E, 'M', 'אַ'), + (0xFB2F, 'M', 'אָ'), + (0xFB30, 'M', 'אּ'), + (0xFB31, 'M', 'בּ'), + (0xFB32, 'M', 'גּ'), + (0xFB33, 'M', 'דּ'), + (0xFB34, 'M', 'הּ'), + (0xFB35, 'M', 'וּ'), + (0xFB36, 'M', 'זּ'), + (0xFB37, 'X'), + (0xFB38, 'M', 'טּ'), + (0xFB39, 'M', 'יּ'), + (0xFB3A, 'M', 'ךּ'), + (0xFB3B, 'M', 'כּ'), + (0xFB3C, 'M', 'לּ'), + (0xFB3D, 'X'), + (0xFB3E, 'M', 'מּ'), + (0xFB3F, 'X'), + (0xFB40, 'M', 'נּ'), + (0xFB41, 'M', 'סּ'), + (0xFB42, 'X'), + (0xFB43, 'M', 'ףּ'), + (0xFB44, 'M', 'פּ'), + (0xFB45, 'X'), + (0xFB46, 'M', 'צּ'), + (0xFB47, 'M', 'קּ'), + (0xFB48, 'M', 'רּ'), + (0xFB49, 'M', 'שּ'), + (0xFB4A, 'M', 'תּ'), + (0xFB4B, 'M', 'וֹ'), + (0xFB4C, 'M', 'בֿ'), + (0xFB4D, 'M', 'כֿ'), + (0xFB4E, 'M', 'פֿ'), + (0xFB4F, 'M', 'אל'), + (0xFB50, 'M', 'ٱ'), + (0xFB52, 'M', 'ٻ'), + (0xFB56, 'M', 'پ'), + (0xFB5A, 'M', 'ڀ'), + (0xFB5E, 'M', 'ٺ'), + (0xFB62, 'M', 'ٿ'), + (0xFB66, 'M', 'ٹ'), + (0xFB6A, 'M', 'ڤ'), + (0xFB6E, 'M', 'ڦ'), + (0xFB72, 'M', 'ڄ'), + (0xFB76, 'M', 'ڃ'), + (0xFB7A, 'M', 'چ'), + (0xFB7E, 'M', 'ڇ'), + (0xFB82, 'M', 'ڍ'), + (0xFB84, 'M', 'ڌ'), + (0xFB86, 'M', 'ڎ'), + (0xFB88, 'M', 'ڈ'), + (0xFB8A, 'M', 'ژ'), + (0xFB8C, 'M', 'ڑ'), + (0xFB8E, 'M', 'ک'), + (0xFB92, 'M', 'گ'), + (0xFB96, 'M', 'ڳ'), + (0xFB9A, 'M', 'ڱ'), + (0xFB9E, 'M', 'ں'), + (0xFBA0, 'M', 'ڻ'), + (0xFBA4, 'M', 'ۀ'), + (0xFBA6, 'M', 'ہ'), + (0xFBAA, 'M', 'ھ'), + (0xFBAE, 'M', 'ے'), + (0xFBB0, 'M', 'ۓ'), + (0xFBB2, 'V'), + (0xFBC3, 'X'), + (0xFBD3, 'M', 'ڭ'), + (0xFBD7, 'M', 'ۇ'), + (0xFBD9, 'M', 'ۆ'), + (0xFBDB, 'M', 'ۈ'), + (0xFBDD, 'M', 'ۇٴ'), + (0xFBDE, 'M', 'ۋ'), + ] + +def _seg_45() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFBE0, 'M', 'ۅ'), + (0xFBE2, 'M', 'ۉ'), + (0xFBE4, 'M', 'ې'), + (0xFBE8, 'M', 'ى'), + (0xFBEA, 'M', 'ئا'), + (0xFBEC, 'M', 'ئە'), + (0xFBEE, 'M', 'ئو'), + (0xFBF0, 'M', 'ئۇ'), + (0xFBF2, 'M', 'ئۆ'), + (0xFBF4, 'M', 'ئۈ'), + (0xFBF6, 'M', 'ئې'), + (0xFBF9, 'M', 'ئى'), + (0xFBFC, 'M', 'ی'), + (0xFC00, 'M', 'ئج'), + (0xFC01, 'M', 'ئح'), + (0xFC02, 'M', 'ئم'), + (0xFC03, 'M', 'ئى'), + (0xFC04, 'M', 'ئي'), + (0xFC05, 'M', 'بج'), + (0xFC06, 'M', 'بح'), + (0xFC07, 'M', 'بخ'), + (0xFC08, 'M', 'بم'), + (0xFC09, 'M', 'بى'), + (0xFC0A, 'M', 'بي'), + (0xFC0B, 'M', 'تج'), + (0xFC0C, 'M', 'تح'), + (0xFC0D, 'M', 'تخ'), + (0xFC0E, 'M', 'تم'), + (0xFC0F, 'M', 'تى'), + (0xFC10, 'M', 'تي'), + (0xFC11, 'M', 'ثج'), + (0xFC12, 'M', 'ثم'), + (0xFC13, 'M', 'ثى'), + (0xFC14, 'M', 'ثي'), + (0xFC15, 'M', 'جح'), + (0xFC16, 'M', 'جم'), + (0xFC17, 'M', 'حج'), + (0xFC18, 'M', 'حم'), + (0xFC19, 'M', 'خج'), + (0xFC1A, 'M', 'خح'), + (0xFC1B, 'M', 'خم'), + (0xFC1C, 'M', 'سج'), + (0xFC1D, 'M', 'سح'), + (0xFC1E, 'M', 'سخ'), + (0xFC1F, 'M', 'سم'), + (0xFC20, 'M', 'صح'), + (0xFC21, 'M', 'صم'), + (0xFC22, 'M', 'ضج'), + (0xFC23, 'M', 'ضح'), + (0xFC24, 'M', 'ضخ'), + (0xFC25, 'M', 'ضم'), + (0xFC26, 'M', 'طح'), + (0xFC27, 'M', 'طم'), + (0xFC28, 'M', 'ظم'), + (0xFC29, 'M', 'عج'), + (0xFC2A, 'M', 'عم'), + (0xFC2B, 'M', 'غج'), + (0xFC2C, 'M', 'غم'), + (0xFC2D, 'M', 'فج'), + (0xFC2E, 'M', 'فح'), + (0xFC2F, 'M', 'فخ'), + (0xFC30, 'M', 'فم'), + (0xFC31, 'M', 'فى'), + (0xFC32, 'M', 'في'), + (0xFC33, 'M', 'قح'), + (0xFC34, 'M', 'قم'), + (0xFC35, 'M', 'قى'), + (0xFC36, 'M', 'قي'), + (0xFC37, 'M', 'كا'), + (0xFC38, 'M', 'كج'), + (0xFC39, 'M', 'كح'), + (0xFC3A, 'M', 'كخ'), + (0xFC3B, 'M', 'كل'), + (0xFC3C, 'M', 'كم'), + (0xFC3D, 'M', 'كى'), + (0xFC3E, 'M', 'كي'), + (0xFC3F, 'M', 'لج'), + (0xFC40, 'M', 'لح'), + (0xFC41, 'M', 'لخ'), + (0xFC42, 'M', 'لم'), + (0xFC43, 'M', 'لى'), + (0xFC44, 'M', 'لي'), + (0xFC45, 'M', 'مج'), + (0xFC46, 'M', 'مح'), + (0xFC47, 'M', 'مخ'), + (0xFC48, 'M', 'مم'), + (0xFC49, 'M', 'مى'), + (0xFC4A, 'M', 'مي'), + (0xFC4B, 'M', 'نج'), + (0xFC4C, 'M', 'نح'), + (0xFC4D, 'M', 'نخ'), + (0xFC4E, 'M', 'نم'), + (0xFC4F, 'M', 'نى'), + (0xFC50, 'M', 'ني'), + (0xFC51, 'M', 'هج'), + (0xFC52, 'M', 'هم'), + (0xFC53, 'M', 'هى'), + (0xFC54, 'M', 'هي'), + (0xFC55, 'M', 'يج'), + (0xFC56, 'M', 'يح'), + ] + +def _seg_46() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFC57, 'M', 'يخ'), + (0xFC58, 'M', 'يم'), + (0xFC59, 'M', 'يى'), + (0xFC5A, 'M', 'يي'), + (0xFC5B, 'M', 'ذٰ'), + (0xFC5C, 'M', 'رٰ'), + (0xFC5D, 'M', 'ىٰ'), + (0xFC5E, '3', ' ٌّ'), + (0xFC5F, '3', ' ٍّ'), + (0xFC60, '3', ' َّ'), + (0xFC61, '3', ' ُّ'), + (0xFC62, '3', ' ِّ'), + (0xFC63, '3', ' ّٰ'), + (0xFC64, 'M', 'ئر'), + (0xFC65, 'M', 'ئز'), + (0xFC66, 'M', 'ئم'), + (0xFC67, 'M', 'ئن'), + (0xFC68, 'M', 'ئى'), + (0xFC69, 'M', 'ئي'), + (0xFC6A, 'M', 'بر'), + (0xFC6B, 'M', 'بز'), + (0xFC6C, 'M', 'بم'), + (0xFC6D, 'M', 'بن'), + (0xFC6E, 'M', 'بى'), + (0xFC6F, 'M', 'بي'), + (0xFC70, 'M', 'تر'), + (0xFC71, 'M', 'تز'), + (0xFC72, 'M', 'تم'), + (0xFC73, 'M', 'تن'), + (0xFC74, 'M', 'تى'), + (0xFC75, 'M', 'تي'), + (0xFC76, 'M', 'ثر'), + (0xFC77, 'M', 'ثز'), + (0xFC78, 'M', 'ثم'), + (0xFC79, 'M', 'ثن'), + (0xFC7A, 'M', 'ثى'), + (0xFC7B, 'M', 'ثي'), + (0xFC7C, 'M', 'فى'), + (0xFC7D, 'M', 'في'), + (0xFC7E, 'M', 'قى'), + (0xFC7F, 'M', 'قي'), + (0xFC80, 'M', 'كا'), + (0xFC81, 'M', 'كل'), + (0xFC82, 'M', 'كم'), + (0xFC83, 'M', 'كى'), + (0xFC84, 'M', 'كي'), + (0xFC85, 'M', 'لم'), + (0xFC86, 'M', 'لى'), + (0xFC87, 'M', 'لي'), + (0xFC88, 'M', 'ما'), + (0xFC89, 'M', 'مم'), + (0xFC8A, 'M', 'نر'), + (0xFC8B, 'M', 'نز'), + (0xFC8C, 'M', 'نم'), + (0xFC8D, 'M', 'نن'), + (0xFC8E, 'M', 'نى'), + (0xFC8F, 'M', 'ني'), + (0xFC90, 'M', 'ىٰ'), + (0xFC91, 'M', 'ير'), + (0xFC92, 'M', 'يز'), + (0xFC93, 'M', 'يم'), + (0xFC94, 'M', 'ين'), + (0xFC95, 'M', 'يى'), + (0xFC96, 'M', 'يي'), + (0xFC97, 'M', 'ئج'), + (0xFC98, 'M', 'ئح'), + (0xFC99, 'M', 'ئخ'), + (0xFC9A, 'M', 'ئم'), + (0xFC9B, 'M', 'ئه'), + (0xFC9C, 'M', 'بج'), + (0xFC9D, 'M', 'بح'), + (0xFC9E, 'M', 'بخ'), + (0xFC9F, 'M', 'بم'), + (0xFCA0, 'M', 'به'), + (0xFCA1, 'M', 'تج'), + (0xFCA2, 'M', 'تح'), + (0xFCA3, 'M', 'تخ'), + (0xFCA4, 'M', 'تم'), + (0xFCA5, 'M', 'ته'), + (0xFCA6, 'M', 'ثم'), + (0xFCA7, 'M', 'جح'), + (0xFCA8, 'M', 'جم'), + (0xFCA9, 'M', 'حج'), + (0xFCAA, 'M', 'حم'), + (0xFCAB, 'M', 'خج'), + (0xFCAC, 'M', 'خم'), + (0xFCAD, 'M', 'سج'), + (0xFCAE, 'M', 'سح'), + (0xFCAF, 'M', 'سخ'), + (0xFCB0, 'M', 'سم'), + (0xFCB1, 'M', 'صح'), + (0xFCB2, 'M', 'صخ'), + (0xFCB3, 'M', 'صم'), + (0xFCB4, 'M', 'ضج'), + (0xFCB5, 'M', 'ضح'), + (0xFCB6, 'M', 'ضخ'), + (0xFCB7, 'M', 'ضم'), + (0xFCB8, 'M', 'طح'), + (0xFCB9, 'M', 'ظم'), + (0xFCBA, 'M', 'عج'), + ] + +def _seg_47() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFCBB, 'M', 'عم'), + (0xFCBC, 'M', 'غج'), + (0xFCBD, 'M', 'غم'), + (0xFCBE, 'M', 'فج'), + (0xFCBF, 'M', 'فح'), + (0xFCC0, 'M', 'فخ'), + (0xFCC1, 'M', 'فم'), + (0xFCC2, 'M', 'قح'), + (0xFCC3, 'M', 'قم'), + (0xFCC4, 'M', 'كج'), + (0xFCC5, 'M', 'كح'), + (0xFCC6, 'M', 'كخ'), + (0xFCC7, 'M', 'كل'), + (0xFCC8, 'M', 'كم'), + (0xFCC9, 'M', 'لج'), + (0xFCCA, 'M', 'لح'), + (0xFCCB, 'M', 'لخ'), + (0xFCCC, 'M', 'لم'), + (0xFCCD, 'M', 'له'), + (0xFCCE, 'M', 'مج'), + (0xFCCF, 'M', 'مح'), + (0xFCD0, 'M', 'مخ'), + (0xFCD1, 'M', 'مم'), + (0xFCD2, 'M', 'نج'), + (0xFCD3, 'M', 'نح'), + (0xFCD4, 'M', 'نخ'), + (0xFCD5, 'M', 'نم'), + (0xFCD6, 'M', 'نه'), + (0xFCD7, 'M', 'هج'), + (0xFCD8, 'M', 'هم'), + (0xFCD9, 'M', 'هٰ'), + (0xFCDA, 'M', 'يج'), + (0xFCDB, 'M', 'يح'), + (0xFCDC, 'M', 'يخ'), + (0xFCDD, 'M', 'يم'), + (0xFCDE, 'M', 'يه'), + (0xFCDF, 'M', 'ئم'), + (0xFCE0, 'M', 'ئه'), + (0xFCE1, 'M', 'بم'), + (0xFCE2, 'M', 'به'), + (0xFCE3, 'M', 'تم'), + (0xFCE4, 'M', 'ته'), + (0xFCE5, 'M', 'ثم'), + (0xFCE6, 'M', 'ثه'), + (0xFCE7, 'M', 'سم'), + (0xFCE8, 'M', 'سه'), + (0xFCE9, 'M', 'شم'), + (0xFCEA, 'M', 'شه'), + (0xFCEB, 'M', 'كل'), + (0xFCEC, 'M', 'كم'), + (0xFCED, 'M', 'لم'), + (0xFCEE, 'M', 'نم'), + (0xFCEF, 'M', 'نه'), + (0xFCF0, 'M', 'يم'), + (0xFCF1, 'M', 'يه'), + (0xFCF2, 'M', 'ـَّ'), + (0xFCF3, 'M', 'ـُّ'), + (0xFCF4, 'M', 'ـِّ'), + (0xFCF5, 'M', 'طى'), + (0xFCF6, 'M', 'طي'), + (0xFCF7, 'M', 'عى'), + (0xFCF8, 'M', 'عي'), + (0xFCF9, 'M', 'غى'), + (0xFCFA, 'M', 'غي'), + (0xFCFB, 'M', 'سى'), + (0xFCFC, 'M', 'سي'), + (0xFCFD, 'M', 'شى'), + (0xFCFE, 'M', 'شي'), + (0xFCFF, 'M', 'حى'), + (0xFD00, 'M', 'حي'), + (0xFD01, 'M', 'جى'), + (0xFD02, 'M', 'جي'), + (0xFD03, 'M', 'خى'), + (0xFD04, 'M', 'خي'), + (0xFD05, 'M', 'صى'), + (0xFD06, 'M', 'صي'), + (0xFD07, 'M', 'ضى'), + (0xFD08, 'M', 'ضي'), + (0xFD09, 'M', 'شج'), + (0xFD0A, 'M', 'شح'), + (0xFD0B, 'M', 'شخ'), + (0xFD0C, 'M', 'شم'), + (0xFD0D, 'M', 'شر'), + (0xFD0E, 'M', 'سر'), + (0xFD0F, 'M', 'صر'), + (0xFD10, 'M', 'ضر'), + (0xFD11, 'M', 'طى'), + (0xFD12, 'M', 'طي'), + (0xFD13, 'M', 'عى'), + (0xFD14, 'M', 'عي'), + (0xFD15, 'M', 'غى'), + (0xFD16, 'M', 'غي'), + (0xFD17, 'M', 'سى'), + (0xFD18, 'M', 'سي'), + (0xFD19, 'M', 'شى'), + (0xFD1A, 'M', 'شي'), + (0xFD1B, 'M', 'حى'), + (0xFD1C, 'M', 'حي'), + (0xFD1D, 'M', 'جى'), + (0xFD1E, 'M', 'جي'), + ] + +def _seg_48() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFD1F, 'M', 'خى'), + (0xFD20, 'M', 'خي'), + (0xFD21, 'M', 'صى'), + (0xFD22, 'M', 'صي'), + (0xFD23, 'M', 'ضى'), + (0xFD24, 'M', 'ضي'), + (0xFD25, 'M', 'شج'), + (0xFD26, 'M', 'شح'), + (0xFD27, 'M', 'شخ'), + (0xFD28, 'M', 'شم'), + (0xFD29, 'M', 'شر'), + (0xFD2A, 'M', 'سر'), + (0xFD2B, 'M', 'صر'), + (0xFD2C, 'M', 'ضر'), + (0xFD2D, 'M', 'شج'), + (0xFD2E, 'M', 'شح'), + (0xFD2F, 'M', 'شخ'), + (0xFD30, 'M', 'شم'), + (0xFD31, 'M', 'سه'), + (0xFD32, 'M', 'شه'), + (0xFD33, 'M', 'طم'), + (0xFD34, 'M', 'سج'), + (0xFD35, 'M', 'سح'), + (0xFD36, 'M', 'سخ'), + (0xFD37, 'M', 'شج'), + (0xFD38, 'M', 'شح'), + (0xFD39, 'M', 'شخ'), + (0xFD3A, 'M', 'طم'), + (0xFD3B, 'M', 'ظم'), + (0xFD3C, 'M', 'اً'), + (0xFD3E, 'V'), + (0xFD50, 'M', 'تجم'), + (0xFD51, 'M', 'تحج'), + (0xFD53, 'M', 'تحم'), + (0xFD54, 'M', 'تخم'), + (0xFD55, 'M', 'تمج'), + (0xFD56, 'M', 'تمح'), + (0xFD57, 'M', 'تمخ'), + (0xFD58, 'M', 'جمح'), + (0xFD5A, 'M', 'حمي'), + (0xFD5B, 'M', 'حمى'), + (0xFD5C, 'M', 'سحج'), + (0xFD5D, 'M', 'سجح'), + (0xFD5E, 'M', 'سجى'), + (0xFD5F, 'M', 'سمح'), + (0xFD61, 'M', 'سمج'), + (0xFD62, 'M', 'سمم'), + (0xFD64, 'M', 'صحح'), + (0xFD66, 'M', 'صمم'), + (0xFD67, 'M', 'شحم'), + (0xFD69, 'M', 'شجي'), + (0xFD6A, 'M', 'شمخ'), + (0xFD6C, 'M', 'شمم'), + (0xFD6E, 'M', 'ضحى'), + (0xFD6F, 'M', 'ضخم'), + (0xFD71, 'M', 'طمح'), + (0xFD73, 'M', 'طمم'), + (0xFD74, 'M', 'طمي'), + (0xFD75, 'M', 'عجم'), + (0xFD76, 'M', 'عمم'), + (0xFD78, 'M', 'عمى'), + (0xFD79, 'M', 'غمم'), + (0xFD7A, 'M', 'غمي'), + (0xFD7B, 'M', 'غمى'), + (0xFD7C, 'M', 'فخم'), + (0xFD7E, 'M', 'قمح'), + (0xFD7F, 'M', 'قمم'), + (0xFD80, 'M', 'لحم'), + (0xFD81, 'M', 'لحي'), + (0xFD82, 'M', 'لحى'), + (0xFD83, 'M', 'لجج'), + (0xFD85, 'M', 'لخم'), + (0xFD87, 'M', 'لمح'), + (0xFD89, 'M', 'محج'), + (0xFD8A, 'M', 'محم'), + (0xFD8B, 'M', 'محي'), + (0xFD8C, 'M', 'مجح'), + (0xFD8D, 'M', 'مجم'), + (0xFD8E, 'M', 'مخج'), + (0xFD8F, 'M', 'مخم'), + (0xFD90, 'X'), + (0xFD92, 'M', 'مجخ'), + (0xFD93, 'M', 'همج'), + (0xFD94, 'M', 'همم'), + (0xFD95, 'M', 'نحم'), + (0xFD96, 'M', 'نحى'), + (0xFD97, 'M', 'نجم'), + (0xFD99, 'M', 'نجى'), + (0xFD9A, 'M', 'نمي'), + (0xFD9B, 'M', 'نمى'), + (0xFD9C, 'M', 'يمم'), + (0xFD9E, 'M', 'بخي'), + (0xFD9F, 'M', 'تجي'), + (0xFDA0, 'M', 'تجى'), + (0xFDA1, 'M', 'تخي'), + (0xFDA2, 'M', 'تخى'), + (0xFDA3, 'M', 'تمي'), + (0xFDA4, 'M', 'تمى'), + (0xFDA5, 'M', 'جمي'), + (0xFDA6, 'M', 'جحى'), + ] + +def _seg_49() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFDA7, 'M', 'جمى'), + (0xFDA8, 'M', 'سخى'), + (0xFDA9, 'M', 'صحي'), + (0xFDAA, 'M', 'شحي'), + (0xFDAB, 'M', 'ضحي'), + (0xFDAC, 'M', 'لجي'), + (0xFDAD, 'M', 'لمي'), + (0xFDAE, 'M', 'يحي'), + (0xFDAF, 'M', 'يجي'), + (0xFDB0, 'M', 'يمي'), + (0xFDB1, 'M', 'ممي'), + (0xFDB2, 'M', 'قمي'), + (0xFDB3, 'M', 'نحي'), + (0xFDB4, 'M', 'قمح'), + (0xFDB5, 'M', 'لحم'), + (0xFDB6, 'M', 'عمي'), + (0xFDB7, 'M', 'كمي'), + (0xFDB8, 'M', 'نجح'), + (0xFDB9, 'M', 'مخي'), + (0xFDBA, 'M', 'لجم'), + (0xFDBB, 'M', 'كمم'), + (0xFDBC, 'M', 'لجم'), + (0xFDBD, 'M', 'نجح'), + (0xFDBE, 'M', 'جحي'), + (0xFDBF, 'M', 'حجي'), + (0xFDC0, 'M', 'مجي'), + (0xFDC1, 'M', 'فمي'), + (0xFDC2, 'M', 'بحي'), + (0xFDC3, 'M', 'كمم'), + (0xFDC4, 'M', 'عجم'), + (0xFDC5, 'M', 'صمم'), + (0xFDC6, 'M', 'سخي'), + (0xFDC7, 'M', 'نجي'), + (0xFDC8, 'X'), + (0xFDCF, 'V'), + (0xFDD0, 'X'), + (0xFDF0, 'M', 'صلے'), + (0xFDF1, 'M', 'قلے'), + (0xFDF2, 'M', 'الله'), + (0xFDF3, 'M', 'اكبر'), + (0xFDF4, 'M', 'محمد'), + (0xFDF5, 'M', 'صلعم'), + (0xFDF6, 'M', 'رسول'), + (0xFDF7, 'M', 'عليه'), + (0xFDF8, 'M', 'وسلم'), + (0xFDF9, 'M', 'صلى'), + (0xFDFA, '3', 'صلى الله عليه وسلم'), + (0xFDFB, '3', 'جل جلاله'), + (0xFDFC, 'M', 'ریال'), + (0xFDFD, 'V'), + (0xFE00, 'I'), + (0xFE10, '3', ','), + (0xFE11, 'M', '、'), + (0xFE12, 'X'), + (0xFE13, '3', ':'), + (0xFE14, '3', ';'), + (0xFE15, '3', '!'), + (0xFE16, '3', '?'), + (0xFE17, 'M', '〖'), + (0xFE18, 'M', '〗'), + (0xFE19, 'X'), + (0xFE20, 'V'), + (0xFE30, 'X'), + (0xFE31, 'M', '—'), + (0xFE32, 'M', '–'), + (0xFE33, '3', '_'), + (0xFE35, '3', '('), + (0xFE36, '3', ')'), + (0xFE37, '3', '{'), + (0xFE38, '3', '}'), + (0xFE39, 'M', '〔'), + (0xFE3A, 'M', '〕'), + (0xFE3B, 'M', '【'), + (0xFE3C, 'M', '】'), + (0xFE3D, 'M', '《'), + (0xFE3E, 'M', '》'), + (0xFE3F, 'M', '〈'), + (0xFE40, 'M', '〉'), + (0xFE41, 'M', '「'), + (0xFE42, 'M', '」'), + (0xFE43, 'M', '『'), + (0xFE44, 'M', '』'), + (0xFE45, 'V'), + (0xFE47, '3', '['), + (0xFE48, '3', ']'), + (0xFE49, '3', ' ̅'), + (0xFE4D, '3', '_'), + (0xFE50, '3', ','), + (0xFE51, 'M', '、'), + (0xFE52, 'X'), + (0xFE54, '3', ';'), + (0xFE55, '3', ':'), + (0xFE56, '3', '?'), + (0xFE57, '3', '!'), + (0xFE58, 'M', '—'), + (0xFE59, '3', '('), + (0xFE5A, '3', ')'), + (0xFE5B, '3', '{'), + (0xFE5C, '3', '}'), + (0xFE5D, 'M', '〔'), + ] + +def _seg_50() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFE5E, 'M', '〕'), + (0xFE5F, '3', '#'), + (0xFE60, '3', '&'), + (0xFE61, '3', '*'), + (0xFE62, '3', '+'), + (0xFE63, 'M', '-'), + (0xFE64, '3', '<'), + (0xFE65, '3', '>'), + (0xFE66, '3', '='), + (0xFE67, 'X'), + (0xFE68, '3', '\\'), + (0xFE69, '3', '$'), + (0xFE6A, '3', '%'), + (0xFE6B, '3', '@'), + (0xFE6C, 'X'), + (0xFE70, '3', ' ً'), + (0xFE71, 'M', 'ـً'), + (0xFE72, '3', ' ٌ'), + (0xFE73, 'V'), + (0xFE74, '3', ' ٍ'), + (0xFE75, 'X'), + (0xFE76, '3', ' َ'), + (0xFE77, 'M', 'ـَ'), + (0xFE78, '3', ' ُ'), + (0xFE79, 'M', 'ـُ'), + (0xFE7A, '3', ' ِ'), + (0xFE7B, 'M', 'ـِ'), + (0xFE7C, '3', ' ّ'), + (0xFE7D, 'M', 'ـّ'), + (0xFE7E, '3', ' ْ'), + (0xFE7F, 'M', 'ـْ'), + (0xFE80, 'M', 'ء'), + (0xFE81, 'M', 'آ'), + (0xFE83, 'M', 'أ'), + (0xFE85, 'M', 'ؤ'), + (0xFE87, 'M', 'إ'), + (0xFE89, 'M', 'ئ'), + (0xFE8D, 'M', 'ا'), + (0xFE8F, 'M', 'ب'), + (0xFE93, 'M', 'ة'), + (0xFE95, 'M', 'ت'), + (0xFE99, 'M', 'ث'), + (0xFE9D, 'M', 'ج'), + (0xFEA1, 'M', 'ح'), + (0xFEA5, 'M', 'خ'), + (0xFEA9, 'M', 'د'), + (0xFEAB, 'M', 'ذ'), + (0xFEAD, 'M', 'ر'), + (0xFEAF, 'M', 'ز'), + (0xFEB1, 'M', 'س'), + (0xFEB5, 'M', 'ش'), + (0xFEB9, 'M', 'ص'), + (0xFEBD, 'M', 'ض'), + (0xFEC1, 'M', 'ط'), + (0xFEC5, 'M', 'ظ'), + (0xFEC9, 'M', 'ع'), + (0xFECD, 'M', 'غ'), + (0xFED1, 'M', 'ف'), + (0xFED5, 'M', 'ق'), + (0xFED9, 'M', 'ك'), + (0xFEDD, 'M', 'ل'), + (0xFEE1, 'M', 'م'), + (0xFEE5, 'M', 'ن'), + (0xFEE9, 'M', 'ه'), + (0xFEED, 'M', 'و'), + (0xFEEF, 'M', 'ى'), + (0xFEF1, 'M', 'ي'), + (0xFEF5, 'M', 'لآ'), + (0xFEF7, 'M', 'لأ'), + (0xFEF9, 'M', 'لإ'), + (0xFEFB, 'M', 'لا'), + (0xFEFD, 'X'), + (0xFEFF, 'I'), + (0xFF00, 'X'), + (0xFF01, '3', '!'), + (0xFF02, '3', '"'), + (0xFF03, '3', '#'), + (0xFF04, '3', '$'), + (0xFF05, '3', '%'), + (0xFF06, '3', '&'), + (0xFF07, '3', '\''), + (0xFF08, '3', '('), + (0xFF09, '3', ')'), + (0xFF0A, '3', '*'), + (0xFF0B, '3', '+'), + (0xFF0C, '3', ','), + (0xFF0D, 'M', '-'), + (0xFF0E, 'M', '.'), + (0xFF0F, '3', '/'), + (0xFF10, 'M', '0'), + (0xFF11, 'M', '1'), + (0xFF12, 'M', '2'), + (0xFF13, 'M', '3'), + (0xFF14, 'M', '4'), + (0xFF15, 'M', '5'), + (0xFF16, 'M', '6'), + (0xFF17, 'M', '7'), + (0xFF18, 'M', '8'), + (0xFF19, 'M', '9'), + (0xFF1A, '3', ':'), + ] + +def _seg_51() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFF1B, '3', ';'), + (0xFF1C, '3', '<'), + (0xFF1D, '3', '='), + (0xFF1E, '3', '>'), + (0xFF1F, '3', '?'), + (0xFF20, '3', '@'), + (0xFF21, 'M', 'a'), + (0xFF22, 'M', 'b'), + (0xFF23, 'M', 'c'), + (0xFF24, 'M', 'd'), + (0xFF25, 'M', 'e'), + (0xFF26, 'M', 'f'), + (0xFF27, 'M', 'g'), + (0xFF28, 'M', 'h'), + (0xFF29, 'M', 'i'), + (0xFF2A, 'M', 'j'), + (0xFF2B, 'M', 'k'), + (0xFF2C, 'M', 'l'), + (0xFF2D, 'M', 'm'), + (0xFF2E, 'M', 'n'), + (0xFF2F, 'M', 'o'), + (0xFF30, 'M', 'p'), + (0xFF31, 'M', 'q'), + (0xFF32, 'M', 'r'), + (0xFF33, 'M', 's'), + (0xFF34, 'M', 't'), + (0xFF35, 'M', 'u'), + (0xFF36, 'M', 'v'), + (0xFF37, 'M', 'w'), + (0xFF38, 'M', 'x'), + (0xFF39, 'M', 'y'), + (0xFF3A, 'M', 'z'), + (0xFF3B, '3', '['), + (0xFF3C, '3', '\\'), + (0xFF3D, '3', ']'), + (0xFF3E, '3', '^'), + (0xFF3F, '3', '_'), + (0xFF40, '3', '`'), + (0xFF41, 'M', 'a'), + (0xFF42, 'M', 'b'), + (0xFF43, 'M', 'c'), + (0xFF44, 'M', 'd'), + (0xFF45, 'M', 'e'), + (0xFF46, 'M', 'f'), + (0xFF47, 'M', 'g'), + (0xFF48, 'M', 'h'), + (0xFF49, 'M', 'i'), + (0xFF4A, 'M', 'j'), + (0xFF4B, 'M', 'k'), + (0xFF4C, 'M', 'l'), + (0xFF4D, 'M', 'm'), + (0xFF4E, 'M', 'n'), + (0xFF4F, 'M', 'o'), + (0xFF50, 'M', 'p'), + (0xFF51, 'M', 'q'), + (0xFF52, 'M', 'r'), + (0xFF53, 'M', 's'), + (0xFF54, 'M', 't'), + (0xFF55, 'M', 'u'), + (0xFF56, 'M', 'v'), + (0xFF57, 'M', 'w'), + (0xFF58, 'M', 'x'), + (0xFF59, 'M', 'y'), + (0xFF5A, 'M', 'z'), + (0xFF5B, '3', '{'), + (0xFF5C, '3', '|'), + (0xFF5D, '3', '}'), + (0xFF5E, '3', '~'), + (0xFF5F, 'M', '⦅'), + (0xFF60, 'M', '⦆'), + (0xFF61, 'M', '.'), + (0xFF62, 'M', '「'), + (0xFF63, 'M', '」'), + (0xFF64, 'M', '、'), + (0xFF65, 'M', '・'), + (0xFF66, 'M', 'ヲ'), + (0xFF67, 'M', 'ァ'), + (0xFF68, 'M', 'ィ'), + (0xFF69, 'M', 'ゥ'), + (0xFF6A, 'M', 'ェ'), + (0xFF6B, 'M', 'ォ'), + (0xFF6C, 'M', 'ャ'), + (0xFF6D, 'M', 'ュ'), + (0xFF6E, 'M', 'ョ'), + (0xFF6F, 'M', 'ッ'), + (0xFF70, 'M', 'ー'), + (0xFF71, 'M', 'ア'), + (0xFF72, 'M', 'イ'), + (0xFF73, 'M', 'ウ'), + (0xFF74, 'M', 'エ'), + (0xFF75, 'M', 'オ'), + (0xFF76, 'M', 'カ'), + (0xFF77, 'M', 'キ'), + (0xFF78, 'M', 'ク'), + (0xFF79, 'M', 'ケ'), + (0xFF7A, 'M', 'コ'), + (0xFF7B, 'M', 'サ'), + (0xFF7C, 'M', 'シ'), + (0xFF7D, 'M', 'ス'), + (0xFF7E, 'M', 'セ'), + ] + +def _seg_52() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFF7F, 'M', 'ソ'), + (0xFF80, 'M', 'タ'), + (0xFF81, 'M', 'チ'), + (0xFF82, 'M', 'ツ'), + (0xFF83, 'M', 'テ'), + (0xFF84, 'M', 'ト'), + (0xFF85, 'M', 'ナ'), + (0xFF86, 'M', 'ニ'), + (0xFF87, 'M', 'ヌ'), + (0xFF88, 'M', 'ネ'), + (0xFF89, 'M', 'ノ'), + (0xFF8A, 'M', 'ハ'), + (0xFF8B, 'M', 'ヒ'), + (0xFF8C, 'M', 'フ'), + (0xFF8D, 'M', 'ヘ'), + (0xFF8E, 'M', 'ホ'), + (0xFF8F, 'M', 'マ'), + (0xFF90, 'M', 'ミ'), + (0xFF91, 'M', 'ム'), + (0xFF92, 'M', 'メ'), + (0xFF93, 'M', 'モ'), + (0xFF94, 'M', 'ヤ'), + (0xFF95, 'M', 'ユ'), + (0xFF96, 'M', 'ヨ'), + (0xFF97, 'M', 'ラ'), + (0xFF98, 'M', 'リ'), + (0xFF99, 'M', 'ル'), + (0xFF9A, 'M', 'レ'), + (0xFF9B, 'M', 'ロ'), + (0xFF9C, 'M', 'ワ'), + (0xFF9D, 'M', 'ン'), + (0xFF9E, 'M', '゙'), + (0xFF9F, 'M', '゚'), + (0xFFA0, 'X'), + (0xFFA1, 'M', 'ᄀ'), + (0xFFA2, 'M', 'ᄁ'), + (0xFFA3, 'M', 'ᆪ'), + (0xFFA4, 'M', 'ᄂ'), + (0xFFA5, 'M', 'ᆬ'), + (0xFFA6, 'M', 'ᆭ'), + (0xFFA7, 'M', 'ᄃ'), + (0xFFA8, 'M', 'ᄄ'), + (0xFFA9, 'M', 'ᄅ'), + (0xFFAA, 'M', 'ᆰ'), + (0xFFAB, 'M', 'ᆱ'), + (0xFFAC, 'M', 'ᆲ'), + (0xFFAD, 'M', 'ᆳ'), + (0xFFAE, 'M', 'ᆴ'), + (0xFFAF, 'M', 'ᆵ'), + (0xFFB0, 'M', 'ᄚ'), + (0xFFB1, 'M', 'ᄆ'), + (0xFFB2, 'M', 'ᄇ'), + (0xFFB3, 'M', 'ᄈ'), + (0xFFB4, 'M', 'ᄡ'), + (0xFFB5, 'M', 'ᄉ'), + (0xFFB6, 'M', 'ᄊ'), + (0xFFB7, 'M', 'ᄋ'), + (0xFFB8, 'M', 'ᄌ'), + (0xFFB9, 'M', 'ᄍ'), + (0xFFBA, 'M', 'ᄎ'), + (0xFFBB, 'M', 'ᄏ'), + (0xFFBC, 'M', 'ᄐ'), + (0xFFBD, 'M', 'ᄑ'), + (0xFFBE, 'M', 'ᄒ'), + (0xFFBF, 'X'), + (0xFFC2, 'M', 'ᅡ'), + (0xFFC3, 'M', 'ᅢ'), + (0xFFC4, 'M', 'ᅣ'), + (0xFFC5, 'M', 'ᅤ'), + (0xFFC6, 'M', 'ᅥ'), + (0xFFC7, 'M', 'ᅦ'), + (0xFFC8, 'X'), + (0xFFCA, 'M', 'ᅧ'), + (0xFFCB, 'M', 'ᅨ'), + (0xFFCC, 'M', 'ᅩ'), + (0xFFCD, 'M', 'ᅪ'), + (0xFFCE, 'M', 'ᅫ'), + (0xFFCF, 'M', 'ᅬ'), + (0xFFD0, 'X'), + (0xFFD2, 'M', 'ᅭ'), + (0xFFD3, 'M', 'ᅮ'), + (0xFFD4, 'M', 'ᅯ'), + (0xFFD5, 'M', 'ᅰ'), + (0xFFD6, 'M', 'ᅱ'), + (0xFFD7, 'M', 'ᅲ'), + (0xFFD8, 'X'), + (0xFFDA, 'M', 'ᅳ'), + (0xFFDB, 'M', 'ᅴ'), + (0xFFDC, 'M', 'ᅵ'), + (0xFFDD, 'X'), + (0xFFE0, 'M', '¢'), + (0xFFE1, 'M', '£'), + (0xFFE2, 'M', '¬'), + (0xFFE3, '3', ' ̄'), + (0xFFE4, 'M', '¦'), + (0xFFE5, 'M', '¥'), + (0xFFE6, 'M', '₩'), + (0xFFE7, 'X'), + (0xFFE8, 'M', '│'), + (0xFFE9, 'M', '←'), + ] + +def _seg_53() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFFEA, 'M', '↑'), + (0xFFEB, 'M', '→'), + (0xFFEC, 'M', '↓'), + (0xFFED, 'M', '■'), + (0xFFEE, 'M', '○'), + (0xFFEF, 'X'), + (0x10000, 'V'), + (0x1000C, 'X'), + (0x1000D, 'V'), + (0x10027, 'X'), + (0x10028, 'V'), + (0x1003B, 'X'), + (0x1003C, 'V'), + (0x1003E, 'X'), + (0x1003F, 'V'), + (0x1004E, 'X'), + (0x10050, 'V'), + (0x1005E, 'X'), + (0x10080, 'V'), + (0x100FB, 'X'), + (0x10100, 'V'), + (0x10103, 'X'), + (0x10107, 'V'), + (0x10134, 'X'), + (0x10137, 'V'), + (0x1018F, 'X'), + (0x10190, 'V'), + (0x1019D, 'X'), + (0x101A0, 'V'), + (0x101A1, 'X'), + (0x101D0, 'V'), + (0x101FE, 'X'), + (0x10280, 'V'), + (0x1029D, 'X'), + (0x102A0, 'V'), + (0x102D1, 'X'), + (0x102E0, 'V'), + (0x102FC, 'X'), + (0x10300, 'V'), + (0x10324, 'X'), + (0x1032D, 'V'), + (0x1034B, 'X'), + (0x10350, 'V'), + (0x1037B, 'X'), + (0x10380, 'V'), + (0x1039E, 'X'), + (0x1039F, 'V'), + (0x103C4, 'X'), + (0x103C8, 'V'), + (0x103D6, 'X'), + (0x10400, 'M', '𐐨'), + (0x10401, 'M', '𐐩'), + (0x10402, 'M', '𐐪'), + (0x10403, 'M', '𐐫'), + (0x10404, 'M', '𐐬'), + (0x10405, 'M', '𐐭'), + (0x10406, 'M', '𐐮'), + (0x10407, 'M', '𐐯'), + (0x10408, 'M', '𐐰'), + (0x10409, 'M', '𐐱'), + (0x1040A, 'M', '𐐲'), + (0x1040B, 'M', '𐐳'), + (0x1040C, 'M', '𐐴'), + (0x1040D, 'M', '𐐵'), + (0x1040E, 'M', '𐐶'), + (0x1040F, 'M', '𐐷'), + (0x10410, 'M', '𐐸'), + (0x10411, 'M', '𐐹'), + (0x10412, 'M', '𐐺'), + (0x10413, 'M', '𐐻'), + (0x10414, 'M', '𐐼'), + (0x10415, 'M', '𐐽'), + (0x10416, 'M', '𐐾'), + (0x10417, 'M', '𐐿'), + (0x10418, 'M', '𐑀'), + (0x10419, 'M', '𐑁'), + (0x1041A, 'M', '𐑂'), + (0x1041B, 'M', '𐑃'), + (0x1041C, 'M', '𐑄'), + (0x1041D, 'M', '𐑅'), + (0x1041E, 'M', '𐑆'), + (0x1041F, 'M', '𐑇'), + (0x10420, 'M', '𐑈'), + (0x10421, 'M', '𐑉'), + (0x10422, 'M', '𐑊'), + (0x10423, 'M', '𐑋'), + (0x10424, 'M', '𐑌'), + (0x10425, 'M', '𐑍'), + (0x10426, 'M', '𐑎'), + (0x10427, 'M', '𐑏'), + (0x10428, 'V'), + (0x1049E, 'X'), + (0x104A0, 'V'), + (0x104AA, 'X'), + (0x104B0, 'M', '𐓘'), + (0x104B1, 'M', '𐓙'), + (0x104B2, 'M', '𐓚'), + (0x104B3, 'M', '𐓛'), + (0x104B4, 'M', '𐓜'), + (0x104B5, 'M', '𐓝'), + ] + +def _seg_54() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x104B6, 'M', '𐓞'), + (0x104B7, 'M', '𐓟'), + (0x104B8, 'M', '𐓠'), + (0x104B9, 'M', '𐓡'), + (0x104BA, 'M', '𐓢'), + (0x104BB, 'M', '𐓣'), + (0x104BC, 'M', '𐓤'), + (0x104BD, 'M', '𐓥'), + (0x104BE, 'M', '𐓦'), + (0x104BF, 'M', '𐓧'), + (0x104C0, 'M', '𐓨'), + (0x104C1, 'M', '𐓩'), + (0x104C2, 'M', '𐓪'), + (0x104C3, 'M', '𐓫'), + (0x104C4, 'M', '𐓬'), + (0x104C5, 'M', '𐓭'), + (0x104C6, 'M', '𐓮'), + (0x104C7, 'M', '𐓯'), + (0x104C8, 'M', '𐓰'), + (0x104C9, 'M', '𐓱'), + (0x104CA, 'M', '𐓲'), + (0x104CB, 'M', '𐓳'), + (0x104CC, 'M', '𐓴'), + (0x104CD, 'M', '𐓵'), + (0x104CE, 'M', '𐓶'), + (0x104CF, 'M', '𐓷'), + (0x104D0, 'M', '𐓸'), + (0x104D1, 'M', '𐓹'), + (0x104D2, 'M', '𐓺'), + (0x104D3, 'M', '𐓻'), + (0x104D4, 'X'), + (0x104D8, 'V'), + (0x104FC, 'X'), + (0x10500, 'V'), + (0x10528, 'X'), + (0x10530, 'V'), + (0x10564, 'X'), + (0x1056F, 'V'), + (0x10570, 'M', '𐖗'), + (0x10571, 'M', '𐖘'), + (0x10572, 'M', '𐖙'), + (0x10573, 'M', '𐖚'), + (0x10574, 'M', '𐖛'), + (0x10575, 'M', '𐖜'), + (0x10576, 'M', '𐖝'), + (0x10577, 'M', '𐖞'), + (0x10578, 'M', '𐖟'), + (0x10579, 'M', '𐖠'), + (0x1057A, 'M', '𐖡'), + (0x1057B, 'X'), + (0x1057C, 'M', '𐖣'), + (0x1057D, 'M', '𐖤'), + (0x1057E, 'M', '𐖥'), + (0x1057F, 'M', '𐖦'), + (0x10580, 'M', '𐖧'), + (0x10581, 'M', '𐖨'), + (0x10582, 'M', '𐖩'), + (0x10583, 'M', '𐖪'), + (0x10584, 'M', '𐖫'), + (0x10585, 'M', '𐖬'), + (0x10586, 'M', '𐖭'), + (0x10587, 'M', '𐖮'), + (0x10588, 'M', '𐖯'), + (0x10589, 'M', '𐖰'), + (0x1058A, 'M', '𐖱'), + (0x1058B, 'X'), + (0x1058C, 'M', '𐖳'), + (0x1058D, 'M', '𐖴'), + (0x1058E, 'M', '𐖵'), + (0x1058F, 'M', '𐖶'), + (0x10590, 'M', '𐖷'), + (0x10591, 'M', '𐖸'), + (0x10592, 'M', '𐖹'), + (0x10593, 'X'), + (0x10594, 'M', '𐖻'), + (0x10595, 'M', '𐖼'), + (0x10596, 'X'), + (0x10597, 'V'), + (0x105A2, 'X'), + (0x105A3, 'V'), + (0x105B2, 'X'), + (0x105B3, 'V'), + (0x105BA, 'X'), + (0x105BB, 'V'), + (0x105BD, 'X'), + (0x10600, 'V'), + (0x10737, 'X'), + (0x10740, 'V'), + (0x10756, 'X'), + (0x10760, 'V'), + (0x10768, 'X'), + (0x10780, 'V'), + (0x10781, 'M', 'ː'), + (0x10782, 'M', 'ˑ'), + (0x10783, 'M', 'æ'), + (0x10784, 'M', 'ʙ'), + (0x10785, 'M', 'ɓ'), + (0x10786, 'X'), + (0x10787, 'M', 'ʣ'), + (0x10788, 'M', 'ꭦ'), + ] + +def _seg_55() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x10789, 'M', 'ʥ'), + (0x1078A, 'M', 'ʤ'), + (0x1078B, 'M', 'ɖ'), + (0x1078C, 'M', 'ɗ'), + (0x1078D, 'M', 'ᶑ'), + (0x1078E, 'M', 'ɘ'), + (0x1078F, 'M', 'ɞ'), + (0x10790, 'M', 'ʩ'), + (0x10791, 'M', 'ɤ'), + (0x10792, 'M', 'ɢ'), + (0x10793, 'M', 'ɠ'), + (0x10794, 'M', 'ʛ'), + (0x10795, 'M', 'ħ'), + (0x10796, 'M', 'ʜ'), + (0x10797, 'M', 'ɧ'), + (0x10798, 'M', 'ʄ'), + (0x10799, 'M', 'ʪ'), + (0x1079A, 'M', 'ʫ'), + (0x1079B, 'M', 'ɬ'), + (0x1079C, 'M', '𝼄'), + (0x1079D, 'M', 'ꞎ'), + (0x1079E, 'M', 'ɮ'), + (0x1079F, 'M', '𝼅'), + (0x107A0, 'M', 'ʎ'), + (0x107A1, 'M', '𝼆'), + (0x107A2, 'M', 'ø'), + (0x107A3, 'M', 'ɶ'), + (0x107A4, 'M', 'ɷ'), + (0x107A5, 'M', 'q'), + (0x107A6, 'M', 'ɺ'), + (0x107A7, 'M', '𝼈'), + (0x107A8, 'M', 'ɽ'), + (0x107A9, 'M', 'ɾ'), + (0x107AA, 'M', 'ʀ'), + (0x107AB, 'M', 'ʨ'), + (0x107AC, 'M', 'ʦ'), + (0x107AD, 'M', 'ꭧ'), + (0x107AE, 'M', 'ʧ'), + (0x107AF, 'M', 'ʈ'), + (0x107B0, 'M', 'ⱱ'), + (0x107B1, 'X'), + (0x107B2, 'M', 'ʏ'), + (0x107B3, 'M', 'ʡ'), + (0x107B4, 'M', 'ʢ'), + (0x107B5, 'M', 'ʘ'), + (0x107B6, 'M', 'ǀ'), + (0x107B7, 'M', 'ǁ'), + (0x107B8, 'M', 'ǂ'), + (0x107B9, 'M', '𝼊'), + (0x107BA, 'M', '𝼞'), + (0x107BB, 'X'), + (0x10800, 'V'), + (0x10806, 'X'), + (0x10808, 'V'), + (0x10809, 'X'), + (0x1080A, 'V'), + (0x10836, 'X'), + (0x10837, 'V'), + (0x10839, 'X'), + (0x1083C, 'V'), + (0x1083D, 'X'), + (0x1083F, 'V'), + (0x10856, 'X'), + (0x10857, 'V'), + (0x1089F, 'X'), + (0x108A7, 'V'), + (0x108B0, 'X'), + (0x108E0, 'V'), + (0x108F3, 'X'), + (0x108F4, 'V'), + (0x108F6, 'X'), + (0x108FB, 'V'), + (0x1091C, 'X'), + (0x1091F, 'V'), + (0x1093A, 'X'), + (0x1093F, 'V'), + (0x10940, 'X'), + (0x10980, 'V'), + (0x109B8, 'X'), + (0x109BC, 'V'), + (0x109D0, 'X'), + (0x109D2, 'V'), + (0x10A04, 'X'), + (0x10A05, 'V'), + (0x10A07, 'X'), + (0x10A0C, 'V'), + (0x10A14, 'X'), + (0x10A15, 'V'), + (0x10A18, 'X'), + (0x10A19, 'V'), + (0x10A36, 'X'), + (0x10A38, 'V'), + (0x10A3B, 'X'), + (0x10A3F, 'V'), + (0x10A49, 'X'), + (0x10A50, 'V'), + (0x10A59, 'X'), + (0x10A60, 'V'), + (0x10AA0, 'X'), + (0x10AC0, 'V'), + ] + +def _seg_56() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x10AE7, 'X'), + (0x10AEB, 'V'), + (0x10AF7, 'X'), + (0x10B00, 'V'), + (0x10B36, 'X'), + (0x10B39, 'V'), + (0x10B56, 'X'), + (0x10B58, 'V'), + (0x10B73, 'X'), + (0x10B78, 'V'), + (0x10B92, 'X'), + (0x10B99, 'V'), + (0x10B9D, 'X'), + (0x10BA9, 'V'), + (0x10BB0, 'X'), + (0x10C00, 'V'), + (0x10C49, 'X'), + (0x10C80, 'M', '𐳀'), + (0x10C81, 'M', '𐳁'), + (0x10C82, 'M', '𐳂'), + (0x10C83, 'M', '𐳃'), + (0x10C84, 'M', '𐳄'), + (0x10C85, 'M', '𐳅'), + (0x10C86, 'M', '𐳆'), + (0x10C87, 'M', '𐳇'), + (0x10C88, 'M', '𐳈'), + (0x10C89, 'M', '𐳉'), + (0x10C8A, 'M', '𐳊'), + (0x10C8B, 'M', '𐳋'), + (0x10C8C, 'M', '𐳌'), + (0x10C8D, 'M', '𐳍'), + (0x10C8E, 'M', '𐳎'), + (0x10C8F, 'M', '𐳏'), + (0x10C90, 'M', '𐳐'), + (0x10C91, 'M', '𐳑'), + (0x10C92, 'M', '𐳒'), + (0x10C93, 'M', '𐳓'), + (0x10C94, 'M', '𐳔'), + (0x10C95, 'M', '𐳕'), + (0x10C96, 'M', '𐳖'), + (0x10C97, 'M', '𐳗'), + (0x10C98, 'M', '𐳘'), + (0x10C99, 'M', '𐳙'), + (0x10C9A, 'M', '𐳚'), + (0x10C9B, 'M', '𐳛'), + (0x10C9C, 'M', '𐳜'), + (0x10C9D, 'M', '𐳝'), + (0x10C9E, 'M', '𐳞'), + (0x10C9F, 'M', '𐳟'), + (0x10CA0, 'M', '𐳠'), + (0x10CA1, 'M', '𐳡'), + (0x10CA2, 'M', '𐳢'), + (0x10CA3, 'M', '𐳣'), + (0x10CA4, 'M', '𐳤'), + (0x10CA5, 'M', '𐳥'), + (0x10CA6, 'M', '𐳦'), + (0x10CA7, 'M', '𐳧'), + (0x10CA8, 'M', '𐳨'), + (0x10CA9, 'M', '𐳩'), + (0x10CAA, 'M', '𐳪'), + (0x10CAB, 'M', '𐳫'), + (0x10CAC, 'M', '𐳬'), + (0x10CAD, 'M', '𐳭'), + (0x10CAE, 'M', '𐳮'), + (0x10CAF, 'M', '𐳯'), + (0x10CB0, 'M', '𐳰'), + (0x10CB1, 'M', '𐳱'), + (0x10CB2, 'M', '𐳲'), + (0x10CB3, 'X'), + (0x10CC0, 'V'), + (0x10CF3, 'X'), + (0x10CFA, 'V'), + (0x10D28, 'X'), + (0x10D30, 'V'), + (0x10D3A, 'X'), + (0x10E60, 'V'), + (0x10E7F, 'X'), + (0x10E80, 'V'), + (0x10EAA, 'X'), + (0x10EAB, 'V'), + (0x10EAE, 'X'), + (0x10EB0, 'V'), + (0x10EB2, 'X'), + (0x10EFD, 'V'), + (0x10F28, 'X'), + (0x10F30, 'V'), + (0x10F5A, 'X'), + (0x10F70, 'V'), + (0x10F8A, 'X'), + (0x10FB0, 'V'), + (0x10FCC, 'X'), + (0x10FE0, 'V'), + (0x10FF7, 'X'), + (0x11000, 'V'), + (0x1104E, 'X'), + (0x11052, 'V'), + (0x11076, 'X'), + (0x1107F, 'V'), + (0x110BD, 'X'), + (0x110BE, 'V'), + ] + +def _seg_57() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x110C3, 'X'), + (0x110D0, 'V'), + (0x110E9, 'X'), + (0x110F0, 'V'), + (0x110FA, 'X'), + (0x11100, 'V'), + (0x11135, 'X'), + (0x11136, 'V'), + (0x11148, 'X'), + (0x11150, 'V'), + (0x11177, 'X'), + (0x11180, 'V'), + (0x111E0, 'X'), + (0x111E1, 'V'), + (0x111F5, 'X'), + (0x11200, 'V'), + (0x11212, 'X'), + (0x11213, 'V'), + (0x11242, 'X'), + (0x11280, 'V'), + (0x11287, 'X'), + (0x11288, 'V'), + (0x11289, 'X'), + (0x1128A, 'V'), + (0x1128E, 'X'), + (0x1128F, 'V'), + (0x1129E, 'X'), + (0x1129F, 'V'), + (0x112AA, 'X'), + (0x112B0, 'V'), + (0x112EB, 'X'), + (0x112F0, 'V'), + (0x112FA, 'X'), + (0x11300, 'V'), + (0x11304, 'X'), + (0x11305, 'V'), + (0x1130D, 'X'), + (0x1130F, 'V'), + (0x11311, 'X'), + (0x11313, 'V'), + (0x11329, 'X'), + (0x1132A, 'V'), + (0x11331, 'X'), + (0x11332, 'V'), + (0x11334, 'X'), + (0x11335, 'V'), + (0x1133A, 'X'), + (0x1133B, 'V'), + (0x11345, 'X'), + (0x11347, 'V'), + (0x11349, 'X'), + (0x1134B, 'V'), + (0x1134E, 'X'), + (0x11350, 'V'), + (0x11351, 'X'), + (0x11357, 'V'), + (0x11358, 'X'), + (0x1135D, 'V'), + (0x11364, 'X'), + (0x11366, 'V'), + (0x1136D, 'X'), + (0x11370, 'V'), + (0x11375, 'X'), + (0x11400, 'V'), + (0x1145C, 'X'), + (0x1145D, 'V'), + (0x11462, 'X'), + (0x11480, 'V'), + (0x114C8, 'X'), + (0x114D0, 'V'), + (0x114DA, 'X'), + (0x11580, 'V'), + (0x115B6, 'X'), + (0x115B8, 'V'), + (0x115DE, 'X'), + (0x11600, 'V'), + (0x11645, 'X'), + (0x11650, 'V'), + (0x1165A, 'X'), + (0x11660, 'V'), + (0x1166D, 'X'), + (0x11680, 'V'), + (0x116BA, 'X'), + (0x116C0, 'V'), + (0x116CA, 'X'), + (0x11700, 'V'), + (0x1171B, 'X'), + (0x1171D, 'V'), + (0x1172C, 'X'), + (0x11730, 'V'), + (0x11747, 'X'), + (0x11800, 'V'), + (0x1183C, 'X'), + (0x118A0, 'M', '𑣀'), + (0x118A1, 'M', '𑣁'), + (0x118A2, 'M', '𑣂'), + (0x118A3, 'M', '𑣃'), + (0x118A4, 'M', '𑣄'), + (0x118A5, 'M', '𑣅'), + (0x118A6, 'M', '𑣆'), + ] + +def _seg_58() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x118A7, 'M', '𑣇'), + (0x118A8, 'M', '𑣈'), + (0x118A9, 'M', '𑣉'), + (0x118AA, 'M', '𑣊'), + (0x118AB, 'M', '𑣋'), + (0x118AC, 'M', '𑣌'), + (0x118AD, 'M', '𑣍'), + (0x118AE, 'M', '𑣎'), + (0x118AF, 'M', '𑣏'), + (0x118B0, 'M', '𑣐'), + (0x118B1, 'M', '𑣑'), + (0x118B2, 'M', '𑣒'), + (0x118B3, 'M', '𑣓'), + (0x118B4, 'M', '𑣔'), + (0x118B5, 'M', '𑣕'), + (0x118B6, 'M', '𑣖'), + (0x118B7, 'M', '𑣗'), + (0x118B8, 'M', '𑣘'), + (0x118B9, 'M', '𑣙'), + (0x118BA, 'M', '𑣚'), + (0x118BB, 'M', '𑣛'), + (0x118BC, 'M', '𑣜'), + (0x118BD, 'M', '𑣝'), + (0x118BE, 'M', '𑣞'), + (0x118BF, 'M', '𑣟'), + (0x118C0, 'V'), + (0x118F3, 'X'), + (0x118FF, 'V'), + (0x11907, 'X'), + (0x11909, 'V'), + (0x1190A, 'X'), + (0x1190C, 'V'), + (0x11914, 'X'), + (0x11915, 'V'), + (0x11917, 'X'), + (0x11918, 'V'), + (0x11936, 'X'), + (0x11937, 'V'), + (0x11939, 'X'), + (0x1193B, 'V'), + (0x11947, 'X'), + (0x11950, 'V'), + (0x1195A, 'X'), + (0x119A0, 'V'), + (0x119A8, 'X'), + (0x119AA, 'V'), + (0x119D8, 'X'), + (0x119DA, 'V'), + (0x119E5, 'X'), + (0x11A00, 'V'), + (0x11A48, 'X'), + (0x11A50, 'V'), + (0x11AA3, 'X'), + (0x11AB0, 'V'), + (0x11AF9, 'X'), + (0x11B00, 'V'), + (0x11B0A, 'X'), + (0x11C00, 'V'), + (0x11C09, 'X'), + (0x11C0A, 'V'), + (0x11C37, 'X'), + (0x11C38, 'V'), + (0x11C46, 'X'), + (0x11C50, 'V'), + (0x11C6D, 'X'), + (0x11C70, 'V'), + (0x11C90, 'X'), + (0x11C92, 'V'), + (0x11CA8, 'X'), + (0x11CA9, 'V'), + (0x11CB7, 'X'), + (0x11D00, 'V'), + (0x11D07, 'X'), + (0x11D08, 'V'), + (0x11D0A, 'X'), + (0x11D0B, 'V'), + (0x11D37, 'X'), + (0x11D3A, 'V'), + (0x11D3B, 'X'), + (0x11D3C, 'V'), + (0x11D3E, 'X'), + (0x11D3F, 'V'), + (0x11D48, 'X'), + (0x11D50, 'V'), + (0x11D5A, 'X'), + (0x11D60, 'V'), + (0x11D66, 'X'), + (0x11D67, 'V'), + (0x11D69, 'X'), + (0x11D6A, 'V'), + (0x11D8F, 'X'), + (0x11D90, 'V'), + (0x11D92, 'X'), + (0x11D93, 'V'), + (0x11D99, 'X'), + (0x11DA0, 'V'), + (0x11DAA, 'X'), + (0x11EE0, 'V'), + (0x11EF9, 'X'), + (0x11F00, 'V'), + ] + +def _seg_59() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x11F11, 'X'), + (0x11F12, 'V'), + (0x11F3B, 'X'), + (0x11F3E, 'V'), + (0x11F5A, 'X'), + (0x11FB0, 'V'), + (0x11FB1, 'X'), + (0x11FC0, 'V'), + (0x11FF2, 'X'), + (0x11FFF, 'V'), + (0x1239A, 'X'), + (0x12400, 'V'), + (0x1246F, 'X'), + (0x12470, 'V'), + (0x12475, 'X'), + (0x12480, 'V'), + (0x12544, 'X'), + (0x12F90, 'V'), + (0x12FF3, 'X'), + (0x13000, 'V'), + (0x13430, 'X'), + (0x13440, 'V'), + (0x13456, 'X'), + (0x14400, 'V'), + (0x14647, 'X'), + (0x16800, 'V'), + (0x16A39, 'X'), + (0x16A40, 'V'), + (0x16A5F, 'X'), + (0x16A60, 'V'), + (0x16A6A, 'X'), + (0x16A6E, 'V'), + (0x16ABF, 'X'), + (0x16AC0, 'V'), + (0x16ACA, 'X'), + (0x16AD0, 'V'), + (0x16AEE, 'X'), + (0x16AF0, 'V'), + (0x16AF6, 'X'), + (0x16B00, 'V'), + (0x16B46, 'X'), + (0x16B50, 'V'), + (0x16B5A, 'X'), + (0x16B5B, 'V'), + (0x16B62, 'X'), + (0x16B63, 'V'), + (0x16B78, 'X'), + (0x16B7D, 'V'), + (0x16B90, 'X'), + (0x16E40, 'M', '𖹠'), + (0x16E41, 'M', '𖹡'), + (0x16E42, 'M', '𖹢'), + (0x16E43, 'M', '𖹣'), + (0x16E44, 'M', '𖹤'), + (0x16E45, 'M', '𖹥'), + (0x16E46, 'M', '𖹦'), + (0x16E47, 'M', '𖹧'), + (0x16E48, 'M', '𖹨'), + (0x16E49, 'M', '𖹩'), + (0x16E4A, 'M', '𖹪'), + (0x16E4B, 'M', '𖹫'), + (0x16E4C, 'M', '𖹬'), + (0x16E4D, 'M', '𖹭'), + (0x16E4E, 'M', '𖹮'), + (0x16E4F, 'M', '𖹯'), + (0x16E50, 'M', '𖹰'), + (0x16E51, 'M', '𖹱'), + (0x16E52, 'M', '𖹲'), + (0x16E53, 'M', '𖹳'), + (0x16E54, 'M', '𖹴'), + (0x16E55, 'M', '𖹵'), + (0x16E56, 'M', '𖹶'), + (0x16E57, 'M', '𖹷'), + (0x16E58, 'M', '𖹸'), + (0x16E59, 'M', '𖹹'), + (0x16E5A, 'M', '𖹺'), + (0x16E5B, 'M', '𖹻'), + (0x16E5C, 'M', '𖹼'), + (0x16E5D, 'M', '𖹽'), + (0x16E5E, 'M', '𖹾'), + (0x16E5F, 'M', '𖹿'), + (0x16E60, 'V'), + (0x16E9B, 'X'), + (0x16F00, 'V'), + (0x16F4B, 'X'), + (0x16F4F, 'V'), + (0x16F88, 'X'), + (0x16F8F, 'V'), + (0x16FA0, 'X'), + (0x16FE0, 'V'), + (0x16FE5, 'X'), + (0x16FF0, 'V'), + (0x16FF2, 'X'), + (0x17000, 'V'), + (0x187F8, 'X'), + (0x18800, 'V'), + (0x18CD6, 'X'), + (0x18D00, 'V'), + (0x18D09, 'X'), + (0x1AFF0, 'V'), + ] + +def _seg_60() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1AFF4, 'X'), + (0x1AFF5, 'V'), + (0x1AFFC, 'X'), + (0x1AFFD, 'V'), + (0x1AFFF, 'X'), + (0x1B000, 'V'), + (0x1B123, 'X'), + (0x1B132, 'V'), + (0x1B133, 'X'), + (0x1B150, 'V'), + (0x1B153, 'X'), + (0x1B155, 'V'), + (0x1B156, 'X'), + (0x1B164, 'V'), + (0x1B168, 'X'), + (0x1B170, 'V'), + (0x1B2FC, 'X'), + (0x1BC00, 'V'), + (0x1BC6B, 'X'), + (0x1BC70, 'V'), + (0x1BC7D, 'X'), + (0x1BC80, 'V'), + (0x1BC89, 'X'), + (0x1BC90, 'V'), + (0x1BC9A, 'X'), + (0x1BC9C, 'V'), + (0x1BCA0, 'I'), + (0x1BCA4, 'X'), + (0x1CF00, 'V'), + (0x1CF2E, 'X'), + (0x1CF30, 'V'), + (0x1CF47, 'X'), + (0x1CF50, 'V'), + (0x1CFC4, 'X'), + (0x1D000, 'V'), + (0x1D0F6, 'X'), + (0x1D100, 'V'), + (0x1D127, 'X'), + (0x1D129, 'V'), + (0x1D15E, 'M', '𝅗𝅥'), + (0x1D15F, 'M', '𝅘𝅥'), + (0x1D160, 'M', '𝅘𝅥𝅮'), + (0x1D161, 'M', '𝅘𝅥𝅯'), + (0x1D162, 'M', '𝅘𝅥𝅰'), + (0x1D163, 'M', '𝅘𝅥𝅱'), + (0x1D164, 'M', '𝅘𝅥𝅲'), + (0x1D165, 'V'), + (0x1D173, 'X'), + (0x1D17B, 'V'), + (0x1D1BB, 'M', '𝆹𝅥'), + (0x1D1BC, 'M', '𝆺𝅥'), + (0x1D1BD, 'M', '𝆹𝅥𝅮'), + (0x1D1BE, 'M', '𝆺𝅥𝅮'), + (0x1D1BF, 'M', '𝆹𝅥𝅯'), + (0x1D1C0, 'M', '𝆺𝅥𝅯'), + (0x1D1C1, 'V'), + (0x1D1EB, 'X'), + (0x1D200, 'V'), + (0x1D246, 'X'), + (0x1D2C0, 'V'), + (0x1D2D4, 'X'), + (0x1D2E0, 'V'), + (0x1D2F4, 'X'), + (0x1D300, 'V'), + (0x1D357, 'X'), + (0x1D360, 'V'), + (0x1D379, 'X'), + (0x1D400, 'M', 'a'), + (0x1D401, 'M', 'b'), + (0x1D402, 'M', 'c'), + (0x1D403, 'M', 'd'), + (0x1D404, 'M', 'e'), + (0x1D405, 'M', 'f'), + (0x1D406, 'M', 'g'), + (0x1D407, 'M', 'h'), + (0x1D408, 'M', 'i'), + (0x1D409, 'M', 'j'), + (0x1D40A, 'M', 'k'), + (0x1D40B, 'M', 'l'), + (0x1D40C, 'M', 'm'), + (0x1D40D, 'M', 'n'), + (0x1D40E, 'M', 'o'), + (0x1D40F, 'M', 'p'), + (0x1D410, 'M', 'q'), + (0x1D411, 'M', 'r'), + (0x1D412, 'M', 's'), + (0x1D413, 'M', 't'), + (0x1D414, 'M', 'u'), + (0x1D415, 'M', 'v'), + (0x1D416, 'M', 'w'), + (0x1D417, 'M', 'x'), + (0x1D418, 'M', 'y'), + (0x1D419, 'M', 'z'), + (0x1D41A, 'M', 'a'), + (0x1D41B, 'M', 'b'), + (0x1D41C, 'M', 'c'), + (0x1D41D, 'M', 'd'), + (0x1D41E, 'M', 'e'), + (0x1D41F, 'M', 'f'), + (0x1D420, 'M', 'g'), + ] + +def _seg_61() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D421, 'M', 'h'), + (0x1D422, 'M', 'i'), + (0x1D423, 'M', 'j'), + (0x1D424, 'M', 'k'), + (0x1D425, 'M', 'l'), + (0x1D426, 'M', 'm'), + (0x1D427, 'M', 'n'), + (0x1D428, 'M', 'o'), + (0x1D429, 'M', 'p'), + (0x1D42A, 'M', 'q'), + (0x1D42B, 'M', 'r'), + (0x1D42C, 'M', 's'), + (0x1D42D, 'M', 't'), + (0x1D42E, 'M', 'u'), + (0x1D42F, 'M', 'v'), + (0x1D430, 'M', 'w'), + (0x1D431, 'M', 'x'), + (0x1D432, 'M', 'y'), + (0x1D433, 'M', 'z'), + (0x1D434, 'M', 'a'), + (0x1D435, 'M', 'b'), + (0x1D436, 'M', 'c'), + (0x1D437, 'M', 'd'), + (0x1D438, 'M', 'e'), + (0x1D439, 'M', 'f'), + (0x1D43A, 'M', 'g'), + (0x1D43B, 'M', 'h'), + (0x1D43C, 'M', 'i'), + (0x1D43D, 'M', 'j'), + (0x1D43E, 'M', 'k'), + (0x1D43F, 'M', 'l'), + (0x1D440, 'M', 'm'), + (0x1D441, 'M', 'n'), + (0x1D442, 'M', 'o'), + (0x1D443, 'M', 'p'), + (0x1D444, 'M', 'q'), + (0x1D445, 'M', 'r'), + (0x1D446, 'M', 's'), + (0x1D447, 'M', 't'), + (0x1D448, 'M', 'u'), + (0x1D449, 'M', 'v'), + (0x1D44A, 'M', 'w'), + (0x1D44B, 'M', 'x'), + (0x1D44C, 'M', 'y'), + (0x1D44D, 'M', 'z'), + (0x1D44E, 'M', 'a'), + (0x1D44F, 'M', 'b'), + (0x1D450, 'M', 'c'), + (0x1D451, 'M', 'd'), + (0x1D452, 'M', 'e'), + (0x1D453, 'M', 'f'), + (0x1D454, 'M', 'g'), + (0x1D455, 'X'), + (0x1D456, 'M', 'i'), + (0x1D457, 'M', 'j'), + (0x1D458, 'M', 'k'), + (0x1D459, 'M', 'l'), + (0x1D45A, 'M', 'm'), + (0x1D45B, 'M', 'n'), + (0x1D45C, 'M', 'o'), + (0x1D45D, 'M', 'p'), + (0x1D45E, 'M', 'q'), + (0x1D45F, 'M', 'r'), + (0x1D460, 'M', 's'), + (0x1D461, 'M', 't'), + (0x1D462, 'M', 'u'), + (0x1D463, 'M', 'v'), + (0x1D464, 'M', 'w'), + (0x1D465, 'M', 'x'), + (0x1D466, 'M', 'y'), + (0x1D467, 'M', 'z'), + (0x1D468, 'M', 'a'), + (0x1D469, 'M', 'b'), + (0x1D46A, 'M', 'c'), + (0x1D46B, 'M', 'd'), + (0x1D46C, 'M', 'e'), + (0x1D46D, 'M', 'f'), + (0x1D46E, 'M', 'g'), + (0x1D46F, 'M', 'h'), + (0x1D470, 'M', 'i'), + (0x1D471, 'M', 'j'), + (0x1D472, 'M', 'k'), + (0x1D473, 'M', 'l'), + (0x1D474, 'M', 'm'), + (0x1D475, 'M', 'n'), + (0x1D476, 'M', 'o'), + (0x1D477, 'M', 'p'), + (0x1D478, 'M', 'q'), + (0x1D479, 'M', 'r'), + (0x1D47A, 'M', 's'), + (0x1D47B, 'M', 't'), + (0x1D47C, 'M', 'u'), + (0x1D47D, 'M', 'v'), + (0x1D47E, 'M', 'w'), + (0x1D47F, 'M', 'x'), + (0x1D480, 'M', 'y'), + (0x1D481, 'M', 'z'), + (0x1D482, 'M', 'a'), + (0x1D483, 'M', 'b'), + (0x1D484, 'M', 'c'), + ] + +def _seg_62() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D485, 'M', 'd'), + (0x1D486, 'M', 'e'), + (0x1D487, 'M', 'f'), + (0x1D488, 'M', 'g'), + (0x1D489, 'M', 'h'), + (0x1D48A, 'M', 'i'), + (0x1D48B, 'M', 'j'), + (0x1D48C, 'M', 'k'), + (0x1D48D, 'M', 'l'), + (0x1D48E, 'M', 'm'), + (0x1D48F, 'M', 'n'), + (0x1D490, 'M', 'o'), + (0x1D491, 'M', 'p'), + (0x1D492, 'M', 'q'), + (0x1D493, 'M', 'r'), + (0x1D494, 'M', 's'), + (0x1D495, 'M', 't'), + (0x1D496, 'M', 'u'), + (0x1D497, 'M', 'v'), + (0x1D498, 'M', 'w'), + (0x1D499, 'M', 'x'), + (0x1D49A, 'M', 'y'), + (0x1D49B, 'M', 'z'), + (0x1D49C, 'M', 'a'), + (0x1D49D, 'X'), + (0x1D49E, 'M', 'c'), + (0x1D49F, 'M', 'd'), + (0x1D4A0, 'X'), + (0x1D4A2, 'M', 'g'), + (0x1D4A3, 'X'), + (0x1D4A5, 'M', 'j'), + (0x1D4A6, 'M', 'k'), + (0x1D4A7, 'X'), + (0x1D4A9, 'M', 'n'), + (0x1D4AA, 'M', 'o'), + (0x1D4AB, 'M', 'p'), + (0x1D4AC, 'M', 'q'), + (0x1D4AD, 'X'), + (0x1D4AE, 'M', 's'), + (0x1D4AF, 'M', 't'), + (0x1D4B0, 'M', 'u'), + (0x1D4B1, 'M', 'v'), + (0x1D4B2, 'M', 'w'), + (0x1D4B3, 'M', 'x'), + (0x1D4B4, 'M', 'y'), + (0x1D4B5, 'M', 'z'), + (0x1D4B6, 'M', 'a'), + (0x1D4B7, 'M', 'b'), + (0x1D4B8, 'M', 'c'), + (0x1D4B9, 'M', 'd'), + (0x1D4BA, 'X'), + (0x1D4BB, 'M', 'f'), + (0x1D4BC, 'X'), + (0x1D4BD, 'M', 'h'), + (0x1D4BE, 'M', 'i'), + (0x1D4BF, 'M', 'j'), + (0x1D4C0, 'M', 'k'), + (0x1D4C1, 'M', 'l'), + (0x1D4C2, 'M', 'm'), + (0x1D4C3, 'M', 'n'), + (0x1D4C4, 'X'), + (0x1D4C5, 'M', 'p'), + (0x1D4C6, 'M', 'q'), + (0x1D4C7, 'M', 'r'), + (0x1D4C8, 'M', 's'), + (0x1D4C9, 'M', 't'), + (0x1D4CA, 'M', 'u'), + (0x1D4CB, 'M', 'v'), + (0x1D4CC, 'M', 'w'), + (0x1D4CD, 'M', 'x'), + (0x1D4CE, 'M', 'y'), + (0x1D4CF, 'M', 'z'), + (0x1D4D0, 'M', 'a'), + (0x1D4D1, 'M', 'b'), + (0x1D4D2, 'M', 'c'), + (0x1D4D3, 'M', 'd'), + (0x1D4D4, 'M', 'e'), + (0x1D4D5, 'M', 'f'), + (0x1D4D6, 'M', 'g'), + (0x1D4D7, 'M', 'h'), + (0x1D4D8, 'M', 'i'), + (0x1D4D9, 'M', 'j'), + (0x1D4DA, 'M', 'k'), + (0x1D4DB, 'M', 'l'), + (0x1D4DC, 'M', 'm'), + (0x1D4DD, 'M', 'n'), + (0x1D4DE, 'M', 'o'), + (0x1D4DF, 'M', 'p'), + (0x1D4E0, 'M', 'q'), + (0x1D4E1, 'M', 'r'), + (0x1D4E2, 'M', 's'), + (0x1D4E3, 'M', 't'), + (0x1D4E4, 'M', 'u'), + (0x1D4E5, 'M', 'v'), + (0x1D4E6, 'M', 'w'), + (0x1D4E7, 'M', 'x'), + (0x1D4E8, 'M', 'y'), + (0x1D4E9, 'M', 'z'), + (0x1D4EA, 'M', 'a'), + (0x1D4EB, 'M', 'b'), + ] + +def _seg_63() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D4EC, 'M', 'c'), + (0x1D4ED, 'M', 'd'), + (0x1D4EE, 'M', 'e'), + (0x1D4EF, 'M', 'f'), + (0x1D4F0, 'M', 'g'), + (0x1D4F1, 'M', 'h'), + (0x1D4F2, 'M', 'i'), + (0x1D4F3, 'M', 'j'), + (0x1D4F4, 'M', 'k'), + (0x1D4F5, 'M', 'l'), + (0x1D4F6, 'M', 'm'), + (0x1D4F7, 'M', 'n'), + (0x1D4F8, 'M', 'o'), + (0x1D4F9, 'M', 'p'), + (0x1D4FA, 'M', 'q'), + (0x1D4FB, 'M', 'r'), + (0x1D4FC, 'M', 's'), + (0x1D4FD, 'M', 't'), + (0x1D4FE, 'M', 'u'), + (0x1D4FF, 'M', 'v'), + (0x1D500, 'M', 'w'), + (0x1D501, 'M', 'x'), + (0x1D502, 'M', 'y'), + (0x1D503, 'M', 'z'), + (0x1D504, 'M', 'a'), + (0x1D505, 'M', 'b'), + (0x1D506, 'X'), + (0x1D507, 'M', 'd'), + (0x1D508, 'M', 'e'), + (0x1D509, 'M', 'f'), + (0x1D50A, 'M', 'g'), + (0x1D50B, 'X'), + (0x1D50D, 'M', 'j'), + (0x1D50E, 'M', 'k'), + (0x1D50F, 'M', 'l'), + (0x1D510, 'M', 'm'), + (0x1D511, 'M', 'n'), + (0x1D512, 'M', 'o'), + (0x1D513, 'M', 'p'), + (0x1D514, 'M', 'q'), + (0x1D515, 'X'), + (0x1D516, 'M', 's'), + (0x1D517, 'M', 't'), + (0x1D518, 'M', 'u'), + (0x1D519, 'M', 'v'), + (0x1D51A, 'M', 'w'), + (0x1D51B, 'M', 'x'), + (0x1D51C, 'M', 'y'), + (0x1D51D, 'X'), + (0x1D51E, 'M', 'a'), + (0x1D51F, 'M', 'b'), + (0x1D520, 'M', 'c'), + (0x1D521, 'M', 'd'), + (0x1D522, 'M', 'e'), + (0x1D523, 'M', 'f'), + (0x1D524, 'M', 'g'), + (0x1D525, 'M', 'h'), + (0x1D526, 'M', 'i'), + (0x1D527, 'M', 'j'), + (0x1D528, 'M', 'k'), + (0x1D529, 'M', 'l'), + (0x1D52A, 'M', 'm'), + (0x1D52B, 'M', 'n'), + (0x1D52C, 'M', 'o'), + (0x1D52D, 'M', 'p'), + (0x1D52E, 'M', 'q'), + (0x1D52F, 'M', 'r'), + (0x1D530, 'M', 's'), + (0x1D531, 'M', 't'), + (0x1D532, 'M', 'u'), + (0x1D533, 'M', 'v'), + (0x1D534, 'M', 'w'), + (0x1D535, 'M', 'x'), + (0x1D536, 'M', 'y'), + (0x1D537, 'M', 'z'), + (0x1D538, 'M', 'a'), + (0x1D539, 'M', 'b'), + (0x1D53A, 'X'), + (0x1D53B, 'M', 'd'), + (0x1D53C, 'M', 'e'), + (0x1D53D, 'M', 'f'), + (0x1D53E, 'M', 'g'), + (0x1D53F, 'X'), + (0x1D540, 'M', 'i'), + (0x1D541, 'M', 'j'), + (0x1D542, 'M', 'k'), + (0x1D543, 'M', 'l'), + (0x1D544, 'M', 'm'), + (0x1D545, 'X'), + (0x1D546, 'M', 'o'), + (0x1D547, 'X'), + (0x1D54A, 'M', 's'), + (0x1D54B, 'M', 't'), + (0x1D54C, 'M', 'u'), + (0x1D54D, 'M', 'v'), + (0x1D54E, 'M', 'w'), + (0x1D54F, 'M', 'x'), + (0x1D550, 'M', 'y'), + (0x1D551, 'X'), + (0x1D552, 'M', 'a'), + ] + +def _seg_64() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D553, 'M', 'b'), + (0x1D554, 'M', 'c'), + (0x1D555, 'M', 'd'), + (0x1D556, 'M', 'e'), + (0x1D557, 'M', 'f'), + (0x1D558, 'M', 'g'), + (0x1D559, 'M', 'h'), + (0x1D55A, 'M', 'i'), + (0x1D55B, 'M', 'j'), + (0x1D55C, 'M', 'k'), + (0x1D55D, 'M', 'l'), + (0x1D55E, 'M', 'm'), + (0x1D55F, 'M', 'n'), + (0x1D560, 'M', 'o'), + (0x1D561, 'M', 'p'), + (0x1D562, 'M', 'q'), + (0x1D563, 'M', 'r'), + (0x1D564, 'M', 's'), + (0x1D565, 'M', 't'), + (0x1D566, 'M', 'u'), + (0x1D567, 'M', 'v'), + (0x1D568, 'M', 'w'), + (0x1D569, 'M', 'x'), + (0x1D56A, 'M', 'y'), + (0x1D56B, 'M', 'z'), + (0x1D56C, 'M', 'a'), + (0x1D56D, 'M', 'b'), + (0x1D56E, 'M', 'c'), + (0x1D56F, 'M', 'd'), + (0x1D570, 'M', 'e'), + (0x1D571, 'M', 'f'), + (0x1D572, 'M', 'g'), + (0x1D573, 'M', 'h'), + (0x1D574, 'M', 'i'), + (0x1D575, 'M', 'j'), + (0x1D576, 'M', 'k'), + (0x1D577, 'M', 'l'), + (0x1D578, 'M', 'm'), + (0x1D579, 'M', 'n'), + (0x1D57A, 'M', 'o'), + (0x1D57B, 'M', 'p'), + (0x1D57C, 'M', 'q'), + (0x1D57D, 'M', 'r'), + (0x1D57E, 'M', 's'), + (0x1D57F, 'M', 't'), + (0x1D580, 'M', 'u'), + (0x1D581, 'M', 'v'), + (0x1D582, 'M', 'w'), + (0x1D583, 'M', 'x'), + (0x1D584, 'M', 'y'), + (0x1D585, 'M', 'z'), + (0x1D586, 'M', 'a'), + (0x1D587, 'M', 'b'), + (0x1D588, 'M', 'c'), + (0x1D589, 'M', 'd'), + (0x1D58A, 'M', 'e'), + (0x1D58B, 'M', 'f'), + (0x1D58C, 'M', 'g'), + (0x1D58D, 'M', 'h'), + (0x1D58E, 'M', 'i'), + (0x1D58F, 'M', 'j'), + (0x1D590, 'M', 'k'), + (0x1D591, 'M', 'l'), + (0x1D592, 'M', 'm'), + (0x1D593, 'M', 'n'), + (0x1D594, 'M', 'o'), + (0x1D595, 'M', 'p'), + (0x1D596, 'M', 'q'), + (0x1D597, 'M', 'r'), + (0x1D598, 'M', 's'), + (0x1D599, 'M', 't'), + (0x1D59A, 'M', 'u'), + (0x1D59B, 'M', 'v'), + (0x1D59C, 'M', 'w'), + (0x1D59D, 'M', 'x'), + (0x1D59E, 'M', 'y'), + (0x1D59F, 'M', 'z'), + (0x1D5A0, 'M', 'a'), + (0x1D5A1, 'M', 'b'), + (0x1D5A2, 'M', 'c'), + (0x1D5A3, 'M', 'd'), + (0x1D5A4, 'M', 'e'), + (0x1D5A5, 'M', 'f'), + (0x1D5A6, 'M', 'g'), + (0x1D5A7, 'M', 'h'), + (0x1D5A8, 'M', 'i'), + (0x1D5A9, 'M', 'j'), + (0x1D5AA, 'M', 'k'), + (0x1D5AB, 'M', 'l'), + (0x1D5AC, 'M', 'm'), + (0x1D5AD, 'M', 'n'), + (0x1D5AE, 'M', 'o'), + (0x1D5AF, 'M', 'p'), + (0x1D5B0, 'M', 'q'), + (0x1D5B1, 'M', 'r'), + (0x1D5B2, 'M', 's'), + (0x1D5B3, 'M', 't'), + (0x1D5B4, 'M', 'u'), + (0x1D5B5, 'M', 'v'), + (0x1D5B6, 'M', 'w'), + ] + +def _seg_65() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D5B7, 'M', 'x'), + (0x1D5B8, 'M', 'y'), + (0x1D5B9, 'M', 'z'), + (0x1D5BA, 'M', 'a'), + (0x1D5BB, 'M', 'b'), + (0x1D5BC, 'M', 'c'), + (0x1D5BD, 'M', 'd'), + (0x1D5BE, 'M', 'e'), + (0x1D5BF, 'M', 'f'), + (0x1D5C0, 'M', 'g'), + (0x1D5C1, 'M', 'h'), + (0x1D5C2, 'M', 'i'), + (0x1D5C3, 'M', 'j'), + (0x1D5C4, 'M', 'k'), + (0x1D5C5, 'M', 'l'), + (0x1D5C6, 'M', 'm'), + (0x1D5C7, 'M', 'n'), + (0x1D5C8, 'M', 'o'), + (0x1D5C9, 'M', 'p'), + (0x1D5CA, 'M', 'q'), + (0x1D5CB, 'M', 'r'), + (0x1D5CC, 'M', 's'), + (0x1D5CD, 'M', 't'), + (0x1D5CE, 'M', 'u'), + (0x1D5CF, 'M', 'v'), + (0x1D5D0, 'M', 'w'), + (0x1D5D1, 'M', 'x'), + (0x1D5D2, 'M', 'y'), + (0x1D5D3, 'M', 'z'), + (0x1D5D4, 'M', 'a'), + (0x1D5D5, 'M', 'b'), + (0x1D5D6, 'M', 'c'), + (0x1D5D7, 'M', 'd'), + (0x1D5D8, 'M', 'e'), + (0x1D5D9, 'M', 'f'), + (0x1D5DA, 'M', 'g'), + (0x1D5DB, 'M', 'h'), + (0x1D5DC, 'M', 'i'), + (0x1D5DD, 'M', 'j'), + (0x1D5DE, 'M', 'k'), + (0x1D5DF, 'M', 'l'), + (0x1D5E0, 'M', 'm'), + (0x1D5E1, 'M', 'n'), + (0x1D5E2, 'M', 'o'), + (0x1D5E3, 'M', 'p'), + (0x1D5E4, 'M', 'q'), + (0x1D5E5, 'M', 'r'), + (0x1D5E6, 'M', 's'), + (0x1D5E7, 'M', 't'), + (0x1D5E8, 'M', 'u'), + (0x1D5E9, 'M', 'v'), + (0x1D5EA, 'M', 'w'), + (0x1D5EB, 'M', 'x'), + (0x1D5EC, 'M', 'y'), + (0x1D5ED, 'M', 'z'), + (0x1D5EE, 'M', 'a'), + (0x1D5EF, 'M', 'b'), + (0x1D5F0, 'M', 'c'), + (0x1D5F1, 'M', 'd'), + (0x1D5F2, 'M', 'e'), + (0x1D5F3, 'M', 'f'), + (0x1D5F4, 'M', 'g'), + (0x1D5F5, 'M', 'h'), + (0x1D5F6, 'M', 'i'), + (0x1D5F7, 'M', 'j'), + (0x1D5F8, 'M', 'k'), + (0x1D5F9, 'M', 'l'), + (0x1D5FA, 'M', 'm'), + (0x1D5FB, 'M', 'n'), + (0x1D5FC, 'M', 'o'), + (0x1D5FD, 'M', 'p'), + (0x1D5FE, 'M', 'q'), + (0x1D5FF, 'M', 'r'), + (0x1D600, 'M', 's'), + (0x1D601, 'M', 't'), + (0x1D602, 'M', 'u'), + (0x1D603, 'M', 'v'), + (0x1D604, 'M', 'w'), + (0x1D605, 'M', 'x'), + (0x1D606, 'M', 'y'), + (0x1D607, 'M', 'z'), + (0x1D608, 'M', 'a'), + (0x1D609, 'M', 'b'), + (0x1D60A, 'M', 'c'), + (0x1D60B, 'M', 'd'), + (0x1D60C, 'M', 'e'), + (0x1D60D, 'M', 'f'), + (0x1D60E, 'M', 'g'), + (0x1D60F, 'M', 'h'), + (0x1D610, 'M', 'i'), + (0x1D611, 'M', 'j'), + (0x1D612, 'M', 'k'), + (0x1D613, 'M', 'l'), + (0x1D614, 'M', 'm'), + (0x1D615, 'M', 'n'), + (0x1D616, 'M', 'o'), + (0x1D617, 'M', 'p'), + (0x1D618, 'M', 'q'), + (0x1D619, 'M', 'r'), + (0x1D61A, 'M', 's'), + ] + +def _seg_66() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D61B, 'M', 't'), + (0x1D61C, 'M', 'u'), + (0x1D61D, 'M', 'v'), + (0x1D61E, 'M', 'w'), + (0x1D61F, 'M', 'x'), + (0x1D620, 'M', 'y'), + (0x1D621, 'M', 'z'), + (0x1D622, 'M', 'a'), + (0x1D623, 'M', 'b'), + (0x1D624, 'M', 'c'), + (0x1D625, 'M', 'd'), + (0x1D626, 'M', 'e'), + (0x1D627, 'M', 'f'), + (0x1D628, 'M', 'g'), + (0x1D629, 'M', 'h'), + (0x1D62A, 'M', 'i'), + (0x1D62B, 'M', 'j'), + (0x1D62C, 'M', 'k'), + (0x1D62D, 'M', 'l'), + (0x1D62E, 'M', 'm'), + (0x1D62F, 'M', 'n'), + (0x1D630, 'M', 'o'), + (0x1D631, 'M', 'p'), + (0x1D632, 'M', 'q'), + (0x1D633, 'M', 'r'), + (0x1D634, 'M', 's'), + (0x1D635, 'M', 't'), + (0x1D636, 'M', 'u'), + (0x1D637, 'M', 'v'), + (0x1D638, 'M', 'w'), + (0x1D639, 'M', 'x'), + (0x1D63A, 'M', 'y'), + (0x1D63B, 'M', 'z'), + (0x1D63C, 'M', 'a'), + (0x1D63D, 'M', 'b'), + (0x1D63E, 'M', 'c'), + (0x1D63F, 'M', 'd'), + (0x1D640, 'M', 'e'), + (0x1D641, 'M', 'f'), + (0x1D642, 'M', 'g'), + (0x1D643, 'M', 'h'), + (0x1D644, 'M', 'i'), + (0x1D645, 'M', 'j'), + (0x1D646, 'M', 'k'), + (0x1D647, 'M', 'l'), + (0x1D648, 'M', 'm'), + (0x1D649, 'M', 'n'), + (0x1D64A, 'M', 'o'), + (0x1D64B, 'M', 'p'), + (0x1D64C, 'M', 'q'), + (0x1D64D, 'M', 'r'), + (0x1D64E, 'M', 's'), + (0x1D64F, 'M', 't'), + (0x1D650, 'M', 'u'), + (0x1D651, 'M', 'v'), + (0x1D652, 'M', 'w'), + (0x1D653, 'M', 'x'), + (0x1D654, 'M', 'y'), + (0x1D655, 'M', 'z'), + (0x1D656, 'M', 'a'), + (0x1D657, 'M', 'b'), + (0x1D658, 'M', 'c'), + (0x1D659, 'M', 'd'), + (0x1D65A, 'M', 'e'), + (0x1D65B, 'M', 'f'), + (0x1D65C, 'M', 'g'), + (0x1D65D, 'M', 'h'), + (0x1D65E, 'M', 'i'), + (0x1D65F, 'M', 'j'), + (0x1D660, 'M', 'k'), + (0x1D661, 'M', 'l'), + (0x1D662, 'M', 'm'), + (0x1D663, 'M', 'n'), + (0x1D664, 'M', 'o'), + (0x1D665, 'M', 'p'), + (0x1D666, 'M', 'q'), + (0x1D667, 'M', 'r'), + (0x1D668, 'M', 's'), + (0x1D669, 'M', 't'), + (0x1D66A, 'M', 'u'), + (0x1D66B, 'M', 'v'), + (0x1D66C, 'M', 'w'), + (0x1D66D, 'M', 'x'), + (0x1D66E, 'M', 'y'), + (0x1D66F, 'M', 'z'), + (0x1D670, 'M', 'a'), + (0x1D671, 'M', 'b'), + (0x1D672, 'M', 'c'), + (0x1D673, 'M', 'd'), + (0x1D674, 'M', 'e'), + (0x1D675, 'M', 'f'), + (0x1D676, 'M', 'g'), + (0x1D677, 'M', 'h'), + (0x1D678, 'M', 'i'), + (0x1D679, 'M', 'j'), + (0x1D67A, 'M', 'k'), + (0x1D67B, 'M', 'l'), + (0x1D67C, 'M', 'm'), + (0x1D67D, 'M', 'n'), + (0x1D67E, 'M', 'o'), + ] + +def _seg_67() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D67F, 'M', 'p'), + (0x1D680, 'M', 'q'), + (0x1D681, 'M', 'r'), + (0x1D682, 'M', 's'), + (0x1D683, 'M', 't'), + (0x1D684, 'M', 'u'), + (0x1D685, 'M', 'v'), + (0x1D686, 'M', 'w'), + (0x1D687, 'M', 'x'), + (0x1D688, 'M', 'y'), + (0x1D689, 'M', 'z'), + (0x1D68A, 'M', 'a'), + (0x1D68B, 'M', 'b'), + (0x1D68C, 'M', 'c'), + (0x1D68D, 'M', 'd'), + (0x1D68E, 'M', 'e'), + (0x1D68F, 'M', 'f'), + (0x1D690, 'M', 'g'), + (0x1D691, 'M', 'h'), + (0x1D692, 'M', 'i'), + (0x1D693, 'M', 'j'), + (0x1D694, 'M', 'k'), + (0x1D695, 'M', 'l'), + (0x1D696, 'M', 'm'), + (0x1D697, 'M', 'n'), + (0x1D698, 'M', 'o'), + (0x1D699, 'M', 'p'), + (0x1D69A, 'M', 'q'), + (0x1D69B, 'M', 'r'), + (0x1D69C, 'M', 's'), + (0x1D69D, 'M', 't'), + (0x1D69E, 'M', 'u'), + (0x1D69F, 'M', 'v'), + (0x1D6A0, 'M', 'w'), + (0x1D6A1, 'M', 'x'), + (0x1D6A2, 'M', 'y'), + (0x1D6A3, 'M', 'z'), + (0x1D6A4, 'M', 'ı'), + (0x1D6A5, 'M', 'ȷ'), + (0x1D6A6, 'X'), + (0x1D6A8, 'M', 'α'), + (0x1D6A9, 'M', 'β'), + (0x1D6AA, 'M', 'γ'), + (0x1D6AB, 'M', 'δ'), + (0x1D6AC, 'M', 'ε'), + (0x1D6AD, 'M', 'ζ'), + (0x1D6AE, 'M', 'η'), + (0x1D6AF, 'M', 'θ'), + (0x1D6B0, 'M', 'ι'), + (0x1D6B1, 'M', 'κ'), + (0x1D6B2, 'M', 'λ'), + (0x1D6B3, 'M', 'μ'), + (0x1D6B4, 'M', 'ν'), + (0x1D6B5, 'M', 'ξ'), + (0x1D6B6, 'M', 'ο'), + (0x1D6B7, 'M', 'π'), + (0x1D6B8, 'M', 'ρ'), + (0x1D6B9, 'M', 'θ'), + (0x1D6BA, 'M', 'σ'), + (0x1D6BB, 'M', 'τ'), + (0x1D6BC, 'M', 'υ'), + (0x1D6BD, 'M', 'φ'), + (0x1D6BE, 'M', 'χ'), + (0x1D6BF, 'M', 'ψ'), + (0x1D6C0, 'M', 'ω'), + (0x1D6C1, 'M', '∇'), + (0x1D6C2, 'M', 'α'), + (0x1D6C3, 'M', 'β'), + (0x1D6C4, 'M', 'γ'), + (0x1D6C5, 'M', 'δ'), + (0x1D6C6, 'M', 'ε'), + (0x1D6C7, 'M', 'ζ'), + (0x1D6C8, 'M', 'η'), + (0x1D6C9, 'M', 'θ'), + (0x1D6CA, 'M', 'ι'), + (0x1D6CB, 'M', 'κ'), + (0x1D6CC, 'M', 'λ'), + (0x1D6CD, 'M', 'μ'), + (0x1D6CE, 'M', 'ν'), + (0x1D6CF, 'M', 'ξ'), + (0x1D6D0, 'M', 'ο'), + (0x1D6D1, 'M', 'π'), + (0x1D6D2, 'M', 'ρ'), + (0x1D6D3, 'M', 'σ'), + (0x1D6D5, 'M', 'τ'), + (0x1D6D6, 'M', 'υ'), + (0x1D6D7, 'M', 'φ'), + (0x1D6D8, 'M', 'χ'), + (0x1D6D9, 'M', 'ψ'), + (0x1D6DA, 'M', 'ω'), + (0x1D6DB, 'M', '∂'), + (0x1D6DC, 'M', 'ε'), + (0x1D6DD, 'M', 'θ'), + (0x1D6DE, 'M', 'κ'), + (0x1D6DF, 'M', 'φ'), + (0x1D6E0, 'M', 'ρ'), + (0x1D6E1, 'M', 'π'), + (0x1D6E2, 'M', 'α'), + (0x1D6E3, 'M', 'β'), + (0x1D6E4, 'M', 'γ'), + ] + +def _seg_68() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D6E5, 'M', 'δ'), + (0x1D6E6, 'M', 'ε'), + (0x1D6E7, 'M', 'ζ'), + (0x1D6E8, 'M', 'η'), + (0x1D6E9, 'M', 'θ'), + (0x1D6EA, 'M', 'ι'), + (0x1D6EB, 'M', 'κ'), + (0x1D6EC, 'M', 'λ'), + (0x1D6ED, 'M', 'μ'), + (0x1D6EE, 'M', 'ν'), + (0x1D6EF, 'M', 'ξ'), + (0x1D6F0, 'M', 'ο'), + (0x1D6F1, 'M', 'π'), + (0x1D6F2, 'M', 'ρ'), + (0x1D6F3, 'M', 'θ'), + (0x1D6F4, 'M', 'σ'), + (0x1D6F5, 'M', 'τ'), + (0x1D6F6, 'M', 'υ'), + (0x1D6F7, 'M', 'φ'), + (0x1D6F8, 'M', 'χ'), + (0x1D6F9, 'M', 'ψ'), + (0x1D6FA, 'M', 'ω'), + (0x1D6FB, 'M', '∇'), + (0x1D6FC, 'M', 'α'), + (0x1D6FD, 'M', 'β'), + (0x1D6FE, 'M', 'γ'), + (0x1D6FF, 'M', 'δ'), + (0x1D700, 'M', 'ε'), + (0x1D701, 'M', 'ζ'), + (0x1D702, 'M', 'η'), + (0x1D703, 'M', 'θ'), + (0x1D704, 'M', 'ι'), + (0x1D705, 'M', 'κ'), + (0x1D706, 'M', 'λ'), + (0x1D707, 'M', 'μ'), + (0x1D708, 'M', 'ν'), + (0x1D709, 'M', 'ξ'), + (0x1D70A, 'M', 'ο'), + (0x1D70B, 'M', 'π'), + (0x1D70C, 'M', 'ρ'), + (0x1D70D, 'M', 'σ'), + (0x1D70F, 'M', 'τ'), + (0x1D710, 'M', 'υ'), + (0x1D711, 'M', 'φ'), + (0x1D712, 'M', 'χ'), + (0x1D713, 'M', 'ψ'), + (0x1D714, 'M', 'ω'), + (0x1D715, 'M', '∂'), + (0x1D716, 'M', 'ε'), + (0x1D717, 'M', 'θ'), + (0x1D718, 'M', 'κ'), + (0x1D719, 'M', 'φ'), + (0x1D71A, 'M', 'ρ'), + (0x1D71B, 'M', 'π'), + (0x1D71C, 'M', 'α'), + (0x1D71D, 'M', 'β'), + (0x1D71E, 'M', 'γ'), + (0x1D71F, 'M', 'δ'), + (0x1D720, 'M', 'ε'), + (0x1D721, 'M', 'ζ'), + (0x1D722, 'M', 'η'), + (0x1D723, 'M', 'θ'), + (0x1D724, 'M', 'ι'), + (0x1D725, 'M', 'κ'), + (0x1D726, 'M', 'λ'), + (0x1D727, 'M', 'μ'), + (0x1D728, 'M', 'ν'), + (0x1D729, 'M', 'ξ'), + (0x1D72A, 'M', 'ο'), + (0x1D72B, 'M', 'π'), + (0x1D72C, 'M', 'ρ'), + (0x1D72D, 'M', 'θ'), + (0x1D72E, 'M', 'σ'), + (0x1D72F, 'M', 'τ'), + (0x1D730, 'M', 'υ'), + (0x1D731, 'M', 'φ'), + (0x1D732, 'M', 'χ'), + (0x1D733, 'M', 'ψ'), + (0x1D734, 'M', 'ω'), + (0x1D735, 'M', '∇'), + (0x1D736, 'M', 'α'), + (0x1D737, 'M', 'β'), + (0x1D738, 'M', 'γ'), + (0x1D739, 'M', 'δ'), + (0x1D73A, 'M', 'ε'), + (0x1D73B, 'M', 'ζ'), + (0x1D73C, 'M', 'η'), + (0x1D73D, 'M', 'θ'), + (0x1D73E, 'M', 'ι'), + (0x1D73F, 'M', 'κ'), + (0x1D740, 'M', 'λ'), + (0x1D741, 'M', 'μ'), + (0x1D742, 'M', 'ν'), + (0x1D743, 'M', 'ξ'), + (0x1D744, 'M', 'ο'), + (0x1D745, 'M', 'π'), + (0x1D746, 'M', 'ρ'), + (0x1D747, 'M', 'σ'), + (0x1D749, 'M', 'τ'), + (0x1D74A, 'M', 'υ'), + ] + +def _seg_69() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D74B, 'M', 'φ'), + (0x1D74C, 'M', 'χ'), + (0x1D74D, 'M', 'ψ'), + (0x1D74E, 'M', 'ω'), + (0x1D74F, 'M', '∂'), + (0x1D750, 'M', 'ε'), + (0x1D751, 'M', 'θ'), + (0x1D752, 'M', 'κ'), + (0x1D753, 'M', 'φ'), + (0x1D754, 'M', 'ρ'), + (0x1D755, 'M', 'π'), + (0x1D756, 'M', 'α'), + (0x1D757, 'M', 'β'), + (0x1D758, 'M', 'γ'), + (0x1D759, 'M', 'δ'), + (0x1D75A, 'M', 'ε'), + (0x1D75B, 'M', 'ζ'), + (0x1D75C, 'M', 'η'), + (0x1D75D, 'M', 'θ'), + (0x1D75E, 'M', 'ι'), + (0x1D75F, 'M', 'κ'), + (0x1D760, 'M', 'λ'), + (0x1D761, 'M', 'μ'), + (0x1D762, 'M', 'ν'), + (0x1D763, 'M', 'ξ'), + (0x1D764, 'M', 'ο'), + (0x1D765, 'M', 'π'), + (0x1D766, 'M', 'ρ'), + (0x1D767, 'M', 'θ'), + (0x1D768, 'M', 'σ'), + (0x1D769, 'M', 'τ'), + (0x1D76A, 'M', 'υ'), + (0x1D76B, 'M', 'φ'), + (0x1D76C, 'M', 'χ'), + (0x1D76D, 'M', 'ψ'), + (0x1D76E, 'M', 'ω'), + (0x1D76F, 'M', '∇'), + (0x1D770, 'M', 'α'), + (0x1D771, 'M', 'β'), + (0x1D772, 'M', 'γ'), + (0x1D773, 'M', 'δ'), + (0x1D774, 'M', 'ε'), + (0x1D775, 'M', 'ζ'), + (0x1D776, 'M', 'η'), + (0x1D777, 'M', 'θ'), + (0x1D778, 'M', 'ι'), + (0x1D779, 'M', 'κ'), + (0x1D77A, 'M', 'λ'), + (0x1D77B, 'M', 'μ'), + (0x1D77C, 'M', 'ν'), + (0x1D77D, 'M', 'ξ'), + (0x1D77E, 'M', 'ο'), + (0x1D77F, 'M', 'π'), + (0x1D780, 'M', 'ρ'), + (0x1D781, 'M', 'σ'), + (0x1D783, 'M', 'τ'), + (0x1D784, 'M', 'υ'), + (0x1D785, 'M', 'φ'), + (0x1D786, 'M', 'χ'), + (0x1D787, 'M', 'ψ'), + (0x1D788, 'M', 'ω'), + (0x1D789, 'M', '∂'), + (0x1D78A, 'M', 'ε'), + (0x1D78B, 'M', 'θ'), + (0x1D78C, 'M', 'κ'), + (0x1D78D, 'M', 'φ'), + (0x1D78E, 'M', 'ρ'), + (0x1D78F, 'M', 'π'), + (0x1D790, 'M', 'α'), + (0x1D791, 'M', 'β'), + (0x1D792, 'M', 'γ'), + (0x1D793, 'M', 'δ'), + (0x1D794, 'M', 'ε'), + (0x1D795, 'M', 'ζ'), + (0x1D796, 'M', 'η'), + (0x1D797, 'M', 'θ'), + (0x1D798, 'M', 'ι'), + (0x1D799, 'M', 'κ'), + (0x1D79A, 'M', 'λ'), + (0x1D79B, 'M', 'μ'), + (0x1D79C, 'M', 'ν'), + (0x1D79D, 'M', 'ξ'), + (0x1D79E, 'M', 'ο'), + (0x1D79F, 'M', 'π'), + (0x1D7A0, 'M', 'ρ'), + (0x1D7A1, 'M', 'θ'), + (0x1D7A2, 'M', 'σ'), + (0x1D7A3, 'M', 'τ'), + (0x1D7A4, 'M', 'υ'), + (0x1D7A5, 'M', 'φ'), + (0x1D7A6, 'M', 'χ'), + (0x1D7A7, 'M', 'ψ'), + (0x1D7A8, 'M', 'ω'), + (0x1D7A9, 'M', '∇'), + (0x1D7AA, 'M', 'α'), + (0x1D7AB, 'M', 'β'), + (0x1D7AC, 'M', 'γ'), + (0x1D7AD, 'M', 'δ'), + (0x1D7AE, 'M', 'ε'), + (0x1D7AF, 'M', 'ζ'), + ] + +def _seg_70() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D7B0, 'M', 'η'), + (0x1D7B1, 'M', 'θ'), + (0x1D7B2, 'M', 'ι'), + (0x1D7B3, 'M', 'κ'), + (0x1D7B4, 'M', 'λ'), + (0x1D7B5, 'M', 'μ'), + (0x1D7B6, 'M', 'ν'), + (0x1D7B7, 'M', 'ξ'), + (0x1D7B8, 'M', 'ο'), + (0x1D7B9, 'M', 'π'), + (0x1D7BA, 'M', 'ρ'), + (0x1D7BB, 'M', 'σ'), + (0x1D7BD, 'M', 'τ'), + (0x1D7BE, 'M', 'υ'), + (0x1D7BF, 'M', 'φ'), + (0x1D7C0, 'M', 'χ'), + (0x1D7C1, 'M', 'ψ'), + (0x1D7C2, 'M', 'ω'), + (0x1D7C3, 'M', '∂'), + (0x1D7C4, 'M', 'ε'), + (0x1D7C5, 'M', 'θ'), + (0x1D7C6, 'M', 'κ'), + (0x1D7C7, 'M', 'φ'), + (0x1D7C8, 'M', 'ρ'), + (0x1D7C9, 'M', 'π'), + (0x1D7CA, 'M', 'ϝ'), + (0x1D7CC, 'X'), + (0x1D7CE, 'M', '0'), + (0x1D7CF, 'M', '1'), + (0x1D7D0, 'M', '2'), + (0x1D7D1, 'M', '3'), + (0x1D7D2, 'M', '4'), + (0x1D7D3, 'M', '5'), + (0x1D7D4, 'M', '6'), + (0x1D7D5, 'M', '7'), + (0x1D7D6, 'M', '8'), + (0x1D7D7, 'M', '9'), + (0x1D7D8, 'M', '0'), + (0x1D7D9, 'M', '1'), + (0x1D7DA, 'M', '2'), + (0x1D7DB, 'M', '3'), + (0x1D7DC, 'M', '4'), + (0x1D7DD, 'M', '5'), + (0x1D7DE, 'M', '6'), + (0x1D7DF, 'M', '7'), + (0x1D7E0, 'M', '8'), + (0x1D7E1, 'M', '9'), + (0x1D7E2, 'M', '0'), + (0x1D7E3, 'M', '1'), + (0x1D7E4, 'M', '2'), + (0x1D7E5, 'M', '3'), + (0x1D7E6, 'M', '4'), + (0x1D7E7, 'M', '5'), + (0x1D7E8, 'M', '6'), + (0x1D7E9, 'M', '7'), + (0x1D7EA, 'M', '8'), + (0x1D7EB, 'M', '9'), + (0x1D7EC, 'M', '0'), + (0x1D7ED, 'M', '1'), + (0x1D7EE, 'M', '2'), + (0x1D7EF, 'M', '3'), + (0x1D7F0, 'M', '4'), + (0x1D7F1, 'M', '5'), + (0x1D7F2, 'M', '6'), + (0x1D7F3, 'M', '7'), + (0x1D7F4, 'M', '8'), + (0x1D7F5, 'M', '9'), + (0x1D7F6, 'M', '0'), + (0x1D7F7, 'M', '1'), + (0x1D7F8, 'M', '2'), + (0x1D7F9, 'M', '3'), + (0x1D7FA, 'M', '4'), + (0x1D7FB, 'M', '5'), + (0x1D7FC, 'M', '6'), + (0x1D7FD, 'M', '7'), + (0x1D7FE, 'M', '8'), + (0x1D7FF, 'M', '9'), + (0x1D800, 'V'), + (0x1DA8C, 'X'), + (0x1DA9B, 'V'), + (0x1DAA0, 'X'), + (0x1DAA1, 'V'), + (0x1DAB0, 'X'), + (0x1DF00, 'V'), + (0x1DF1F, 'X'), + (0x1DF25, 'V'), + (0x1DF2B, 'X'), + (0x1E000, 'V'), + (0x1E007, 'X'), + (0x1E008, 'V'), + (0x1E019, 'X'), + (0x1E01B, 'V'), + (0x1E022, 'X'), + (0x1E023, 'V'), + (0x1E025, 'X'), + (0x1E026, 'V'), + (0x1E02B, 'X'), + (0x1E030, 'M', 'а'), + (0x1E031, 'M', 'б'), + (0x1E032, 'M', 'в'), + ] + +def _seg_71() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1E033, 'M', 'г'), + (0x1E034, 'M', 'д'), + (0x1E035, 'M', 'е'), + (0x1E036, 'M', 'ж'), + (0x1E037, 'M', 'з'), + (0x1E038, 'M', 'и'), + (0x1E039, 'M', 'к'), + (0x1E03A, 'M', 'л'), + (0x1E03B, 'M', 'м'), + (0x1E03C, 'M', 'о'), + (0x1E03D, 'M', 'п'), + (0x1E03E, 'M', 'р'), + (0x1E03F, 'M', 'с'), + (0x1E040, 'M', 'т'), + (0x1E041, 'M', 'у'), + (0x1E042, 'M', 'ф'), + (0x1E043, 'M', 'х'), + (0x1E044, 'M', 'ц'), + (0x1E045, 'M', 'ч'), + (0x1E046, 'M', 'ш'), + (0x1E047, 'M', 'ы'), + (0x1E048, 'M', 'э'), + (0x1E049, 'M', 'ю'), + (0x1E04A, 'M', 'ꚉ'), + (0x1E04B, 'M', 'ә'), + (0x1E04C, 'M', 'і'), + (0x1E04D, 'M', 'ј'), + (0x1E04E, 'M', 'ө'), + (0x1E04F, 'M', 'ү'), + (0x1E050, 'M', 'ӏ'), + (0x1E051, 'M', 'а'), + (0x1E052, 'M', 'б'), + (0x1E053, 'M', 'в'), + (0x1E054, 'M', 'г'), + (0x1E055, 'M', 'д'), + (0x1E056, 'M', 'е'), + (0x1E057, 'M', 'ж'), + (0x1E058, 'M', 'з'), + (0x1E059, 'M', 'и'), + (0x1E05A, 'M', 'к'), + (0x1E05B, 'M', 'л'), + (0x1E05C, 'M', 'о'), + (0x1E05D, 'M', 'п'), + (0x1E05E, 'M', 'с'), + (0x1E05F, 'M', 'у'), + (0x1E060, 'M', 'ф'), + (0x1E061, 'M', 'х'), + (0x1E062, 'M', 'ц'), + (0x1E063, 'M', 'ч'), + (0x1E064, 'M', 'ш'), + (0x1E065, 'M', 'ъ'), + (0x1E066, 'M', 'ы'), + (0x1E067, 'M', 'ґ'), + (0x1E068, 'M', 'і'), + (0x1E069, 'M', 'ѕ'), + (0x1E06A, 'M', 'џ'), + (0x1E06B, 'M', 'ҫ'), + (0x1E06C, 'M', 'ꙑ'), + (0x1E06D, 'M', 'ұ'), + (0x1E06E, 'X'), + (0x1E08F, 'V'), + (0x1E090, 'X'), + (0x1E100, 'V'), + (0x1E12D, 'X'), + (0x1E130, 'V'), + (0x1E13E, 'X'), + (0x1E140, 'V'), + (0x1E14A, 'X'), + (0x1E14E, 'V'), + (0x1E150, 'X'), + (0x1E290, 'V'), + (0x1E2AF, 'X'), + (0x1E2C0, 'V'), + (0x1E2FA, 'X'), + (0x1E2FF, 'V'), + (0x1E300, 'X'), + (0x1E4D0, 'V'), + (0x1E4FA, 'X'), + (0x1E7E0, 'V'), + (0x1E7E7, 'X'), + (0x1E7E8, 'V'), + (0x1E7EC, 'X'), + (0x1E7ED, 'V'), + (0x1E7EF, 'X'), + (0x1E7F0, 'V'), + (0x1E7FF, 'X'), + (0x1E800, 'V'), + (0x1E8C5, 'X'), + (0x1E8C7, 'V'), + (0x1E8D7, 'X'), + (0x1E900, 'M', '𞤢'), + (0x1E901, 'M', '𞤣'), + (0x1E902, 'M', '𞤤'), + (0x1E903, 'M', '𞤥'), + (0x1E904, 'M', '𞤦'), + (0x1E905, 'M', '𞤧'), + (0x1E906, 'M', '𞤨'), + (0x1E907, 'M', '𞤩'), + (0x1E908, 'M', '𞤪'), + (0x1E909, 'M', '𞤫'), + ] + +def _seg_72() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1E90A, 'M', '𞤬'), + (0x1E90B, 'M', '𞤭'), + (0x1E90C, 'M', '𞤮'), + (0x1E90D, 'M', '𞤯'), + (0x1E90E, 'M', '𞤰'), + (0x1E90F, 'M', '𞤱'), + (0x1E910, 'M', '𞤲'), + (0x1E911, 'M', '𞤳'), + (0x1E912, 'M', '𞤴'), + (0x1E913, 'M', '𞤵'), + (0x1E914, 'M', '𞤶'), + (0x1E915, 'M', '𞤷'), + (0x1E916, 'M', '𞤸'), + (0x1E917, 'M', '𞤹'), + (0x1E918, 'M', '𞤺'), + (0x1E919, 'M', '𞤻'), + (0x1E91A, 'M', '𞤼'), + (0x1E91B, 'M', '𞤽'), + (0x1E91C, 'M', '𞤾'), + (0x1E91D, 'M', '𞤿'), + (0x1E91E, 'M', '𞥀'), + (0x1E91F, 'M', '𞥁'), + (0x1E920, 'M', '𞥂'), + (0x1E921, 'M', '𞥃'), + (0x1E922, 'V'), + (0x1E94C, 'X'), + (0x1E950, 'V'), + (0x1E95A, 'X'), + (0x1E95E, 'V'), + (0x1E960, 'X'), + (0x1EC71, 'V'), + (0x1ECB5, 'X'), + (0x1ED01, 'V'), + (0x1ED3E, 'X'), + (0x1EE00, 'M', 'ا'), + (0x1EE01, 'M', 'ب'), + (0x1EE02, 'M', 'ج'), + (0x1EE03, 'M', 'د'), + (0x1EE04, 'X'), + (0x1EE05, 'M', 'و'), + (0x1EE06, 'M', 'ز'), + (0x1EE07, 'M', 'ح'), + (0x1EE08, 'M', 'ط'), + (0x1EE09, 'M', 'ي'), + (0x1EE0A, 'M', 'ك'), + (0x1EE0B, 'M', 'ل'), + (0x1EE0C, 'M', 'م'), + (0x1EE0D, 'M', 'ن'), + (0x1EE0E, 'M', 'س'), + (0x1EE0F, 'M', 'ع'), + (0x1EE10, 'M', 'ف'), + (0x1EE11, 'M', 'ص'), + (0x1EE12, 'M', 'ق'), + (0x1EE13, 'M', 'ر'), + (0x1EE14, 'M', 'ش'), + (0x1EE15, 'M', 'ت'), + (0x1EE16, 'M', 'ث'), + (0x1EE17, 'M', 'خ'), + (0x1EE18, 'M', 'ذ'), + (0x1EE19, 'M', 'ض'), + (0x1EE1A, 'M', 'ظ'), + (0x1EE1B, 'M', 'غ'), + (0x1EE1C, 'M', 'ٮ'), + (0x1EE1D, 'M', 'ں'), + (0x1EE1E, 'M', 'ڡ'), + (0x1EE1F, 'M', 'ٯ'), + (0x1EE20, 'X'), + (0x1EE21, 'M', 'ب'), + (0x1EE22, 'M', 'ج'), + (0x1EE23, 'X'), + (0x1EE24, 'M', 'ه'), + (0x1EE25, 'X'), + (0x1EE27, 'M', 'ح'), + (0x1EE28, 'X'), + (0x1EE29, 'M', 'ي'), + (0x1EE2A, 'M', 'ك'), + (0x1EE2B, 'M', 'ل'), + (0x1EE2C, 'M', 'م'), + (0x1EE2D, 'M', 'ن'), + (0x1EE2E, 'M', 'س'), + (0x1EE2F, 'M', 'ع'), + (0x1EE30, 'M', 'ف'), + (0x1EE31, 'M', 'ص'), + (0x1EE32, 'M', 'ق'), + (0x1EE33, 'X'), + (0x1EE34, 'M', 'ش'), + (0x1EE35, 'M', 'ت'), + (0x1EE36, 'M', 'ث'), + (0x1EE37, 'M', 'خ'), + (0x1EE38, 'X'), + (0x1EE39, 'M', 'ض'), + (0x1EE3A, 'X'), + (0x1EE3B, 'M', 'غ'), + (0x1EE3C, 'X'), + (0x1EE42, 'M', 'ج'), + (0x1EE43, 'X'), + (0x1EE47, 'M', 'ح'), + (0x1EE48, 'X'), + (0x1EE49, 'M', 'ي'), + (0x1EE4A, 'X'), + ] + +def _seg_73() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1EE4B, 'M', 'ل'), + (0x1EE4C, 'X'), + (0x1EE4D, 'M', 'ن'), + (0x1EE4E, 'M', 'س'), + (0x1EE4F, 'M', 'ع'), + (0x1EE50, 'X'), + (0x1EE51, 'M', 'ص'), + (0x1EE52, 'M', 'ق'), + (0x1EE53, 'X'), + (0x1EE54, 'M', 'ش'), + (0x1EE55, 'X'), + (0x1EE57, 'M', 'خ'), + (0x1EE58, 'X'), + (0x1EE59, 'M', 'ض'), + (0x1EE5A, 'X'), + (0x1EE5B, 'M', 'غ'), + (0x1EE5C, 'X'), + (0x1EE5D, 'M', 'ں'), + (0x1EE5E, 'X'), + (0x1EE5F, 'M', 'ٯ'), + (0x1EE60, 'X'), + (0x1EE61, 'M', 'ب'), + (0x1EE62, 'M', 'ج'), + (0x1EE63, 'X'), + (0x1EE64, 'M', 'ه'), + (0x1EE65, 'X'), + (0x1EE67, 'M', 'ح'), + (0x1EE68, 'M', 'ط'), + (0x1EE69, 'M', 'ي'), + (0x1EE6A, 'M', 'ك'), + (0x1EE6B, 'X'), + (0x1EE6C, 'M', 'م'), + (0x1EE6D, 'M', 'ن'), + (0x1EE6E, 'M', 'س'), + (0x1EE6F, 'M', 'ع'), + (0x1EE70, 'M', 'ف'), + (0x1EE71, 'M', 'ص'), + (0x1EE72, 'M', 'ق'), + (0x1EE73, 'X'), + (0x1EE74, 'M', 'ش'), + (0x1EE75, 'M', 'ت'), + (0x1EE76, 'M', 'ث'), + (0x1EE77, 'M', 'خ'), + (0x1EE78, 'X'), + (0x1EE79, 'M', 'ض'), + (0x1EE7A, 'M', 'ظ'), + (0x1EE7B, 'M', 'غ'), + (0x1EE7C, 'M', 'ٮ'), + (0x1EE7D, 'X'), + (0x1EE7E, 'M', 'ڡ'), + (0x1EE7F, 'X'), + (0x1EE80, 'M', 'ا'), + (0x1EE81, 'M', 'ب'), + (0x1EE82, 'M', 'ج'), + (0x1EE83, 'M', 'د'), + (0x1EE84, 'M', 'ه'), + (0x1EE85, 'M', 'و'), + (0x1EE86, 'M', 'ز'), + (0x1EE87, 'M', 'ح'), + (0x1EE88, 'M', 'ط'), + (0x1EE89, 'M', 'ي'), + (0x1EE8A, 'X'), + (0x1EE8B, 'M', 'ل'), + (0x1EE8C, 'M', 'م'), + (0x1EE8D, 'M', 'ن'), + (0x1EE8E, 'M', 'س'), + (0x1EE8F, 'M', 'ع'), + (0x1EE90, 'M', 'ف'), + (0x1EE91, 'M', 'ص'), + (0x1EE92, 'M', 'ق'), + (0x1EE93, 'M', 'ر'), + (0x1EE94, 'M', 'ش'), + (0x1EE95, 'M', 'ت'), + (0x1EE96, 'M', 'ث'), + (0x1EE97, 'M', 'خ'), + (0x1EE98, 'M', 'ذ'), + (0x1EE99, 'M', 'ض'), + (0x1EE9A, 'M', 'ظ'), + (0x1EE9B, 'M', 'غ'), + (0x1EE9C, 'X'), + (0x1EEA1, 'M', 'ب'), + (0x1EEA2, 'M', 'ج'), + (0x1EEA3, 'M', 'د'), + (0x1EEA4, 'X'), + (0x1EEA5, 'M', 'و'), + (0x1EEA6, 'M', 'ز'), + (0x1EEA7, 'M', 'ح'), + (0x1EEA8, 'M', 'ط'), + (0x1EEA9, 'M', 'ي'), + (0x1EEAA, 'X'), + (0x1EEAB, 'M', 'ل'), + (0x1EEAC, 'M', 'م'), + (0x1EEAD, 'M', 'ن'), + (0x1EEAE, 'M', 'س'), + (0x1EEAF, 'M', 'ع'), + (0x1EEB0, 'M', 'ف'), + (0x1EEB1, 'M', 'ص'), + (0x1EEB2, 'M', 'ق'), + (0x1EEB3, 'M', 'ر'), + (0x1EEB4, 'M', 'ش'), + ] + +def _seg_74() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1EEB5, 'M', 'ت'), + (0x1EEB6, 'M', 'ث'), + (0x1EEB7, 'M', 'خ'), + (0x1EEB8, 'M', 'ذ'), + (0x1EEB9, 'M', 'ض'), + (0x1EEBA, 'M', 'ظ'), + (0x1EEBB, 'M', 'غ'), + (0x1EEBC, 'X'), + (0x1EEF0, 'V'), + (0x1EEF2, 'X'), + (0x1F000, 'V'), + (0x1F02C, 'X'), + (0x1F030, 'V'), + (0x1F094, 'X'), + (0x1F0A0, 'V'), + (0x1F0AF, 'X'), + (0x1F0B1, 'V'), + (0x1F0C0, 'X'), + (0x1F0C1, 'V'), + (0x1F0D0, 'X'), + (0x1F0D1, 'V'), + (0x1F0F6, 'X'), + (0x1F101, '3', '0,'), + (0x1F102, '3', '1,'), + (0x1F103, '3', '2,'), + (0x1F104, '3', '3,'), + (0x1F105, '3', '4,'), + (0x1F106, '3', '5,'), + (0x1F107, '3', '6,'), + (0x1F108, '3', '7,'), + (0x1F109, '3', '8,'), + (0x1F10A, '3', '9,'), + (0x1F10B, 'V'), + (0x1F110, '3', '(a)'), + (0x1F111, '3', '(b)'), + (0x1F112, '3', '(c)'), + (0x1F113, '3', '(d)'), + (0x1F114, '3', '(e)'), + (0x1F115, '3', '(f)'), + (0x1F116, '3', '(g)'), + (0x1F117, '3', '(h)'), + (0x1F118, '3', '(i)'), + (0x1F119, '3', '(j)'), + (0x1F11A, '3', '(k)'), + (0x1F11B, '3', '(l)'), + (0x1F11C, '3', '(m)'), + (0x1F11D, '3', '(n)'), + (0x1F11E, '3', '(o)'), + (0x1F11F, '3', '(p)'), + (0x1F120, '3', '(q)'), + (0x1F121, '3', '(r)'), + (0x1F122, '3', '(s)'), + (0x1F123, '3', '(t)'), + (0x1F124, '3', '(u)'), + (0x1F125, '3', '(v)'), + (0x1F126, '3', '(w)'), + (0x1F127, '3', '(x)'), + (0x1F128, '3', '(y)'), + (0x1F129, '3', '(z)'), + (0x1F12A, 'M', '〔s〕'), + (0x1F12B, 'M', 'c'), + (0x1F12C, 'M', 'r'), + (0x1F12D, 'M', 'cd'), + (0x1F12E, 'M', 'wz'), + (0x1F12F, 'V'), + (0x1F130, 'M', 'a'), + (0x1F131, 'M', 'b'), + (0x1F132, 'M', 'c'), + (0x1F133, 'M', 'd'), + (0x1F134, 'M', 'e'), + (0x1F135, 'M', 'f'), + (0x1F136, 'M', 'g'), + (0x1F137, 'M', 'h'), + (0x1F138, 'M', 'i'), + (0x1F139, 'M', 'j'), + (0x1F13A, 'M', 'k'), + (0x1F13B, 'M', 'l'), + (0x1F13C, 'M', 'm'), + (0x1F13D, 'M', 'n'), + (0x1F13E, 'M', 'o'), + (0x1F13F, 'M', 'p'), + (0x1F140, 'M', 'q'), + (0x1F141, 'M', 'r'), + (0x1F142, 'M', 's'), + (0x1F143, 'M', 't'), + (0x1F144, 'M', 'u'), + (0x1F145, 'M', 'v'), + (0x1F146, 'M', 'w'), + (0x1F147, 'M', 'x'), + (0x1F148, 'M', 'y'), + (0x1F149, 'M', 'z'), + (0x1F14A, 'M', 'hv'), + (0x1F14B, 'M', 'mv'), + (0x1F14C, 'M', 'sd'), + (0x1F14D, 'M', 'ss'), + (0x1F14E, 'M', 'ppv'), + (0x1F14F, 'M', 'wc'), + (0x1F150, 'V'), + (0x1F16A, 'M', 'mc'), + (0x1F16B, 'M', 'md'), + ] + +def _seg_75() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1F16C, 'M', 'mr'), + (0x1F16D, 'V'), + (0x1F190, 'M', 'dj'), + (0x1F191, 'V'), + (0x1F1AE, 'X'), + (0x1F1E6, 'V'), + (0x1F200, 'M', 'ほか'), + (0x1F201, 'M', 'ココ'), + (0x1F202, 'M', 'サ'), + (0x1F203, 'X'), + (0x1F210, 'M', '手'), + (0x1F211, 'M', '字'), + (0x1F212, 'M', '双'), + (0x1F213, 'M', 'デ'), + (0x1F214, 'M', '二'), + (0x1F215, 'M', '多'), + (0x1F216, 'M', '解'), + (0x1F217, 'M', '天'), + (0x1F218, 'M', '交'), + (0x1F219, 'M', '映'), + (0x1F21A, 'M', '無'), + (0x1F21B, 'M', '料'), + (0x1F21C, 'M', '前'), + (0x1F21D, 'M', '後'), + (0x1F21E, 'M', '再'), + (0x1F21F, 'M', '新'), + (0x1F220, 'M', '初'), + (0x1F221, 'M', '終'), + (0x1F222, 'M', '生'), + (0x1F223, 'M', '販'), + (0x1F224, 'M', '声'), + (0x1F225, 'M', '吹'), + (0x1F226, 'M', '演'), + (0x1F227, 'M', '投'), + (0x1F228, 'M', '捕'), + (0x1F229, 'M', '一'), + (0x1F22A, 'M', '三'), + (0x1F22B, 'M', '遊'), + (0x1F22C, 'M', '左'), + (0x1F22D, 'M', '中'), + (0x1F22E, 'M', '右'), + (0x1F22F, 'M', '指'), + (0x1F230, 'M', '走'), + (0x1F231, 'M', '打'), + (0x1F232, 'M', '禁'), + (0x1F233, 'M', '空'), + (0x1F234, 'M', '合'), + (0x1F235, 'M', '満'), + (0x1F236, 'M', '有'), + (0x1F237, 'M', '月'), + (0x1F238, 'M', '申'), + (0x1F239, 'M', '割'), + (0x1F23A, 'M', '営'), + (0x1F23B, 'M', '配'), + (0x1F23C, 'X'), + (0x1F240, 'M', '〔本〕'), + (0x1F241, 'M', '〔三〕'), + (0x1F242, 'M', '〔二〕'), + (0x1F243, 'M', '〔安〕'), + (0x1F244, 'M', '〔点〕'), + (0x1F245, 'M', '〔打〕'), + (0x1F246, 'M', '〔盗〕'), + (0x1F247, 'M', '〔勝〕'), + (0x1F248, 'M', '〔敗〕'), + (0x1F249, 'X'), + (0x1F250, 'M', '得'), + (0x1F251, 'M', '可'), + (0x1F252, 'X'), + (0x1F260, 'V'), + (0x1F266, 'X'), + (0x1F300, 'V'), + (0x1F6D8, 'X'), + (0x1F6DC, 'V'), + (0x1F6ED, 'X'), + (0x1F6F0, 'V'), + (0x1F6FD, 'X'), + (0x1F700, 'V'), + (0x1F777, 'X'), + (0x1F77B, 'V'), + (0x1F7DA, 'X'), + (0x1F7E0, 'V'), + (0x1F7EC, 'X'), + (0x1F7F0, 'V'), + (0x1F7F1, 'X'), + (0x1F800, 'V'), + (0x1F80C, 'X'), + (0x1F810, 'V'), + (0x1F848, 'X'), + (0x1F850, 'V'), + (0x1F85A, 'X'), + (0x1F860, 'V'), + (0x1F888, 'X'), + (0x1F890, 'V'), + (0x1F8AE, 'X'), + (0x1F8B0, 'V'), + (0x1F8B2, 'X'), + (0x1F900, 'V'), + (0x1FA54, 'X'), + (0x1FA60, 'V'), + (0x1FA6E, 'X'), + ] + +def _seg_76() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1FA70, 'V'), + (0x1FA7D, 'X'), + (0x1FA80, 'V'), + (0x1FA89, 'X'), + (0x1FA90, 'V'), + (0x1FABE, 'X'), + (0x1FABF, 'V'), + (0x1FAC6, 'X'), + (0x1FACE, 'V'), + (0x1FADC, 'X'), + (0x1FAE0, 'V'), + (0x1FAE9, 'X'), + (0x1FAF0, 'V'), + (0x1FAF9, 'X'), + (0x1FB00, 'V'), + (0x1FB93, 'X'), + (0x1FB94, 'V'), + (0x1FBCB, 'X'), + (0x1FBF0, 'M', '0'), + (0x1FBF1, 'M', '1'), + (0x1FBF2, 'M', '2'), + (0x1FBF3, 'M', '3'), + (0x1FBF4, 'M', '4'), + (0x1FBF5, 'M', '5'), + (0x1FBF6, 'M', '6'), + (0x1FBF7, 'M', '7'), + (0x1FBF8, 'M', '8'), + (0x1FBF9, 'M', '9'), + (0x1FBFA, 'X'), + (0x20000, 'V'), + (0x2A6E0, 'X'), + (0x2A700, 'V'), + (0x2B73A, 'X'), + (0x2B740, 'V'), + (0x2B81E, 'X'), + (0x2B820, 'V'), + (0x2CEA2, 'X'), + (0x2CEB0, 'V'), + (0x2EBE1, 'X'), + (0x2F800, 'M', '丽'), + (0x2F801, 'M', '丸'), + (0x2F802, 'M', '乁'), + (0x2F803, 'M', '𠄢'), + (0x2F804, 'M', '你'), + (0x2F805, 'M', '侮'), + (0x2F806, 'M', '侻'), + (0x2F807, 'M', '倂'), + (0x2F808, 'M', '偺'), + (0x2F809, 'M', '備'), + (0x2F80A, 'M', '僧'), + (0x2F80B, 'M', '像'), + (0x2F80C, 'M', '㒞'), + (0x2F80D, 'M', '𠘺'), + (0x2F80E, 'M', '免'), + (0x2F80F, 'M', '兔'), + (0x2F810, 'M', '兤'), + (0x2F811, 'M', '具'), + (0x2F812, 'M', '𠔜'), + (0x2F813, 'M', '㒹'), + (0x2F814, 'M', '內'), + (0x2F815, 'M', '再'), + (0x2F816, 'M', '𠕋'), + (0x2F817, 'M', '冗'), + (0x2F818, 'M', '冤'), + (0x2F819, 'M', '仌'), + (0x2F81A, 'M', '冬'), + (0x2F81B, 'M', '况'), + (0x2F81C, 'M', '𩇟'), + (0x2F81D, 'M', '凵'), + (0x2F81E, 'M', '刃'), + (0x2F81F, 'M', '㓟'), + (0x2F820, 'M', '刻'), + (0x2F821, 'M', '剆'), + (0x2F822, 'M', '割'), + (0x2F823, 'M', '剷'), + (0x2F824, 'M', '㔕'), + (0x2F825, 'M', '勇'), + (0x2F826, 'M', '勉'), + (0x2F827, 'M', '勤'), + (0x2F828, 'M', '勺'), + (0x2F829, 'M', '包'), + (0x2F82A, 'M', '匆'), + (0x2F82B, 'M', '北'), + (0x2F82C, 'M', '卉'), + (0x2F82D, 'M', '卑'), + (0x2F82E, 'M', '博'), + (0x2F82F, 'M', '即'), + (0x2F830, 'M', '卽'), + (0x2F831, 'M', '卿'), + (0x2F834, 'M', '𠨬'), + (0x2F835, 'M', '灰'), + (0x2F836, 'M', '及'), + (0x2F837, 'M', '叟'), + (0x2F838, 'M', '𠭣'), + (0x2F839, 'M', '叫'), + (0x2F83A, 'M', '叱'), + (0x2F83B, 'M', '吆'), + (0x2F83C, 'M', '咞'), + (0x2F83D, 'M', '吸'), + (0x2F83E, 'M', '呈'), + ] + +def _seg_77() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F83F, 'M', '周'), + (0x2F840, 'M', '咢'), + (0x2F841, 'M', '哶'), + (0x2F842, 'M', '唐'), + (0x2F843, 'M', '啓'), + (0x2F844, 'M', '啣'), + (0x2F845, 'M', '善'), + (0x2F847, 'M', '喙'), + (0x2F848, 'M', '喫'), + (0x2F849, 'M', '喳'), + (0x2F84A, 'M', '嗂'), + (0x2F84B, 'M', '圖'), + (0x2F84C, 'M', '嘆'), + (0x2F84D, 'M', '圗'), + (0x2F84E, 'M', '噑'), + (0x2F84F, 'M', '噴'), + (0x2F850, 'M', '切'), + (0x2F851, 'M', '壮'), + (0x2F852, 'M', '城'), + (0x2F853, 'M', '埴'), + (0x2F854, 'M', '堍'), + (0x2F855, 'M', '型'), + (0x2F856, 'M', '堲'), + (0x2F857, 'M', '報'), + (0x2F858, 'M', '墬'), + (0x2F859, 'M', '𡓤'), + (0x2F85A, 'M', '売'), + (0x2F85B, 'M', '壷'), + (0x2F85C, 'M', '夆'), + (0x2F85D, 'M', '多'), + (0x2F85E, 'M', '夢'), + (0x2F85F, 'M', '奢'), + (0x2F860, 'M', '𡚨'), + (0x2F861, 'M', '𡛪'), + (0x2F862, 'M', '姬'), + (0x2F863, 'M', '娛'), + (0x2F864, 'M', '娧'), + (0x2F865, 'M', '姘'), + (0x2F866, 'M', '婦'), + (0x2F867, 'M', '㛮'), + (0x2F868, 'X'), + (0x2F869, 'M', '嬈'), + (0x2F86A, 'M', '嬾'), + (0x2F86C, 'M', '𡧈'), + (0x2F86D, 'M', '寃'), + (0x2F86E, 'M', '寘'), + (0x2F86F, 'M', '寧'), + (0x2F870, 'M', '寳'), + (0x2F871, 'M', '𡬘'), + (0x2F872, 'M', '寿'), + (0x2F873, 'M', '将'), + (0x2F874, 'X'), + (0x2F875, 'M', '尢'), + (0x2F876, 'M', '㞁'), + (0x2F877, 'M', '屠'), + (0x2F878, 'M', '屮'), + (0x2F879, 'M', '峀'), + (0x2F87A, 'M', '岍'), + (0x2F87B, 'M', '𡷤'), + (0x2F87C, 'M', '嵃'), + (0x2F87D, 'M', '𡷦'), + (0x2F87E, 'M', '嵮'), + (0x2F87F, 'M', '嵫'), + (0x2F880, 'M', '嵼'), + (0x2F881, 'M', '巡'), + (0x2F882, 'M', '巢'), + (0x2F883, 'M', '㠯'), + (0x2F884, 'M', '巽'), + (0x2F885, 'M', '帨'), + (0x2F886, 'M', '帽'), + (0x2F887, 'M', '幩'), + (0x2F888, 'M', '㡢'), + (0x2F889, 'M', '𢆃'), + (0x2F88A, 'M', '㡼'), + (0x2F88B, 'M', '庰'), + (0x2F88C, 'M', '庳'), + (0x2F88D, 'M', '庶'), + (0x2F88E, 'M', '廊'), + (0x2F88F, 'M', '𪎒'), + (0x2F890, 'M', '廾'), + (0x2F891, 'M', '𢌱'), + (0x2F893, 'M', '舁'), + (0x2F894, 'M', '弢'), + (0x2F896, 'M', '㣇'), + (0x2F897, 'M', '𣊸'), + (0x2F898, 'M', '𦇚'), + (0x2F899, 'M', '形'), + (0x2F89A, 'M', '彫'), + (0x2F89B, 'M', '㣣'), + (0x2F89C, 'M', '徚'), + (0x2F89D, 'M', '忍'), + (0x2F89E, 'M', '志'), + (0x2F89F, 'M', '忹'), + (0x2F8A0, 'M', '悁'), + (0x2F8A1, 'M', '㤺'), + (0x2F8A2, 'M', '㤜'), + (0x2F8A3, 'M', '悔'), + (0x2F8A4, 'M', '𢛔'), + (0x2F8A5, 'M', '惇'), + (0x2F8A6, 'M', '慈'), + ] + +def _seg_78() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F8A7, 'M', '慌'), + (0x2F8A8, 'M', '慎'), + (0x2F8A9, 'M', '慌'), + (0x2F8AA, 'M', '慺'), + (0x2F8AB, 'M', '憎'), + (0x2F8AC, 'M', '憲'), + (0x2F8AD, 'M', '憤'), + (0x2F8AE, 'M', '憯'), + (0x2F8AF, 'M', '懞'), + (0x2F8B0, 'M', '懲'), + (0x2F8B1, 'M', '懶'), + (0x2F8B2, 'M', '成'), + (0x2F8B3, 'M', '戛'), + (0x2F8B4, 'M', '扝'), + (0x2F8B5, 'M', '抱'), + (0x2F8B6, 'M', '拔'), + (0x2F8B7, 'M', '捐'), + (0x2F8B8, 'M', '𢬌'), + (0x2F8B9, 'M', '挽'), + (0x2F8BA, 'M', '拼'), + (0x2F8BB, 'M', '捨'), + (0x2F8BC, 'M', '掃'), + (0x2F8BD, 'M', '揤'), + (0x2F8BE, 'M', '𢯱'), + (0x2F8BF, 'M', '搢'), + (0x2F8C0, 'M', '揅'), + (0x2F8C1, 'M', '掩'), + (0x2F8C2, 'M', '㨮'), + (0x2F8C3, 'M', '摩'), + (0x2F8C4, 'M', '摾'), + (0x2F8C5, 'M', '撝'), + (0x2F8C6, 'M', '摷'), + (0x2F8C7, 'M', '㩬'), + (0x2F8C8, 'M', '敏'), + (0x2F8C9, 'M', '敬'), + (0x2F8CA, 'M', '𣀊'), + (0x2F8CB, 'M', '旣'), + (0x2F8CC, 'M', '書'), + (0x2F8CD, 'M', '晉'), + (0x2F8CE, 'M', '㬙'), + (0x2F8CF, 'M', '暑'), + (0x2F8D0, 'M', '㬈'), + (0x2F8D1, 'M', '㫤'), + (0x2F8D2, 'M', '冒'), + (0x2F8D3, 'M', '冕'), + (0x2F8D4, 'M', '最'), + (0x2F8D5, 'M', '暜'), + (0x2F8D6, 'M', '肭'), + (0x2F8D7, 'M', '䏙'), + (0x2F8D8, 'M', '朗'), + (0x2F8D9, 'M', '望'), + (0x2F8DA, 'M', '朡'), + (0x2F8DB, 'M', '杞'), + (0x2F8DC, 'M', '杓'), + (0x2F8DD, 'M', '𣏃'), + (0x2F8DE, 'M', '㭉'), + (0x2F8DF, 'M', '柺'), + (0x2F8E0, 'M', '枅'), + (0x2F8E1, 'M', '桒'), + (0x2F8E2, 'M', '梅'), + (0x2F8E3, 'M', '𣑭'), + (0x2F8E4, 'M', '梎'), + (0x2F8E5, 'M', '栟'), + (0x2F8E6, 'M', '椔'), + (0x2F8E7, 'M', '㮝'), + (0x2F8E8, 'M', '楂'), + (0x2F8E9, 'M', '榣'), + (0x2F8EA, 'M', '槪'), + (0x2F8EB, 'M', '檨'), + (0x2F8EC, 'M', '𣚣'), + (0x2F8ED, 'M', '櫛'), + (0x2F8EE, 'M', '㰘'), + (0x2F8EF, 'M', '次'), + (0x2F8F0, 'M', '𣢧'), + (0x2F8F1, 'M', '歔'), + (0x2F8F2, 'M', '㱎'), + (0x2F8F3, 'M', '歲'), + (0x2F8F4, 'M', '殟'), + (0x2F8F5, 'M', '殺'), + (0x2F8F6, 'M', '殻'), + (0x2F8F7, 'M', '𣪍'), + (0x2F8F8, 'M', '𡴋'), + (0x2F8F9, 'M', '𣫺'), + (0x2F8FA, 'M', '汎'), + (0x2F8FB, 'M', '𣲼'), + (0x2F8FC, 'M', '沿'), + (0x2F8FD, 'M', '泍'), + (0x2F8FE, 'M', '汧'), + (0x2F8FF, 'M', '洖'), + (0x2F900, 'M', '派'), + (0x2F901, 'M', '海'), + (0x2F902, 'M', '流'), + (0x2F903, 'M', '浩'), + (0x2F904, 'M', '浸'), + (0x2F905, 'M', '涅'), + (0x2F906, 'M', '𣴞'), + (0x2F907, 'M', '洴'), + (0x2F908, 'M', '港'), + (0x2F909, 'M', '湮'), + (0x2F90A, 'M', '㴳'), + ] + +def _seg_79() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F90B, 'M', '滋'), + (0x2F90C, 'M', '滇'), + (0x2F90D, 'M', '𣻑'), + (0x2F90E, 'M', '淹'), + (0x2F90F, 'M', '潮'), + (0x2F910, 'M', '𣽞'), + (0x2F911, 'M', '𣾎'), + (0x2F912, 'M', '濆'), + (0x2F913, 'M', '瀹'), + (0x2F914, 'M', '瀞'), + (0x2F915, 'M', '瀛'), + (0x2F916, 'M', '㶖'), + (0x2F917, 'M', '灊'), + (0x2F918, 'M', '災'), + (0x2F919, 'M', '灷'), + (0x2F91A, 'M', '炭'), + (0x2F91B, 'M', '𠔥'), + (0x2F91C, 'M', '煅'), + (0x2F91D, 'M', '𤉣'), + (0x2F91E, 'M', '熜'), + (0x2F91F, 'X'), + (0x2F920, 'M', '爨'), + (0x2F921, 'M', '爵'), + (0x2F922, 'M', '牐'), + (0x2F923, 'M', '𤘈'), + (0x2F924, 'M', '犀'), + (0x2F925, 'M', '犕'), + (0x2F926, 'M', '𤜵'), + (0x2F927, 'M', '𤠔'), + (0x2F928, 'M', '獺'), + (0x2F929, 'M', '王'), + (0x2F92A, 'M', '㺬'), + (0x2F92B, 'M', '玥'), + (0x2F92C, 'M', '㺸'), + (0x2F92E, 'M', '瑇'), + (0x2F92F, 'M', '瑜'), + (0x2F930, 'M', '瑱'), + (0x2F931, 'M', '璅'), + (0x2F932, 'M', '瓊'), + (0x2F933, 'M', '㼛'), + (0x2F934, 'M', '甤'), + (0x2F935, 'M', '𤰶'), + (0x2F936, 'M', '甾'), + (0x2F937, 'M', '𤲒'), + (0x2F938, 'M', '異'), + (0x2F939, 'M', '𢆟'), + (0x2F93A, 'M', '瘐'), + (0x2F93B, 'M', '𤾡'), + (0x2F93C, 'M', '𤾸'), + (0x2F93D, 'M', '𥁄'), + (0x2F93E, 'M', '㿼'), + (0x2F93F, 'M', '䀈'), + (0x2F940, 'M', '直'), + (0x2F941, 'M', '𥃳'), + (0x2F942, 'M', '𥃲'), + (0x2F943, 'M', '𥄙'), + (0x2F944, 'M', '𥄳'), + (0x2F945, 'M', '眞'), + (0x2F946, 'M', '真'), + (0x2F948, 'M', '睊'), + (0x2F949, 'M', '䀹'), + (0x2F94A, 'M', '瞋'), + (0x2F94B, 'M', '䁆'), + (0x2F94C, 'M', '䂖'), + (0x2F94D, 'M', '𥐝'), + (0x2F94E, 'M', '硎'), + (0x2F94F, 'M', '碌'), + (0x2F950, 'M', '磌'), + (0x2F951, 'M', '䃣'), + (0x2F952, 'M', '𥘦'), + (0x2F953, 'M', '祖'), + (0x2F954, 'M', '𥚚'), + (0x2F955, 'M', '𥛅'), + (0x2F956, 'M', '福'), + (0x2F957, 'M', '秫'), + (0x2F958, 'M', '䄯'), + (0x2F959, 'M', '穀'), + (0x2F95A, 'M', '穊'), + (0x2F95B, 'M', '穏'), + (0x2F95C, 'M', '𥥼'), + (0x2F95D, 'M', '𥪧'), + (0x2F95F, 'X'), + (0x2F960, 'M', '䈂'), + (0x2F961, 'M', '𥮫'), + (0x2F962, 'M', '篆'), + (0x2F963, 'M', '築'), + (0x2F964, 'M', '䈧'), + (0x2F965, 'M', '𥲀'), + (0x2F966, 'M', '糒'), + (0x2F967, 'M', '䊠'), + (0x2F968, 'M', '糨'), + (0x2F969, 'M', '糣'), + (0x2F96A, 'M', '紀'), + (0x2F96B, 'M', '𥾆'), + (0x2F96C, 'M', '絣'), + (0x2F96D, 'M', '䌁'), + (0x2F96E, 'M', '緇'), + (0x2F96F, 'M', '縂'), + (0x2F970, 'M', '繅'), + (0x2F971, 'M', '䌴'), + ] + +def _seg_80() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F972, 'M', '𦈨'), + (0x2F973, 'M', '𦉇'), + (0x2F974, 'M', '䍙'), + (0x2F975, 'M', '𦋙'), + (0x2F976, 'M', '罺'), + (0x2F977, 'M', '𦌾'), + (0x2F978, 'M', '羕'), + (0x2F979, 'M', '翺'), + (0x2F97A, 'M', '者'), + (0x2F97B, 'M', '𦓚'), + (0x2F97C, 'M', '𦔣'), + (0x2F97D, 'M', '聠'), + (0x2F97E, 'M', '𦖨'), + (0x2F97F, 'M', '聰'), + (0x2F980, 'M', '𣍟'), + (0x2F981, 'M', '䏕'), + (0x2F982, 'M', '育'), + (0x2F983, 'M', '脃'), + (0x2F984, 'M', '䐋'), + (0x2F985, 'M', '脾'), + (0x2F986, 'M', '媵'), + (0x2F987, 'M', '𦞧'), + (0x2F988, 'M', '𦞵'), + (0x2F989, 'M', '𣎓'), + (0x2F98A, 'M', '𣎜'), + (0x2F98B, 'M', '舁'), + (0x2F98C, 'M', '舄'), + (0x2F98D, 'M', '辞'), + (0x2F98E, 'M', '䑫'), + (0x2F98F, 'M', '芑'), + (0x2F990, 'M', '芋'), + (0x2F991, 'M', '芝'), + (0x2F992, 'M', '劳'), + (0x2F993, 'M', '花'), + (0x2F994, 'M', '芳'), + (0x2F995, 'M', '芽'), + (0x2F996, 'M', '苦'), + (0x2F997, 'M', '𦬼'), + (0x2F998, 'M', '若'), + (0x2F999, 'M', '茝'), + (0x2F99A, 'M', '荣'), + (0x2F99B, 'M', '莭'), + (0x2F99C, 'M', '茣'), + (0x2F99D, 'M', '莽'), + (0x2F99E, 'M', '菧'), + (0x2F99F, 'M', '著'), + (0x2F9A0, 'M', '荓'), + (0x2F9A1, 'M', '菊'), + (0x2F9A2, 'M', '菌'), + (0x2F9A3, 'M', '菜'), + (0x2F9A4, 'M', '𦰶'), + (0x2F9A5, 'M', '𦵫'), + (0x2F9A6, 'M', '𦳕'), + (0x2F9A7, 'M', '䔫'), + (0x2F9A8, 'M', '蓱'), + (0x2F9A9, 'M', '蓳'), + (0x2F9AA, 'M', '蔖'), + (0x2F9AB, 'M', '𧏊'), + (0x2F9AC, 'M', '蕤'), + (0x2F9AD, 'M', '𦼬'), + (0x2F9AE, 'M', '䕝'), + (0x2F9AF, 'M', '䕡'), + (0x2F9B0, 'M', '𦾱'), + (0x2F9B1, 'M', '𧃒'), + (0x2F9B2, 'M', '䕫'), + (0x2F9B3, 'M', '虐'), + (0x2F9B4, 'M', '虜'), + (0x2F9B5, 'M', '虧'), + (0x2F9B6, 'M', '虩'), + (0x2F9B7, 'M', '蚩'), + (0x2F9B8, 'M', '蚈'), + (0x2F9B9, 'M', '蜎'), + (0x2F9BA, 'M', '蛢'), + (0x2F9BB, 'M', '蝹'), + (0x2F9BC, 'M', '蜨'), + (0x2F9BD, 'M', '蝫'), + (0x2F9BE, 'M', '螆'), + (0x2F9BF, 'X'), + (0x2F9C0, 'M', '蟡'), + (0x2F9C1, 'M', '蠁'), + (0x2F9C2, 'M', '䗹'), + (0x2F9C3, 'M', '衠'), + (0x2F9C4, 'M', '衣'), + (0x2F9C5, 'M', '𧙧'), + (0x2F9C6, 'M', '裗'), + (0x2F9C7, 'M', '裞'), + (0x2F9C8, 'M', '䘵'), + (0x2F9C9, 'M', '裺'), + (0x2F9CA, 'M', '㒻'), + (0x2F9CB, 'M', '𧢮'), + (0x2F9CC, 'M', '𧥦'), + (0x2F9CD, 'M', '䚾'), + (0x2F9CE, 'M', '䛇'), + (0x2F9CF, 'M', '誠'), + (0x2F9D0, 'M', '諭'), + (0x2F9D1, 'M', '變'), + (0x2F9D2, 'M', '豕'), + (0x2F9D3, 'M', '𧲨'), + (0x2F9D4, 'M', '貫'), + (0x2F9D5, 'M', '賁'), + ] + +def _seg_81() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F9D6, 'M', '贛'), + (0x2F9D7, 'M', '起'), + (0x2F9D8, 'M', '𧼯'), + (0x2F9D9, 'M', '𠠄'), + (0x2F9DA, 'M', '跋'), + (0x2F9DB, 'M', '趼'), + (0x2F9DC, 'M', '跰'), + (0x2F9DD, 'M', '𠣞'), + (0x2F9DE, 'M', '軔'), + (0x2F9DF, 'M', '輸'), + (0x2F9E0, 'M', '𨗒'), + (0x2F9E1, 'M', '𨗭'), + (0x2F9E2, 'M', '邔'), + (0x2F9E3, 'M', '郱'), + (0x2F9E4, 'M', '鄑'), + (0x2F9E5, 'M', '𨜮'), + (0x2F9E6, 'M', '鄛'), + (0x2F9E7, 'M', '鈸'), + (0x2F9E8, 'M', '鋗'), + (0x2F9E9, 'M', '鋘'), + (0x2F9EA, 'M', '鉼'), + (0x2F9EB, 'M', '鏹'), + (0x2F9EC, 'M', '鐕'), + (0x2F9ED, 'M', '𨯺'), + (0x2F9EE, 'M', '開'), + (0x2F9EF, 'M', '䦕'), + (0x2F9F0, 'M', '閷'), + (0x2F9F1, 'M', '𨵷'), + (0x2F9F2, 'M', '䧦'), + (0x2F9F3, 'M', '雃'), + (0x2F9F4, 'M', '嶲'), + (0x2F9F5, 'M', '霣'), + (0x2F9F6, 'M', '𩅅'), + (0x2F9F7, 'M', '𩈚'), + (0x2F9F8, 'M', '䩮'), + (0x2F9F9, 'M', '䩶'), + (0x2F9FA, 'M', '韠'), + (0x2F9FB, 'M', '𩐊'), + (0x2F9FC, 'M', '䪲'), + (0x2F9FD, 'M', '𩒖'), + (0x2F9FE, 'M', '頋'), + (0x2FA00, 'M', '頩'), + (0x2FA01, 'M', '𩖶'), + (0x2FA02, 'M', '飢'), + (0x2FA03, 'M', '䬳'), + (0x2FA04, 'M', '餩'), + (0x2FA05, 'M', '馧'), + (0x2FA06, 'M', '駂'), + (0x2FA07, 'M', '駾'), + (0x2FA08, 'M', '䯎'), + (0x2FA09, 'M', '𩬰'), + (0x2FA0A, 'M', '鬒'), + (0x2FA0B, 'M', '鱀'), + (0x2FA0C, 'M', '鳽'), + (0x2FA0D, 'M', '䳎'), + (0x2FA0E, 'M', '䳭'), + (0x2FA0F, 'M', '鵧'), + (0x2FA10, 'M', '𪃎'), + (0x2FA11, 'M', '䳸'), + (0x2FA12, 'M', '𪄅'), + (0x2FA13, 'M', '𪈎'), + (0x2FA14, 'M', '𪊑'), + (0x2FA15, 'M', '麻'), + (0x2FA16, 'M', '䵖'), + (0x2FA17, 'M', '黹'), + (0x2FA18, 'M', '黾'), + (0x2FA19, 'M', '鼅'), + (0x2FA1A, 'M', '鼏'), + (0x2FA1B, 'M', '鼖'), + (0x2FA1C, 'M', '鼻'), + (0x2FA1D, 'M', '𪘀'), + (0x2FA1E, 'X'), + (0x30000, 'V'), + (0x3134B, 'X'), + (0x31350, 'V'), + (0x323B0, 'X'), + (0xE0100, 'I'), + (0xE01F0, 'X'), + ] + +uts46data = tuple( + _seg_0() + + _seg_1() + + _seg_2() + + _seg_3() + + _seg_4() + + _seg_5() + + _seg_6() + + _seg_7() + + _seg_8() + + _seg_9() + + _seg_10() + + _seg_11() + + _seg_12() + + _seg_13() + + _seg_14() + + _seg_15() + + _seg_16() + + _seg_17() + + _seg_18() + + _seg_19() + + _seg_20() + + _seg_21() + + _seg_22() + + _seg_23() + + _seg_24() + + _seg_25() + + _seg_26() + + _seg_27() + + _seg_28() + + _seg_29() + + _seg_30() + + _seg_31() + + _seg_32() + + _seg_33() + + _seg_34() + + _seg_35() + + _seg_36() + + _seg_37() + + _seg_38() + + _seg_39() + + _seg_40() + + _seg_41() + + _seg_42() + + _seg_43() + + _seg_44() + + _seg_45() + + _seg_46() + + _seg_47() + + _seg_48() + + _seg_49() + + _seg_50() + + _seg_51() + + _seg_52() + + _seg_53() + + _seg_54() + + _seg_55() + + _seg_56() + + _seg_57() + + _seg_58() + + _seg_59() + + _seg_60() + + _seg_61() + + _seg_62() + + _seg_63() + + _seg_64() + + _seg_65() + + _seg_66() + + _seg_67() + + _seg_68() + + _seg_69() + + _seg_70() + + _seg_71() + + _seg_72() + + _seg_73() + + _seg_74() + + _seg_75() + + _seg_76() + + _seg_77() + + _seg_78() + + _seg_79() + + _seg_80() + + _seg_81() +) # type: Tuple[Union[Tuple[int, str], Tuple[int, str, str]], ...] diff --git a/konfig.py b/konfig.py new file mode 100644 index 0000000..cd3857f --- /dev/null +++ b/konfig.py @@ -0,0 +1,62 @@ +"""Zugangsdaten aus config.ini. + +Passwoerter standen frueher im Quelltext - in solarManager.py, zeit.py, +gatherWaterData.py und wecker.py jeweils noch einmal. Beim Anlegen des +Repositorys waeren sie damit dauerhaft in der Historie gelandet, und genau das +musste beim Web-Repository schon einmal muehsam rueckgaengig gemacht werden. + +Jetzt steht alles in config.ini, die nicht mit eingecheckt wird. Was dort +hineingehoert, zeigt config.ini.example - beim Aufsetzen kopieren und +ausfuellen. Dasselbe Verfahren benutzt der AutoAction-Runner unter +autoActions/ schon laenger. + + import konfig + zug = konfig.datenbank() # dict fuer mysql.connector.connect + with connect(**zug) as c: + ... + + pw = konfig.wert("wattpilot", "password") +""" +import configparser +import os + +_PFAD = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.ini") +_konf = None + + +def _lesen(): + """Die Datei einmal einlesen und behalten.""" + global _konf + if _konf is None: + _konf = configparser.ConfigParser(interpolation=None) # Prozentzeichen im Passwort + if not _konf.read(_PFAD, encoding="utf-8"): + raise SystemExit( + "%s fehlt - config.ini.example kopieren und ausfuellen." % _PFAD) + return _konf + + +def wert(abschnitt, schluessel, vorgabe=None): + """Ein einzelner Eintrag. Ohne Vorgabe ist ein fehlender Eintrag ein Fehler. + + Lieber hier laut abbrechen als spaeter mit einem leeren Passwort an der + Datenbank scheitern - der Grund waere dann nicht mehr zu sehen. + """ + konf = _lesen() + if konf.has_option(abschnitt, schluessel): + return konf.get(abschnitt, schluessel) + if vorgabe is not None: + return vorgabe + raise SystemExit("config.ini: [%s] %s fehlt." % (abschnitt, schluessel)) + + +def datenbank(abschnitt="database"): + """Verbindungsdaten als dict, direkt fuer mysql.connector.connect geeignet. + + Der Port bleibt Text, weil connect() beides nimmt und der Aufrufer ihn + frueher auch als Text stehen hatte. + """ + return {"host": wert(abschnitt, "host", "localhost"), + "port": wert(abschnitt, "port", "3310"), + "user": wert(abschnitt, "user"), + "password": wert(abschnitt, "password"), + "database": wert(abschnitt, "database")} diff --git a/logging.ini b/logging.ini new file mode 100644 index 0000000..46248d6 --- /dev/null +++ b/logging.ini @@ -0,0 +1,74 @@ +[loggers] +keys=root,main,websockets,Wattpilot,gatherOpenDTUData,gatherDTUBIData,gatherModbusData,charger_goE,gatherWaterData,gatherHeaterData + +[handlers] +keys=consoleHandler + +[formatters] +keys=defaultFormatter + +[logger_root] +handlers=consoleHandler +level=INFO + +[logger_websockets] +handlers=consoleHandler +level=WARNING +qualname=websockets +propagate=0 + +[logger_Wattpilot] +handlers=consoleHandler +level=WARNING +qualname=Wattpilot +propagate=0 + +[logger_gatherOpenDTUData] +handlers=consoleHandler +level=DEBUG +qualname=gatherOpenDTUData +propagate=0 + +[logger_gatherDTUBIData] +handlers=consoleHandler +level=DEBUG +qualname=gatherDTUBIData +propagate=0 + +[logger_gatherModbusData] +handlers=consoleHandler +level=DEBUG +qualname=gatherModbusData +propagate=0 + +[logger_charger_goE] +handlers=consoleHandler +level=DEBUG +qualname=charger_goE +propagate=0 + +[logger_gatherWaterData] +handlers=consoleHandler +level=DEBUG +qualname=gatherWaterData +propagate=0 + +[logger_gatherHeaterData] +handlers=consoleHandler +level=DEBUG +qualname=gatherHeaterData +propagate=0 + +[handler_consoleHandler] +class=logging.StreamHandler +formatter=defaultFormatter +args=(sys.stdout,) + +[formatter_defaultFormatter] +format=%(levelname)s %(asctime)s %(pathname)s %(filename)s - %(message)s + +[logger_main] +handlers=consoleHandler +level=INFO +qualname=__main__ +propagate=0 \ No newline at end of file diff --git a/mqttClient.py b/mqttClient.py new file mode 100644 index 0000000..6c3128d --- /dev/null +++ b/mqttClient.py @@ -0,0 +1,89 @@ +import sys +import dataclasses +import logging +import json +sys.path.append("./") +import paho.mqtt.client as mqtt +logging.config.fileConfig('./logging.ini') +_LOGGER = logging.getLogger() + +class EnhancedJSONEncoder(json.JSONEncoder): + def default(self, o): + if dataclasses.is_dataclass(o): + return dataclasses.asdict(o) + return super().default(o) + +unacked_publish = set() + + +mqttClient=mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) +#mqttClient.on_publish = on_publish +mqttClient.user_data_set(unacked_publish) + +logging.basicConfig( + format="%(asctime)s %(message)s", + level=logging.WARN, +) + +def publish(dict,topic="solarManager"): + if dataclasses.is_dataclass(dict): + for field in dataclasses.fields(dict): + key = field.name + value = getattr(dict, field.name) + if hasattr(value, '__len__') and (not isinstance(value, str)): + if dataclasses.is_dataclass(value): #make subtopics of dataclasses + publish(value,topic+"/"+key) + else: + #try: #make subtopics of dataclass arrays + if len(value): + if dataclasses.is_dataclass(value[0]): + i=0 + for d in value: + publish(d,topic+"/"+key+str(i)) + i=i+1 + #except: + else: + mqttClient.publish(topic+"/"+key,json.dumps(value, cls=EnhancedJSONEncoder)) + else: #output empty array anyway to keep data consistent + mqttClient.publish(topic+"/"+key,json.dumps(value, cls=EnhancedJSONEncoder)) + else: + try: + flt = float(value) + mqttClient.publish(topic+"/"+key,round(flt,2)) + except ValueError: + mqttClient.publish(topic+"/"+key,value) + else: + for key, value in dict.items(): + if hasattr(value, '__len__') and (not isinstance(value, str)): + if dataclasses.is_dataclass(value): #make subtopics of dataclasses + publish(value,topic+"/"+key) + else: + #try: #make subtopics of dataclass arrays + if len(value): + if dataclasses.is_dataclass(value[0]): + i=0 + for d in value: + publish(d,topic+"/"+key+str(i)) + i=i+1 + #except: + else: + mqttClient.publish(topic+"/"+key,json.dumps(value, cls=EnhancedJSONEncoder)) + else: #output empty array anyway to keep data consistent + mqttClient.publish(topic+"/"+key,json.dumps(value, cls=EnhancedJSONEncoder)) + else: + try: + flt = float(value) + mqttClient.publish(topic+"/"+key,round(flt,2)) + except ValueError: + mqttClient.publish(topic+"/"+key,value) + except: + _LOGGER.error(f"value is no number") + + #print(message) + +def startMqttClient(): + mqttClient.connect("localhost", 1883, 60) + mqttClient.loop_start() + #await asyncio.Future() # run forever +def closeMqttClient(): + mqttClient.disconnect() \ No newline at end of file diff --git a/multidict/__init__.py b/multidict/__init__.py new file mode 100644 index 0000000..d9ea722 --- /dev/null +++ b/multidict/__init__.py @@ -0,0 +1,48 @@ +"""Multidict implementation. + +HTTP Headers and URL query string require specific data structure: +multidict. It behaves mostly like a dict but it can have +several values for the same key. +""" + +from ._abc import MultiMapping, MutableMultiMapping +from ._compat import USE_EXTENSIONS + +__all__ = ( + "MultiMapping", + "MutableMultiMapping", + "MultiDictProxy", + "CIMultiDictProxy", + "MultiDict", + "CIMultiDict", + "upstr", + "istr", + "getversion", +) + +__version__ = "6.0.4" + + +try: + if not USE_EXTENSIONS: + raise ImportError + from ._multidict import ( + CIMultiDict, + CIMultiDictProxy, + MultiDict, + MultiDictProxy, + getversion, + istr, + ) +except ImportError: # pragma: no cover + from ._multidict_py import ( + CIMultiDict, + CIMultiDictProxy, + MultiDict, + MultiDictProxy, + getversion, + istr, + ) + + +upstr = istr diff --git a/multidict/__init__.pyi b/multidict/__init__.pyi new file mode 100644 index 0000000..bbc5a5c --- /dev/null +++ b/multidict/__init__.pyi @@ -0,0 +1,150 @@ +import abc +from typing import ( + Generic, + Iterable, + Iterator, + Mapping, + MutableMapping, + TypeVar, + overload, +) + +class istr(str): ... + +upstr = istr + +_S = str | istr + +_T = TypeVar("_T") + +_T_co = TypeVar("_T_co", covariant=True) + +_D = TypeVar("_D") + +class MultiMapping(Mapping[_S, _T_co]): + @overload + @abc.abstractmethod + def getall(self, key: _S) -> list[_T_co]: ... + @overload + @abc.abstractmethod + def getall(self, key: _S, default: _D) -> list[_T_co] | _D: ... + @overload + @abc.abstractmethod + def getone(self, key: _S) -> _T_co: ... + @overload + @abc.abstractmethod + def getone(self, key: _S, default: _D) -> _T_co | _D: ... + +_Arg = (Mapping[str, _T] | Mapping[istr, _T] | dict[str, _T] + | dict[istr, _T] | MultiMapping[_T] + | Iterable[tuple[str, _T]] | Iterable[tuple[istr, _T]]) + +class MutableMultiMapping(MultiMapping[_T], MutableMapping[_S, _T], Generic[_T]): + @abc.abstractmethod + def add(self, key: _S, value: _T) -> None: ... + @abc.abstractmethod + def extend(self, arg: _Arg[_T] = ..., **kwargs: _T) -> None: ... + @overload + @abc.abstractmethod + def popone(self, key: _S) -> _T: ... + @overload + @abc.abstractmethod + def popone(self, key: _S, default: _D) -> _T | _D: ... + @overload + @abc.abstractmethod + def popall(self, key: _S) -> list[_T]: ... + @overload + @abc.abstractmethod + def popall(self, key: _S, default: _D) -> list[_T] | _D: ... + +class MultiDict(MutableMultiMapping[_T], Generic[_T]): + def __init__(self, arg: _Arg[_T] = ..., **kwargs: _T) -> None: ... + def copy(self) -> MultiDict[_T]: ... + def __getitem__(self, k: _S) -> _T: ... + def __setitem__(self, k: _S, v: _T) -> None: ... + def __delitem__(self, v: _S) -> None: ... + def __iter__(self) -> Iterator[_S]: ... + def __len__(self) -> int: ... + @overload + def getall(self, key: _S) -> list[_T]: ... + @overload + def getall(self, key: _S, default: _D) -> list[_T] | _D: ... + @overload + def getone(self, key: _S) -> _T: ... + @overload + def getone(self, key: _S, default: _D) -> _T | _D: ... + def add(self, key: _S, value: _T) -> None: ... + def extend(self, arg: _Arg[_T] = ..., **kwargs: _T) -> None: ... + @overload + def popone(self, key: _S) -> _T: ... + @overload + def popone(self, key: _S, default: _D) -> _T | _D: ... + @overload + def popall(self, key: _S) -> list[_T]: ... + @overload + def popall(self, key: _S, default: _D) -> list[_T] | _D: ... + +class CIMultiDict(MutableMultiMapping[_T], Generic[_T]): + def __init__(self, arg: _Arg[_T] = ..., **kwargs: _T) -> None: ... + def copy(self) -> CIMultiDict[_T]: ... + def __getitem__(self, k: _S) -> _T: ... + def __setitem__(self, k: _S, v: _T) -> None: ... + def __delitem__(self, v: _S) -> None: ... + def __iter__(self) -> Iterator[_S]: ... + def __len__(self) -> int: ... + @overload + def getall(self, key: _S) -> list[_T]: ... + @overload + def getall(self, key: _S, default: _D) -> list[_T] | _D: ... + @overload + def getone(self, key: _S) -> _T: ... + @overload + def getone(self, key: _S, default: _D) -> _T | _D: ... + def add(self, key: _S, value: _T) -> None: ... + def extend(self, arg: _Arg[_T] = ..., **kwargs: _T) -> None: ... + @overload + def popone(self, key: _S) -> _T: ... + @overload + def popone(self, key: _S, default: _D) -> _T | _D: ... + @overload + def popall(self, key: _S) -> list[_T]: ... + @overload + def popall(self, key: _S, default: _D) -> list[_T] | _D: ... + +class MultiDictProxy(MultiMapping[_T], Generic[_T]): + def __init__( + self, arg: MultiMapping[_T] | MutableMultiMapping[_T] + ) -> None: ... + def copy(self) -> MultiDict[_T]: ... + def __getitem__(self, k: _S) -> _T: ... + def __iter__(self) -> Iterator[_S]: ... + def __len__(self) -> int: ... + @overload + def getall(self, key: _S) -> list[_T]: ... + @overload + def getall(self, key: _S, default: _D) -> list[_T] | _D: ... + @overload + def getone(self, key: _S) -> _T: ... + @overload + def getone(self, key: _S, default: _D) -> _T | _D: ... + +class CIMultiDictProxy(MultiMapping[_T], Generic[_T]): + def __init__( + self, arg: MultiMapping[_T] | MutableMultiMapping[_T] + ) -> None: ... + def __getitem__(self, k: _S) -> _T: ... + def __iter__(self) -> Iterator[_S]: ... + def __len__(self) -> int: ... + @overload + def getall(self, key: _S) -> list[_T]: ... + @overload + def getall(self, key: _S, default: _D) -> list[_T] | _D: ... + @overload + def getone(self, key: _S) -> _T: ... + @overload + def getone(self, key: _S, default: _D) -> _T | _D: ... + def copy(self) -> CIMultiDict[_T]: ... + +def getversion( + md: MultiDict[_T] | CIMultiDict[_T] | MultiDictProxy[_T] | CIMultiDictProxy[_T] +) -> int: ... diff --git a/multidict/_abc.py b/multidict/_abc.py new file mode 100644 index 0000000..0603cdd --- /dev/null +++ b/multidict/_abc.py @@ -0,0 +1,48 @@ +import abc +import sys +import types +from collections.abc import Mapping, MutableMapping + + +class _TypingMeta(abc.ABCMeta): + # A fake metaclass to satisfy typing deps in runtime + # basically MultiMapping[str] and other generic-like type instantiations + # are emulated. + # Note: real type hints are provided by __init__.pyi stub file + if sys.version_info >= (3, 9): + + def __getitem__(self, key): + return types.GenericAlias(self, key) + + else: + + def __getitem__(self, key): + return self + + +class MultiMapping(Mapping, metaclass=_TypingMeta): + @abc.abstractmethod + def getall(self, key, default=None): + raise KeyError + + @abc.abstractmethod + def getone(self, key, default=None): + raise KeyError + + +class MutableMultiMapping(MultiMapping, MutableMapping): + @abc.abstractmethod + def add(self, key, value): + raise NotImplementedError + + @abc.abstractmethod + def extend(self, *args, **kwargs): + raise NotImplementedError + + @abc.abstractmethod + def popone(self, key, default=None): + raise KeyError + + @abc.abstractmethod + def popall(self, key, default=None): + raise KeyError diff --git a/multidict/_compat.py b/multidict/_compat.py new file mode 100644 index 0000000..d1ff392 --- /dev/null +++ b/multidict/_compat.py @@ -0,0 +1,14 @@ +import os +import platform + +NO_EXTENSIONS = bool(os.environ.get("MULTIDICT_NO_EXTENSIONS")) + +PYPY = platform.python_implementation() == "PyPy" + +USE_EXTENSIONS = not NO_EXTENSIONS and not PYPY + +if USE_EXTENSIONS: + try: + from . import _multidict # noqa + except ImportError: + USE_EXTENSIONS = False diff --git a/multidict/_multidict.c b/multidict/_multidict.c new file mode 100644 index 0000000..1ba79df --- /dev/null +++ b/multidict/_multidict.c @@ -0,0 +1,1824 @@ +#include "Python.h" +#include "structmember.h" + +// Include order important +#include "_multilib/defs.h" +#include "_multilib/istr.h" +#include "_multilib/pair_list.h" +#include "_multilib/dict.h" +#include "_multilib/iter.h" +#include "_multilib/views.h" + +#ifndef _PyArg_UnpackKeywords +#define FASTCALL_OLD +#endif + + +static PyObject *collections_abc_mapping; +static PyObject *collections_abc_mut_mapping; +static PyObject *collections_abc_mut_multi_mapping; + +static PyTypeObject multidict_type; +static PyTypeObject cimultidict_type; +static PyTypeObject multidict_proxy_type; +static PyTypeObject cimultidict_proxy_type; + +static PyObject *repr_func; + +#define MultiDict_CheckExact(o) (Py_TYPE(o) == &multidict_type) +#define CIMultiDict_CheckExact(o) (Py_TYPE(o) == &cimultidict_type) +#define MultiDictProxy_CheckExact(o) (Py_TYPE(o) == &multidict_proxy_type) +#define CIMultiDictProxy_CheckExact(o) (Py_TYPE(o) == &cimultidict_proxy_type) + +/* Helper macro for something like isinstance(obj, Base) */ +#define _MultiDict_Check(o) \ + ((MultiDict_CheckExact(o)) || \ + (CIMultiDict_CheckExact(o)) || \ + (MultiDictProxy_CheckExact(o)) || \ + (CIMultiDictProxy_CheckExact(o))) + +/******************** Internal Methods ********************/ + +/* Forward declaration */ +static PyObject *multidict_items(MultiDictObject *self); + +static inline PyObject * +_multidict_getone(MultiDictObject *self, PyObject *key, PyObject *_default) +{ + PyObject *val = pair_list_get_one(&self->pairs, key); + + if (val == NULL && + PyErr_ExceptionMatches(PyExc_KeyError) && + _default != NULL) + { + PyErr_Clear(); + Py_INCREF(_default); + return _default; + } + + return val; +} + +static inline int +_multidict_eq(MultiDictObject *self, MultiDictObject *other) +{ + Py_ssize_t pos1 = 0, + pos2 = 0; + + Py_hash_t h1 = 0, + h2 = 0; + + PyObject *identity1 = NULL, + *identity2 = NULL, + *value1 = NULL, + *value2 = NULL; + + int cmp_identity = 0, + cmp_value = 0; + + if (self == other) { + return 1; + } + + if (pair_list_len(&self->pairs) != pair_list_len(&other->pairs)) { + return 0; + } + + while (_pair_list_next(&self->pairs, &pos1, &identity1, NULL, &value1, &h1) && + _pair_list_next(&other->pairs, &pos2, &identity2, NULL, &value2, &h2)) + { + if (h1 != h2) { + return 0; + } + cmp_identity = PyObject_RichCompareBool(identity1, identity2, Py_NE); + if (cmp_identity < 0) { + return -1; + } + cmp_value = PyObject_RichCompareBool(value1, value2, Py_NE); + if (cmp_value < 0) { + return -1; + } + if (cmp_identity || cmp_value) { + return 0; + } + } + + return 1; +} + +static inline int +_multidict_update_items(MultiDictObject *self, pair_list_t *pairs) +{ + return pair_list_update(&self->pairs, pairs); +} + +static inline int +_multidict_append_items(MultiDictObject *self, pair_list_t *pairs) +{ + PyObject *key = NULL, + *value = NULL; + + Py_ssize_t pos = 0; + + while (_pair_list_next(pairs, &pos, NULL, &key, &value, NULL)) { + if (pair_list_add(&self->pairs, key, value) < 0) { + return -1; + } + } + + return 0; +} + +static inline int +_multidict_append_items_seq(MultiDictObject *self, PyObject *arg, + const char *name) +{ + PyObject *key = NULL, + *value = NULL, + *item = NULL, + *iter = PyObject_GetIter(arg); + + if (iter == NULL) { + return -1; + } + + while ((item = PyIter_Next(iter)) != NULL) { + if (PyTuple_CheckExact(item)) { + if (PyTuple_GET_SIZE(item) != 2) { + goto invalid_type; + } + key = PyTuple_GET_ITEM(item, 0); + Py_INCREF(key); + value = PyTuple_GET_ITEM(item, 1); + Py_INCREF(value); + } + else if (PyList_CheckExact(item)) { + if (PyList_GET_SIZE(item) != 2) { + goto invalid_type; + } + key = PyList_GET_ITEM(item, 0); + Py_INCREF(key); + value = PyList_GET_ITEM(item, 1); + Py_INCREF(value); + } + else if (PySequence_Check(item)) { + if (PySequence_Size(item) != 2) { + goto invalid_type; + } + key = PySequence_GetItem(item, 0); + value = PySequence_GetItem(item, 1); + } else { + goto invalid_type; + } + + if (pair_list_add(&self->pairs, key, value) < 0) { + goto fail; + } + Py_CLEAR(key); + Py_CLEAR(value); + Py_CLEAR(item); + } + + Py_DECREF(iter); + + if (PyErr_Occurred()) { + return -1; + } + + return 0; +invalid_type: + PyErr_Format( + PyExc_TypeError, + "%s takes either dict or list of (key, value) pairs", + name, + NULL + ); + goto fail; +fail: + Py_XDECREF(key); + Py_XDECREF(value); + Py_XDECREF(item); + Py_DECREF(iter); + return -1; +} + +static inline int +_multidict_list_extend(PyObject *list, PyObject *target_list) +{ + PyObject *item = NULL, + *iter = PyObject_GetIter(target_list); + + if (iter == NULL) { + return -1; + } + + while ((item = PyIter_Next(iter)) != NULL) { + if (PyList_Append(list, item) < 0) { + Py_DECREF(item); + Py_DECREF(iter); + return -1; + } + Py_DECREF(item); + } + + Py_DECREF(iter); + + if (PyErr_Occurred()) { + return -1; + } + + return 0; +} + +static inline int +_multidict_extend_with_args(MultiDictObject *self, PyObject *arg, + PyObject *kwds, const char *name, int do_add) +{ + PyObject *arg_items = NULL, /* tracked by GC */ + *kwds_items = NULL; /* new reference */ + pair_list_t *pairs = NULL; + + int err = 0; + + if (kwds && !PyArg_ValidateKeywordArguments(kwds)) { + return -1; + } + + // TODO: mb can be refactored more clear + if (_MultiDict_Check(arg) && kwds == NULL) { + if (MultiDict_CheckExact(arg) || CIMultiDict_CheckExact(arg)) { + pairs = &((MultiDictObject*)arg)->pairs; + } else if (MultiDictProxy_CheckExact(arg) || CIMultiDictProxy_CheckExact(arg)) { + pairs = &((MultiDictProxyObject*)arg)->md->pairs; + } + + if (do_add) { + return _multidict_append_items(self, pairs); + } + + return _multidict_update_items(self, pairs); + } + + if (PyObject_HasAttrString(arg, "items")) { + if (_MultiDict_Check(arg)) { + arg_items = multidict_items((MultiDictObject*)arg); + } else { + arg_items = PyMapping_Items(arg); + } + if (arg_items == NULL) { + return -1; + } + } else { + arg_items = arg; + Py_INCREF(arg_items); + } + + if (kwds) { + PyObject *tmp = PySequence_List(arg_items); + Py_DECREF(arg_items); + arg_items = tmp; + if (arg_items == NULL) { + return -1; + } + + kwds_items = PyDict_Items(kwds); + if (kwds_items == NULL) { + Py_DECREF(arg_items); + return -1; + } + err = _multidict_list_extend(arg_items, kwds_items); + Py_DECREF(kwds_items); + if (err < 0) { + Py_DECREF(arg_items); + return -1; + } + } + + if (do_add) { + err = _multidict_append_items_seq(self, arg_items, name); + } else { + err = pair_list_update_from_seq(&self->pairs, arg_items); + } + + Py_DECREF(arg_items); + + return err; +} + +static inline int +_multidict_extend_with_kwds(MultiDictObject *self, PyObject *kwds, + const char *name, int do_add) +{ + PyObject *arg = NULL; + + int err = 0; + + if (!PyArg_ValidateKeywordArguments(kwds)) { + return -1; + } + + arg = PyDict_Items(kwds); + if (do_add) { + err = _multidict_append_items_seq(self, arg, name); + } else { + err = pair_list_update_from_seq(&self->pairs, arg); + } + + Py_DECREF(arg); + return err; +} + +static inline int +_multidict_extend(MultiDictObject *self, PyObject *args, PyObject *kwds, + const char *name, int do_add) +{ + PyObject *arg = NULL; + + if (args && PyObject_Length(args) > 1) { + PyErr_Format( + PyExc_TypeError, + "%s takes at most 1 positional argument (%zd given)", + name, PyObject_Length(args), NULL + ); + return -1; + } + + if (args && PyObject_Length(args) > 0) { + if (!PyArg_UnpackTuple(args, name, 0, 1, &arg)) { + return -1; + } + if (_multidict_extend_with_args(self, arg, kwds, name, do_add) < 0) { + return -1; + } + } else if (kwds && PyObject_Length(kwds) > 0) { + if (_multidict_extend_with_kwds(self, kwds, name, do_add) < 0) { + return -1; + } + } + + return 0; +} + +static inline PyObject * +_multidict_copy(MultiDictObject *self, PyTypeObject *multidict_tp_object) +{ + MultiDictObject *new_multidict = NULL; + + PyObject *arg_items = NULL, + *items = NULL; + + new_multidict = (MultiDictObject*)PyType_GenericNew( + multidict_tp_object, NULL, NULL); + if (new_multidict == NULL) { + return NULL; + } + + if (multidict_tp_object->tp_init( + (PyObject*)new_multidict, NULL, NULL) < 0) + { + return NULL; + } + + items = multidict_items(self); + if (items == NULL) { + goto fail; + } + + // TODO: "Implementation looks as slow as possible ..." + arg_items = PyTuple_New(1); + if (arg_items == NULL) { + goto fail; + } + + Py_INCREF(items); + PyTuple_SET_ITEM(arg_items, 0, items); + + if (_multidict_extend( + new_multidict, arg_items, NULL, "copy", 1) < 0) + { + goto fail; + } + + Py_DECREF(items); + Py_DECREF(arg_items); + + return (PyObject*)new_multidict; + +fail: + Py_XDECREF(items); + Py_XDECREF(arg_items); + + Py_DECREF(new_multidict); + + return NULL; +} + +static inline PyObject * +_multidict_proxy_copy(MultiDictProxyObject *self, PyTypeObject *type) +{ + PyObject *new_multidict = PyType_GenericNew(type, NULL, NULL); + if (new_multidict == NULL) { + goto fail; + } + if (type->tp_init(new_multidict, NULL, NULL) < 0) { + goto fail; + } + if (_multidict_extend_with_args( + (MultiDictObject*)new_multidict, (PyObject*)self, NULL, "copy", 1) < 0) + { + goto fail; + } + + return new_multidict; + +fail: + Py_XDECREF(new_multidict); + return NULL; +} + + +/******************** Base Methods ********************/ + +static inline PyObject * +multidict_getall(MultiDictObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *list = NULL, + *key = NULL, + *_default = NULL; + + static const char * const _keywords[] = {"key", "default", NULL}; +#ifdef FASTCALL_OLD + static _PyArg_Parser _parser = {"O|O:getall", _keywords, 0}; + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &key, &_default)) { + return NULL; + } +#else + static _PyArg_Parser _parser = {NULL, _keywords, "getall", 0}; + PyObject *argsbuf[2]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, + &_parser, 1, 2, 0, argsbuf); + if (!args) { + return NULL; + } + key = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } + + _default = args[1]; +skip_optional_pos: +#endif + list = pair_list_get_all(&self->pairs, key); + + if (list == NULL && + PyErr_ExceptionMatches(PyExc_KeyError) && + _default != NULL) + { + PyErr_Clear(); + Py_INCREF(_default); + return _default; + } + + return list; +} + +static inline PyObject * +multidict_getone(MultiDictObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *key = NULL, + *_default = NULL; + + static const char * const _keywords[] = {"key", "default", NULL}; +#ifdef FASTCALL_OLD + static _PyArg_Parser _parser = {"O|O:getone", _keywords, 0}; + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &key, &_default)) { + return NULL; + } +#else + static _PyArg_Parser _parser = {NULL, _keywords, "getone", 0}; + PyObject *argsbuf[2]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, + &_parser, 1, 2, 0, argsbuf); + if (!args) { + return NULL; + } + key = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } + + _default = args[1]; +skip_optional_pos: +#endif + return _multidict_getone(self, key, _default); +} + +static inline PyObject * +multidict_get(MultiDictObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *key = NULL, + *_default = Py_None, + *ret; + + static const char * const _keywords[] = {"key", "default", NULL}; +#ifdef FASTCALL_OLD + static _PyArg_Parser _parser = {"O|O:get", _keywords, 0}; + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &key, &_default)) { + return NULL; + } +#else + static _PyArg_Parser _parser = {NULL, _keywords, "get", 0}; + PyObject *argsbuf[2]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, + &_parser, 1, 2, 0, argsbuf); + if (!args) { + return NULL; + } + key = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } + + _default = args[1]; +skip_optional_pos: +#endif + ret = _multidict_getone(self, key, _default); + return ret; +} + +static inline PyObject * +multidict_keys(MultiDictObject *self) +{ + return multidict_keysview_new((PyObject*)self); +} + +static inline PyObject * +multidict_items(MultiDictObject *self) +{ + return multidict_itemsview_new((PyObject*)self); +} + +static inline PyObject * +multidict_values(MultiDictObject *self) +{ + return multidict_valuesview_new((PyObject*)self); +} + +static inline PyObject * +multidict_reduce(MultiDictObject *self) +{ + PyObject *items = NULL, + *items_list = NULL, + *args = NULL, + *result = NULL; + + items = multidict_items(self); + if (items == NULL) { + goto ret; + } + + items_list = PySequence_List(items); + if (items_list == NULL) { + goto ret; + } + + args = PyTuple_Pack(1, items_list); + if (args == NULL) { + goto ret; + } + + result = PyTuple_Pack(2, Py_TYPE(self), args); + +ret: + Py_XDECREF(args); + Py_XDECREF(items_list); + Py_XDECREF(items); + + return result; +} + +static inline PyObject * +multidict_repr(PyObject *self) +{ + return PyObject_CallFunctionObjArgs( + repr_func, self, NULL); +} + +static inline Py_ssize_t +multidict_mp_len(MultiDictObject *self) +{ + return pair_list_len(&self->pairs); +} + +static inline PyObject * +multidict_mp_subscript(MultiDictObject *self, PyObject *key) +{ + return _multidict_getone(self, key, NULL); +} + +static inline int +multidict_mp_as_subscript(MultiDictObject *self, PyObject *key, PyObject *val) +{ + if (val == NULL) { + return pair_list_del(&self->pairs, key); + } else { + return pair_list_replace(&self->pairs, key, val); + } +} + +static inline int +multidict_sq_contains(MultiDictObject *self, PyObject *key) +{ + return pair_list_contains(&self->pairs, key); +} + +static inline PyObject * +multidict_tp_iter(MultiDictObject *self) +{ + return multidict_keys_iter_new(self); +} + +static inline PyObject * +multidict_tp_richcompare(PyObject *self, PyObject *other, int op) +{ + // TODO: refactoring me with love + + int cmp = 0; + + if (op != Py_EQ && op != Py_NE) { + Py_RETURN_NOTIMPLEMENTED; + } + + if (MultiDict_CheckExact(other) || CIMultiDict_CheckExact(other)) { + cmp = _multidict_eq( + (MultiDictObject*)self, + (MultiDictObject*)other + ); + if (cmp < 0) { + return NULL; + } + if (op == Py_NE) { + cmp = !cmp; + } + return PyBool_FromLong(cmp); + } + + if (MultiDictProxy_CheckExact(other) || CIMultiDictProxy_CheckExact(other)) { + cmp = _multidict_eq( + (MultiDictObject*)self, + ((MultiDictProxyObject*)other)->md + ); + if (cmp < 0) { + return NULL; + } + if (op == Py_NE) { + cmp = !cmp; + } + return PyBool_FromLong(cmp); + } + + cmp = PyObject_IsInstance(other, (PyObject*)collections_abc_mapping); + if (cmp < 0) { + return NULL; + } + + if (cmp) { + cmp = pair_list_eq_to_mapping(&((MultiDictObject*)self)->pairs, other); + if (cmp < 0) { + return NULL; + } + if (op == Py_NE) { + cmp = !cmp; + } + return PyBool_FromLong(cmp); + } + + Py_RETURN_NOTIMPLEMENTED; +} + +static inline void +multidict_tp_dealloc(MultiDictObject *self) +{ + PyObject_GC_UnTrack(self); + Py_TRASHCAN_SAFE_BEGIN(self); + if (self->weaklist != NULL) { + PyObject_ClearWeakRefs((PyObject *)self); + }; + pair_list_dealloc(&self->pairs); + Py_TYPE(self)->tp_free((PyObject *)self); + Py_TRASHCAN_SAFE_END(self); +} + +static inline int +multidict_tp_traverse(MultiDictObject *self, visitproc visit, void *arg) +{ + return pair_list_traverse(&self->pairs, visit, arg); +} + +static inline int +multidict_tp_clear(MultiDictObject *self) +{ + return pair_list_clear(&self->pairs); +} + +PyDoc_STRVAR(multidict_getall_doc, +"Return a list of all values matching the key."); + +PyDoc_STRVAR(multidict_getone_doc, +"Get first value matching the key."); + +PyDoc_STRVAR(multidict_get_doc, +"Get first value matching the key.\n\nThe method is alias for .getone()."); + +PyDoc_STRVAR(multidict_keys_doc, +"Return a new view of the dictionary's keys."); + +PyDoc_STRVAR(multidict_items_doc, +"Return a new view of the dictionary's items *(key, value) pairs)."); + +PyDoc_STRVAR(multidict_values_doc, +"Return a new view of the dictionary's values."); + +/******************** MultiDict ********************/ + +static inline int +multidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) +{ + if (pair_list_init(&self->pairs) < 0) { + return -1; + } + if (_multidict_extend(self, args, kwds, "MultiDict", 1) < 0) { + return -1; + } + return 0; +} + +static inline PyObject * +multidict_add(MultiDictObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *key = NULL, + *val = NULL; + + static const char * const _keywords[] = {"key", "value", NULL}; +#ifdef FASTCALL_OLD + static _PyArg_Parser _parser = {"OO:add", _keywords, 0}; + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &key, &val)) { + return NULL; + } +#else + static _PyArg_Parser _parser = {NULL, _keywords, "add", 0}; + PyObject *argsbuf[2]; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, + &_parser, 2, 2, 0, argsbuf); + if (!args) { + return NULL; + } + key = args[0]; + val = args[1]; +#endif + if (pair_list_add(&self->pairs, key, val) < 0) { + return NULL; + } + + Py_RETURN_NONE; +} + +static inline PyObject * +multidict_copy(MultiDictObject *self) +{ + return _multidict_copy(self, &multidict_type); +} + +static inline PyObject * +multidict_extend(MultiDictObject *self, PyObject *args, PyObject *kwds) +{ + if (_multidict_extend(self, args, kwds, "extend", 1) < 0) { + return NULL; + } + + Py_RETURN_NONE; +} + +static inline PyObject * +multidict_clear(MultiDictObject *self) +{ + if (pair_list_clear(&self->pairs) < 0) { + return NULL; + } + + Py_RETURN_NONE; +} + +static inline PyObject * +multidict_setdefault(MultiDictObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *key = NULL, + *_default = NULL; + + static const char * const _keywords[] = {"key", "default", NULL}; +#ifdef FASTCALL_OLD + static _PyArg_Parser _parser = {"O|O:setdefault", _keywords, 0}; + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &key, &_default)) { + return NULL; + } +#else + static _PyArg_Parser _parser = {NULL, _keywords, "setdefault", 0}; + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, + &_parser, 1, 2, 0, argsbuf); + if (!args) { + return NULL; + } + key = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } + _default = args[1]; + +skip_optional_pos: +#endif + return pair_list_set_default(&self->pairs, key, _default); +} + +static inline PyObject * +multidict_popone(MultiDictObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *key = NULL, + *_default = NULL, + *ret_val = NULL; + + static const char * const _keywords[] = {"key", "default", NULL}; +#ifdef FASTCALL_OLD + static _PyArg_Parser _parser = {"O|O:popone", _keywords, 0}; + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &key, &_default)) { + return NULL; + } +#else + static _PyArg_Parser _parser = {NULL, _keywords, "popone", 0}; + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, + &_parser, 1, 2, 0, argsbuf); + if (!args) { + return NULL; + } + key = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } + _default = args[1]; + +skip_optional_pos: +#endif + ret_val = pair_list_pop_one(&self->pairs, key); + + if (ret_val == NULL && + PyErr_ExceptionMatches(PyExc_KeyError) && + _default != NULL) + { + PyErr_Clear(); + Py_INCREF(_default); + return _default; + } + + return ret_val; +} + +static inline PyObject * +multidict_pop(MultiDictObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *key = NULL, + *_default = NULL, + *ret_val = NULL; + + static const char * const _keywords[] = {"key", "default", NULL}; +#ifdef FASTCALL_OLD + static _PyArg_Parser _parser = {"O|O:pop", _keywords, 0}; + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &key, &_default)) { + return NULL; + } +#else + static _PyArg_Parser _parser = {NULL, _keywords, "pop", 0}; + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, + &_parser, 1, 2, 0, argsbuf); + if (!args) { + return NULL; + } + key = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } + _default = args[1]; + +skip_optional_pos: +#endif + ret_val = pair_list_pop_one(&self->pairs, key); + + if (ret_val == NULL && + PyErr_ExceptionMatches(PyExc_KeyError) && + _default != NULL) + { + PyErr_Clear(); + Py_INCREF(_default); + return _default; + } + + return ret_val; +} + +static inline PyObject * +multidict_popall(MultiDictObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *key = NULL, + *_default = NULL, + *ret_val = NULL; + + + static const char * const _keywords[] = {"key", "default", NULL}; +#ifdef FASTCALL_OLD + static _PyArg_Parser _parser = {"O|O:popall", _keywords, 0}; + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &key, &_default)) { + return NULL; + } +#else + static _PyArg_Parser _parser = {NULL, _keywords, "popall", 0}; + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, + &_parser, 1, 2, 0, argsbuf); + if (!args) { + return NULL; + } + key = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } + _default = args[1]; + +skip_optional_pos: +#endif + ret_val = pair_list_pop_all(&self->pairs, key); + + if (ret_val == NULL && + PyErr_ExceptionMatches(PyExc_KeyError) && + _default != NULL) + { + PyErr_Clear(); + Py_INCREF(_default); + return _default; + } + + return ret_val; +} + +static inline PyObject * +multidict_popitem(MultiDictObject *self) +{ + return pair_list_pop_item(&self->pairs); +} + +static inline PyObject * +multidict_update(MultiDictObject *self, PyObject *args, PyObject *kwds) +{ + if (_multidict_extend(self, args, kwds, "update", 0) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + +PyDoc_STRVAR(multidict_add_doc, +"Add the key and value, not overwriting any previous value."); + +PyDoc_STRVAR(multidict_copy_doc, +"Return a copy of itself."); + +PyDoc_STRVAR(multdicit_method_extend_doc, +"Extend current MultiDict with more values.\n\ +This method must be used instead of update."); + +PyDoc_STRVAR(multidict_clear_doc, +"Remove all items from MultiDict"); + +PyDoc_STRVAR(multidict_setdefault_doc, +"Return value for key, set value to default if key is not present."); + +PyDoc_STRVAR(multidict_popone_doc, +"Remove the last occurrence of key and return the corresponding value.\n\n\ +If key is not found, default is returned if given, otherwise KeyError is \ +raised.\n"); + +PyDoc_STRVAR(multidict_pop_doc, +"Remove the last occurrence of key and return the corresponding value.\n\n\ +If key is not found, default is returned if given, otherwise KeyError is \ +raised.\n"); + +PyDoc_STRVAR(multidict_popall_doc, +"Remove all occurrences of key and return the list of corresponding values.\n\n\ +If key is not found, default is returned if given, otherwise KeyError is \ +raised.\n"); + +PyDoc_STRVAR(multidict_popitem_doc, +"Remove and return an arbitrary (key, value) pair."); + +PyDoc_STRVAR(multidict_update_doc, +"Update the dictionary from *other*, overwriting existing keys."); + + +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 9 +#define multidict_class_getitem Py_GenericAlias +#else +static inline PyObject * +multidict_class_getitem(PyObject *self, PyObject *arg) +{ + Py_INCREF(self); + return self; +} +#endif + + +PyDoc_STRVAR(sizeof__doc__, +"D.__sizeof__() -> size of D in memory, in bytes"); + +static inline PyObject * +_multidict_sizeof(MultiDictObject *self) +{ + Py_ssize_t size = sizeof(MultiDictObject); + if (self->pairs.pairs != self->pairs.buffer) { + size += (Py_ssize_t)sizeof(pair_t) * self->pairs.capacity; + } + return PyLong_FromSsize_t(size); +} + + +static PySequenceMethods multidict_sequence = { + .sq_contains = (objobjproc)multidict_sq_contains, +}; + +static PyMappingMethods multidict_mapping = { + .mp_length = (lenfunc)multidict_mp_len, + .mp_subscript = (binaryfunc)multidict_mp_subscript, + .mp_ass_subscript = (objobjargproc)multidict_mp_as_subscript, +}; + +static PyMethodDef multidict_methods[] = { + { + "getall", + (PyCFunction)multidict_getall, + METH_FASTCALL | METH_KEYWORDS, + multidict_getall_doc + }, + { + "getone", + (PyCFunction)multidict_getone, + METH_FASTCALL | METH_KEYWORDS, + multidict_getone_doc + }, + { + "get", + (PyCFunction)multidict_get, + METH_FASTCALL | METH_KEYWORDS, + multidict_get_doc + }, + { + "keys", + (PyCFunction)multidict_keys, + METH_NOARGS, + multidict_keys_doc + }, + { + "items", + (PyCFunction)multidict_items, + METH_NOARGS, + multidict_items_doc + }, + { + "values", + (PyCFunction)multidict_values, + METH_NOARGS, + multidict_values_doc + }, + { + "add", + (PyCFunction)multidict_add, + METH_FASTCALL | METH_KEYWORDS, + multidict_add_doc + }, + { + "copy", + (PyCFunction)multidict_copy, + METH_NOARGS, + multidict_copy_doc + }, + { + "extend", + (PyCFunction)multidict_extend, + METH_VARARGS | METH_KEYWORDS, + multdicit_method_extend_doc + }, + { + "clear", + (PyCFunction)multidict_clear, + METH_NOARGS, + multidict_clear_doc + }, + { + "setdefault", + (PyCFunction)multidict_setdefault, + METH_FASTCALL | METH_KEYWORDS, + multidict_setdefault_doc + }, + { + "popone", + (PyCFunction)multidict_popone, + METH_FASTCALL | METH_KEYWORDS, + multidict_popone_doc + }, + { + "pop", + (PyCFunction)multidict_pop, + METH_FASTCALL | METH_KEYWORDS, + multidict_pop_doc + }, + { + "popall", + (PyCFunction)multidict_popall, + METH_FASTCALL | METH_KEYWORDS, + multidict_popall_doc + }, + { + "popitem", + (PyCFunction)multidict_popitem, + METH_NOARGS, + multidict_popitem_doc + }, + { + "update", + (PyCFunction)multidict_update, + METH_VARARGS | METH_KEYWORDS, + multidict_update_doc + }, + { + "__reduce__", + (PyCFunction)multidict_reduce, + METH_NOARGS, + NULL, + }, + { + "__class_getitem__", + (PyCFunction)multidict_class_getitem, + METH_O | METH_CLASS, + NULL + }, + { + "__sizeof__", + (PyCFunction)_multidict_sizeof, + METH_NOARGS, + sizeof__doc__, + }, + { + NULL, + NULL + } /* sentinel */ +}; + + +PyDoc_STRVAR(MultDict_doc, +"Dictionary with the support for duplicate keys."); + + +static PyTypeObject multidict_type = { + PyVarObject_HEAD_INIT(NULL, 0) + "multidict._multidict.MultiDict", /* tp_name */ + sizeof(MultiDictObject), /* tp_basicsize */ + .tp_dealloc = (destructor)multidict_tp_dealloc, + .tp_repr = (reprfunc)multidict_repr, + .tp_as_sequence = &multidict_sequence, + .tp_as_mapping = &multidict_mapping, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, + .tp_doc = MultDict_doc, + .tp_traverse = (traverseproc)multidict_tp_traverse, + .tp_clear = (inquiry)multidict_tp_clear, + .tp_richcompare = (richcmpfunc)multidict_tp_richcompare, + .tp_weaklistoffset = offsetof(MultiDictObject, weaklist), + .tp_iter = (getiterfunc)multidict_tp_iter, + .tp_methods = multidict_methods, + .tp_init = (initproc)multidict_tp_init, + .tp_alloc = PyType_GenericAlloc, + .tp_new = PyType_GenericNew, + .tp_free = PyObject_GC_Del, +}; + +/******************** CIMultiDict ********************/ + +static inline int +cimultidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) +{ + if (ci_pair_list_init(&self->pairs) < 0) { + return -1; + } + if (_multidict_extend(self, args, kwds, "CIMultiDict", 1) < 0) { + return -1; + } + return 0; +} + +static inline PyObject * +cimultidict_copy(MultiDictObject *self) +{ + return _multidict_copy(self, &cimultidict_type); +} + +PyDoc_STRVAR(cimultidict_copy_doc, +"Return a copy of itself."); + +static PyMethodDef cimultidict_methods[] = { + { + "copy", + (PyCFunction)cimultidict_copy, + METH_NOARGS, + cimultidict_copy_doc + }, + { + NULL, + NULL + } /* sentinel */ +}; + +PyDoc_STRVAR(CIMultDict_doc, +"Dictionary with the support for duplicate case-insensitive keys."); + + +static PyTypeObject cimultidict_type = { + PyVarObject_HEAD_INIT(NULL, 0) + "multidict._multidict.CIMultiDict", /* tp_name */ + sizeof(MultiDictObject), /* tp_basicsize */ + .tp_dealloc = (destructor)multidict_tp_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, + .tp_doc = CIMultDict_doc, + .tp_traverse = (traverseproc)multidict_tp_traverse, + .tp_clear = (inquiry)multidict_tp_clear, + .tp_weaklistoffset = offsetof(MultiDictObject, weaklist), + .tp_methods = cimultidict_methods, + .tp_base = &multidict_type, + .tp_init = (initproc)cimultidict_tp_init, + .tp_alloc = PyType_GenericAlloc, + .tp_new = PyType_GenericNew, + .tp_free = PyObject_GC_Del, +}; + +/******************** MultiDictProxy ********************/ + +static inline int +multidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, + PyObject *kwds) +{ + PyObject *arg = NULL; + MultiDictObject *md = NULL; + + if (!PyArg_UnpackTuple(args, "multidict._multidict.MultiDictProxy", + 0, 1, &arg)) + { + return -1; + } + if (arg == NULL) { + PyErr_Format( + PyExc_TypeError, + "__init__() missing 1 required positional argument: 'arg'" + ); + return -1; + } + if (!MultiDictProxy_CheckExact(arg) && + !CIMultiDict_CheckExact(arg) && + !MultiDict_CheckExact(arg)) + { + PyErr_Format( + PyExc_TypeError, + "ctor requires MultiDict or MultiDictProxy instance, " + "not ", + Py_TYPE(arg)->tp_name + ); + return -1; + } + + md = (MultiDictObject*)arg; + if (MultiDictProxy_CheckExact(arg)) { + md = ((MultiDictProxyObject*)arg)->md; + } + Py_INCREF(md); + self->md = md; + + return 0; +} + +static inline PyObject * +multidict_proxy_getall(MultiDictProxyObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) +{ + return multidict_getall(self->md, args, nargs, kwnames); +} + +static inline PyObject * +multidict_proxy_getone(MultiDictProxyObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) +{ + return multidict_getone(self->md, args, nargs, kwnames); +} + +static inline PyObject * +multidict_proxy_get(MultiDictProxyObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) +{ + return multidict_get(self->md, args, nargs, kwnames); +} + +static inline PyObject * +multidict_proxy_keys(MultiDictProxyObject *self) +{ + return multidict_keys(self->md); +} + +static inline PyObject * +multidict_proxy_items(MultiDictProxyObject *self) +{ + return multidict_items(self->md); +} + +static inline PyObject * +multidict_proxy_values(MultiDictProxyObject *self) +{ + return multidict_values(self->md); +} + +static inline PyObject * +multidict_proxy_copy(MultiDictProxyObject *self) +{ + return _multidict_proxy_copy(self, &multidict_type); +} + +static inline PyObject * +multidict_proxy_reduce(MultiDictProxyObject *self) +{ + PyErr_Format( + PyExc_TypeError, + "can't pickle %s objects", Py_TYPE(self)->tp_name + ); + + return NULL; +} + +static inline Py_ssize_t +multidict_proxy_mp_len(MultiDictProxyObject *self) +{ + return multidict_mp_len(self->md); +} + +static inline PyObject * +multidict_proxy_mp_subscript(MultiDictProxyObject *self, PyObject *key) +{ + return multidict_mp_subscript(self->md, key); +} + +static inline int +multidict_proxy_sq_contains(MultiDictProxyObject *self, PyObject *key) +{ + return multidict_sq_contains(self->md, key); +} + +static inline PyObject * +multidict_proxy_tp_iter(MultiDictProxyObject *self) +{ + return multidict_tp_iter(self->md); +} + +static inline PyObject * +multidict_proxy_tp_richcompare(MultiDictProxyObject *self, PyObject *other, + int op) +{ + return multidict_tp_richcompare((PyObject*)self->md, other, op); +} + +static inline void +multidict_proxy_tp_dealloc(MultiDictProxyObject *self) +{ + PyObject_GC_UnTrack(self); + if (self->weaklist != NULL) { + PyObject_ClearWeakRefs((PyObject *)self); + }; + Py_XDECREF(self->md); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static inline int +multidict_proxy_tp_traverse(MultiDictProxyObject *self, visitproc visit, + void *arg) +{ + Py_VISIT(self->md); + return 0; +} + +static inline int +multidict_proxy_tp_clear(MultiDictProxyObject *self) +{ + Py_CLEAR(self->md); + return 0; +} + +static PySequenceMethods multidict_proxy_sequence = { + .sq_contains = (objobjproc)multidict_proxy_sq_contains, +}; + +static PyMappingMethods multidict_proxy_mapping = { + .mp_length = (lenfunc)multidict_proxy_mp_len, + .mp_subscript = (binaryfunc)multidict_proxy_mp_subscript, +}; + +static PyMethodDef multidict_proxy_methods[] = { + { + "getall", + (PyCFunction)multidict_proxy_getall, + METH_FASTCALL | METH_KEYWORDS, + multidict_getall_doc + }, + { + "getone", + (PyCFunction)multidict_proxy_getone, + METH_FASTCALL | METH_KEYWORDS, + multidict_getone_doc + }, + { + "get", + (PyCFunction)multidict_proxy_get, + METH_FASTCALL | METH_KEYWORDS, + multidict_get_doc + }, + { + "keys", + (PyCFunction)multidict_proxy_keys, + METH_NOARGS, + multidict_keys_doc + }, + { + "items", + (PyCFunction)multidict_proxy_items, + METH_NOARGS, + multidict_items_doc + }, + { + "values", + (PyCFunction)multidict_proxy_values, + METH_NOARGS, + multidict_values_doc + }, + { + "copy", + (PyCFunction)multidict_proxy_copy, + METH_NOARGS, + multidict_copy_doc + }, + { + "__reduce__", + (PyCFunction)multidict_proxy_reduce, + METH_NOARGS, + NULL + }, + { + "__class_getitem__", + (PyCFunction)multidict_class_getitem, + METH_O | METH_CLASS, + NULL + }, + { + NULL, + NULL + } /* sentinel */ +}; + + +PyDoc_STRVAR(MultDictProxy_doc, +"Read-only proxy for MultiDict instance."); + + +static PyTypeObject multidict_proxy_type = { + PyVarObject_HEAD_INIT(NULL, 0) + "multidict._multidict.MultiDictProxy", /* tp_name */ + sizeof(MultiDictProxyObject), /* tp_basicsize */ + .tp_dealloc = (destructor)multidict_proxy_tp_dealloc, + .tp_repr = (reprfunc)multidict_repr, + .tp_as_sequence = &multidict_proxy_sequence, + .tp_as_mapping = &multidict_proxy_mapping, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, + .tp_doc = MultDictProxy_doc, + .tp_traverse = (traverseproc)multidict_proxy_tp_traverse, + .tp_clear = (inquiry)multidict_proxy_tp_clear, + .tp_richcompare = (richcmpfunc)multidict_proxy_tp_richcompare, + .tp_weaklistoffset = offsetof(MultiDictProxyObject, weaklist), + .tp_iter = (getiterfunc)multidict_proxy_tp_iter, + .tp_methods = multidict_proxy_methods, + .tp_init = (initproc)multidict_proxy_tp_init, + .tp_alloc = PyType_GenericAlloc, + .tp_new = PyType_GenericNew, + .tp_free = PyObject_GC_Del, +}; + +/******************** CIMultiDictProxy ********************/ + +static inline int +cimultidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, + PyObject *kwds) +{ + PyObject *arg = NULL; + MultiDictObject *md = NULL; + + if (!PyArg_UnpackTuple(args, "multidict._multidict.CIMultiDictProxy", + 1, 1, &arg)) + { + return -1; + } + if (arg == NULL) { + PyErr_Format( + PyExc_TypeError, + "__init__() missing 1 required positional argument: 'arg'" + ); + return -1; + } + if (!CIMultiDictProxy_CheckExact(arg) && !CIMultiDict_CheckExact(arg)) { + PyErr_Format( + PyExc_TypeError, + "ctor requires CIMultiDict or CIMultiDictProxy instance, " + "not ", + Py_TYPE(arg)->tp_name + ); + return -1; + } + + md = (MultiDictObject*)arg; + if (CIMultiDictProxy_CheckExact(arg)) { + md = ((MultiDictProxyObject*)arg)->md; + } + Py_INCREF(md); + self->md = md; + + return 0; +} + +static inline PyObject * +cimultidict_proxy_copy(MultiDictProxyObject *self) +{ + return _multidict_proxy_copy(self, &cimultidict_type); +} + + +PyDoc_STRVAR(CIMultDictProxy_doc, +"Read-only proxy for CIMultiDict instance."); + +PyDoc_STRVAR(cimultidict_proxy_copy_doc, +"Return copy of itself"); + +static PyMethodDef cimultidict_proxy_methods[] = { + { + "copy", + (PyCFunction)cimultidict_proxy_copy, + METH_NOARGS, + cimultidict_proxy_copy_doc + }, + { + NULL, + NULL + } /* sentinel */ +}; + +static PyTypeObject cimultidict_proxy_type = { + PyVarObject_HEAD_INIT(NULL, 0) + "multidict._multidict.CIMultiDictProxy", /* tp_name */ + sizeof(MultiDictProxyObject), /* tp_basicsize */ + .tp_dealloc = (destructor)multidict_proxy_tp_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, + .tp_doc = CIMultDictProxy_doc, + .tp_traverse = (traverseproc)multidict_proxy_tp_traverse, + .tp_clear = (inquiry)multidict_proxy_tp_clear, + .tp_richcompare = (richcmpfunc)multidict_proxy_tp_richcompare, + .tp_weaklistoffset = offsetof(MultiDictProxyObject, weaklist), + .tp_methods = cimultidict_proxy_methods, + .tp_base = &multidict_proxy_type, + .tp_init = (initproc)cimultidict_proxy_tp_init, + .tp_alloc = PyType_GenericAlloc, + .tp_new = PyType_GenericNew, + .tp_free = PyObject_GC_Del, +}; + +/******************** Other functions ********************/ + +static inline PyObject * +getversion(PyObject *self, PyObject *md) +{ + pair_list_t *pairs = NULL; + if (MultiDict_CheckExact(md) || CIMultiDict_CheckExact(md)) { + pairs = &((MultiDictObject*)md)->pairs; + } else if (MultiDictProxy_CheckExact(md) || CIMultiDictProxy_CheckExact(md)) { + pairs = &((MultiDictProxyObject*)md)->md->pairs; + } else { + PyErr_Format(PyExc_TypeError, "unexpected type"); + return NULL; + } + return PyLong_FromUnsignedLong(pair_list_version(pairs)); +} + +/******************** Module ********************/ + +static inline void +module_free(void *m) +{ + Py_CLEAR(collections_abc_mapping); + Py_CLEAR(collections_abc_mut_mapping); + Py_CLEAR(collections_abc_mut_multi_mapping); +} + +static PyMethodDef multidict_module_methods[] = { + { + "getversion", + (PyCFunction)getversion, + METH_O + }, + { + NULL, + NULL + } /* sentinel */ +}; + +static PyModuleDef multidict_module = { + PyModuleDef_HEAD_INIT, /* m_base */ + "_multidict", /* m_name */ + .m_size = -1, + .m_methods = multidict_module_methods, + .m_free = (freefunc)module_free, +}; + +PyMODINIT_FUNC +PyInit__multidict() +{ + PyObject *module = NULL, + *reg_func_call_result = NULL; + +#define WITH_MOD(NAME) \ + Py_CLEAR(module); \ + module = PyImport_ImportModule(NAME); \ + if (module == NULL) { \ + goto fail; \ + } + +#define GET_MOD_ATTR(VAR, NAME) \ + VAR = PyObject_GetAttrString(module, NAME); \ + if (VAR == NULL) { \ + goto fail; \ + } + + if (multidict_views_init() < 0) { + goto fail; + } + + if (multidict_iter_init() < 0) { + goto fail; + } + + if (istr_init() < 0) { + goto fail; + } + + if (PyType_Ready(&multidict_type) < 0 || + PyType_Ready(&cimultidict_type) < 0 || + PyType_Ready(&multidict_proxy_type) < 0 || + PyType_Ready(&cimultidict_proxy_type) < 0) + { + goto fail; + } + + WITH_MOD("collections.abc"); + GET_MOD_ATTR(collections_abc_mapping, "Mapping"); + + WITH_MOD("multidict._abc"); + GET_MOD_ATTR(collections_abc_mut_mapping, "MultiMapping"); + + WITH_MOD("multidict._abc"); + GET_MOD_ATTR(collections_abc_mut_multi_mapping, "MutableMultiMapping"); + + WITH_MOD("multidict._multidict_base"); + GET_MOD_ATTR(repr_func, "_mdrepr"); + + /* Register in _abc mappings (CI)MultiDict and (CI)MultiDictProxy */ + reg_func_call_result = PyObject_CallMethod( + collections_abc_mut_mapping, + "register", "O", + (PyObject*)&multidict_proxy_type + ); + if (reg_func_call_result == NULL) { + goto fail; + } + Py_DECREF(reg_func_call_result); + + reg_func_call_result = PyObject_CallMethod( + collections_abc_mut_mapping, + "register", "O", + (PyObject*)&cimultidict_proxy_type + ); + if (reg_func_call_result == NULL) { + goto fail; + } + Py_DECREF(reg_func_call_result); + + reg_func_call_result = PyObject_CallMethod( + collections_abc_mut_multi_mapping, + "register", "O", + (PyObject*)&multidict_type + ); + if (reg_func_call_result == NULL) { + goto fail; + } + Py_DECREF(reg_func_call_result); + + reg_func_call_result = PyObject_CallMethod( + collections_abc_mut_multi_mapping, + "register", "O", + (PyObject*)&cimultidict_type + ); + if (reg_func_call_result == NULL) { + goto fail; + } + Py_DECREF(reg_func_call_result); + + /* Instantiate this module */ + module = PyModule_Create(&multidict_module); + + Py_INCREF(&istr_type); + if (PyModule_AddObject( + module, "istr", (PyObject*)&istr_type) < 0) + { + goto fail; + } + + Py_INCREF(&multidict_type); + if (PyModule_AddObject( + module, "MultiDict", (PyObject*)&multidict_type) < 0) + { + goto fail; + } + + Py_INCREF(&cimultidict_type); + if (PyModule_AddObject( + module, "CIMultiDict", (PyObject*)&cimultidict_type) < 0) + { + goto fail; + } + + Py_INCREF(&multidict_proxy_type); + if (PyModule_AddObject( + module, "MultiDictProxy", (PyObject*)&multidict_proxy_type) < 0) + { + goto fail; + } + + Py_INCREF(&cimultidict_proxy_type); + if (PyModule_AddObject( + module, "CIMultiDictProxy", (PyObject*)&cimultidict_proxy_type) < 0) + { + goto fail; + } + + return module; + +fail: + Py_XDECREF(collections_abc_mapping); + Py_XDECREF(collections_abc_mut_mapping); + Py_XDECREF(collections_abc_mut_multi_mapping); + + return NULL; + +#undef WITH_MOD +#undef GET_MOD_ATTR +} diff --git a/multidict/_multidict_base.py b/multidict/_multidict_base.py new file mode 100644 index 0000000..3944665 --- /dev/null +++ b/multidict/_multidict_base.py @@ -0,0 +1,144 @@ +from collections.abc import ItemsView, Iterable, KeysView, Set, ValuesView + + +def _abc_itemsview_register(view_cls): + ItemsView.register(view_cls) + + +def _abc_keysview_register(view_cls): + KeysView.register(view_cls) + + +def _abc_valuesview_register(view_cls): + ValuesView.register(view_cls) + + +def _viewbaseset_richcmp(view, other, op): + if op == 0: # < + if not isinstance(other, Set): + return NotImplemented + return len(view) < len(other) and view <= other + elif op == 1: # <= + if not isinstance(other, Set): + return NotImplemented + if len(view) > len(other): + return False + for elem in view: + if elem not in other: + return False + return True + elif op == 2: # == + if not isinstance(other, Set): + return NotImplemented + return len(view) == len(other) and view <= other + elif op == 3: # != + return not view == other + elif op == 4: # > + if not isinstance(other, Set): + return NotImplemented + return len(view) > len(other) and view >= other + elif op == 5: # >= + if not isinstance(other, Set): + return NotImplemented + if len(view) < len(other): + return False + for elem in other: + if elem not in view: + return False + return True + + +def _viewbaseset_and(view, other): + if not isinstance(other, Iterable): + return NotImplemented + if isinstance(view, Set): + view = set(iter(view)) + if isinstance(other, Set): + other = set(iter(other)) + if not isinstance(other, Set): + other = set(iter(other)) + return view & other + + +def _viewbaseset_or(view, other): + if not isinstance(other, Iterable): + return NotImplemented + if isinstance(view, Set): + view = set(iter(view)) + if isinstance(other, Set): + other = set(iter(other)) + if not isinstance(other, Set): + other = set(iter(other)) + return view | other + + +def _viewbaseset_sub(view, other): + if not isinstance(other, Iterable): + return NotImplemented + if isinstance(view, Set): + view = set(iter(view)) + if isinstance(other, Set): + other = set(iter(other)) + if not isinstance(other, Set): + other = set(iter(other)) + return view - other + + +def _viewbaseset_xor(view, other): + if not isinstance(other, Iterable): + return NotImplemented + if isinstance(view, Set): + view = set(iter(view)) + if isinstance(other, Set): + other = set(iter(other)) + if not isinstance(other, Set): + other = set(iter(other)) + return view ^ other + + +def _itemsview_isdisjoint(view, other): + "Return True if two sets have a null intersection." + for v in other: + if v in view: + return False + return True + + +def _itemsview_repr(view): + lst = [] + for k, v in view: + lst.append("{!r}: {!r}".format(k, v)) + body = ", ".join(lst) + return "{}({})".format(view.__class__.__name__, body) + + +def _keysview_isdisjoint(view, other): + "Return True if two sets have a null intersection." + for k in other: + if k in view: + return False + return True + + +def _keysview_repr(view): + lst = [] + for k in view: + lst.append("{!r}".format(k)) + body = ", ".join(lst) + return "{}({})".format(view.__class__.__name__, body) + + +def _valuesview_repr(view): + lst = [] + for v in view: + lst.append("{!r}".format(v)) + body = ", ".join(lst) + return "{}({})".format(view.__class__.__name__, body) + + +def _mdrepr(md): + lst = [] + for k, v in md.items(): + lst.append("'{}': {!r}".format(k, v)) + body = ", ".join(lst) + return "<{}({})>".format(md.__class__.__name__, body) diff --git a/multidict/_multidict_py.py b/multidict/_multidict_py.py new file mode 100644 index 0000000..cdbc328 --- /dev/null +++ b/multidict/_multidict_py.py @@ -0,0 +1,526 @@ +import sys +import types +from array import array +from collections import abc + +from ._abc import MultiMapping, MutableMultiMapping + +_marker = object() + +if sys.version_info >= (3, 9): + GenericAlias = types.GenericAlias +else: + def GenericAlias(cls): + return cls + + +class istr(str): + + """Case insensitive str.""" + + __is_istr__ = True + + +upstr = istr # for relaxing backward compatibility problems + + +def getversion(md): + if not isinstance(md, _Base): + raise TypeError("Parameter should be multidict or proxy") + return md._impl._version + + +_version = array("Q", [0]) + + +class _Impl: + __slots__ = ("_items", "_version") + + def __init__(self): + self._items = [] + self.incr_version() + + def incr_version(self): + global _version + v = _version + v[0] += 1 + self._version = v[0] + + if sys.implementation.name != "pypy": + + def __sizeof__(self): + return object.__sizeof__(self) + sys.getsizeof(self._items) + + +class _Base: + def _title(self, key): + return key + + def getall(self, key, default=_marker): + """Return a list of all values matching the key.""" + identity = self._title(key) + res = [v for i, k, v in self._impl._items if i == identity] + if res: + return res + if not res and default is not _marker: + return default + raise KeyError("Key not found: %r" % key) + + def getone(self, key, default=_marker): + """Get first value matching the key. + + Raises KeyError if the key is not found and no default is provided. + """ + identity = self._title(key) + for i, k, v in self._impl._items: + if i == identity: + return v + if default is not _marker: + return default + raise KeyError("Key not found: %r" % key) + + # Mapping interface # + + def __getitem__(self, key): + return self.getone(key) + + def get(self, key, default=None): + """Get first value matching the key. + + If the key is not found, returns the default (or None if no default is provided) + """ + return self.getone(key, default) + + def __iter__(self): + return iter(self.keys()) + + def __len__(self): + return len(self._impl._items) + + def keys(self): + """Return a new view of the dictionary's keys.""" + return _KeysView(self._impl) + + def items(self): + """Return a new view of the dictionary's items *(key, value) pairs).""" + return _ItemsView(self._impl) + + def values(self): + """Return a new view of the dictionary's values.""" + return _ValuesView(self._impl) + + def __eq__(self, other): + if not isinstance(other, abc.Mapping): + return NotImplemented + if isinstance(other, _Base): + lft = self._impl._items + rht = other._impl._items + if len(lft) != len(rht): + return False + for (i1, k2, v1), (i2, k2, v2) in zip(lft, rht): + if i1 != i2 or v1 != v2: + return False + return True + if len(self._impl._items) != len(other): + return False + for k, v in self.items(): + nv = other.get(k, _marker) + if v != nv: + return False + return True + + def __contains__(self, key): + identity = self._title(key) + for i, k, v in self._impl._items: + if i == identity: + return True + return False + + def __repr__(self): + body = ", ".join("'{}': {!r}".format(k, v) for k, v in self.items()) + return "<{}({})>".format(self.__class__.__name__, body) + + __class_getitem__ = classmethod(GenericAlias) + + +class MultiDictProxy(_Base, MultiMapping): + """Read-only proxy for MultiDict instance.""" + + def __init__(self, arg): + if not isinstance(arg, (MultiDict, MultiDictProxy)): + raise TypeError( + "ctor requires MultiDict or MultiDictProxy instance" + ", not {}".format(type(arg)) + ) + + self._impl = arg._impl + + def __reduce__(self): + raise TypeError("can't pickle {} objects".format(self.__class__.__name__)) + + def copy(self): + """Return a copy of itself.""" + return MultiDict(self.items()) + + +class CIMultiDictProxy(MultiDictProxy): + """Read-only proxy for CIMultiDict instance.""" + + def __init__(self, arg): + if not isinstance(arg, (CIMultiDict, CIMultiDictProxy)): + raise TypeError( + "ctor requires CIMultiDict or CIMultiDictProxy instance" + ", not {}".format(type(arg)) + ) + + self._impl = arg._impl + + def _title(self, key): + return key.title() + + def copy(self): + """Return a copy of itself.""" + return CIMultiDict(self.items()) + + +class MultiDict(_Base, MutableMultiMapping): + """Dictionary with the support for duplicate keys.""" + + def __init__(self, *args, **kwargs): + self._impl = _Impl() + + self._extend(args, kwargs, self.__class__.__name__, self._extend_items) + + if sys.implementation.name != "pypy": + + def __sizeof__(self): + return object.__sizeof__(self) + sys.getsizeof(self._impl) + + def __reduce__(self): + return (self.__class__, (list(self.items()),)) + + def _title(self, key): + return key + + def _key(self, key): + if isinstance(key, str): + return key + else: + raise TypeError( + "MultiDict keys should be either str " "or subclasses of str" + ) + + def add(self, key, value): + identity = self._title(key) + self._impl._items.append((identity, self._key(key), value)) + self._impl.incr_version() + + def copy(self): + """Return a copy of itself.""" + cls = self.__class__ + return cls(self.items()) + + __copy__ = copy + + def extend(self, *args, **kwargs): + """Extend current MultiDict with more values. + + This method must be used instead of update. + """ + self._extend(args, kwargs, "extend", self._extend_items) + + def _extend(self, args, kwargs, name, method): + if len(args) > 1: + raise TypeError( + "{} takes at most 1 positional argument" + " ({} given)".format(name, len(args)) + ) + if args: + arg = args[0] + if isinstance(args[0], (MultiDict, MultiDictProxy)) and not kwargs: + items = arg._impl._items + else: + if hasattr(arg, "items"): + arg = arg.items() + if kwargs: + arg = list(arg) + arg.extend(list(kwargs.items())) + items = [] + for item in arg: + if not len(item) == 2: + raise TypeError( + "{} takes either dict or list of (key, value) " + "tuples".format(name) + ) + items.append((self._title(item[0]), self._key(item[0]), item[1])) + + method(items) + else: + method( + [ + (self._title(key), self._key(key), value) + for key, value in kwargs.items() + ] + ) + + def _extend_items(self, items): + for identity, key, value in items: + self.add(key, value) + + def clear(self): + """Remove all items from MultiDict.""" + self._impl._items.clear() + self._impl.incr_version() + + # Mapping interface # + + def __setitem__(self, key, value): + self._replace(key, value) + + def __delitem__(self, key): + identity = self._title(key) + items = self._impl._items + found = False + for i in range(len(items) - 1, -1, -1): + if items[i][0] == identity: + del items[i] + found = True + if not found: + raise KeyError(key) + else: + self._impl.incr_version() + + def setdefault(self, key, default=None): + """Return value for key, set value to default if key is not present.""" + identity = self._title(key) + for i, k, v in self._impl._items: + if i == identity: + return v + self.add(key, default) + return default + + def popone(self, key, default=_marker): + """Remove specified key and return the corresponding value. + + If key is not found, d is returned if given, otherwise + KeyError is raised. + + """ + identity = self._title(key) + for i in range(len(self._impl._items)): + if self._impl._items[i][0] == identity: + value = self._impl._items[i][2] + del self._impl._items[i] + self._impl.incr_version() + return value + if default is _marker: + raise KeyError(key) + else: + return default + + pop = popone # type: ignore + + def popall(self, key, default=_marker): + """Remove all occurrences of key and return the list of corresponding + values. + + If key is not found, default is returned if given, otherwise + KeyError is raised. + + """ + found = False + identity = self._title(key) + ret = [] + for i in range(len(self._impl._items) - 1, -1, -1): + item = self._impl._items[i] + if item[0] == identity: + ret.append(item[2]) + del self._impl._items[i] + self._impl.incr_version() + found = True + if not found: + if default is _marker: + raise KeyError(key) + else: + return default + else: + ret.reverse() + return ret + + def popitem(self): + """Remove and return an arbitrary (key, value) pair.""" + if self._impl._items: + i = self._impl._items.pop(0) + self._impl.incr_version() + return i[1], i[2] + else: + raise KeyError("empty multidict") + + def update(self, *args, **kwargs): + """Update the dictionary from *other*, overwriting existing keys.""" + self._extend(args, kwargs, "update", self._update_items) + + def _update_items(self, items): + if not items: + return + used_keys = {} + for identity, key, value in items: + start = used_keys.get(identity, 0) + for i in range(start, len(self._impl._items)): + item = self._impl._items[i] + if item[0] == identity: + used_keys[identity] = i + 1 + self._impl._items[i] = (identity, key, value) + break + else: + self._impl._items.append((identity, key, value)) + used_keys[identity] = len(self._impl._items) + + # drop tails + i = 0 + while i < len(self._impl._items): + item = self._impl._items[i] + identity = item[0] + pos = used_keys.get(identity) + if pos is None: + i += 1 + continue + if i >= pos: + del self._impl._items[i] + else: + i += 1 + + self._impl.incr_version() + + def _replace(self, key, value): + key = self._key(key) + identity = self._title(key) + items = self._impl._items + + for i in range(len(items)): + item = items[i] + if item[0] == identity: + items[i] = (identity, key, value) + # i points to last found item + rgt = i + self._impl.incr_version() + break + else: + self._impl._items.append((identity, key, value)) + self._impl.incr_version() + return + + # remove all tail items + i = rgt + 1 + while i < len(items): + item = items[i] + if item[0] == identity: + del items[i] + else: + i += 1 + + +class CIMultiDict(MultiDict): + """Dictionary with the support for duplicate case-insensitive keys.""" + + def _title(self, key): + return key.title() + + +class _Iter: + __slots__ = ("_size", "_iter") + + def __init__(self, size, iterator): + self._size = size + self._iter = iterator + + def __iter__(self): + return self + + def __next__(self): + return next(self._iter) + + def __length_hint__(self): + return self._size + + +class _ViewBase: + def __init__(self, impl): + self._impl = impl + + def __len__(self): + return len(self._impl._items) + + +class _ItemsView(_ViewBase, abc.ItemsView): + def __contains__(self, item): + assert isinstance(item, tuple) or isinstance(item, list) + assert len(item) == 2 + for i, k, v in self._impl._items: + if item[0] == k and item[1] == v: + return True + return False + + def __iter__(self): + return _Iter(len(self), self._iter(self._impl._version)) + + def _iter(self, version): + for i, k, v in self._impl._items: + if version != self._impl._version: + raise RuntimeError("Dictionary changed during iteration") + yield k, v + + def __repr__(self): + lst = [] + for item in self._impl._items: + lst.append("{!r}: {!r}".format(item[1], item[2])) + body = ", ".join(lst) + return "{}({})".format(self.__class__.__name__, body) + + +class _ValuesView(_ViewBase, abc.ValuesView): + def __contains__(self, value): + for item in self._impl._items: + if item[2] == value: + return True + return False + + def __iter__(self): + return _Iter(len(self), self._iter(self._impl._version)) + + def _iter(self, version): + for item in self._impl._items: + if version != self._impl._version: + raise RuntimeError("Dictionary changed during iteration") + yield item[2] + + def __repr__(self): + lst = [] + for item in self._impl._items: + lst.append("{!r}".format(item[2])) + body = ", ".join(lst) + return "{}({})".format(self.__class__.__name__, body) + + +class _KeysView(_ViewBase, abc.KeysView): + def __contains__(self, key): + for item in self._impl._items: + if item[1] == key: + return True + return False + + def __iter__(self): + return _Iter(len(self), self._iter(self._impl._version)) + + def _iter(self, version): + for item in self._impl._items: + if version != self._impl._version: + raise RuntimeError("Dictionary changed during iteration") + yield item[1] + + def __repr__(self): + lst = [] + for item in self._impl._items: + lst.append("{!r}".format(item[1])) + body = ", ".join(lst) + return "{}({})".format(self.__class__.__name__, body) diff --git a/multidict/_multilib/defs.h b/multidict/_multilib/defs.h new file mode 100644 index 0000000..c7027c8 --- /dev/null +++ b/multidict/_multilib/defs.h @@ -0,0 +1,22 @@ +#ifndef _MULTIDICT_DEFS_H +#define _MULTIDICT_DEFS_H + +#ifdef __cplusplus +extern "C" { +#endif + +_Py_IDENTIFIER(lower); + +/* We link this module statically for convenience. If compiled as a shared + library instead, some compilers don't allow addresses of Python objects + defined in other libraries to be used in static initializers here. The + DEFERRED_ADDRESS macro is used to tag the slots where such addresses + appear; the module init function must fill in the tagged slots at runtime. + The argument is for documentation -- the macro ignores it. +*/ +#define DEFERRED_ADDRESS(ADDR) 0 + +#ifdef __cplusplus +} +#endif +#endif diff --git a/multidict/_multilib/dict.h b/multidict/_multilib/dict.h new file mode 100644 index 0000000..3caf83e --- /dev/null +++ b/multidict/_multilib/dict.h @@ -0,0 +1,24 @@ +#ifndef _MULTIDICT_C_H +#define _MULTIDICT_C_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { // 16 or 24 for GC prefix + PyObject_HEAD // 16 + PyObject *weaklist; + pair_list_t pairs; +} MultiDictObject; + +typedef struct { + PyObject_HEAD + PyObject *weaklist; + MultiDictObject *md; +} MultiDictProxyObject; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/multidict/_multilib/istr.h b/multidict/_multilib/istr.h new file mode 100644 index 0000000..2688f48 --- /dev/null +++ b/multidict/_multilib/istr.h @@ -0,0 +1,85 @@ +#ifndef _MULTIDICT_ISTR_H +#define _MULTIDICT_ISTR_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + PyUnicodeObject str; + PyObject * canonical; +} istrobject; + +PyDoc_STRVAR(istr__doc__, "istr class implementation"); + +static PyTypeObject istr_type; + +static inline void +istr_dealloc(istrobject *self) +{ + Py_XDECREF(self->canonical); + PyUnicode_Type.tp_dealloc((PyObject*)self); +} + +static inline PyObject * +istr_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyObject *x = NULL; + static char *kwlist[] = {"object", "encoding", "errors", 0}; + PyObject *encoding = NULL; + PyObject *errors = NULL; + PyObject *s = NULL; + PyObject * ret = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO:str", + kwlist, &x, &encoding, &errors)) { + return NULL; + } + if (x != NULL && Py_TYPE(x) == &istr_type) { + Py_INCREF(x); + return x; + } + ret = PyUnicode_Type.tp_new(type, args, kwds); + if (!ret) { + goto fail; + } + s =_PyObject_CallMethodId(ret, &PyId_lower, NULL); + if (!s) { + goto fail; + } + ((istrobject*)ret)->canonical = s; + s = NULL; /* the reference is stollen by .canonical */ + return ret; +fail: + Py_XDECREF(ret); + return NULL; +} + +static PyTypeObject istr_type = { + PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) + "multidict._multidict.istr", + sizeof(istrobject), + .tp_dealloc = (destructor)istr_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_BASETYPE + | Py_TPFLAGS_UNICODE_SUBCLASS, + .tp_doc = istr__doc__, + .tp_base = DEFERRED_ADDRESS(&PyUnicode_Type), + .tp_new = (newfunc)istr_new, +}; + + +static inline int +istr_init(void) +{ + istr_type.tp_base = &PyUnicode_Type; + if (PyType_Ready(&istr_type) < 0) { + return -1; + } + return 0; +} + +#ifdef __cplusplus +} +#endif +#endif diff --git a/multidict/_multilib/iter.h b/multidict/_multilib/iter.h new file mode 100644 index 0000000..4e2e32b --- /dev/null +++ b/multidict/_multilib/iter.h @@ -0,0 +1,238 @@ +#ifndef _MULTIDICT_ITER_H +#define _MULTIDICT_ITER_H + +#ifdef __cplusplus +extern "C" { +#endif + +static PyTypeObject multidict_items_iter_type; +static PyTypeObject multidict_values_iter_type; +static PyTypeObject multidict_keys_iter_type; + +typedef struct multidict_iter { + PyObject_HEAD + MultiDictObject *md; // MultiDict or CIMultiDict + Py_ssize_t current; + uint64_t version; +} MultidictIter; + +static inline void +_init_iter(MultidictIter *it, MultiDictObject *md) +{ + Py_INCREF(md); + + it->md = md; + it->current = 0; + it->version = pair_list_version(&md->pairs); +} + +static inline PyObject * +multidict_items_iter_new(MultiDictObject *md) +{ + MultidictIter *it = PyObject_GC_New( + MultidictIter, &multidict_items_iter_type); + if (it == NULL) { + return NULL; + } + + _init_iter(it, md); + + PyObject_GC_Track(it); + return (PyObject *)it; +} + +static inline PyObject * +multidict_keys_iter_new(MultiDictObject *md) +{ + MultidictIter *it = PyObject_GC_New( + MultidictIter, &multidict_keys_iter_type); + if (it == NULL) { + return NULL; + } + + _init_iter(it, md); + + PyObject_GC_Track(it); + return (PyObject *)it; +} + +static inline PyObject * +multidict_values_iter_new(MultiDictObject *md) +{ + MultidictIter *it = PyObject_GC_New( + MultidictIter, &multidict_values_iter_type); + if (it == NULL) { + return NULL; + } + + _init_iter(it, md); + + PyObject_GC_Track(it); + return (PyObject *)it; +} + +static inline PyObject * +multidict_items_iter_iternext(MultidictIter *self) +{ + PyObject *key = NULL; + PyObject *value = NULL; + PyObject *ret = NULL; + + if (self->version != pair_list_version(&self->md->pairs)) { + PyErr_SetString(PyExc_RuntimeError, "Dictionary changed during iteration"); + return NULL; + } + + if (!_pair_list_next(&self->md->pairs, &self->current, NULL, &key, &value, NULL)) { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + + ret = PyTuple_Pack(2, key, value); + if (ret == NULL) { + return NULL; + } + + return ret; +} + +static inline PyObject * +multidict_values_iter_iternext(MultidictIter *self) +{ + PyObject *value = NULL; + + if (self->version != pair_list_version(&self->md->pairs)) { + PyErr_SetString(PyExc_RuntimeError, "Dictionary changed during iteration"); + return NULL; + } + + if (!pair_list_next(&self->md->pairs, &self->current, NULL, NULL, &value)) { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + + Py_INCREF(value); + + return value; +} + +static inline PyObject * +multidict_keys_iter_iternext(MultidictIter *self) +{ + PyObject *key = NULL; + + if (self->version != pair_list_version(&self->md->pairs)) { + PyErr_SetString(PyExc_RuntimeError, "Dictionary changed during iteration"); + return NULL; + } + + if (!pair_list_next(&self->md->pairs, &self->current, NULL, &key, NULL)) { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + + Py_INCREF(key); + + return key; +} + +static inline void +multidict_iter_dealloc(MultidictIter *self) +{ + PyObject_GC_UnTrack(self); + Py_XDECREF(self->md); + PyObject_GC_Del(self); +} + +static inline int +multidict_iter_traverse(MultidictIter *self, visitproc visit, void *arg) +{ + Py_VISIT(self->md); + return 0; +} + +static inline int +multidict_iter_clear(MultidictIter *self) +{ + Py_CLEAR(self->md); + return 0; +} + +static inline PyObject * +multidict_iter_len(MultidictIter *self) +{ + return PyLong_FromLong(pair_list_len(&self->md->pairs)); +} + +PyDoc_STRVAR(length_hint_doc, + "Private method returning an estimate of len(list(it))."); + +static PyMethodDef multidict_iter_methods[] = { + { + "__length_hint__", + (PyCFunction)(void(*)(void))multidict_iter_len, + METH_NOARGS, + length_hint_doc + }, + { + NULL, + NULL + } /* sentinel */ +}; + +/***********************************************************************/ + +static PyTypeObject multidict_items_iter_type = { + PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) + "multidict._multidict._itemsiter", /* tp_name */ + sizeof(MultidictIter), /* tp_basicsize */ + .tp_dealloc = (destructor)multidict_iter_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = (traverseproc)multidict_iter_traverse, + .tp_clear = (inquiry)multidict_iter_clear, + .tp_iter = PyObject_SelfIter, + .tp_iternext = (iternextfunc)multidict_items_iter_iternext, + .tp_methods = multidict_iter_methods, +}; + +static PyTypeObject multidict_values_iter_type = { + PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) + "multidict._multidict._valuesiter", /* tp_name */ + sizeof(MultidictIter), /* tp_basicsize */ + .tp_dealloc = (destructor)multidict_iter_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = (traverseproc)multidict_iter_traverse, + .tp_clear = (inquiry)multidict_iter_clear, + .tp_iter = PyObject_SelfIter, + .tp_iternext = (iternextfunc)multidict_values_iter_iternext, + .tp_methods = multidict_iter_methods, +}; + +static PyTypeObject multidict_keys_iter_type = { + PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) + "multidict._multidict._keysiter", /* tp_name */ + sizeof(MultidictIter), /* tp_basicsize */ + .tp_dealloc = (destructor)multidict_iter_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = (traverseproc)multidict_iter_traverse, + .tp_clear = (inquiry)multidict_iter_clear, + .tp_iter = PyObject_SelfIter, + .tp_iternext = (iternextfunc)multidict_keys_iter_iternext, + .tp_methods = multidict_iter_methods, +}; + +static inline int +multidict_iter_init() +{ + if (PyType_Ready(&multidict_items_iter_type) < 0 || + PyType_Ready(&multidict_values_iter_type) < 0 || + PyType_Ready(&multidict_keys_iter_type) < 0) { + return -1; + } + return 0; +} + +#ifdef __cplusplus +} +#endif +#endif diff --git a/multidict/_multilib/pair_list.h b/multidict/_multilib/pair_list.h new file mode 100644 index 0000000..7eafd21 --- /dev/null +++ b/multidict/_multilib/pair_list.h @@ -0,0 +1,1244 @@ +#ifndef _MULTIDICT_PAIR_LIST_H +#define _MULTIDICT_PAIR_LIST_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +typedef PyObject * (*calc_identity_func)(PyObject *key); + +typedef struct pair { + PyObject *identity; // 8 + PyObject *key; // 8 + PyObject *value; // 8 + Py_hash_t hash; // 8 +} pair_t; + +/* Note about the structure size +With 29 pairs the MultiDict object size is slightly less than 1KiB +(1000-1008 bytes depending on Python version, +plus extra 12 bytes for memory allocator internal structures). +As the result the max reserved size is 1020 bytes at most. + +To fit into 512 bytes, the structure can contain only 13 pairs +which is too small, e.g. https://www.python.org returns 16 headers +(9 of them are caching proxy information though). + +The embedded buffer intention is to fit the vast majority of possible +HTTP headers into the buffer without allocating an extra memory block. +*/ + +#if (PY_VERSION_HEX < 0x03080000) +#define EMBEDDED_CAPACITY 28 +#else +#define EMBEDDED_CAPACITY 29 +#endif + +typedef struct pair_list { // 40 + Py_ssize_t capacity; // 8 + Py_ssize_t size; // 8 + uint64_t version; // 8 + calc_identity_func calc_identity; // 8 + pair_t *pairs; // 8 + pair_t buffer[EMBEDDED_CAPACITY]; +} pair_list_t; + +#define MIN_CAPACITY 63 +#define CAPACITY_STEP 64 + +/* Global counter used to set ma_version_tag field of dictionary. + * It is incremented each time that a dictionary is created and each + * time that a dictionary is modified. */ +static uint64_t pair_list_global_version = 0; + +#define NEXT_VERSION() (++pair_list_global_version) + + +static inline int +str_cmp(PyObject *s1, PyObject *s2) +{ + PyObject *ret = PyUnicode_RichCompare(s1, s2, Py_EQ); + if (ret == Py_True) { + Py_DECREF(ret); + return 1; + } + else if (ret == NULL) { + return -1; + } + else { + Py_DECREF(ret); + return 0; + } +} + + +static inline PyObject * +key_to_str(PyObject *key) +{ + PyObject *ret; + PyTypeObject *type = Py_TYPE(key); + if (type == &istr_type) { + ret = ((istrobject*)key)->canonical; + Py_INCREF(ret); + return ret; + } + if (PyUnicode_CheckExact(key)) { + Py_INCREF(key); + return key; + } + if (PyUnicode_Check(key)) { + return PyObject_Str(key); + } + PyErr_SetString(PyExc_TypeError, + "MultiDict keys should be either str " + "or subclasses of str"); + return NULL; +} + + +static inline PyObject * +ci_key_to_str(PyObject *key) +{ + PyObject *ret; + PyTypeObject *type = Py_TYPE(key); + if (type == &istr_type) { + ret = ((istrobject*)key)->canonical; + Py_INCREF(ret); + return ret; + } + if (PyUnicode_Check(key)) { + return _PyObject_CallMethodId(key, &PyId_lower, NULL); + } + PyErr_SetString(PyExc_TypeError, + "CIMultiDict keys should be either str " + "or subclasses of str"); + return NULL; +} + +static inline pair_t * +pair_list_get(pair_list_t *list, Py_ssize_t i) +{ + pair_t *item = list->pairs + i; + return item; +} + + +static inline int +pair_list_grow(pair_list_t *list) +{ + // Grow by one element if needed + Py_ssize_t new_capacity; + pair_t *new_pairs; + + if (list->size < list->capacity) { + return 0; + } + + if (list->pairs == list->buffer) { + new_pairs = PyMem_New(pair_t, MIN_CAPACITY); + memcpy(new_pairs, list->buffer, (size_t)list->capacity * sizeof(pair_t)); + + list->pairs = new_pairs; + list->capacity = MIN_CAPACITY; + return 0; + } else { + new_capacity = list->capacity + CAPACITY_STEP; + new_pairs = PyMem_Resize(list->pairs, pair_t, (size_t)new_capacity); + + if (NULL == new_pairs) { + // Resizing error + return -1; + } + + list->pairs = new_pairs; + list->capacity = new_capacity; + return 0; + } +} + + +static inline int +pair_list_shrink(pair_list_t *list) +{ + // Shrink by one element if needed. + // Optimization is applied to prevent jitter + // (grow-shrink-grow-shrink on adding-removing the single element + // when the buffer is full). + // To prevent this, the buffer is resized if the size is less than the capacity + // by 2*CAPACITY_STEP factor. + // The switch back to embedded buffer is never performed for both reasons: + // the code simplicity and the jitter prevention. + + pair_t *new_pairs; + Py_ssize_t new_capacity; + + if (list->capacity - list->size < 2 * CAPACITY_STEP) { + return 0; + } + new_capacity = list->capacity - CAPACITY_STEP; + if (new_capacity < MIN_CAPACITY) { + return 0; + } + + new_pairs = PyMem_Resize(list->pairs, pair_t, (size_t)new_capacity); + + if (NULL == new_pairs) { + // Resizing error + return -1; + } + + list->pairs = new_pairs; + list->capacity = new_capacity; + + return 0; +} + + +static inline int +_pair_list_init(pair_list_t *list, calc_identity_func calc_identity) +{ + list->pairs = list->buffer; + list->capacity = EMBEDDED_CAPACITY; + list->size = 0; + list->version = NEXT_VERSION(); + list->calc_identity = calc_identity; + return 0; +} + +static inline int +pair_list_init(pair_list_t *list) +{ + return _pair_list_init(list, key_to_str); +} + + +static inline int +ci_pair_list_init(pair_list_t *list) +{ + return _pair_list_init(list, ci_key_to_str); +} + + +static inline void +pair_list_dealloc(pair_list_t *list) +{ + pair_t *pair; + Py_ssize_t pos; + + for (pos = 0; pos < list->size; pos++) { + pair = pair_list_get(list, pos); + + Py_XDECREF(pair->identity); + Py_XDECREF(pair->key); + Py_XDECREF(pair->value); + } + + /* + Strictly speaking, resetting size and capacity and + assigning pairs to buffer is not necessary. + Do it to consistency and idemotency. + The cleanup doesn't hurt performance. + !!! + !!! The buffer deletion is crucial though. + !!! + */ + list->size = 0; + if (list->pairs != list->buffer) { + PyMem_Del(list->pairs); + list->pairs = list->buffer; + list->capacity = EMBEDDED_CAPACITY; + } +} + + +static inline Py_ssize_t +pair_list_len(pair_list_t *list) +{ + return list->size; +} + + +static inline int +_pair_list_add_with_hash(pair_list_t *list, + PyObject *identity, + PyObject *key, + PyObject *value, + Py_hash_t hash) +{ + pair_t *pair; + + if (pair_list_grow(list) < 0) { + return -1; + } + + pair = pair_list_get(list, list->size); + + Py_INCREF(identity); + pair->identity = identity; + + Py_INCREF(key); + pair->key = key; + + Py_INCREF(value); + pair->value = value; + + pair->hash = hash; + + list->version = NEXT_VERSION(); + list->size += 1; + + return 0; +} + + +static inline int +pair_list_add(pair_list_t *list, + PyObject *key, + PyObject *value) +{ + Py_hash_t hash; + PyObject *identity = NULL; + int ret; + + identity = list->calc_identity(key); + if (identity == NULL) { + goto fail; + } + hash = PyObject_Hash(identity); + if (hash == -1) { + goto fail; + } + ret = _pair_list_add_with_hash(list, identity, key, value, hash); + Py_DECREF(identity); + return ret; +fail: + Py_XDECREF(identity); + return -1; +} + + +static inline int +pair_list_del_at(pair_list_t *list, Py_ssize_t pos) +{ + // return 1 on success, -1 on failure + Py_ssize_t tail; + pair_t *pair; + + pair = pair_list_get(list, pos); + Py_DECREF(pair->identity); + Py_DECREF(pair->key); + Py_DECREF(pair->value); + + list->size -= 1; + list->version = NEXT_VERSION(); + + if (list->size == pos) { + // remove from tail, no need to shift body + return 0; + } + + tail = list->size - pos; + // TODO: raise an error if tail < 0 + memmove((void *)pair_list_get(list, pos), + (void *)pair_list_get(list, pos + 1), + sizeof(pair_t) * (size_t)tail); + + return pair_list_shrink(list); +} + + +static inline int +_pair_list_drop_tail(pair_list_t *list, PyObject *identity, Py_hash_t hash, + Py_ssize_t pos) +{ + // return 1 if deleted, 0 if not found + pair_t *pair; + int ret; + int found = 0; + + if (pos >= list->size) { + return 0; + } + + for (; pos < list->size; pos++) { + pair = pair_list_get(list, pos); + if (pair->hash != hash) { + continue; + } + ret = str_cmp(pair->identity, identity); + if (ret > 0) { + if (pair_list_del_at(list, pos) < 0) { + return -1; + } + found = 1; + pos--; + } + else if (ret == -1) { + return -1; + } + } + + return found; +} + +static inline int +_pair_list_del_hash(pair_list_t *list, PyObject *identity, + PyObject *key, Py_hash_t hash) +{ + int ret = _pair_list_drop_tail(list, identity, hash, 0); + + if (ret < 0) { + return -1; + } + else if (ret == 0) { + PyErr_SetObject(PyExc_KeyError, key); + return -1; + } + else { + list->version = NEXT_VERSION(); + return 0; + } +} + + +static inline int +pair_list_del(pair_list_t *list, PyObject *key) +{ + PyObject *identity = NULL; + Py_hash_t hash; + int ret; + + identity = list->calc_identity(key); + if (identity == NULL) { + goto fail; + } + + hash = PyObject_Hash(identity); + if (hash == -1) { + goto fail; + } + + ret = _pair_list_del_hash(list, identity, key, hash); + Py_DECREF(identity); + return ret; +fail: + Py_XDECREF(identity); + return -1; +} + + +static inline uint64_t +pair_list_version(pair_list_t *list) +{ + return list->version; +} + + +static inline int +_pair_list_next(pair_list_t *list, Py_ssize_t *ppos, PyObject **pidentity, + PyObject **pkey, PyObject **pvalue, Py_hash_t *phash) +{ + pair_t *pair; + + if (*ppos >= list->size) { + return 0; + } + + pair = pair_list_get(list, *ppos); + + if (pidentity) { + *pidentity = pair->identity; + } + if (pkey) { + *pkey = pair->key; + } + if (pvalue) { + *pvalue = pair->value; + } + if (phash) { + *phash = pair->hash; + } + + *ppos += 1; + return 1; +} + + +static inline int +pair_list_next(pair_list_t *list, Py_ssize_t *ppos, PyObject **pidentity, + PyObject **pkey, PyObject **pvalue) +{ + Py_hash_t hash; + return _pair_list_next(list, ppos, pidentity, pkey, pvalue, &hash); +} + + +static inline int +pair_list_contains(pair_list_t *list, PyObject *key) +{ + Py_hash_t hash1, hash2; + Py_ssize_t pos = 0; + PyObject *ident = NULL; + PyObject *identity = NULL; + int tmp; + + ident = list->calc_identity(key); + if (ident == NULL) { + goto fail; + } + + hash1 = PyObject_Hash(ident); + if (hash1 == -1) { + goto fail; + } + + while (_pair_list_next(list, &pos, &identity, NULL, NULL, &hash2)) { + if (hash1 != hash2) { + continue; + } + tmp = str_cmp(ident, identity); + if (tmp > 0) { + Py_DECREF(ident); + return 1; + } + else if (tmp < 0) { + goto fail; + } + } + + Py_DECREF(ident); + return 0; +fail: + Py_XDECREF(ident); + return -1; +} + + +static inline PyObject * +pair_list_get_one(pair_list_t *list, PyObject *key) +{ + Py_hash_t hash1, hash2; + Py_ssize_t pos = 0; + PyObject *ident = NULL; + PyObject *identity = NULL; + PyObject *value = NULL; + int tmp; + + ident = list->calc_identity(key); + if (ident == NULL) { + goto fail; + } + + hash1 = PyObject_Hash(ident); + if (hash1 == -1) { + goto fail; + } + + while (_pair_list_next(list, &pos, &identity, NULL, &value, &hash2)) { + if (hash1 != hash2) { + continue; + } + tmp = str_cmp(ident, identity); + if (tmp > 0) { + Py_INCREF(value); + Py_DECREF(ident); + return value; + } + else if (tmp < 0) { + goto fail; + } + } + + Py_DECREF(ident); + PyErr_SetObject(PyExc_KeyError, key); + return NULL; +fail: + Py_XDECREF(ident); + return NULL; +} + + +static inline PyObject * +pair_list_get_all(pair_list_t *list, PyObject *key) +{ + Py_hash_t hash1, hash2; + Py_ssize_t pos = 0; + PyObject *ident = NULL; + PyObject *identity = NULL; + PyObject *value = NULL; + PyObject *res = NULL; + int tmp; + + ident = list->calc_identity(key); + if (ident == NULL) { + goto fail; + } + + hash1 = PyObject_Hash(ident); + if (hash1 == -1) { + goto fail; + } + + while (_pair_list_next(list, &pos, &identity, NULL, &value, &hash2)) { + if (hash1 != hash2) { + continue; + } + tmp = str_cmp(ident, identity); + if (tmp > 0) { + if (res == NULL) { + res = PyList_New(1); + if (res == NULL) { + goto fail; + } + if (PyList_SetItem(res, 0, value) < 0) { + goto fail; + } + Py_INCREF(value); + } + else if (PyList_Append(res, value) < 0) { + goto fail; + } + } + else if (tmp < 0) { + goto fail; + } + } + + if (res == NULL) { + PyErr_SetObject(PyExc_KeyError, key); + } + Py_DECREF(ident); + return res; + +fail: + Py_XDECREF(ident); + Py_XDECREF(res); + return NULL; +} + + +static inline PyObject * +pair_list_set_default(pair_list_t *list, PyObject *key, PyObject *value) +{ + Py_hash_t hash1, hash2; + Py_ssize_t pos = 0; + PyObject *ident = NULL; + PyObject *identity = NULL; + PyObject *value2 = NULL; + int tmp; + + ident = list->calc_identity(key); + if (ident == NULL) { + goto fail; + } + + hash1 = PyObject_Hash(ident); + if (hash1 == -1) { + goto fail; + } + + while (_pair_list_next(list, &pos, &identity, NULL, &value2, &hash2)) { + if (hash1 != hash2) { + continue; + } + tmp = str_cmp(ident, identity); + if (tmp > 0) { + Py_INCREF(value2); + Py_DECREF(ident); + return value2; + } + else if (tmp < 0) { + goto fail; + } + } + + if (_pair_list_add_with_hash(list, ident, key, value, hash1) < 0) { + goto fail; + } + + Py_INCREF(value); + Py_DECREF(ident); + return value; +fail: + Py_XDECREF(ident); + return NULL; +} + + +static inline PyObject * +pair_list_pop_one(pair_list_t *list, PyObject *key) +{ + pair_t *pair; + + Py_hash_t hash; + Py_ssize_t pos; + PyObject *value = NULL; + int tmp; + PyObject *ident = NULL; + + ident = list->calc_identity(key); + if (ident == NULL) { + goto fail; + } + + hash = PyObject_Hash(ident); + if (hash == -1) { + goto fail; + } + + for (pos=0; pos < list->size; pos++) { + pair = pair_list_get(list, pos); + if (pair->hash != hash) { + continue; + } + tmp = str_cmp(ident, pair->identity); + if (tmp > 0) { + value = pair->value; + Py_INCREF(value); + if (pair_list_del_at(list, pos) < 0) { + goto fail; + } + Py_DECREF(ident); + return value; + } + else if (tmp < 0) { + goto fail; + } + } + + PyErr_SetObject(PyExc_KeyError, key); + goto fail; + +fail: + Py_XDECREF(value); + Py_XDECREF(ident); + return NULL; +} + + +static inline PyObject * +pair_list_pop_all(pair_list_t *list, PyObject *key) +{ + Py_hash_t hash; + Py_ssize_t pos; + pair_t *pair; + int tmp; + PyObject *res = NULL; + PyObject *ident = NULL; + + ident = list->calc_identity(key); + if (ident == NULL) { + goto fail; + } + + hash = PyObject_Hash(ident); + if (hash == -1) { + goto fail; + } + + if (list->size == 0) { + PyErr_SetObject(PyExc_KeyError, ident); + goto fail; + } + + for (pos = list->size - 1; pos >= 0; pos--) { + pair = pair_list_get(list, pos); + if (hash != pair->hash) { + continue; + } + tmp = str_cmp(ident, pair->identity); + if (tmp > 0) { + if (res == NULL) { + res = PyList_New(1); + if (res == NULL) { + goto fail; + } + if (PyList_SetItem(res, 0, pair->value) < 0) { + goto fail; + } + Py_INCREF(pair->value); + } else if (PyList_Append(res, pair->value) < 0) { + goto fail; + } + if (pair_list_del_at(list, pos) < 0) { + goto fail; + } + } + else if (tmp < 0) { + goto fail; + } + } + + if (res == NULL) { + PyErr_SetObject(PyExc_KeyError, key); + } else if (PyList_Reverse(res) < 0) { + goto fail; + } + Py_DECREF(ident); + return res; + +fail: + Py_XDECREF(ident); + Py_XDECREF(res); + return NULL; +} + + +static inline PyObject * +pair_list_pop_item(pair_list_t *list) +{ + PyObject *ret; + pair_t *pair; + + if (list->size == 0) { + PyErr_SetString(PyExc_KeyError, "empty multidict"); + return NULL; + } + + pair = pair_list_get(list, 0); + ret = PyTuple_Pack(2, pair->key, pair->value); + if (ret == NULL) { + return NULL; + } + + if (pair_list_del_at(list, 0) < 0) { + Py_DECREF(ret); + return NULL; + } + + return ret; +} + + +static inline int +pair_list_replace(pair_list_t *list, PyObject * key, PyObject *value) +{ + pair_t *pair; + + Py_ssize_t pos; + int tmp; + int found = 0; + + PyObject *identity = NULL; + Py_hash_t hash; + + identity = list->calc_identity(key); + if (identity == NULL) { + goto fail; + } + + hash = PyObject_Hash(identity); + if (hash == -1) { + goto fail; + } + + + for (pos = 0; pos < list->size; pos++) { + pair = pair_list_get(list, pos); + if (hash != pair->hash) { + continue; + } + tmp = str_cmp(identity, pair->identity); + if (tmp > 0) { + found = 1; + Py_INCREF(key); + Py_DECREF(pair->key); + pair->key = key; + Py_INCREF(value); + Py_DECREF(pair->value); + pair->value = value; + break; + } + else if (tmp < 0) { + goto fail; + } + } + + if (!found) { + if (_pair_list_add_with_hash(list, identity, key, value, hash) < 0) { + goto fail; + } + Py_DECREF(identity); + return 0; + } + else { + list->version = NEXT_VERSION(); + if (_pair_list_drop_tail(list, identity, hash, pos+1) < 0) { + goto fail; + } + Py_DECREF(identity); + return 0; + } +fail: + Py_XDECREF(identity); + return -1; +} + + +static inline int +_dict_set_number(PyObject *dict, PyObject *key, Py_ssize_t num) +{ + PyObject *tmp = PyLong_FromSsize_t(num); + if (tmp == NULL) { + return -1; + } + + if (PyDict_SetItem(dict, key, tmp) < 0) { + Py_DECREF(tmp); + return -1; + } + + return 0; +} + + +static inline int +_pair_list_post_update(pair_list_t *list, PyObject* used_keys, Py_ssize_t pos) +{ + pair_t *pair; + PyObject *tmp; + Py_ssize_t num; + + for (; pos < list->size; pos++) { + pair = pair_list_get(list, pos); + tmp = PyDict_GetItem(used_keys, pair->identity); + if (tmp == NULL) { + // not found + continue; + } + + num = PyLong_AsSsize_t(tmp); + if (num == -1) { + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_RuntimeError, "invalid internal state"); + } + return -1; + } + + if (pos >= num) { + // del self[pos] + if (pair_list_del_at(list, pos) < 0) { + return -1; + } + pos--; + } + } + + list->version = NEXT_VERSION(); + return 0; +} + +// TODO: need refactoring function name +static inline int +_pair_list_update(pair_list_t *list, PyObject *key, + PyObject *value, PyObject *used_keys, + PyObject *identity, Py_hash_t hash) +{ + PyObject *item = NULL; + pair_t *pair = NULL; + Py_ssize_t pos; + int found; + int ident_cmp_res; + + item = PyDict_GetItem(used_keys, identity); + if (item == NULL) { + pos = 0; + } + else { + pos = PyLong_AsSsize_t(item); + if (pos == -1) { + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_RuntimeError, "invalid internal state"); + } + return -1; + } + } + + found = 0; + for (; pos < list->size; pos++) { + pair = pair_list_get(list, pos); + if (pair->hash != hash) { + continue; + } + + ident_cmp_res = str_cmp(pair->identity, identity); + if (ident_cmp_res > 0) { + Py_INCREF(key); + Py_DECREF(pair->key); + pair->key = key; + + Py_INCREF(value); + Py_DECREF(pair->value); + pair->value = value; + + if (_dict_set_number(used_keys, pair->identity, pos + 1) < 0) { + return -1; + } + + found = 1; + break; + } + else if (ident_cmp_res < 0) { + return -1; + } + } + + if (!found) { + if (_pair_list_add_with_hash(list, identity, key, value, hash) < 0) { + return -1; + } + if (_dict_set_number(used_keys, identity, list->size) < 0) { + return -1; + } + } + + return 0; +} + + +static inline int +pair_list_update(pair_list_t *list, pair_list_t *other) +{ + PyObject *used_keys = NULL; + pair_t *pair = NULL; + + Py_ssize_t pos; + + if (other->size == 0) { + return 0; + } + + used_keys = PyDict_New(); + if (used_keys == NULL) { + return -1; + } + + for (pos = 0; pos < other->size; pos++) { + pair = pair_list_get(other, pos); + if (_pair_list_update(list, pair->key, pair->value, used_keys, + pair->identity, pair->hash) < 0) { + goto fail; + } + } + + if (_pair_list_post_update(list, used_keys, 0) < 0) { + goto fail; + } + + Py_DECREF(used_keys); + return 0; + +fail: + Py_XDECREF(used_keys); + return -1; +} + + +static inline int +pair_list_update_from_seq(pair_list_t *list, PyObject *seq) +{ + PyObject *it = NULL; // iter(seq) + PyObject *fast = NULL; // item as a 2-tuple or 2-list + PyObject *item = NULL; // seq[i] + PyObject *used_keys = NULL; // dict() + + PyObject *key = NULL; + PyObject *value = NULL; + PyObject *identity = NULL; + + Py_hash_t hash; + + Py_ssize_t i; + Py_ssize_t n; + + it = PyObject_GetIter(seq); + if (it == NULL) { + return -1; + } + + used_keys = PyDict_New(); + if (used_keys == NULL) { + goto fail_1; + } + + for (i = 0; ; ++i) { // i - index into seq of current element + fast = NULL; + item = PyIter_Next(it); + if (item == NULL) { + if (PyErr_Occurred()) { + goto fail_1; + } + break; + } + + // Convert item to sequence, and verify length 2. + fast = PySequence_Fast(item, ""); + if (fast == NULL) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Format(PyExc_TypeError, + "multidict cannot convert sequence element #%zd" + " to a sequence", + i); + } + goto fail_1; + } + + n = PySequence_Fast_GET_SIZE(fast); + if (n != 2) { + PyErr_Format(PyExc_ValueError, + "multidict update sequence element #%zd " + "has length %zd; 2 is required", + i, n); + goto fail_1; + } + + key = PySequence_Fast_GET_ITEM(fast, 0); + value = PySequence_Fast_GET_ITEM(fast, 1); + Py_INCREF(key); + Py_INCREF(value); + + identity = list->calc_identity(key); + if (identity == NULL) { + goto fail_1; + } + + hash = PyObject_Hash(identity); + if (hash == -1) { + goto fail_1; + } + + if (_pair_list_update(list, key, value, used_keys, identity, hash) < 0) { + goto fail_1; + } + + Py_DECREF(key); + Py_DECREF(value); + Py_DECREF(fast); + Py_DECREF(item); + Py_DECREF(identity); + } + + if (_pair_list_post_update(list, used_keys, 0) < 0) { + goto fail_2; + } + + Py_DECREF(it); + Py_DECREF(used_keys); + return 0; + +fail_1: + Py_XDECREF(key); + Py_XDECREF(value); + Py_XDECREF(fast); + Py_XDECREF(item); + Py_XDECREF(identity); + +fail_2: + Py_XDECREF(it); + Py_XDECREF(used_keys); + return -1; +} + +static inline int +pair_list_eq_to_mapping(pair_list_t *list, PyObject *other) +{ + PyObject *key = NULL; + PyObject *avalue = NULL; + PyObject *bvalue; + + Py_ssize_t pos, other_len; + + int eq; + + if (!PyMapping_Check(other)) { + PyErr_Format(PyExc_TypeError, + "other argument must be a mapping, not %s", + Py_TYPE(other)->tp_name); + return -1; + } + + other_len = PyMapping_Size(other); + if (other_len < 0) { + return -1; + } + if (pair_list_len(list) != other_len) { + return 0; + } + + pos = 0; + while (pair_list_next(list, &pos, NULL, &key, &avalue)) { + bvalue = PyObject_GetItem(other, key); + if (bvalue == NULL) { + if (PyErr_ExceptionMatches(PyExc_KeyError)) { + PyErr_Clear(); + return 0; + } + return -1; + } + + eq = PyObject_RichCompareBool(avalue, bvalue, Py_EQ); + Py_DECREF(bvalue); + + if (eq <= 0) { + return eq; + } + } + + return 1; +} + + +/***********************************************************************/ + +static inline int +pair_list_traverse(pair_list_t *list, visitproc visit, void *arg) +{ + pair_t *pair = NULL; + Py_ssize_t pos; + + for (pos = 0; pos < list->size; pos++) { + pair = pair_list_get(list, pos); + // Don't need traverse the identity: it is a terminal + Py_VISIT(pair->key); + Py_VISIT(pair->value); + } + + return 0; +} + + +static inline int +pair_list_clear(pair_list_t *list) +{ + pair_t *pair = NULL; + Py_ssize_t pos; + + if (list->size == 0) { + return 0; + } + + list->version = NEXT_VERSION(); + for (pos = 0; pos < list->size; pos++) { + pair = pair_list_get(list, pos); + Py_CLEAR(pair->key); + Py_CLEAR(pair->identity); + Py_CLEAR(pair->value); + } + list->size = 0; + if (list->pairs != list->buffer) { + PyMem_Del(list->pairs); + list->pairs = list->buffer; + } + + return 0; +} + + +#ifdef __cplusplus +} +#endif +#endif diff --git a/multidict/_multilib/views.h b/multidict/_multilib/views.h new file mode 100644 index 0000000..5b1ebfe --- /dev/null +++ b/multidict/_multilib/views.h @@ -0,0 +1,464 @@ +#ifndef _MULTIDICT_VIEWS_H +#define _MULTIDICT_VIEWS_H + +#ifdef __cplusplus +extern "C" { +#endif + +static PyTypeObject multidict_itemsview_type; +static PyTypeObject multidict_valuesview_type; +static PyTypeObject multidict_keysview_type; + +static PyObject *viewbaseset_richcmp_func; +static PyObject *viewbaseset_and_func; +static PyObject *viewbaseset_or_func; +static PyObject *viewbaseset_sub_func; +static PyObject *viewbaseset_xor_func; + +static PyObject *abc_itemsview_register_func; +static PyObject *abc_keysview_register_func; +static PyObject *abc_valuesview_register_func; + +static PyObject *itemsview_isdisjoint_func; +static PyObject *itemsview_repr_func; + +static PyObject *keysview_repr_func; +static PyObject *keysview_isdisjoint_func; + +static PyObject *valuesview_repr_func; + +typedef struct { + PyObject_HEAD + PyObject *md; +} _Multidict_ViewObject; + + +/********** Base **********/ + +static inline void +_init_view(_Multidict_ViewObject *self, PyObject *md) +{ + Py_INCREF(md); + self->md = md; +} + +static inline void +multidict_view_dealloc(_Multidict_ViewObject *self) +{ + PyObject_GC_UnTrack(self); + Py_XDECREF(self->md); + PyObject_GC_Del(self); +} + +static inline int +multidict_view_traverse(_Multidict_ViewObject *self, visitproc visit, void *arg) +{ + Py_VISIT(self->md); + return 0; +} + +static inline int +multidict_view_clear(_Multidict_ViewObject *self) +{ + Py_CLEAR(self->md); + return 0; +} + +static inline Py_ssize_t +multidict_view_len(_Multidict_ViewObject *self) +{ + return pair_list_len(&((MultiDictObject*)self->md)->pairs); +} + +static inline PyObject * +multidict_view_richcompare(PyObject *self, PyObject *other, int op) +{ + PyObject *ret; + PyObject *op_obj = PyLong_FromLong(op); + if (op_obj == NULL) { + return NULL; + } + ret = PyObject_CallFunctionObjArgs( + viewbaseset_richcmp_func, self, other, op_obj, NULL); + Py_DECREF(op_obj); + return ret; +} + +static inline PyObject * +multidict_view_and(PyObject *self, PyObject *other) +{ + return PyObject_CallFunctionObjArgs( + viewbaseset_and_func, self, other, NULL); +} + +static inline PyObject * +multidict_view_or(PyObject *self, PyObject *other) +{ + return PyObject_CallFunctionObjArgs( + viewbaseset_or_func, self, other, NULL); +} + +static inline PyObject * +multidict_view_sub(PyObject *self, PyObject *other) +{ + return PyObject_CallFunctionObjArgs( + viewbaseset_sub_func, self, other, NULL); +} + +static inline PyObject * +multidict_view_xor(PyObject *self, PyObject *other) +{ + return PyObject_CallFunctionObjArgs( + viewbaseset_xor_func, self, other, NULL); +} + +static PyNumberMethods multidict_view_as_number = { + .nb_subtract = (binaryfunc)multidict_view_sub, + .nb_and = (binaryfunc)multidict_view_and, + .nb_xor = (binaryfunc)multidict_view_xor, + .nb_or = (binaryfunc)multidict_view_or, +}; + +/********** Items **********/ + +static inline PyObject * +multidict_itemsview_new(PyObject *md) +{ + _Multidict_ViewObject *mv = PyObject_GC_New( + _Multidict_ViewObject, &multidict_itemsview_type); + if (mv == NULL) { + return NULL; + } + + _init_view(mv, md); + + PyObject_GC_Track(mv); + return (PyObject *)mv; +} + +static inline PyObject * +multidict_itemsview_iter(_Multidict_ViewObject *self) +{ + return multidict_items_iter_new((MultiDictObject*)self->md); +} + +static inline PyObject * +multidict_itemsview_repr(_Multidict_ViewObject *self) +{ + return PyObject_CallFunctionObjArgs( + itemsview_repr_func, self, NULL); +} + +static inline PyObject * +multidict_itemsview_isdisjoint(_Multidict_ViewObject *self, PyObject *other) +{ + return PyObject_CallFunctionObjArgs( + itemsview_isdisjoint_func, self, other, NULL); +} + +PyDoc_STRVAR(itemsview_isdisjoint_doc, + "Return True if two sets have a null intersection."); + +static PyMethodDef multidict_itemsview_methods[] = { + { + "isdisjoint", + (PyCFunction)multidict_itemsview_isdisjoint, + METH_O, + itemsview_isdisjoint_doc + }, + { + NULL, + NULL + } /* sentinel */ +}; + +static inline int +multidict_itemsview_contains(_Multidict_ViewObject *self, PyObject *obj) +{ + PyObject *akey = NULL, + *aval = NULL, + *bkey = NULL, + *bval = NULL, + *iter = NULL, + *item = NULL; + int ret1, ret2; + + if (!PyTuple_Check(obj) || PyTuple_GET_SIZE(obj) != 2) { + return 0; + } + + bkey = PyTuple_GET_ITEM(obj, 0); + bval = PyTuple_GET_ITEM(obj, 1); + + iter = multidict_itemsview_iter(self); + if (iter == NULL) { + return 0; + } + + while ((item = PyIter_Next(iter)) != NULL) { + akey = PyTuple_GET_ITEM(item, 0); + aval = PyTuple_GET_ITEM(item, 1); + + ret1 = PyObject_RichCompareBool(akey, bkey, Py_EQ); + if (ret1 < 0) { + Py_DECREF(iter); + Py_DECREF(item); + return -1; + } + ret2 = PyObject_RichCompareBool(aval, bval, Py_EQ); + if (ret2 < 0) { + Py_DECREF(iter); + Py_DECREF(item); + return -1; + } + if (ret1 > 0 && ret2 > 0) + { + Py_DECREF(iter); + Py_DECREF(item); + return 1; + } + + Py_DECREF(item); + } + + Py_DECREF(iter); + + if (PyErr_Occurred()) { + return -1; + } + + return 0; +} + +static PySequenceMethods multidict_itemsview_as_sequence = { + .sq_length = (lenfunc)multidict_view_len, + .sq_contains = (objobjproc)multidict_itemsview_contains, +}; + +static PyTypeObject multidict_itemsview_type = { + PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) + "multidict._multidict._ItemsView", /* tp_name */ + sizeof(_Multidict_ViewObject), /* tp_basicsize */ + .tp_dealloc = (destructor)multidict_view_dealloc, + .tp_repr = (reprfunc)multidict_itemsview_repr, + .tp_as_number = &multidict_view_as_number, + .tp_as_sequence = &multidict_itemsview_as_sequence, + .tp_getattro = PyObject_GenericGetAttr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = (traverseproc)multidict_view_traverse, + .tp_clear = (inquiry)multidict_view_clear, + .tp_richcompare = multidict_view_richcompare, + .tp_iter = (getiterfunc)multidict_itemsview_iter, + .tp_methods = multidict_itemsview_methods, +}; + + +/********** Keys **********/ + +static inline PyObject * +multidict_keysview_new(PyObject *md) +{ + _Multidict_ViewObject *mv = PyObject_GC_New( + _Multidict_ViewObject, &multidict_keysview_type); + if (mv == NULL) { + return NULL; + } + + _init_view(mv, md); + + PyObject_GC_Track(mv); + return (PyObject *)mv; +} + +static inline PyObject * +multidict_keysview_iter(_Multidict_ViewObject *self) +{ + return multidict_keys_iter_new(((MultiDictObject*)self->md)); +} + +static inline PyObject * +multidict_keysview_repr(_Multidict_ViewObject *self) +{ + return PyObject_CallFunctionObjArgs( + keysview_repr_func, self, NULL); +} + +static inline PyObject * +multidict_keysview_isdisjoint(_Multidict_ViewObject *self, PyObject *other) +{ + return PyObject_CallFunctionObjArgs( + keysview_isdisjoint_func, self, other, NULL); +} + +PyDoc_STRVAR(keysview_isdisjoint_doc, + "Return True if two sets have a null intersection."); + +static PyMethodDef multidict_keysview_methods[] = { + { + "isdisjoint", + (PyCFunction)multidict_keysview_isdisjoint, + METH_O, + keysview_isdisjoint_doc + }, + { + NULL, + NULL + } /* sentinel */ +}; + +static inline int +multidict_keysview_contains(_Multidict_ViewObject *self, PyObject *key) +{ + return pair_list_contains(&((MultiDictObject*)self->md)->pairs, key); +} + +static PySequenceMethods multidict_keysview_as_sequence = { + .sq_length = (lenfunc)multidict_view_len, + .sq_contains = (objobjproc)multidict_keysview_contains, +}; + +static PyTypeObject multidict_keysview_type = { + PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) + "multidict._multidict._KeysView", /* tp_name */ + sizeof(_Multidict_ViewObject), /* tp_basicsize */ + .tp_dealloc = (destructor)multidict_view_dealloc, + .tp_repr = (reprfunc)multidict_keysview_repr, + .tp_as_number = &multidict_view_as_number, + .tp_as_sequence = &multidict_keysview_as_sequence, + .tp_getattro = PyObject_GenericGetAttr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = (traverseproc)multidict_view_traverse, + .tp_clear = (inquiry)multidict_view_clear, + .tp_richcompare = multidict_view_richcompare, + .tp_iter = (getiterfunc)multidict_keysview_iter, + .tp_methods = multidict_keysview_methods, +}; + + +/********** Values **********/ + +static inline PyObject * +multidict_valuesview_new(PyObject *md) +{ + _Multidict_ViewObject *mv = PyObject_GC_New( + _Multidict_ViewObject, &multidict_valuesview_type); + if (mv == NULL) { + return NULL; + } + + _init_view(mv, md); + + PyObject_GC_Track(mv); + return (PyObject *)mv; +} + +static inline PyObject * +multidict_valuesview_iter(_Multidict_ViewObject *self) +{ + return multidict_values_iter_new(((MultiDictObject*)self->md)); +} + +static inline PyObject * +multidict_valuesview_repr(_Multidict_ViewObject *self) +{ + return PyObject_CallFunctionObjArgs( + valuesview_repr_func, self, NULL); +} + +static PySequenceMethods multidict_valuesview_as_sequence = { + .sq_length = (lenfunc)multidict_view_len, +}; + +static PyTypeObject multidict_valuesview_type = { + PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) + "multidict._multidict._ValuesView", /* tp_name */ + sizeof(_Multidict_ViewObject), /* tp_basicsize */ + .tp_dealloc = (destructor)multidict_view_dealloc, + .tp_repr = (reprfunc)multidict_valuesview_repr, + .tp_as_sequence = &multidict_valuesview_as_sequence, + .tp_getattro = PyObject_GenericGetAttr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = (traverseproc)multidict_view_traverse, + .tp_clear = (inquiry)multidict_view_clear, + .tp_iter = (getiterfunc)multidict_valuesview_iter, +}; + + +static inline int +multidict_views_init() +{ + PyObject *reg_func_call_result = NULL; + PyObject *module = PyImport_ImportModule("multidict._multidict_base"); + if (module == NULL) { + goto fail; + } + +#define GET_MOD_ATTR(VAR, NAME) \ + VAR = PyObject_GetAttrString(module, NAME); \ + if (VAR == NULL) { \ + goto fail; \ + } + + GET_MOD_ATTR(viewbaseset_richcmp_func, "_viewbaseset_richcmp"); + GET_MOD_ATTR(viewbaseset_and_func, "_viewbaseset_and"); + GET_MOD_ATTR(viewbaseset_or_func, "_viewbaseset_or"); + GET_MOD_ATTR(viewbaseset_sub_func, "_viewbaseset_sub"); + GET_MOD_ATTR(viewbaseset_xor_func, "_viewbaseset_xor"); + + GET_MOD_ATTR(abc_itemsview_register_func, "_abc_itemsview_register"); + GET_MOD_ATTR(abc_keysview_register_func, "_abc_keysview_register"); + GET_MOD_ATTR(abc_valuesview_register_func, "_abc_valuesview_register"); + + GET_MOD_ATTR(itemsview_repr_func, "_itemsview_isdisjoint"); + GET_MOD_ATTR(itemsview_repr_func, "_itemsview_repr"); + + GET_MOD_ATTR(keysview_repr_func, "_keysview_repr"); + GET_MOD_ATTR(keysview_isdisjoint_func, "_keysview_isdisjoint"); + + GET_MOD_ATTR(valuesview_repr_func, "_valuesview_repr"); + + if (PyType_Ready(&multidict_itemsview_type) < 0 || + PyType_Ready(&multidict_valuesview_type) < 0 || + PyType_Ready(&multidict_keysview_type) < 0) + { + goto fail; + } + + // abc.ItemsView.register(_ItemsView) + reg_func_call_result = PyObject_CallFunctionObjArgs( + abc_itemsview_register_func, (PyObject*)&multidict_itemsview_type, NULL); + if (reg_func_call_result == NULL) { + goto fail; + } + Py_DECREF(reg_func_call_result); + + // abc.KeysView.register(_KeysView) + reg_func_call_result = PyObject_CallFunctionObjArgs( + abc_keysview_register_func, (PyObject*)&multidict_keysview_type, NULL); + if (reg_func_call_result == NULL) { + goto fail; + } + Py_DECREF(reg_func_call_result); + + // abc.ValuesView.register(_KeysView) + reg_func_call_result = PyObject_CallFunctionObjArgs( + abc_valuesview_register_func, (PyObject*)&multidict_valuesview_type, NULL); + if (reg_func_call_result == NULL) { + goto fail; + } + Py_DECREF(reg_func_call_result); + + Py_DECREF(module); + return 0; + +fail: + Py_CLEAR(module); + return -1; + +#undef GET_MOD_ATTR +} + +#ifdef __cplusplus +} +#endif +#endif diff --git a/multidict/py.typed b/multidict/py.typed new file mode 100644 index 0000000..dfe8cc0 --- /dev/null +++ b/multidict/py.typed @@ -0,0 +1 @@ +PEP-561 marker. \ No newline at end of file diff --git a/mysql/__init__.py b/mysql/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mysql/connector/__init__.py b/mysql/connector/__init__.py new file mode 100644 index 0000000..86878f9 --- /dev/null +++ b/mysql/connector/__init__.py @@ -0,0 +1,123 @@ +# Copyright (c) 2009, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""MySQL Connector/Python - MySQL driver written in Python.""" + +try: + from .connection_cext import CMySQLConnection +except ImportError: + HAVE_CEXT = False +else: + HAVE_CEXT = True + + +from . import version +from .connection import MySQLConnection +from .constants import CharacterSet, ClientFlag, FieldFlag, FieldType, RefreshOption +from .dbapi import ( + BINARY, + DATETIME, + NUMBER, + ROWID, + STRING, + Binary, + Date, + DateFromTicks, + Time, + TimeFromTicks, + Timestamp, + TimestampFromTicks, + apilevel, + paramstyle, + threadsafety, +) +from .errors import ( # pylint: disable=redefined-builtin + DatabaseError, + DataError, + Error, + IntegrityError, + InterfaceError, + InternalError, + NotSupportedError, + OperationalError, + PoolError, + ProgrammingError, + Warning, + custom_error_exception, +) +from .pooling import connect + +Connect = connect + +__version_info__ = version.VERSION +__version__ = version.VERSION_TEXT + +__all__ = [ + "MySQLConnection", + "Connect", + "custom_error_exception", + # Some useful constants + "FieldType", + "FieldFlag", + "ClientFlag", + "CharacterSet", + "RefreshOption", + "HAVE_CEXT", + # Error handling + "Error", + "Warning", + "InterfaceError", + "DatabaseError", + "NotSupportedError", + "DataError", + "IntegrityError", + "PoolError", + "ProgrammingError", + "OperationalError", + "InternalError", + # DBAPI PEP 249 required exports + "connect", + "apilevel", + "threadsafety", + "paramstyle", + "Date", + "Time", + "Timestamp", + "Binary", + "DateFromTicks", + "DateFromTicks", + "TimestampFromTicks", + "TimeFromTicks", + "STRING", + "BINARY", + "NUMBER", + "DATETIME", + "ROWID", + # C Extension + "CMySQLConnection", +] diff --git a/mysql/connector/abstracts.py b/mysql/connector/abstracts.py new file mode 100644 index 0000000..90fafcf --- /dev/null +++ b/mysql/connector/abstracts.py @@ -0,0 +1,1806 @@ +# Copyright (c) 2014, 2023, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# mypy: disable-error-code="assignment,attr-defined" + +"""Module gathering all abstract base classes.""" + +from __future__ import annotations + +import importlib +import os +import re +import weakref + +from abc import ABC, abstractmethod +from datetime import date, datetime, time, timedelta +from decimal import Decimal +from inspect import signature +from time import sleep +from types import TracebackType +from typing import ( + Any, + BinaryIO, + Callable, + Dict, + Generator, + List, + Mapping, + Optional, + Sequence, + Tuple, + Type, + Union, +) + +TLS_V1_3_SUPPORTED = False +try: + import ssl + + if hasattr(ssl, "HAS_TLSv1_3") and ssl.HAS_TLSv1_3: + TLS_V1_3_SUPPORTED = True +except ImportError: + # If import fails, we don't have SSL support. + pass + +from .constants import ( + CONN_ATTRS_DN, + DEFAULT_CONFIGURATION, + DEPRECATED_TLS_VERSIONS, + OPENSSL_CS_NAMES, + TLS_CIPHER_SUITES, + TLS_VERSIONS, + CharacterSet, + ClientFlag, +) +from .conversion import MySQLConverter, MySQLConverterBase +from .errors import ( + Error, + InterfaceError, + NotSupportedError, + OperationalError, + ProgrammingError, +) +from .opentelemetry.constants import ( + CONNECTION_SPAN_NAME, + OPTION_CNX_SPAN, + OPTION_CNX_TRACER, + OTEL_ENABLED, +) + +if OTEL_ENABLED: + from .opentelemetry.instrumentation import ( + end_span, + record_exception_event, + set_connection_span_attrs, + trace, + ) + +from .optionfiles import read_option_files +from .types import ( + ConnAttrsType, + DescriptionType, + HandShakeType, + StrOrBytes, + SupportedMysqlBinaryProtocolTypes, + WarningType, +) + +NAMED_TUPLE_CACHE: weakref.WeakValueDictionary[Any, Any] = weakref.WeakValueDictionary() + +DUPLICATED_IN_LIST_ERROR = ( + "The '{list}' list must not contain repeated values, the value " + "'{value}' is duplicated." +) + +TLS_VERSION_ERROR = ( + "The given tls_version: '{}' is not recognized as a valid " + "TLS protocol version (should be one of {})." +) + +TLS_VERSION_DEPRECATED_ERROR = ( + "The given tls_version: '{}' are no longer allowed (should be one of {})." +) + +TLS_VER_NO_SUPPORTED = ( + "No supported TLS protocol version found in the 'tls-versions' list '{}'. " +) + +KRB_SERVICE_PINCIPAL_ERROR = ( + 'Option "krb_service_principal" {error}, must be a string in the form ' + '"primary/instance@realm" e.g "ldap/ldapauth@MYSQL.COM" where "@realm" ' + "is optional and if it is not given will be assumed to belong to the " + "default realm, as configured in the krb5.conf file." +) + +MYSQL_PY_TYPES = ( + Decimal, + bytes, + date, + datetime, + float, + int, + str, + time, + timedelta, +) + + +class MySQLConnectionAbstract(ABC): + """Abstract class for classes connecting to a MySQL server""" + + def __init__(self) -> None: + """Initialize""" + # opentelemetry related + self._tracer: Any = None + self._span: Any = None + self.otel_context_propagation: bool = True + + self._client_flags: int = ClientFlag.get_default() + self._charset_id: int = 45 + self._sql_mode: Optional[str] = None + self._time_zone: Optional[str] = None + self._autocommit: bool = False + self._server_version: Optional[Tuple[int, ...]] = None + self._handshake: Optional[HandShakeType] = None + self._conn_attrs: ConnAttrsType = {} + + self._user: str = "" + self._password: str = "" + self._password1: str = "" + self._password2: str = "" + self._password3: str = "" + self._database: str = "" + self._host: str = "127.0.0.1" + self._port: int = 3306 + self._unix_socket: Optional[str] = None + self._client_host: str = "" + self._client_port: int = 0 + self._ssl: Dict[str, Optional[Union[str, bool, List[str]]]] = {} + self._ssl_disabled: bool = DEFAULT_CONFIGURATION["ssl_disabled"] + self._force_ipv6: bool = False + self._oci_config_file: Optional[str] = None + self._oci_config_profile: Optional[str] = None + self._fido_callback: Optional[Union[str, Callable]] = None + self._krb_service_principal: Optional[str] = None + + self._use_unicode: bool = True + self._get_warnings: bool = False + self._raise_on_warnings: bool = False + self._connection_timeout: Optional[int] = DEFAULT_CONFIGURATION[ + "connect_timeout" + ] + self._buffered: bool = False + self._unread_result: bool = False + self._have_next_result: bool = False + self._raw: bool = False + self._in_transaction: bool = False + self._allow_local_infile: bool = DEFAULT_CONFIGURATION["allow_local_infile"] + self._allow_local_infile_in_path: Optional[str] = DEFAULT_CONFIGURATION[ + "allow_local_infile_in_path" + ] + + self._prepared_statements: Any = None + self._query_attrs: Dict[str, Any] = {} + + self._ssl_active: bool = False + self._auth_plugin: Optional[str] = None + self._auth_plugin_class: Optional[str] = None + self._pool_config_version: Any = None + self.converter: Optional[MySQLConverter] = None + self._converter_class: Optional[Type[MySQLConverter]] = None + self._converter_str_fallback: bool = False + self._compress: bool = False + + self._consume_results: bool = False + self._init_command: Optional[str] = None + + def __enter__(self) -> MySQLConnectionAbstract: + return self + + def __exit__( + self, + exc_type: Type[BaseException], + exc_value: BaseException, + traceback: TracebackType, + ) -> None: + self.close() + + def get_self(self) -> MySQLConnectionAbstract: + """Return self for weakref.proxy + + This method is used when the original object is needed when using + weakref.proxy. + """ + return self + + @property + def is_secure(self) -> bool: + """Return True if is a secure connection.""" + return self._ssl_active or ( + self._unix_socket is not None and os.name == "posix" + ) + + @property + def have_next_result(self) -> bool: + """Return if have next result.""" + return self._have_next_result + + @property + def query_attrs(self) -> List[Tuple[str, Any]]: + """Return query attributes list.""" + return list(self._query_attrs.items()) + + def query_attrs_append( + self, value: Tuple[str, SupportedMysqlBinaryProtocolTypes] + ) -> None: + """Add element to the query attributes list. + + If an element in the query attributes list already matches + the attribute name provided, the new element will NOT be added. + """ + attr_name, attr_value = value + if attr_name not in self._query_attrs: + self._query_attrs[attr_name] = attr_value + + def query_attrs_remove(self, name: str) -> Any: + """Remove element by name from the query attributes list. + + If no match, `None` is returned; else the corresponding value is returned. + """ + return self._query_attrs.pop(name, None) + + def query_attrs_clear(self) -> None: + """Clear query attributes list.""" + self._query_attrs = {} + + def _validate_tls_ciphersuites(self) -> None: + """Validates the tls_ciphersuites option.""" + tls_ciphersuites = [] + tls_cs = self._ssl["tls_ciphersuites"] + + if isinstance(tls_cs, str): + if not (tls_cs.startswith("[") and tls_cs.endswith("]")): + raise AttributeError( + f"tls_ciphersuites must be a list, found: '{tls_cs}'" + ) + tls_css = tls_cs[1:-1].split(",") + if not tls_css: + raise AttributeError( + "No valid cipher suite found in 'tls_ciphersuites' list" + ) + for _tls_cs in tls_css: + _tls_cs = tls_cs.strip().upper() + if _tls_cs: + tls_ciphersuites.append(_tls_cs) + + elif isinstance(tls_cs, (list, set)): + tls_ciphersuites = [tls_cs for tls_cs in tls_cs if tls_cs] + else: + raise AttributeError( + "tls_ciphersuites should be a list with one or more " + f"ciphersuites. Found: '{tls_cs}'" + ) + + tls_versions = ( + TLS_VERSIONS[:] + if self._ssl.get("tls_versions", None) is None + else self._ssl["tls_versions"][:] # type: ignore[index] + ) + + # A newer TLS version can use a cipher introduced on + # an older version. + tls_versions.sort(reverse=True) # type: ignore[union-attr] + newer_tls_ver = tls_versions[0] + # translated_names[0] belongs to TLSv1, TLSv1.1 and TLSv1.2 + # translated_names[1] are TLSv1.3 only + translated_names: List[List[str]] = [[], []] + iani_cipher_suites_names = {} + ossl_cipher_suites_names: List[str] = [] + + # Old ciphers can work with new TLS versions. + # Find all the ciphers introduced on previous TLS versions. + for tls_ver in TLS_VERSIONS[: TLS_VERSIONS.index(newer_tls_ver) + 1]: + iani_cipher_suites_names.update(TLS_CIPHER_SUITES[tls_ver]) + ossl_cipher_suites_names.extend(OPENSSL_CS_NAMES[tls_ver]) + + for name in tls_ciphersuites: + if "-" in name and name in ossl_cipher_suites_names: + if name in OPENSSL_CS_NAMES["TLSv1.3"]: + translated_names[1].append(name) + else: + translated_names[0].append(name) + elif name in iani_cipher_suites_names: + translated_name = iani_cipher_suites_names[name] + if translated_name in translated_names: + raise AttributeError( + DUPLICATED_IN_LIST_ERROR.format( + list="tls_ciphersuites", value=translated_name + ) + ) + if name in TLS_CIPHER_SUITES["TLSv1.3"]: + translated_names[1].append(iani_cipher_suites_names[name]) + else: + translated_names[0].append(iani_cipher_suites_names[name]) + else: + raise AttributeError( + f"The value '{name}' in tls_ciphersuites is not a valid " + "cipher suite" + ) + if not translated_names[0] and not translated_names[1]: + raise AttributeError( + "No valid cipher suite found in the 'tls_ciphersuites' list" + ) + + self._ssl["tls_ciphersuites"] = [ + ":".join(translated_names[0]), + ":".join(translated_names[1]), + ] + + def _validate_tls_versions(self) -> None: + """Validates the tls_versions option.""" + tls_versions = [] + tls_version = self._ssl["tls_versions"] + + if isinstance(tls_version, str): + if not (tls_version.startswith("[") and tls_version.endswith("]")): + raise AttributeError( + f"tls_versions must be a list, found: '{tls_version}'" + ) + tls_vers = tls_version[1:-1].split(",") + for tls_ver in tls_vers: + tls_version = tls_ver.strip() + if tls_version == "": + continue + if tls_version in tls_versions: + raise AttributeError( + DUPLICATED_IN_LIST_ERROR.format( + list="tls_versions", value=tls_version + ) + ) + tls_versions.append(tls_version) + if tls_vers == ["TLSv1.3"] and not TLS_V1_3_SUPPORTED: + raise AttributeError( + TLS_VER_NO_SUPPORTED.format(tls_version, TLS_VERSIONS) + ) + elif isinstance(tls_version, list): + if not tls_version: + raise AttributeError( + "At least one TLS protocol version must be specified in " + "'tls_versions' list" + ) + for tls_ver in tls_version: + if tls_ver in tls_versions: + raise AttributeError( + DUPLICATED_IN_LIST_ERROR.format( + list="tls_versions", value=tls_ver + ) + ) + tls_versions.append(tls_ver) + elif isinstance(tls_version, set): + for tls_ver in tls_version: + tls_versions.append(tls_ver) + else: + raise AttributeError( + "tls_versions should be a list with one or more of versions " + f"in {', '.join(TLS_VERSIONS)}. found: '{tls_versions}'" + ) + + if not tls_versions: + raise AttributeError( + "At least one TLS protocol version must be specified " + "in 'tls_versions' list when this option is given" + ) + + use_tls_versions = [] + deprecated_tls_versions = [] + invalid_tls_versions = [] + for tls_ver in tls_versions: + if tls_ver in TLS_VERSIONS: + use_tls_versions.append(tls_ver) + if tls_ver in DEPRECATED_TLS_VERSIONS: + deprecated_tls_versions.append(tls_ver) + else: + invalid_tls_versions.append(tls_ver) + + if use_tls_versions: + if use_tls_versions == ["TLSv1.3"] and not TLS_V1_3_SUPPORTED: + raise NotSupportedError( + TLS_VER_NO_SUPPORTED.format(tls_version, TLS_VERSIONS) + ) + use_tls_versions.sort() + self._ssl["tls_versions"] = use_tls_versions + elif deprecated_tls_versions: + raise NotSupportedError( + TLS_VERSION_DEPRECATED_ERROR.format( + deprecated_tls_versions, TLS_VERSIONS + ) + ) + elif invalid_tls_versions: + raise AttributeError(TLS_VERSION_ERROR.format(tls_ver, TLS_VERSIONS)) + + @property + def user(self) -> str: + """User used while connecting to MySQL""" + return self._user + + @property + def server_host(self) -> str: + """MySQL server IP address or name""" + return self._host + + @property + def server_port(self) -> int: + "MySQL server TCP/IP port" + return self._port + + @property + def unix_socket(self) -> Optional[str]: + "MySQL Unix socket file location" + return self._unix_socket + + @property + @abstractmethod + def database(self) -> str: + """Get the current database""" + + @database.setter + def database(self, value: str) -> None: + """Set the current database""" + self.cmd_query(f"USE {value}") + + @property + def can_consume_results(self) -> bool: + """Returns whether to consume results""" + return self._consume_results + + @can_consume_results.setter + def can_consume_results(self, value: bool) -> None: + """Set if can consume results.""" + assert isinstance(value, bool) + self._consume_results = value + + @property + def pool_config_version(self) -> Any: + """Return the pool configuration version""" + return self._pool_config_version + + @pool_config_version.setter + def pool_config_version(self, value: Any) -> None: + """Set the pool configuration version""" + self._pool_config_version = value + + def config(self, **kwargs: Any) -> None: + """Configure the MySQL Connection + + This method allows you to configure the MySQLConnection instance. + + Raises on errors. + """ + # opentelemetry related + self._span = kwargs.pop(OPTION_CNX_SPAN, None) + self._tracer = kwargs.pop(OPTION_CNX_TRACER, None) + + config = kwargs.copy() + if "dsn" in config: + raise NotSupportedError("Data source name is not supported") + + # Read option files + config = read_option_files(**config) + + # Configure how we handle MySQL warnings + try: + self.get_warnings = config["get_warnings"] + del config["get_warnings"] + except KeyError: + pass # Leave what was set or default + try: + self.raise_on_warnings = config["raise_on_warnings"] + del config["raise_on_warnings"] + except KeyError: + pass # Leave what was set or default + + # Configure client flags + try: + default = ClientFlag.get_default() + self.set_client_flags(config["client_flags"] or default) + del config["client_flags"] + except KeyError: + pass # Missing client_flags-argument is OK + + try: + if config["compress"]: + self._compress = True + self.set_client_flags([ClientFlag.COMPRESS]) + except KeyError: + pass # Missing compress argument is OK + + self._allow_local_infile = config.get( + "allow_local_infile", DEFAULT_CONFIGURATION["allow_local_infile"] + ) + self._allow_local_infile_in_path = config.get( + "allow_local_infile_in_path", + DEFAULT_CONFIGURATION["allow_local_infile_in_path"], + ) + infile_in_path = None + if self._allow_local_infile_in_path: + infile_in_path = os.path.abspath(self._allow_local_infile_in_path) + if ( + infile_in_path + and os.path.exists(infile_in_path) + and not os.path.isdir(infile_in_path) + or os.path.islink(infile_in_path) + ): + raise AttributeError("allow_local_infile_in_path must be a directory") + if self._allow_local_infile or self._allow_local_infile_in_path: + self.set_client_flags([ClientFlag.LOCAL_FILES]) + else: + self.set_client_flags([-ClientFlag.LOCAL_FILES]) + + try: + if not config["consume_results"]: + self._consume_results = False + else: + self._consume_results = True + except KeyError: + self._consume_results = False + + # Configure auth_plugin + try: + self._auth_plugin = config["auth_plugin"] + del config["auth_plugin"] + except KeyError: + self._auth_plugin = "" + + # Configure character set and collation + if "charset" in config or "collation" in config: + try: + charset = config["charset"] + del config["charset"] + except KeyError: + charset = None + try: + collation = config["collation"] + del config["collation"] + except KeyError: + collation = None + self._charset_id = CharacterSet.get_charset_info(charset, collation)[0] + + # Set converter class + try: + self.set_converter_class(config["converter_class"]) + except KeyError: + pass # Using default converter class + except TypeError as err: + raise AttributeError( + "Converter class should be a subclass of " + "conversion.MySQLConverterBase" + ) from err + + # Compatible configuration with other drivers + compat_map = [ + # (,) + ("db", "database"), + ("username", "user"), + ("passwd", "password"), + ("connect_timeout", "connection_timeout"), + ("read_default_file", "option_files"), + ] + for compat, translate in compat_map: + try: + if translate not in config: + config[translate] = config[compat] + del config[compat] + except KeyError: + pass # Missing compat argument is OK + + # Configure login information + if "user" in config or "password" in config: + try: + user = config["user"] + del config["user"] + except KeyError: + user = self._user + try: + password = config["password"] + del config["password"] + except KeyError: + password = self._password + self.set_login(user, password) + + # Configure host information + if "host" in config and config["host"]: + self._host = config["host"] + + # Check network locations + try: + self._port = int(config["port"]) + del config["port"] + except KeyError: + pass # Missing port argument is OK + except ValueError as err: + raise InterfaceError("TCP/IP port number should be an integer") from err + + if "ssl_disabled" in config: + self._ssl_disabled = config.pop("ssl_disabled") + + # If an init_command is set, keep it, so we can execute it in _post_connection + if "init_command" in config: + self._init_command = config["init_command"] + del config["init_command"] + + # Other configuration + set_ssl_flag = False + for key, value in config.items(): + try: + DEFAULT_CONFIGURATION[key] + except KeyError: + raise AttributeError(f"Unsupported argument '{key}'") from None + # SSL Configuration + if key.startswith("ssl_"): + set_ssl_flag = True + self._ssl.update({key.replace("ssl_", ""): value}) + elif key.startswith("tls_"): + set_ssl_flag = True + self._ssl.update({key: value}) + else: + attribute = "_" + key + try: + setattr(self, attribute, value.strip()) + except AttributeError: + setattr(self, attribute, value) + + # Disable SSL for unix socket connections + if self._unix_socket and os.name == "posix": + self._ssl_disabled = True + + if self._ssl_disabled and self._auth_plugin == "mysql_clear_password": + raise InterfaceError( + "Clear password authentication is not supported over insecure channels" + ) + + if set_ssl_flag: + if "verify_cert" not in self._ssl: + self._ssl["verify_cert"] = DEFAULT_CONFIGURATION["ssl_verify_cert"] + if "verify_identity" not in self._ssl: + self._ssl["verify_identity"] = DEFAULT_CONFIGURATION[ + "ssl_verify_identity" + ] + # Make sure both ssl_key/ssl_cert are set, or neither (XOR) + if "ca" not in self._ssl or self._ssl["ca"] is None: + self._ssl["ca"] = "" + if bool("key" in self._ssl) != bool("cert" in self._ssl): + raise AttributeError( + "ssl_key and ssl_cert need to be both specified, or neither" + ) + # Make sure key/cert are set to None + if not set(("key", "cert")) <= set(self._ssl): + self._ssl["key"] = None + self._ssl["cert"] = None + elif (self._ssl["key"] is None) != (self._ssl["cert"] is None): + raise AttributeError( + "ssl_key and ssl_cert need to be both set, or neither" + ) + if "tls_versions" in self._ssl and self._ssl["tls_versions"] is not None: + self._validate_tls_versions() + + if ( + "tls_ciphersuites" in self._ssl + and self._ssl["tls_ciphersuites"] is not None + ): + self._validate_tls_ciphersuites() + + if self._conn_attrs is None: + self._conn_attrs = {} + elif not isinstance(self._conn_attrs, dict): + raise InterfaceError("conn_attrs must be of type dict") + else: + for attr_name, attr_value in self._conn_attrs.items(): + if attr_name in CONN_ATTRS_DN: + continue + # Validate name type + if not isinstance(attr_name, str): + raise InterfaceError( + "Attribute name should be a string, found: " + f"'{attr_name}' in '{self._conn_attrs}'" + ) + # Validate attribute name limit 32 characters + if len(attr_name) > 32: + raise InterfaceError( + f"Attribute name '{attr_name}' exceeds 32 characters limit size" + ) + # Validate names in connection attributes cannot start with "_" + if attr_name.startswith("_"): + raise InterfaceError( + "Key names in connection attributes cannot start with " + "'_', found: '{attr_name}'" + ) + # Validate value type + if not isinstance(attr_value, str): + raise InterfaceError( + f"Attribute '{attr_name}' value: '{attr_value}' must " + "be a string type" + ) + # Validate attribute value limit 1024 characters + if len(attr_value) > 1024: + raise InterfaceError( + f"Attribute '{attr_name}' value: '{attr_value}' " + "exceeds 1024 characters limit size" + ) + + if self._client_flags & ClientFlag.CONNECT_ARGS: + self._add_default_conn_attrs() + + if "kerberos_auth_mode" in config and config["kerberos_auth_mode"] is not None: + if not isinstance(config["kerberos_auth_mode"], str): + raise InterfaceError("'kerberos_auth_mode' must be of type str") + kerberos_auth_mode = config["kerberos_auth_mode"].lower() + if kerberos_auth_mode == "sspi": + if os.name != "nt": + raise InterfaceError( + "'kerberos_auth_mode=SSPI' is only available on Windows" + ) + self._auth_plugin_class = "MySQLSSPIKerberosAuthPlugin" + elif kerberos_auth_mode == "gssapi": + self._auth_plugin_class = "MySQLKerberosAuthPlugin" + else: + raise InterfaceError( + "Invalid 'kerberos_auth_mode' mode. Please use 'SSPI' or 'GSSAPI'" + ) + + if ( + "krb_service_principal" in config + and config["krb_service_principal"] is not None + ): + self._krb_service_principal = config["krb_service_principal"] + if not isinstance(self._krb_service_principal, str): + raise InterfaceError( + KRB_SERVICE_PINCIPAL_ERROR.format(error="is not a string") + ) + if self._krb_service_principal == "": + raise InterfaceError( + KRB_SERVICE_PINCIPAL_ERROR.format( + error="can not be an empty string" + ) + ) + if "/" not in self._krb_service_principal: + raise InterfaceError( + KRB_SERVICE_PINCIPAL_ERROR.format(error="is incorrectly formatted") + ) + + if self._fido_callback: + # Import the callable if it's a str + if isinstance(self._fido_callback, str): + try: + module, callback = self._fido_callback.rsplit(".", 1) + except ValueError: + raise ProgrammingError( + f"No callable named '{self._fido_callback}'" + ) from None + try: + module = importlib.import_module(module) + self._fido_callback = getattr(module, callback) + except (AttributeError, ModuleNotFoundError) as err: + raise ProgrammingError(f"{err}") from err + # Check if it's a callable + if not callable(self._fido_callback): + raise ProgrammingError("Expected a callable for 'fido_callback'") + # Check the callable signature if has only 1 positional argument + params = len(signature(self._fido_callback).parameters) + if params != 1: + raise ProgrammingError( + "'fido_callback' requires 1 positional argument, but the " + f"callback provided has {params}" + ) + + def _add_default_conn_attrs(self) -> Any: + """Add the default connection attributes.""" + + @staticmethod + def _check_server_version(server_version: StrOrBytes) -> Tuple[int, ...]: + """Check the MySQL version + + This method will check the MySQL version and raise an InterfaceError + when it is not supported or invalid. It will return the version + as a tuple with major, minor and patch. + + Raises InterfaceError if invalid server version. + + Returns tuple + """ + if isinstance(server_version, (bytearray, bytes)): + server_version = server_version.decode() + + regex_ver = re.compile(r"^(\d{1,2})\.(\d{1,2})\.(\d{1,3})(.*)") + match = regex_ver.match(server_version) + if not match: + raise InterfaceError("Failed parsing MySQL version") + + version = tuple(int(v) for v in match.groups()[0:3]) + if version < (4, 1): + raise InterfaceError(f"MySQL Version '{server_version}' is not supported") + + return version + + def get_server_version(self) -> Tuple[int, ...]: + """Get the MySQL version + + This method returns the MySQL server version as a tuple. If not + previously connected, it will return None. + + Returns a tuple or None. + """ + return self._server_version + + def get_server_info(self) -> Optional[str]: + """Get the original MySQL version information + + This method returns the original MySQL server as text. If not + previously connected, it will return None. + + Returns a string or None. + """ + try: + return self._handshake["server_version_original"] # type: ignore[return-value] + except (TypeError, KeyError): + return None + + @property + @abstractmethod + def in_transaction(self) -> Any: + """MySQL session has started a transaction""" + + def set_client_flags(self, flags: Union[int, Sequence[int]]) -> int: + """Set the client flags + + The flags-argument can be either an int or a list (or tuple) of + ClientFlag-values. If it is an integer, it will set client_flags + to flags as is. + If flags is a list (or tuple), each flag will be set or unset + when it's negative. + + set_client_flags([ClientFlag.FOUND_ROWS,-ClientFlag.LONG_FLAG]) + + Raises ProgrammingError when the flags argument is not a set or + an integer bigger than 0. + + Returns self.client_flags + """ + if isinstance(flags, int) and flags > 0: + self._client_flags = flags + elif isinstance(flags, (tuple, list)): + for flag in flags: + if flag < 0: + self._client_flags &= ~abs(flag) + else: + self._client_flags |= flag + else: + raise ProgrammingError("set_client_flags expect integer (>0) or set") + return self._client_flags + + def isset_client_flag(self, flag: int) -> bool: + """Check if a client flag is set""" + if (self._client_flags & flag) > 0: + return True + return False + + @property + def time_zone(self) -> str: + """Get the current time zone""" + return self.info_query("SELECT @@session.time_zone")[0] + + @time_zone.setter + def time_zone(self, value: str) -> None: + """Set the time zone""" + self.cmd_query(f"SET @@session.time_zone = '{value}'") + self._time_zone = value + + @property + def sql_mode(self) -> str: + """Get the SQL mode""" + if self._sql_mode is None: + self._sql_mode = self.info_query("SELECT @@session.sql_mode")[0] + return self._sql_mode + + @sql_mode.setter + def sql_mode(self, value: Union[str, Sequence[int]]) -> None: + """Set the SQL mode + + This method sets the SQL Mode for the current connection. The value + argument can be either a string with comma separate mode names, or + a sequence of mode names. + + It is good practice to use the constants class SQLMode: + from mysql.connector.constants import SQLMode + cnx.sql_mode = [SQLMode.NO_ZERO_DATE, SQLMode.REAL_AS_FLOAT] + """ + if isinstance(value, (list, tuple)): + value = ",".join(value) + self.cmd_query(f"SET @@session.sql_mode = '{value}'") + self._sql_mode = value + + @abstractmethod + def info_query(self, query: Any) -> Any: + """Send a query which only returns 1 row""" + + def set_login( + self, username: Optional[str] = None, password: Optional[str] = None + ) -> None: + """Set login information for MySQL + + Set the username and/or password for the user connecting to + the MySQL Server. + """ + if username is not None: + self._user = username.strip() + else: + self._user = "" + if password is not None: + self._password = password + else: + self._password = "" + + def set_unicode(self, value: bool = True) -> None: + """Toggle unicode mode + + Set whether we return string fields as unicode or not. + Default is True. + """ + self._use_unicode = value + if self.converter: + self.converter.set_unicode(value) + + @property + def autocommit(self) -> bool: + """Get whether autocommit is on or off""" + value = self.info_query("SELECT @@session.autocommit")[0] + return value == 1 + + @autocommit.setter + def autocommit(self, value: bool) -> None: + """Toggle autocommit""" + switch = "ON" if value else "OFF" + self.cmd_query(f"SET @@session.autocommit = {switch}") + self._autocommit = value + + @property + def get_warnings(self) -> bool: + """Get whether this connection retrieves warnings automatically + + This method returns whether this connection retrieves warnings + automatically. + + Returns True, or False when warnings are not retrieved. + """ + return self._get_warnings + + @get_warnings.setter + def get_warnings(self, value: bool) -> None: + """Set whether warnings should be automatically retrieved + + The toggle-argument must be a boolean. When True, cursors for this + connection will retrieve information about warnings (if any). + + Raises ValueError on error. + """ + if not isinstance(value, bool): + raise ValueError("Expected a boolean type") + self._get_warnings = value + + @property + def raise_on_warnings(self) -> bool: + """Get whether this connection raises an error on warnings + + This method returns whether this connection will raise errors when + MySQL reports warnings. + + Returns True or False. + """ + return self._raise_on_warnings + + @raise_on_warnings.setter + def raise_on_warnings(self, value: bool) -> None: + """Set whether warnings raise an error + + The toggle-argument must be a boolean. When True, cursors for this + connection will raise an error when MySQL reports warnings. + + Raising on warnings implies retrieving warnings automatically. In + other words: warnings will be set to True. If set to False, warnings + will be also set to False. + + Raises ValueError on error. + """ + if not isinstance(value, bool): + raise ValueError("Expected a boolean type") + self._raise_on_warnings = value + # Don't disable warning retrieval if raising explicitly disabled + if value: + self._get_warnings = value + + @property + def unread_result(self) -> bool: + """Get whether there is an unread result + + This method is used by cursors to check whether another cursor still + needs to retrieve its result set. + + Returns True, or False when there is no unread result. + """ + return self._unread_result + + @unread_result.setter + def unread_result(self, value: bool) -> None: + """Set whether there is an unread result + + This method is used by cursors to let other cursors know there is + still a result set that needs to be retrieved. + + Raises ValueError on errors. + """ + if not isinstance(value, bool): + raise ValueError("Expected a boolean type") + self._unread_result = value + + @property + def charset(self) -> str: + """Returns the character set for current connection + + This property returns the character set name of the current connection. + The server is queried when the connection is active. If not connected, + the configured character set name is returned. + + Returns a string. + """ + return CharacterSet.get_info(self._charset_id)[0] + + @property + def python_charset(self) -> str: + """Returns the Python character set for current connection + + This property returns the character set name of the current connection. + Note that, unlike property charset, this checks if the previously set + character set is supported by Python and if not, it returns the + equivalent character set that Python supports. + + Returns a string. + """ + encoding = CharacterSet.get_info(self._charset_id)[0] + if encoding in ("utf8mb4", "utf8mb3", "binary"): + return "utf8" + return encoding + + def set_charset_collation( + self, charset: Optional[Union[int, str]] = None, collation: Optional[str] = None + ) -> None: + """Sets the character set and collation for the current connection + + This method sets the character set and collation to be used for + the current connection. The charset argument can be either the + name of a character set as a string, or the numerical equivalent + as defined in constants.CharacterSet. + + When the collation is not given, the default will be looked up and + used. + + For example, the following will set the collation for the latin1 + character set to latin1_general_ci: + + set_charset('latin1','latin1_general_ci') + + """ + err_msg = "{} should be either integer, string or None" + if not isinstance(charset, (int, str)) and charset is not None: + raise ValueError(err_msg.format("charset")) + if not isinstance(collation, str) and collation is not None: + raise ValueError("collation should be either string or None") + + if charset: + if isinstance(charset, int): + ( + self._charset_id, + charset_name, + collation_name, + ) = CharacterSet.get_charset_info(charset) + elif isinstance(charset, str): + ( + self._charset_id, + charset_name, + collation_name, + ) = CharacterSet.get_charset_info(charset, collation) + else: + raise ValueError(err_msg.format("charset")) + elif collation: + ( + self._charset_id, + charset_name, + collation_name, + ) = CharacterSet.get_charset_info(collation=collation) + else: + charset = DEFAULT_CONFIGURATION["charset"] + ( + self._charset_id, + charset_name, + collation_name, + ) = CharacterSet.get_charset_info(charset, collation=None) + + self._execute_query(f"SET NAMES '{charset_name}' COLLATE '{collation_name}'") + + try: + # Required for C Extension + self.set_character_set_name(charset_name) + except AttributeError: + # Not required for pure Python connection + pass + + if self.converter: + self.converter.set_charset(charset_name) + + @property + def collation(self) -> str: + """Returns the collation for current connection + + This property returns the collation name of the current connection. + The server is queried when the connection is active. If not connected, + the configured collation name is returned. + + Returns a string. + """ + return CharacterSet.get_charset_info(self._charset_id)[2] + + @abstractmethod + def _do_handshake(self) -> Any: + """Gather information of the MySQL server before authentication""" + + @abstractmethod + def _open_connection(self) -> Any: + """Open the connection to the MySQL server""" + + def _post_connection(self) -> None: + """Executes commands after connection has been established + + This method executes commands after the connection has been + established. Some setting like autocommit, character set, and SQL mode + are set using this method. + """ + self.set_charset_collation(self._charset_id) + self.autocommit = self._autocommit + if self._time_zone: + self.time_zone = self._time_zone + if self._sql_mode: + self.sql_mode = self._sql_mode + if self._init_command: + self._execute_query(self._init_command) + + @abstractmethod + def disconnect(self) -> Any: + """Disconnect from the MySQL server""" + + close: Callable[[], Any] = disconnect + + def connect(self, **kwargs: Any) -> None: + """Connect to the MySQL server + + This method sets up the connection to the MySQL server. If no + arguments are given, it will use the already configured or default + values. + """ + if kwargs: + self.config(**kwargs) + + self.disconnect() + self._open_connection() + # Server does not allow to run any other statement different from ALTER + # when user's password has been expired. + if not self._client_flags & ClientFlag.CAN_HANDLE_EXPIRED_PASSWORDS: + self._post_connection() + + def reconnect(self, attempts: int = 1, delay: int = 0) -> None: + """Attempt to reconnect to the MySQL server + + The argument attempts should be the number of times a reconnect + is tried. The delay argument is the number of seconds to wait between + each retry. + + You may want to set the number of attempts higher and use delay when + you expect the MySQL server to be down for maintenance or when you + expect the network to be temporary unavailable. + + Raises InterfaceError on errors. + """ + counter = 0 + span = None + + if self._tracer: + span = self._tracer.start_span( + name=CONNECTION_SPAN_NAME, kind=trace.SpanKind.CLIENT + ) + + try: + while counter != attempts: + counter = counter + 1 + try: + self.disconnect() + self.connect() + if self.is_connected(): + break + except (Error, IOError) as err: + if counter == attempts: + msg = ( + f"Can not reconnect to MySQL after {attempts} " + f"attempt(s): {err}" + ) + raise InterfaceError(msg) from err + if delay > 0: + sleep(delay) + except InterfaceError as interface_err: + if OTEL_ENABLED: + set_connection_span_attrs(self, span) + record_exception_event(span, interface_err) + end_span(span) + raise + + self._span = span + if OTEL_ENABLED: + set_connection_span_attrs(self, self._span) + + @abstractmethod + def is_connected(self) -> Any: + """Reports whether the connection to MySQL Server is available""" + + @abstractmethod + def ping(self, reconnect: bool = False, attempts: int = 1, delay: int = 0) -> Any: + """Check availability of the MySQL server""" + + @abstractmethod + def commit(self) -> Any: + """Commit current transaction""" + + @abstractmethod + def cursor( + self, + buffered: Optional[bool] = None, + raw: Optional[bool] = None, + prepared: Optional[bool] = None, + cursor_class: Optional[type] = None, + dictionary: Optional[bool] = None, + named_tuple: Optional[bool] = None, + ) -> "MySQLCursorAbstract": + """Instantiates and returns a cursor""" + + @abstractmethod + def _execute_query(self, query: Any) -> Any: + """Execute a query""" + + @abstractmethod + def rollback(self) -> Any: + """Rollback current transaction""" + + def start_transaction( + self, + consistent_snapshot: bool = False, + isolation_level: Optional[str] = None, + readonly: Optional[bool] = None, + ) -> None: + """Start a transaction + + This method explicitly starts a transaction sending the + START TRANSACTION statement to the MySQL server. You can optionally + set whether there should be a consistent snapshot, which + isolation level you need or which access mode i.e. READ ONLY or + READ WRITE. + + For example, to start a transaction with isolation level SERIALIZABLE, + you would do the following: + >>> cnx = mysql.connector.connect(..) + >>> cnx.start_transaction(isolation_level='SERIALIZABLE') + + Raises ProgrammingError when a transaction is already in progress + and when ValueError when isolation_level specifies an Unknown + level. + """ + if self.in_transaction: + raise ProgrammingError("Transaction already in progress") + + if isolation_level: + level = isolation_level.strip().replace("-", " ").upper() + levels = [ + "READ UNCOMMITTED", + "READ COMMITTED", + "REPEATABLE READ", + "SERIALIZABLE", + ] + + if level not in levels: + raise ValueError(f'Unknown isolation level "{isolation_level}"') + + self._execute_query(f"SET TRANSACTION ISOLATION LEVEL {level}") + + if readonly is not None: + if self._server_version < (5, 6, 5): + raise ValueError( + f"MySQL server version {self._server_version} does not " + "support this feature" + ) + + if readonly: + access_mode = "READ ONLY" + else: + access_mode = "READ WRITE" + self._execute_query(f"SET TRANSACTION {access_mode}") + + query = "START TRANSACTION" + if consistent_snapshot: + query += " WITH CONSISTENT SNAPSHOT" + self.cmd_query(query) + + def reset_session( + self, + user_variables: Optional[Dict[str, Any]] = None, + session_variables: Optional[Dict[str, Any]] = None, + ) -> None: + """Clears the current active session + + This method resets the session state, if the MySQL server is 5.7.3 + or later active session will be reset without re-authenticating. + For other server versions session will be reset by re-authenticating. + + It is possible to provide a sequence of variables and their values to + be set after clearing the session. This is possible for both user + defined variables and session variables. + This method takes two arguments user_variables and session_variables + which are dictionaries. + + Raises OperationalError if not connected, InternalError if there are + unread results and InterfaceError on errors. + """ + if not self.is_connected(): + raise OperationalError("MySQL Connection not available") + + try: + self.cmd_reset_connection() + except (NotSupportedError, NotImplementedError): + if self._compress: + raise NotSupportedError( + "Reset session is not supported with compression for " + "MySQL server version 5.7.2 or earlier" + ) from None + self.cmd_change_user( + self._user, + self._password, + self._database, + self._charset_id, + ) + + if user_variables or session_variables: + cur = self.cursor() + if user_variables: + for key, value in user_variables.items(): + cur.execute(f"SET @`{key}` = {value}") + if session_variables: + for key, value in session_variables.items(): + cur.execute(f"SET SESSION `{key}` = {value}") + cur.close() + + def set_converter_class(self, convclass: Optional[Type[MySQLConverter]]) -> None: + """ + Set the converter class to be used. This should be a class overloading + methods and members of conversion.MySQLConverter. + """ + if convclass and issubclass(convclass, MySQLConverterBase): + charset_name = CharacterSet.get_info(self._charset_id)[0] + self._converter_class = convclass + self.converter = convclass(charset_name, self._use_unicode) + self.converter.str_fallback = self._converter_str_fallback + else: + raise TypeError( + "Converter class should be a subclass of conversion.MySQLConverterBase." + ) + + @abstractmethod + def get_rows( + self, + count: Optional[int] = None, + binary: bool = False, + columns: Optional[List[DescriptionType]] = None, + raw: Optional[bool] = None, + prep_stmt: Any = None, + ) -> Tuple[List[Any], Optional[Mapping[str, Any]]]: + """Get all rows returned by the MySQL server""" + + def cmd_init_db(self, database: str) -> Optional[Mapping[str, Any]]: + """Change the current database""" + raise NotImplementedError + + def cmd_query( + self, + query: Any, + raw: bool = False, + buffered: bool = False, + raw_as_string: bool = False, + ) -> Optional[Mapping[str, Any]]: + """Send a query to the MySQL server""" + raise NotImplementedError + + def cmd_query_iter( + self, statements: Any + ) -> Generator[Mapping[str, Any], None, None]: + """Send one or more statements to the MySQL server""" + raise NotImplementedError + + def cmd_refresh(self, options: int) -> Optional[Mapping[str, Any]]: + """Send the Refresh command to the MySQL server""" + raise NotImplementedError + + def cmd_quit(self) -> Any: + """Close the current connection with the server""" + raise NotImplementedError + + def cmd_shutdown( + self, shutdown_type: Optional[int] = None + ) -> Optional[Mapping[str, Any]]: + """Shut down the MySQL Server""" + raise NotImplementedError + + def cmd_statistics(self) -> Optional[Mapping[str, Any]]: + """Send the statistics command to the MySQL Server""" + raise NotImplementedError + + @staticmethod + def cmd_process_info() -> Any: + """Get the process list of the MySQL Server + + This method is a placeholder to notify that the PROCESS_INFO command + is not supported by raising the NotSupportedError. The command + "SHOW PROCESSLIST" should be send using the cmd_query()-method or + using the INFORMATION_SCHEMA database. + + Raises NotSupportedError exception + """ + raise NotSupportedError( + "Not implemented. Use SHOW PROCESSLIST or INFORMATION_SCHEMA" + ) + + def cmd_process_kill(self, mysql_pid: int) -> Optional[Mapping[str, Any]]: + """Kill a MySQL process""" + raise NotImplementedError + + def cmd_debug(self) -> Optional[Mapping[str, Any]]: + """Send the DEBUG command""" + raise NotImplementedError + + def cmd_ping(self) -> Optional[Mapping[str, Any]]: + """Send the PING command""" + raise NotImplementedError + + def cmd_change_user( + self, + username: str = "", + password: str = "", + database: str = "", + charset: int = 45, + password1: str = "", + password2: str = "", + password3: str = "", + oci_config_file: str = "", + ) -> Optional[Mapping[str, Any]]: + """Change the current logged in user""" + raise NotImplementedError + + def cmd_stmt_prepare(self, statement: Any) -> Optional[Mapping[str, Any]]: + """Prepare a MySQL statement""" + raise NotImplementedError + + def cmd_stmt_execute( + self, + statement_id: Any, + data: Sequence[Any] = (), + parameters: Sequence[Any] = (), + flags: int = 0, + ) -> Any: + """Execute a prepared MySQL statement""" + raise NotImplementedError + + def cmd_stmt_close(self, statement_id: Any) -> Any: + """Deallocate a prepared MySQL statement""" + raise NotImplementedError + + def cmd_stmt_send_long_data( + self, statement_id: Any, param_id: int, data: BinaryIO + ) -> Any: + """Send data for a column""" + raise NotImplementedError + + def cmd_stmt_reset(self, statement_id: Any) -> Any: + """Reset data for prepared statement sent as long data""" + raise NotImplementedError + + def cmd_reset_connection(self) -> Any: + """Resets the session state without re-authenticating""" + raise NotImplementedError + + +class MySQLCursorAbstract(ABC): + """Abstract cursor class + + Abstract class defining cursor class with method and members + required by the Python Database API Specification v2.0. + """ + + def __init__(self) -> None: + """Initialization""" + self._description: Optional[List[DescriptionType]] = None + self._rowcount: int = -1 + self._last_insert_id: Optional[int] = None + self._warnings: Optional[List[WarningType]] = None + self._warning_count: int = 0 + self._executed: Optional[StrOrBytes] = None + self._executed_list: List[StrOrBytes] = [] + self._stored_results: List[Any] = [] + self.arraysize: int = 1 + + def __enter__(self) -> MySQLCursorAbstract: + return self + + def __exit__( + self, + exc_type: Type[BaseException], + exc_value: BaseException, + traceback: TracebackType, + ) -> None: + self.close() + + @abstractmethod + def callproc(self, procname: str, args: Sequence[Any] = ()) -> Any: + """Calls a stored procedure with the given arguments + + The arguments will be set during this session, meaning + they will be called like ___arg where + is an enumeration (+1) of the arguments. + + Coding Example: + 1) Defining the Stored Routine in MySQL: + CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT) + BEGIN + SET pProd := pFac1 * pFac2; + END + + 2) Executing in Python: + args = (5,5,0) # 0 is to hold pprod + cursor.callproc('multiply', args) + print(cursor.fetchone()) + + Does not return a value, but a result set will be + available when the CALL-statement execute successfully. + Raises exceptions when something is wrong. + """ + + @abstractmethod + def close(self) -> Any: + """Close the cursor.""" + + @abstractmethod + def execute( + self, + operation: str, + params: Union[Sequence[Any], Dict[str, Any]] = (), + multi: bool = False, + ) -> Any: + """Executes the given operation + + Executes the given operation substituting any markers with + the given parameters. + + For example, getting all rows where id is 5: + cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,)) + + The multi argument should be set to True when executing multiple + statements in one operation. If not set and multiple results are + found, an InterfaceError will be raised. + + If warnings where generated, and connection.get_warnings is True, then + self._warnings will be a list containing these warnings. + + Returns an iterator when multi is True, otherwise None. + """ + + @abstractmethod + def executemany( + self, operation: str, seq_params: Sequence[Union[Sequence[Any], Dict[str, Any]]] + ) -> Any: + """Execute the given operation multiple times + + The executemany() method will execute the operation iterating + over the list of parameters in seq_params. + + Example: Inserting 3 new employees and their phone number + + data = [ + ('Jane','555-001'), + ('Joe', '555-001'), + ('John', '555-003') + ] + stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s')" + cursor.executemany(stmt, data) + + INSERT statements are optimized by batching the data, that is + using the MySQL multiple rows syntax. + + Results are discarded. If they are needed, consider looping over + data using the execute() method. + """ + + @abstractmethod + def fetchone(self) -> Optional[Sequence[Any]]: + """Returns next row of a query result set + + Returns a tuple or None. + """ + + @abstractmethod + def fetchmany(self, size: int = 1) -> List[Sequence[Any]]: + """Returns the next set of rows of a query result, returning a + list of tuples. When no more rows are available, it returns an + empty list. + + The number of rows returned can be specified using the size argument, + which defaults to one + """ + + @abstractmethod + def fetchall(self) -> Sequence[Any]: + """Returns all rows of a query result set + + Returns a list of tuples. + """ + + def nextset(self) -> Any: + """Not Implemented.""" + + def setinputsizes(self, sizes: Any) -> Any: + """Not Implemented.""" + + def setoutputsize(self, size: Any, column: Any = None) -> Any: + """Not Implemented.""" + + def reset(self, free: bool = True) -> Any: + """Reset the cursor to default""" + + @property + @abstractmethod + def description( + self, + ) -> Optional[List[DescriptionType]]: + """Returns description of columns in a result + + This property returns a list of tuples describing the columns in + in a result set. A tuple is described as follows:: + + (column_name, + type, + None, + None, + None, + None, + null_ok, + column_flags) # Addition to PEP-249 specs + + Returns a list of tuples. + """ + return self._description + + @property + @abstractmethod + def rowcount(self) -> int: + """Returns the number of rows produced or affected + + This property returns the number of rows produced by queries + such as a SELECT, or affected rows when executing DML statements + like INSERT or UPDATE. + + Note that for non-buffered cursors it is impossible to know the + number of rows produced before having fetched them all. For those, + the number of rows will be -1 right after execution, and + incremented when fetching rows. + + Returns an integer. + """ + return self._rowcount + + @property + def lastrowid(self) -> Optional[int]: + """Returns the value generated for an AUTO_INCREMENT column + + Returns the value generated for an AUTO_INCREMENT column by + the previous INSERT or UPDATE statement or None when there is + no such value available. + + Returns a long value or None. + """ + return self._last_insert_id + + @property + def warnings(self) -> Optional[List[WarningType]]: + """Return warnings.""" + return self._warnings + + @property + def warning_count(self) -> int: + """Returns the number of warnings + + This property returns the number of warnings generated by the + previously executed operation. + + Returns an integer value. + """ + return self._warning_count + + def fetchwarnings(self) -> Optional[List[WarningType]]: + """Returns Warnings.""" + return self._warnings + + def get_attributes(self) -> Optional[List[Tuple[str, Any]]]: + """Get the added query attributes so far.""" + if hasattr(self, "_cnx"): + return self._cnx.query_attrs + if hasattr(self, "_connection"): + return self._connection.query_attrs + return None + + def add_attribute(self, name: str, value: Any) -> None: + """Add a query attribute and his value.""" + if not isinstance(name, str): + raise ProgrammingError("Parameter `name` must be a string type") + if value is not None and not isinstance(value, MYSQL_PY_TYPES): + raise ProgrammingError( + f"Object {value} cannot be converted to a MySQL type" + ) + if hasattr(self, "_cnx"): + self._cnx.query_attrs_append((name, value)) + elif hasattr(self, "_connection"): + self._connection.query_attrs_append((name, value)) + + def remove_attribute(self, name: str) -> Any: + """Remove a query attribute by name. + + If no match, `None` is returned; else the corresponding value is returned. + """ + if not isinstance(name, str): + raise ProgrammingError("Parameter `name` must be a string type") + if hasattr(self, "_cnx"): + return self._cnx.query_attrs_remove(name) + if hasattr(self, "_connection"): + return self._connection.query_attrs_remove(name) + return None + + def clear_attributes(self) -> None: + """Remove all the query attributes.""" + if hasattr(self, "_cnx"): + self._cnx.query_attrs_clear() + elif hasattr(self, "_connection"): + self._connection.query_attrs_clear() diff --git a/mysql/connector/authentication.py b/mysql/connector/authentication.py new file mode 100644 index 0000000..d885684 --- /dev/null +++ b/mysql/connector/authentication.py @@ -0,0 +1,77 @@ +# Copyright (c) 2014, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Implementing support for MySQL Authentication Plugins""" + +import importlib + +from functools import lru_cache +from typing import Optional, Type + +from .errors import NotSupportedError, ProgrammingError +from .logger import logger +from .plugins import BaseAuthPlugin + +DEFAULT_PLUGINS_PKG = "mysql.connector.plugins" + + +@lru_cache(maxsize=10, typed=False) +def get_auth_plugin( + plugin_name: str, + auth_plugin_class: Optional[str] = None, +) -> Type[BaseAuthPlugin]: # AUTH_PLUGIN_CLASS_TYPES: + """Return authentication class based on plugin name + + This function returns the class for the authentication plugin plugin_name. + The returned class is a subclass of BaseAuthPlugin. + + Args: + plugin_name (str): Authentication plugin name. + auth_plugin_class (str): Authentication plugin class name. + + Raises: + NotSupportedError: When plugin_name is not supported. + + Returns: + Subclass of `BaseAuthPlugin`. + """ + package = DEFAULT_PLUGINS_PKG + if plugin_name: + try: + logger.info("package: %s", package) + logger.info("plugin_name: %s", plugin_name) + plugin_module = importlib.import_module(f".{plugin_name}", package) + if not auth_plugin_class or not hasattr(plugin_module, auth_plugin_class): + auth_plugin_class = plugin_module.AUTHENTICATION_PLUGIN_CLASS + logger.info("AUTHENTICATION_PLUGIN_CLASS: %s", auth_plugin_class) + return getattr(plugin_module, auth_plugin_class) + except ModuleNotFoundError as err: + logger.warning("Requested Module was not found: %s", err) + except ValueError as err: + raise ProgrammingError(f"Invalid module name: {err}") from err + raise NotSupportedError(f"Authentication plugin '{plugin_name}' is not supported") diff --git a/mysql/connector/charsets.py b/mysql/connector/charsets.py new file mode 100644 index 0000000..5ce9b1a --- /dev/null +++ b/mysql/connector/charsets.py @@ -0,0 +1,620 @@ +# -*- coding: utf-8 -*- # pylint: disable=missing-module-docstring + +# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +from typing import List, Optional, Tuple + +"""This module contains the MySQL Server Character Sets.""" # pylint: disable=pointless-string-statement + +# This file was auto-generated. +_GENERATED_ON: str = "2022-05-09" +_MYSQL_VERSION: Tuple[int, int, int] = (8, 0, 30) + +MYSQL_CHARACTER_SETS: List[Optional[Tuple[str, str, bool]]] = [ + # (character set name, collation, default) + None, + ("big5", "big5_chinese_ci", True), # 1 + ("latin2", "latin2_czech_cs", False), # 2 + ("dec8", "dec8_swedish_ci", True), # 3 + ("cp850", "cp850_general_ci", True), # 4 + ("latin1", "latin1_german1_ci", False), # 5 + ("hp8", "hp8_english_ci", True), # 6 + ("koi8r", "koi8r_general_ci", True), # 7 + ("latin1", "latin1_swedish_ci", True), # 8 + ("latin2", "latin2_general_ci", True), # 9 + ("swe7", "swe7_swedish_ci", True), # 10 + ("ascii", "ascii_general_ci", True), # 11 + ("ujis", "ujis_japanese_ci", True), # 12 + ("sjis", "sjis_japanese_ci", True), # 13 + ("cp1251", "cp1251_bulgarian_ci", False), # 14 + ("latin1", "latin1_danish_ci", False), # 15 + ("hebrew", "hebrew_general_ci", True), # 16 + None, + ("tis620", "tis620_thai_ci", True), # 18 + ("euckr", "euckr_korean_ci", True), # 19 + ("latin7", "latin7_estonian_cs", False), # 20 + ("latin2", "latin2_hungarian_ci", False), # 21 + ("koi8u", "koi8u_general_ci", True), # 22 + ("cp1251", "cp1251_ukrainian_ci", False), # 23 + ("gb2312", "gb2312_chinese_ci", True), # 24 + ("greek", "greek_general_ci", True), # 25 + ("cp1250", "cp1250_general_ci", True), # 26 + ("latin2", "latin2_croatian_ci", False), # 27 + ("gbk", "gbk_chinese_ci", True), # 28 + ("cp1257", "cp1257_lithuanian_ci", False), # 29 + ("latin5", "latin5_turkish_ci", True), # 30 + ("latin1", "latin1_german2_ci", False), # 31 + ("armscii8", "armscii8_general_ci", True), # 32 + ("utf8mb3", "utf8mb3_general_ci", True), # 33 + ("cp1250", "cp1250_czech_cs", False), # 34 + ("ucs2", "ucs2_general_ci", True), # 35 + ("cp866", "cp866_general_ci", True), # 36 + ("keybcs2", "keybcs2_general_ci", True), # 37 + ("macce", "macce_general_ci", True), # 38 + ("macroman", "macroman_general_ci", True), # 39 + ("cp852", "cp852_general_ci", True), # 40 + ("latin7", "latin7_general_ci", True), # 41 + ("latin7", "latin7_general_cs", False), # 42 + ("macce", "macce_bin", False), # 43 + ("cp1250", "cp1250_croatian_ci", False), # 44 + ("utf8mb4", "utf8mb4_general_ci", False), # 45 + ("utf8mb4", "utf8mb4_bin", False), # 46 + ("latin1", "latin1_bin", False), # 47 + ("latin1", "latin1_general_ci", False), # 48 + ("latin1", "latin1_general_cs", False), # 49 + ("cp1251", "cp1251_bin", False), # 50 + ("cp1251", "cp1251_general_ci", True), # 51 + ("cp1251", "cp1251_general_cs", False), # 52 + ("macroman", "macroman_bin", False), # 53 + ("utf16", "utf16_general_ci", True), # 54 + ("utf16", "utf16_bin", False), # 55 + ("utf16le", "utf16le_general_ci", True), # 56 + ("cp1256", "cp1256_general_ci", True), # 57 + ("cp1257", "cp1257_bin", False), # 58 + ("cp1257", "cp1257_general_ci", True), # 59 + ("utf32", "utf32_general_ci", True), # 60 + ("utf32", "utf32_bin", False), # 61 + ("utf16le", "utf16le_bin", False), # 62 + ("binary", "binary", True), # 63 + ("armscii8", "armscii8_bin", False), # 64 + ("ascii", "ascii_bin", False), # 65 + ("cp1250", "cp1250_bin", False), # 66 + ("cp1256", "cp1256_bin", False), # 67 + ("cp866", "cp866_bin", False), # 68 + ("dec8", "dec8_bin", False), # 69 + ("greek", "greek_bin", False), # 70 + ("hebrew", "hebrew_bin", False), # 71 + ("hp8", "hp8_bin", False), # 72 + ("keybcs2", "keybcs2_bin", False), # 73 + ("koi8r", "koi8r_bin", False), # 74 + ("koi8u", "koi8u_bin", False), # 75 + ("utf8mb3", "utf8mb3_tolower_ci", False), # 76 + ("latin2", "latin2_bin", False), # 77 + ("latin5", "latin5_bin", False), # 78 + ("latin7", "latin7_bin", False), # 79 + ("cp850", "cp850_bin", False), # 80 + ("cp852", "cp852_bin", False), # 81 + ("swe7", "swe7_bin", False), # 82 + ("utf8mb3", "utf8mb3_bin", False), # 83 + ("big5", "big5_bin", False), # 84 + ("euckr", "euckr_bin", False), # 85 + ("gb2312", "gb2312_bin", False), # 86 + ("gbk", "gbk_bin", False), # 87 + ("sjis", "sjis_bin", False), # 88 + ("tis620", "tis620_bin", False), # 89 + ("ucs2", "ucs2_bin", False), # 90 + ("ujis", "ujis_bin", False), # 91 + ("geostd8", "geostd8_general_ci", True), # 92 + ("geostd8", "geostd8_bin", False), # 93 + ("latin1", "latin1_spanish_ci", False), # 94 + ("cp932", "cp932_japanese_ci", True), # 95 + ("cp932", "cp932_bin", False), # 96 + ("eucjpms", "eucjpms_japanese_ci", True), # 97 + ("eucjpms", "eucjpms_bin", False), # 98 + ("cp1250", "cp1250_polish_ci", False), # 99 + None, + ("utf16", "utf16_unicode_ci", False), # 101 + ("utf16", "utf16_icelandic_ci", False), # 102 + ("utf16", "utf16_latvian_ci", False), # 103 + ("utf16", "utf16_romanian_ci", False), # 104 + ("utf16", "utf16_slovenian_ci", False), # 105 + ("utf16", "utf16_polish_ci", False), # 106 + ("utf16", "utf16_estonian_ci", False), # 107 + ("utf16", "utf16_spanish_ci", False), # 108 + ("utf16", "utf16_swedish_ci", False), # 109 + ("utf16", "utf16_turkish_ci", False), # 110 + ("utf16", "utf16_czech_ci", False), # 111 + ("utf16", "utf16_danish_ci", False), # 112 + ("utf16", "utf16_lithuanian_ci", False), # 113 + ("utf16", "utf16_slovak_ci", False), # 114 + ("utf16", "utf16_spanish2_ci", False), # 115 + ("utf16", "utf16_roman_ci", False), # 116 + ("utf16", "utf16_persian_ci", False), # 117 + ("utf16", "utf16_esperanto_ci", False), # 118 + ("utf16", "utf16_hungarian_ci", False), # 119 + ("utf16", "utf16_sinhala_ci", False), # 120 + ("utf16", "utf16_german2_ci", False), # 121 + ("utf16", "utf16_croatian_ci", False), # 122 + ("utf16", "utf16_unicode_520_ci", False), # 123 + ("utf16", "utf16_vietnamese_ci", False), # 124 + None, + None, + None, + ("ucs2", "ucs2_unicode_ci", False), # 128 + ("ucs2", "ucs2_icelandic_ci", False), # 129 + ("ucs2", "ucs2_latvian_ci", False), # 130 + ("ucs2", "ucs2_romanian_ci", False), # 131 + ("ucs2", "ucs2_slovenian_ci", False), # 132 + ("ucs2", "ucs2_polish_ci", False), # 133 + ("ucs2", "ucs2_estonian_ci", False), # 134 + ("ucs2", "ucs2_spanish_ci", False), # 135 + ("ucs2", "ucs2_swedish_ci", False), # 136 + ("ucs2", "ucs2_turkish_ci", False), # 137 + ("ucs2", "ucs2_czech_ci", False), # 138 + ("ucs2", "ucs2_danish_ci", False), # 139 + ("ucs2", "ucs2_lithuanian_ci", False), # 140 + ("ucs2", "ucs2_slovak_ci", False), # 141 + ("ucs2", "ucs2_spanish2_ci", False), # 142 + ("ucs2", "ucs2_roman_ci", False), # 143 + ("ucs2", "ucs2_persian_ci", False), # 144 + ("ucs2", "ucs2_esperanto_ci", False), # 145 + ("ucs2", "ucs2_hungarian_ci", False), # 146 + ("ucs2", "ucs2_sinhala_ci", False), # 147 + ("ucs2", "ucs2_german2_ci", False), # 148 + ("ucs2", "ucs2_croatian_ci", False), # 149 + ("ucs2", "ucs2_unicode_520_ci", False), # 150 + ("ucs2", "ucs2_vietnamese_ci", False), # 151 + None, + None, + None, + None, + None, + None, + None, + ("ucs2", "ucs2_general_mysql500_ci", False), # 159 + ("utf32", "utf32_unicode_ci", False), # 160 + ("utf32", "utf32_icelandic_ci", False), # 161 + ("utf32", "utf32_latvian_ci", False), # 162 + ("utf32", "utf32_romanian_ci", False), # 163 + ("utf32", "utf32_slovenian_ci", False), # 164 + ("utf32", "utf32_polish_ci", False), # 165 + ("utf32", "utf32_estonian_ci", False), # 166 + ("utf32", "utf32_spanish_ci", False), # 167 + ("utf32", "utf32_swedish_ci", False), # 168 + ("utf32", "utf32_turkish_ci", False), # 169 + ("utf32", "utf32_czech_ci", False), # 170 + ("utf32", "utf32_danish_ci", False), # 171 + ("utf32", "utf32_lithuanian_ci", False), # 172 + ("utf32", "utf32_slovak_ci", False), # 173 + ("utf32", "utf32_spanish2_ci", False), # 174 + ("utf32", "utf32_roman_ci", False), # 175 + ("utf32", "utf32_persian_ci", False), # 176 + ("utf32", "utf32_esperanto_ci", False), # 177 + ("utf32", "utf32_hungarian_ci", False), # 178 + ("utf32", "utf32_sinhala_ci", False), # 179 + ("utf32", "utf32_german2_ci", False), # 180 + ("utf32", "utf32_croatian_ci", False), # 181 + ("utf32", "utf32_unicode_520_ci", False), # 182 + ("utf32", "utf32_vietnamese_ci", False), # 183 + None, + None, + None, + None, + None, + None, + None, + None, + ("utf8mb3", "utf8mb3_unicode_ci", False), # 192 + ("utf8mb3", "utf8mb3_icelandic_ci", False), # 193 + ("utf8mb3", "utf8mb3_latvian_ci", False), # 194 + ("utf8mb3", "utf8mb3_romanian_ci", False), # 195 + ("utf8mb3", "utf8mb3_slovenian_ci", False), # 196 + ("utf8mb3", "utf8mb3_polish_ci", False), # 197 + ("utf8mb3", "utf8mb3_estonian_ci", False), # 198 + ("utf8mb3", "utf8mb3_spanish_ci", False), # 199 + ("utf8mb3", "utf8mb3_swedish_ci", False), # 200 + ("utf8mb3", "utf8mb3_turkish_ci", False), # 201 + ("utf8mb3", "utf8mb3_czech_ci", False), # 202 + ("utf8mb3", "utf8mb3_danish_ci", False), # 203 + ("utf8mb3", "utf8mb3_lithuanian_ci", False), # 204 + ("utf8mb3", "utf8mb3_slovak_ci", False), # 205 + ("utf8mb3", "utf8mb3_spanish2_ci", False), # 206 + ("utf8mb3", "utf8mb3_roman_ci", False), # 207 + ("utf8mb3", "utf8mb3_persian_ci", False), # 208 + ("utf8mb3", "utf8mb3_esperanto_ci", False), # 209 + ("utf8mb3", "utf8mb3_hungarian_ci", False), # 210 + ("utf8mb3", "utf8mb3_sinhala_ci", False), # 211 + ("utf8mb3", "utf8mb3_german2_ci", False), # 212 + ("utf8mb3", "utf8mb3_croatian_ci", False), # 213 + ("utf8mb3", "utf8mb3_unicode_520_ci", False), # 214 + ("utf8mb3", "utf8mb3_vietnamese_ci", False), # 215 + None, + None, + None, + None, + None, + None, + None, + ("utf8mb3", "utf8mb3_general_mysql500_ci", False), # 223 + ("utf8mb4", "utf8mb4_unicode_ci", False), # 224 + ("utf8mb4", "utf8mb4_icelandic_ci", False), # 225 + ("utf8mb4", "utf8mb4_latvian_ci", False), # 226 + ("utf8mb4", "utf8mb4_romanian_ci", False), # 227 + ("utf8mb4", "utf8mb4_slovenian_ci", False), # 228 + ("utf8mb4", "utf8mb4_polish_ci", False), # 229 + ("utf8mb4", "utf8mb4_estonian_ci", False), # 230 + ("utf8mb4", "utf8mb4_spanish_ci", False), # 231 + ("utf8mb4", "utf8mb4_swedish_ci", False), # 232 + ("utf8mb4", "utf8mb4_turkish_ci", False), # 233 + ("utf8mb4", "utf8mb4_czech_ci", False), # 234 + ("utf8mb4", "utf8mb4_danish_ci", False), # 235 + ("utf8mb4", "utf8mb4_lithuanian_ci", False), # 236 + ("utf8mb4", "utf8mb4_slovak_ci", False), # 237 + ("utf8mb4", "utf8mb4_spanish2_ci", False), # 238 + ("utf8mb4", "utf8mb4_roman_ci", False), # 239 + ("utf8mb4", "utf8mb4_persian_ci", False), # 240 + ("utf8mb4", "utf8mb4_esperanto_ci", False), # 241 + ("utf8mb4", "utf8mb4_hungarian_ci", False), # 242 + ("utf8mb4", "utf8mb4_sinhala_ci", False), # 243 + ("utf8mb4", "utf8mb4_german2_ci", False), # 244 + ("utf8mb4", "utf8mb4_croatian_ci", False), # 245 + ("utf8mb4", "utf8mb4_unicode_520_ci", False), # 246 + ("utf8mb4", "utf8mb4_vietnamese_ci", False), # 247 + ("gb18030", "gb18030_chinese_ci", True), # 248 + ("gb18030", "gb18030_bin", False), # 249 + ("gb18030", "gb18030_unicode_520_ci", False), # 250 + None, + None, + None, + None, + ("utf8mb4", "utf8mb4_0900_ai_ci", True), # 255 + ("utf8mb4", "utf8mb4_de_pb_0900_ai_ci", False), # 256 + ("utf8mb4", "utf8mb4_is_0900_ai_ci", False), # 257 + ("utf8mb4", "utf8mb4_lv_0900_ai_ci", False), # 258 + ("utf8mb4", "utf8mb4_ro_0900_ai_ci", False), # 259 + ("utf8mb4", "utf8mb4_sl_0900_ai_ci", False), # 260 + ("utf8mb4", "utf8mb4_pl_0900_ai_ci", False), # 261 + ("utf8mb4", "utf8mb4_et_0900_ai_ci", False), # 262 + ("utf8mb4", "utf8mb4_es_0900_ai_ci", False), # 263 + ("utf8mb4", "utf8mb4_sv_0900_ai_ci", False), # 264 + ("utf8mb4", "utf8mb4_tr_0900_ai_ci", False), # 265 + ("utf8mb4", "utf8mb4_cs_0900_ai_ci", False), # 266 + ("utf8mb4", "utf8mb4_da_0900_ai_ci", False), # 267 + ("utf8mb4", "utf8mb4_lt_0900_ai_ci", False), # 268 + ("utf8mb4", "utf8mb4_sk_0900_ai_ci", False), # 269 + ("utf8mb4", "utf8mb4_es_trad_0900_ai_ci", False), # 270 + ("utf8mb4", "utf8mb4_la_0900_ai_ci", False), # 271 + None, + ("utf8mb4", "utf8mb4_eo_0900_ai_ci", False), # 273 + ("utf8mb4", "utf8mb4_hu_0900_ai_ci", False), # 274 + ("utf8mb4", "utf8mb4_hr_0900_ai_ci", False), # 275 + None, + ("utf8mb4", "utf8mb4_vi_0900_ai_ci", False), # 277 + ("utf8mb4", "utf8mb4_0900_as_cs", False), # 278 + ("utf8mb4", "utf8mb4_de_pb_0900_as_cs", False), # 279 + ("utf8mb4", "utf8mb4_is_0900_as_cs", False), # 280 + ("utf8mb4", "utf8mb4_lv_0900_as_cs", False), # 281 + ("utf8mb4", "utf8mb4_ro_0900_as_cs", False), # 282 + ("utf8mb4", "utf8mb4_sl_0900_as_cs", False), # 283 + ("utf8mb4", "utf8mb4_pl_0900_as_cs", False), # 284 + ("utf8mb4", "utf8mb4_et_0900_as_cs", False), # 285 + ("utf8mb4", "utf8mb4_es_0900_as_cs", False), # 286 + ("utf8mb4", "utf8mb4_sv_0900_as_cs", False), # 287 + ("utf8mb4", "utf8mb4_tr_0900_as_cs", False), # 288 + ("utf8mb4", "utf8mb4_cs_0900_as_cs", False), # 289 + ("utf8mb4", "utf8mb4_da_0900_as_cs", False), # 290 + ("utf8mb4", "utf8mb4_lt_0900_as_cs", False), # 291 + ("utf8mb4", "utf8mb4_sk_0900_as_cs", False), # 292 + ("utf8mb4", "utf8mb4_es_trad_0900_as_cs", False), # 293 + ("utf8mb4", "utf8mb4_la_0900_as_cs", False), # 294 + None, + ("utf8mb4", "utf8mb4_eo_0900_as_cs", False), # 296 + ("utf8mb4", "utf8mb4_hu_0900_as_cs", False), # 297 + ("utf8mb4", "utf8mb4_hr_0900_as_cs", False), # 298 + None, + ("utf8mb4", "utf8mb4_vi_0900_as_cs", False), # 300 + None, + None, + ("utf8mb4", "utf8mb4_ja_0900_as_cs", False), # 303 + ("utf8mb4", "utf8mb4_ja_0900_as_cs_ks", False), # 304 + ("utf8mb4", "utf8mb4_0900_as_ci", False), # 305 + ("utf8mb4", "utf8mb4_ru_0900_ai_ci", False), # 306 + ("utf8mb4", "utf8mb4_ru_0900_as_cs", False), # 307 + ("utf8mb4", "utf8mb4_zh_0900_as_cs", False), # 308 + ("utf8mb4", "utf8mb4_0900_bin", False), # 309 + ("utf8mb4", "utf8mb4_nb_0900_ai_ci", False), # 310 + ("utf8mb4", "utf8mb4_nb_0900_as_cs", False), # 311 + ("utf8mb4", "utf8mb4_nn_0900_ai_ci", False), # 312 + ("utf8mb4", "utf8mb4_nn_0900_as_cs", False), # 313 + ("utf8mb4", "utf8mb4_sr_latn_0900_ai_ci", False), # 314 + ("utf8mb4", "utf8mb4_sr_latn_0900_as_cs", False), # 315 + ("utf8mb4", "utf8mb4_bs_0900_ai_ci", False), # 316 + ("utf8mb4", "utf8mb4_bs_0900_as_cs", False), # 317 + ("utf8mb4", "utf8mb4_bg_0900_ai_ci", False), # 318 + ("utf8mb4", "utf8mb4_bg_0900_as_cs", False), # 319 + ("utf8mb4", "utf8mb4_gl_0900_ai_ci", False), # 320 + ("utf8mb4", "utf8mb4_gl_0900_as_cs", False), # 321 + ("utf8mb4", "utf8mb4_mn_cyrl_0900_ai_ci", False), # 322 + ("utf8mb4", "utf8mb4_mn_cyrl_0900_as_cs", False), # 323 +] + +MYSQL_CHARACTER_SETS_57: List[Optional[Tuple[str, str, bool]]] = [ + # (character set name, collation, default) + None, + ("big5", "big5_chinese_ci", True), # 1 + ("latin2", "latin2_czech_cs", False), # 2 + ("dec8", "dec8_swedish_ci", True), # 3 + ("cp850", "cp850_general_ci", True), # 4 + ("latin1", "latin1_german1_ci", False), # 5 + ("hp8", "hp8_english_ci", True), # 6 + ("koi8r", "koi8r_general_ci", True), # 7 + ("latin1", "latin1_swedish_ci", True), # 8 + ("latin2", "latin2_general_ci", True), # 9 + ("swe7", "swe7_swedish_ci", True), # 10 + ("ascii", "ascii_general_ci", True), # 11 + ("ujis", "ujis_japanese_ci", True), # 12 + ("sjis", "sjis_japanese_ci", True), # 13 + ("cp1251", "cp1251_bulgarian_ci", False), # 14 + ("latin1", "latin1_danish_ci", False), # 15 + ("hebrew", "hebrew_general_ci", True), # 16 + None, + ("tis620", "tis620_thai_ci", True), # 18 + ("euckr", "euckr_korean_ci", True), # 19 + ("latin7", "latin7_estonian_cs", False), # 20 + ("latin2", "latin2_hungarian_ci", False), # 21 + ("koi8u", "koi8u_general_ci", True), # 22 + ("cp1251", "cp1251_ukrainian_ci", False), # 23 + ("gb2312", "gb2312_chinese_ci", True), # 24 + ("greek", "greek_general_ci", True), # 25 + ("cp1250", "cp1250_general_ci", True), # 26 + ("latin2", "latin2_croatian_ci", False), # 27 + ("gbk", "gbk_chinese_ci", True), # 28 + ("cp1257", "cp1257_lithuanian_ci", False), # 29 + ("latin5", "latin5_turkish_ci", True), # 30 + ("latin1", "latin1_german2_ci", False), # 31 + ("armscii8", "armscii8_general_ci", True), # 32 + ("utf8", "utf8_general_ci", True), # 33 + ("cp1250", "cp1250_czech_cs", False), # 34 + ("ucs2", "ucs2_general_ci", True), # 35 + ("cp866", "cp866_general_ci", True), # 36 + ("keybcs2", "keybcs2_general_ci", True), # 37 + ("macce", "macce_general_ci", True), # 38 + ("macroman", "macroman_general_ci", True), # 39 + ("cp852", "cp852_general_ci", True), # 40 + ("latin7", "latin7_general_ci", True), # 41 + ("latin7", "latin7_general_cs", False), # 42 + ("macce", "macce_bin", False), # 43 + ("cp1250", "cp1250_croatian_ci", False), # 44 + ("utf8mb4", "utf8mb4_general_ci", True), # 45 + ("utf8mb4", "utf8mb4_bin", False), # 46 + ("latin1", "latin1_bin", False), # 47 + ("latin1", "latin1_general_ci", False), # 48 + ("latin1", "latin1_general_cs", False), # 49 + ("cp1251", "cp1251_bin", False), # 50 + ("cp1251", "cp1251_general_ci", True), # 51 + ("cp1251", "cp1251_general_cs", False), # 52 + ("macroman", "macroman_bin", False), # 53 + ("utf16", "utf16_general_ci", True), # 54 + ("utf16", "utf16_bin", False), # 55 + ("utf16le", "utf16le_general_ci", True), # 56 + ("cp1256", "cp1256_general_ci", True), # 57 + ("cp1257", "cp1257_bin", False), # 58 + ("cp1257", "cp1257_general_ci", True), # 59 + ("utf32", "utf32_general_ci", True), # 60 + ("utf32", "utf32_bin", False), # 61 + ("utf16le", "utf16le_bin", False), # 62 + ("binary", "binary", True), # 63 + ("armscii8", "armscii8_bin", False), # 64 + ("ascii", "ascii_bin", False), # 65 + ("cp1250", "cp1250_bin", False), # 66 + ("cp1256", "cp1256_bin", False), # 67 + ("cp866", "cp866_bin", False), # 68 + ("dec8", "dec8_bin", False), # 69 + ("greek", "greek_bin", False), # 70 + ("hebrew", "hebrew_bin", False), # 71 + ("hp8", "hp8_bin", False), # 72 + ("keybcs2", "keybcs2_bin", False), # 73 + ("koi8r", "koi8r_bin", False), # 74 + ("koi8u", "koi8u_bin", False), # 75 + None, + ("latin2", "latin2_bin", False), # 77 + ("latin5", "latin5_bin", False), # 78 + ("latin7", "latin7_bin", False), # 79 + ("cp850", "cp850_bin", False), # 80 + ("cp852", "cp852_bin", False), # 81 + ("swe7", "swe7_bin", False), # 82 + ("utf8", "utf8_bin", False), # 83 + ("big5", "big5_bin", False), # 84 + ("euckr", "euckr_bin", False), # 85 + ("gb2312", "gb2312_bin", False), # 86 + ("gbk", "gbk_bin", False), # 87 + ("sjis", "sjis_bin", False), # 88 + ("tis620", "tis620_bin", False), # 89 + ("ucs2", "ucs2_bin", False), # 90 + ("ujis", "ujis_bin", False), # 91 + ("geostd8", "geostd8_general_ci", True), # 92 + ("geostd8", "geostd8_bin", False), # 93 + ("latin1", "latin1_spanish_ci", False), # 94 + ("cp932", "cp932_japanese_ci", True), # 95 + ("cp932", "cp932_bin", False), # 96 + ("eucjpms", "eucjpms_japanese_ci", True), # 97 + ("eucjpms", "eucjpms_bin", False), # 98 + ("cp1250", "cp1250_polish_ci", False), # 99 + None, + ("utf16", "utf16_unicode_ci", False), # 101 + ("utf16", "utf16_icelandic_ci", False), # 102 + ("utf16", "utf16_latvian_ci", False), # 103 + ("utf16", "utf16_romanian_ci", False), # 104 + ("utf16", "utf16_slovenian_ci", False), # 105 + ("utf16", "utf16_polish_ci", False), # 106 + ("utf16", "utf16_estonian_ci", False), # 107 + ("utf16", "utf16_spanish_ci", False), # 108 + ("utf16", "utf16_swedish_ci", False), # 109 + ("utf16", "utf16_turkish_ci", False), # 110 + ("utf16", "utf16_czech_ci", False), # 111 + ("utf16", "utf16_danish_ci", False), # 112 + ("utf16", "utf16_lithuanian_ci", False), # 113 + ("utf16", "utf16_slovak_ci", False), # 114 + ("utf16", "utf16_spanish2_ci", False), # 115 + ("utf16", "utf16_roman_ci", False), # 116 + ("utf16", "utf16_persian_ci", False), # 117 + ("utf16", "utf16_esperanto_ci", False), # 118 + ("utf16", "utf16_hungarian_ci", False), # 119 + ("utf16", "utf16_sinhala_ci", False), # 120 + ("utf16", "utf16_german2_ci", False), # 121 + ("utf16", "utf16_croatian_ci", False), # 122 + ("utf16", "utf16_unicode_520_ci", False), # 123 + ("utf16", "utf16_vietnamese_ci", False), # 124 + None, + None, + None, + ("ucs2", "ucs2_unicode_ci", False), # 128 + ("ucs2", "ucs2_icelandic_ci", False), # 129 + ("ucs2", "ucs2_latvian_ci", False), # 130 + ("ucs2", "ucs2_romanian_ci", False), # 131 + ("ucs2", "ucs2_slovenian_ci", False), # 132 + ("ucs2", "ucs2_polish_ci", False), # 133 + ("ucs2", "ucs2_estonian_ci", False), # 134 + ("ucs2", "ucs2_spanish_ci", False), # 135 + ("ucs2", "ucs2_swedish_ci", False), # 136 + ("ucs2", "ucs2_turkish_ci", False), # 137 + ("ucs2", "ucs2_czech_ci", False), # 138 + ("ucs2", "ucs2_danish_ci", False), # 139 + ("ucs2", "ucs2_lithuanian_ci", False), # 140 + ("ucs2", "ucs2_slovak_ci", False), # 141 + ("ucs2", "ucs2_spanish2_ci", False), # 142 + ("ucs2", "ucs2_roman_ci", False), # 143 + ("ucs2", "ucs2_persian_ci", False), # 144 + ("ucs2", "ucs2_esperanto_ci", False), # 145 + ("ucs2", "ucs2_hungarian_ci", False), # 146 + ("ucs2", "ucs2_sinhala_ci", False), # 147 + ("ucs2", "ucs2_german2_ci", False), # 148 + ("ucs2", "ucs2_croatian_ci", False), # 149 + ("ucs2", "ucs2_unicode_520_ci", False), # 150 + ("ucs2", "ucs2_vietnamese_ci", False), # 151 + None, + None, + None, + None, + None, + None, + None, + ("ucs2", "ucs2_general_mysql500_ci", False), # 159 + ("utf32", "utf32_unicode_ci", False), # 160 + ("utf32", "utf32_icelandic_ci", False), # 161 + ("utf32", "utf32_latvian_ci", False), # 162 + ("utf32", "utf32_romanian_ci", False), # 163 + ("utf32", "utf32_slovenian_ci", False), # 164 + ("utf32", "utf32_polish_ci", False), # 165 + ("utf32", "utf32_estonian_ci", False), # 166 + ("utf32", "utf32_spanish_ci", False), # 167 + ("utf32", "utf32_swedish_ci", False), # 168 + ("utf32", "utf32_turkish_ci", False), # 169 + ("utf32", "utf32_czech_ci", False), # 170 + ("utf32", "utf32_danish_ci", False), # 171 + ("utf32", "utf32_lithuanian_ci", False), # 172 + ("utf32", "utf32_slovak_ci", False), # 173 + ("utf32", "utf32_spanish2_ci", False), # 174 + ("utf32", "utf32_roman_ci", False), # 175 + ("utf32", "utf32_persian_ci", False), # 176 + ("utf32", "utf32_esperanto_ci", False), # 177 + ("utf32", "utf32_hungarian_ci", False), # 178 + ("utf32", "utf32_sinhala_ci", False), # 179 + ("utf32", "utf32_german2_ci", False), # 180 + ("utf32", "utf32_croatian_ci", False), # 181 + ("utf32", "utf32_unicode_520_ci", False), # 182 + ("utf32", "utf32_vietnamese_ci", False), # 183 + None, + None, + None, + None, + None, + None, + None, + None, + ("utf8", "utf8_unicode_ci", False), # 192 + ("utf8", "utf8_icelandic_ci", False), # 193 + ("utf8", "utf8_latvian_ci", False), # 194 + ("utf8", "utf8_romanian_ci", False), # 195 + ("utf8", "utf8_slovenian_ci", False), # 196 + ("utf8", "utf8_polish_ci", False), # 197 + ("utf8", "utf8_estonian_ci", False), # 198 + ("utf8", "utf8_spanish_ci", False), # 199 + ("utf8", "utf8_swedish_ci", False), # 200 + ("utf8", "utf8_turkish_ci", False), # 201 + ("utf8", "utf8_czech_ci", False), # 202 + ("utf8", "utf8_danish_ci", False), # 203 + ("utf8", "utf8_lithuanian_ci", False), # 204 + ("utf8", "utf8_slovak_ci", False), # 205 + ("utf8", "utf8_spanish2_ci", False), # 206 + ("utf8", "utf8_roman_ci", False), # 207 + ("utf8", "utf8_persian_ci", False), # 208 + ("utf8", "utf8_esperanto_ci", False), # 209 + ("utf8", "utf8_hungarian_ci", False), # 210 + ("utf8", "utf8_sinhala_ci", False), # 211 + ("utf8", "utf8_german2_ci", False), # 212 + ("utf8", "utf8_croatian_ci", False), # 213 + ("utf8", "utf8_unicode_520_ci", False), # 214 + ("utf8", "utf8_vietnamese_ci", False), # 215 + None, + None, + None, + None, + None, + None, + None, + ("utf8", "utf8_general_mysql500_ci", False), # 223 + ("utf8mb4", "utf8mb4_unicode_ci", False), # 224 + ("utf8mb4", "utf8mb4_icelandic_ci", False), # 225 + ("utf8mb4", "utf8mb4_latvian_ci", False), # 226 + ("utf8mb4", "utf8mb4_romanian_ci", False), # 227 + ("utf8mb4", "utf8mb4_slovenian_ci", False), # 228 + ("utf8mb4", "utf8mb4_polish_ci", False), # 229 + ("utf8mb4", "utf8mb4_estonian_ci", False), # 230 + ("utf8mb4", "utf8mb4_spanish_ci", False), # 231 + ("utf8mb4", "utf8mb4_swedish_ci", False), # 232 + ("utf8mb4", "utf8mb4_turkish_ci", False), # 233 + ("utf8mb4", "utf8mb4_czech_ci", False), # 234 + ("utf8mb4", "utf8mb4_danish_ci", False), # 235 + ("utf8mb4", "utf8mb4_lithuanian_ci", False), # 236 + ("utf8mb4", "utf8mb4_slovak_ci", False), # 237 + ("utf8mb4", "utf8mb4_spanish2_ci", False), # 238 + ("utf8mb4", "utf8mb4_roman_ci", False), # 239 + ("utf8mb4", "utf8mb4_persian_ci", False), # 240 + ("utf8mb4", "utf8mb4_esperanto_ci", False), # 241 + ("utf8mb4", "utf8mb4_hungarian_ci", False), # 242 + ("utf8mb4", "utf8mb4_sinhala_ci", False), # 243 + ("utf8mb4", "utf8mb4_german2_ci", False), # 244 + ("utf8mb4", "utf8mb4_croatian_ci", False), # 245 + ("utf8mb4", "utf8mb4_unicode_520_ci", False), # 246 + ("utf8mb4", "utf8mb4_vietnamese_ci", False), # 247 + ("gb18030", "gb18030_chinese_ci", True), # 248 + ("gb18030", "gb18030_bin", False), # 249 + ("gb18030", "gb18030_unicode_520_ci", False), # 250 +] diff --git a/mysql/connector/connection.py b/mysql/connector/connection.py new file mode 100644 index 0000000..850f761 --- /dev/null +++ b/mysql/connector/connection.py @@ -0,0 +1,1741 @@ +# Copyright (c) 2009, 2023, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# mypy: disable-error-code="arg-type,operator,attr-defined,assignment" + +"""Implementing communication with MySQL servers.""" + +import datetime +import getpass +import os +import socket +import struct +import sys +import warnings + +from decimal import Decimal +from io import IOBase +from typing import ( + Any, + BinaryIO, + Dict, + Generator, + List, + Mapping, + Optional, + Sequence, + Tuple, + Type, + Union, +) + +from . import version +from .abstracts import MySQLConnectionAbstract +from .authentication import get_auth_plugin +from .constants import ( + CharacterSet, + ClientFlag, + FieldType, + ServerCmd, + ServerFlag, + ShutdownType, + flag_is_set, +) +from .conversion import MySQLConverter +from .cursor import ( + CursorBase, + MySQLCursor, + MySQLCursorBuffered, + MySQLCursorBufferedDict, + MySQLCursorBufferedNamedTuple, + MySQLCursorBufferedRaw, + MySQLCursorDict, + MySQLCursorNamedTuple, + MySQLCursorPrepared, + MySQLCursorPreparedDict, + MySQLCursorPreparedNamedTuple, + MySQLCursorPreparedRaw, + MySQLCursorRaw, +) +from .errors import ( + DatabaseError, + Error, + InterfaceError, + InternalError, + NotSupportedError, + OperationalError, + ProgrammingError, + get_exception, +) +from .logger import logger +from .network import MySQLSocket, MySQLTCPSocket, MySQLUnixSocket +from .opentelemetry.constants import OTEL_ENABLED +from .opentelemetry.context_propagation import with_context_propagation +from .plugins import BaseAuthPlugin +from .protocol import MySQLProtocol +from .types import ( + ConnAttrsType, + DescriptionType, + EofPacketType, + HandShakeType, + OkPacketType, + ResultType, + RowType, + StatsPacketType, + StrOrBytes, + SupportedMysqlBinaryProtocolTypes, +) +from .utils import get_platform, int1store, int4store, lc_int + +if OTEL_ENABLED: + from .opentelemetry.instrumentation import end_span, record_exception_event + + +class MySQLConnection(MySQLConnectionAbstract): + """Connection to a MySQL Server""" + + def __init__(self, **kwargs: Any) -> None: + self._protocol: Optional[MySQLProtocol] = None + self._socket: Optional[MySQLSocket] = None + self._handshake: Optional[HandShakeType] = None + super().__init__() + + self._converter_class: Type[MySQLConverter] = MySQLConverter + + self._client_flags: int = ClientFlag.get_default() + self._charset_id: int = 45 + self._sql_mode: Optional[str] = None + self._time_zone: Optional[str] = None + self._autocommit: bool = False + + self._user: str = "" + self._password: str = "" + self._database: str = "" + self._host: str = "127.0.0.1" + self._port: int = 3306 + self._unix_socket: Optional[str] = None + self._client_host: str = "" + self._client_port: int = 0 + self._ssl: Dict[str, Optional[Union[str, bool, List[str]]]] = {} + self._force_ipv6: bool = False + + self._use_unicode: bool = True + self._get_warnings: bool = False + self._raise_on_warnings: bool = False + self._buffered: bool = False + self._unread_result: bool = False + self._have_next_result: bool = False + self._raw: bool = False + self._in_transaction: bool = False + + self._prepared_statements: Any = None + + self._ssl_active: bool = False + self._auth_plugin: Optional[str] = None + self._krb_service_principal: Optional[str] = None + self._pool_config_version: Any = None + self._query_attrs_supported: int = False + + self._columns_desc: List[DescriptionType] = [] + self._mfa_nfactor: int = 1 + + if kwargs: + try: + self.connect(**kwargs) + except Exception: + # Tidy-up underlying socket on failure + self.close() + self._socket = None + raise + + def _add_default_conn_attrs(self) -> None: + """Add the default connection attributes.""" + platform = get_platform() + license_chunks = version.LICENSE.split(" ") + if license_chunks[0] == "GPLv2": + client_license = "GPL-2.0" + else: + client_license = "Commercial" + default_conn_attrs = { + "_pid": str(os.getpid()), + "_platform": platform["arch"], + "_source_host": socket.gethostname(), + "_client_name": "mysql-connector-python", + "_client_license": client_license, + "_client_version": ".".join([str(x) for x in version.VERSION[0:3]]), + "_os": platform["version"], + } + + self._conn_attrs.update((default_conn_attrs)) + + def _do_handshake(self) -> None: + """Get the handshake from the MySQL server""" + packet = self._socket.recv() + if packet[4] == 255: + raise get_exception(packet) + + self._handshake = None + handshake = self._protocol.parse_handshake(packet) + + server_version = handshake["server_version_original"] + + self._server_version = self._check_server_version( + server_version + if isinstance(server_version, (str, bytes, bytearray)) + else "Unknown" + ) + CharacterSet.set_mysql_version(self._server_version) + + if not handshake["capabilities"] & ClientFlag.SSL: + if self._auth_plugin == "mysql_clear_password" and not self.is_secure: + raise InterfaceError( + "Clear password authentication is not supported over " + "insecure channels" + ) + if self._ssl.get("verify_cert"): + raise InterfaceError( + "SSL is required but the server doesn't support it", + errno=2026, + ) + self._client_flags &= ~ClientFlag.SSL + elif not self._ssl_disabled: + self._client_flags |= ClientFlag.SSL + + if handshake["capabilities"] & ClientFlag.PLUGIN_AUTH: + self.set_client_flags([ClientFlag.PLUGIN_AUTH]) + + if handshake["capabilities"] & ClientFlag.CLIENT_QUERY_ATTRIBUTES: + self._query_attrs_supported = True + self.set_client_flags([ClientFlag.CLIENT_QUERY_ATTRIBUTES]) + + if handshake["capabilities"] & ClientFlag.MULTI_FACTOR_AUTHENTICATION: + self.set_client_flags([ClientFlag.MULTI_FACTOR_AUTHENTICATION]) + + self._handshake = handshake + + def _do_auth( + self, + username: Optional[str] = None, + password: Optional[str] = None, + database: Optional[str] = None, + client_flags: int = 0, + charset: int = 45, + ssl_options: Optional[Dict[str, Optional[Union[str, bool, List[str]]]]] = None, + conn_attrs: Optional[ConnAttrsType] = None, + ) -> bool: + """Authenticate with the MySQL server + + Authentication happens in two parts. We first send a response to the + handshake. The MySQL server will then send either an AuthSwitchRequest + or an error packet. + + Raises NotSupportedError when we get the old, insecure password + reply back. Raises any error coming from MySQL. + """ + self._ssl_active = False + if ssl_options is None: + ssl_options = {} + if not self._ssl_disabled and (client_flags & ClientFlag.SSL): + packet = self._protocol.make_auth_ssl( + charset=charset, client_flags=client_flags + ) + self._socket.send(packet) + if ssl_options.get("tls_ciphersuites") is not None: + tls_ciphersuites = ":".join(ssl_options.get("tls_ciphersuites")) + else: + tls_ciphersuites = "" + self._socket.switch_to_ssl( + ssl_options.get("ca"), + ssl_options.get("cert"), + ssl_options.get("key"), + ssl_options.get("verify_cert") or False, + ssl_options.get("verify_identity") or False, + tls_ciphersuites, + ssl_options.get("tls_versions"), + ) + self._ssl_active = True + + if self._password1 and password != self._password1: + password = self._password1 + + logger.debug("# _do_auth(): self._auth_plugin: %s", self._auth_plugin) + if ( + self._auth_plugin.startswith("authentication_oci") + or ( + self._auth_plugin.startswith("authentication_kerberos") + and os.name == "nt" + ) + ) and not username: + username = getpass.getuser() + logger.debug( + "MySQL user is empty, OS user: %s will be used for %s", + username, + self._auth_plugin, + ) + + packet = self._protocol.make_auth( + handshake=self._handshake, + username=username, + password=password, + database=database, + charset=charset, + client_flags=client_flags, + ssl_enabled=self._ssl_active, + auth_plugin=self._auth_plugin, + conn_attrs=conn_attrs, + auth_plugin_class=self._auth_plugin_class, + ) + self._socket.send(packet) + self._auth_switch_request(username, password) + + if not (client_flags & ClientFlag.CONNECT_WITH_DB) and database: + self.cmd_init_db(database) + + return True + + def _auth_switch_request( + self, username: Optional[str] = None, password: Optional[str] = None + ) -> Optional[OkPacketType]: + """Handle second part of authentication + + Raises NotSupportedError when we get the old, insecure password + reply back. Raises any error coming from MySQL. + """ + auth = None + new_auth_plugin: Optional[str] = ( + self._auth_plugin or self._handshake["auth_plugin"] + ) + logger.debug("new_auth_plugin: %s", new_auth_plugin) + packet = self._socket.recv() + if packet[4] == 254 and len(packet) == 5: + raise NotSupportedError( + "Authentication with old (insecure) passwords " + "is not supported. For more information, lookup " + "Password Hashing in the latest MySQL manual" + ) + if packet[4] == 254: + # AuthSwitchRequest + ( + new_auth_plugin, + auth_data, + ) = self._protocol.parse_auth_switch_request(packet) + auth = get_auth_plugin(new_auth_plugin, self._auth_plugin_class)( + auth_data, + username=username or self._user, + password=password, + ssl_enabled=self.is_secure, + ) + packet = self._auth_continue(auth, new_auth_plugin, auth_data) + + if packet[4] == 1: + auth_data = self._protocol.parse_auth_more_data(packet) + auth = get_auth_plugin(new_auth_plugin, self._auth_plugin_class)( + auth_data, password=password, ssl_enabled=self.is_secure + ) + if new_auth_plugin == "caching_sha2_password": + response = auth.auth_response() + if response: + self._socket.send(response) + packet = self._socket.recv() + + if packet[4] == 0: + return self._handle_ok(packet) + if packet[4] == 2: + return self._handle_mfa(packet) + if packet[4] == 255: + raise get_exception(packet) + return None + + def _handle_mfa(self, packet: bytes) -> Optional[OkPacketType]: + """Handle Multi Factor Authentication.""" + self._mfa_nfactor += 1 + if self._mfa_nfactor == 2: + password = self._password2 + elif self._mfa_nfactor == 3: + password = self._password3 + else: + raise InterfaceError( + "Failed Multi Factor Authentication (invalid N factor)" + ) + + logger.debug("# MFA N Factor #%d", self._mfa_nfactor) + + packet, auth_plugin = self._protocol.parse_auth_next_factor(packet[4:]) + auth = get_auth_plugin(auth_plugin, self._auth_plugin_class)( + None, + username=self._user, + password=password, + ssl_enabled=self.is_secure, + ) + packet = self._auth_continue(auth, auth_plugin, packet) + + if packet[4] == 1: + auth_data = self._protocol.parse_auth_more_data(packet) + auth = get_auth_plugin(auth_plugin, self._auth_plugin_class)( + auth_data, password=password, ssl_enabled=self.is_secure + ) + if auth_plugin == "caching_sha2_password": + response = auth.auth_response() + if response: + self._socket.send(response) + packet = self._socket.recv() + + if packet[4] == 0: + return self._handle_ok(packet) + if packet[4] == 2: + return self._handle_mfa(packet) + if packet[4] == 255: + raise get_exception(packet) + return None + + def _auth_continue( + self, auth: BaseAuthPlugin, auth_plugin: str, auth_data: bytes + ) -> bytes: + """Continue with the authentication.""" + if auth_plugin == "authentication_ldap_sasl_client": + logger.debug("# auth_data: %s", auth_data) + response = auth.auth_response(self._krb_service_principal) + elif auth_plugin == "authentication_kerberos_client": + logger.debug("# auth_data: %s", auth_data) + response = auth.auth_response(auth_data) + elif auth_plugin == "authentication_oci_client": + logger.debug("# oci configuration file path: %s", self._oci_config_file) + auth.oci_config_file = self._oci_config_file + auth.oci_config_profile = self._oci_config_profile + response = auth.auth_response() + else: + response = auth.auth_response() + + logger.debug("# request: %s size: %s", response, len(response)) + self._socket.send(response) + packet = self._socket.recv() + logger.debug("# server response packet: %s", packet) + if ( + auth_plugin == "authentication_ldap_sasl_client" + and len(packet) >= 6 + and packet[5] == 114 + and packet[6] == 61 + ): # 'r' and '=' + # Continue with sasl authentication + dec_response = packet[5:] + cresponse = auth.auth_continue(dec_response) + self._socket.send(cresponse) + packet = self._socket.recv() + if packet[5] == 118 and packet[6] == 61: # 'v' and '=' + if auth.auth_finalize(packet[5:]): + # receive packed OK + packet = self._socket.recv() + elif ( + auth_plugin == "authentication_ldap_sasl_client" + and auth_data == b"GSSAPI" + and packet[4] != 255 + ): + rcode_size = 5 # header size for the response status code. + logger.debug("# Continue with sasl GSSAPI authentication") + logger.debug("# response header: %s", packet[: rcode_size + 1]) + logger.debug("# response size: %s", len(packet)) + + logger.debug("# Negotiate a service request") + complete = False + tries = 0 # To avoid a infinite loop attempt no more than feedback messages + while not complete and tries < 5: + logger.debug("%s Attempt %s %s", "-" * 20, tries + 1, "-" * 20) + logger.debug("<< server response: %s", packet) + logger.debug("# response code: %s", packet[: rcode_size + 1]) + step, complete = auth.auth_continue_krb(packet[rcode_size:]) + logger.debug(" >> response to server: %s", step) + self._socket.send(step or b"") + packet = self._socket.recv() + tries += 1 + if not complete: + raise InterfaceError( + f"Unable to fulfill server request after {tries} " + f"attempts. Last server response: {packet}" + ) + logger.debug( + " last GSSAPI response from server: %s length: %d", + packet, + len(packet), + ) + last_step = auth.auth_accept_close_handshake(packet[rcode_size:]) + logger.debug( + " >> last response to server: %s length: %d", + last_step, + len(last_step), + ) + self._socket.send(last_step) + # Receive final handshake from server + packet = self._socket.recv() + logger.debug("<< final handshake from server: %s", packet) + + # receive OK packet from server. + packet = self._socket.recv() + logger.debug("<< ok packet from server: %s", packet) + elif auth_plugin == "authentication_kerberos_client" and packet[4] != 255: + rcode_size = 5 # Reader size for the response status code + logger.debug("# Continue with GSSAPI authentication") + logger.debug("# Response header: %s", packet[: rcode_size + 1]) + logger.debug("# Response size: %s", len(packet)) + logger.debug("# Negotiate a service request") + complete = False + tries = 0 + + while not complete and tries < 5: + logger.debug("%s Attempt %s %s", "-" * 20, tries + 1, "-" * 20) + logger.debug("<< Server response: %s", packet) + logger.debug("# Response code: %s", packet[: rcode_size + 1]) + token, complete = auth.auth_continue(packet[rcode_size:]) + if token: + self._socket.send(token) + if complete: + break + packet = self._socket.recv() + + logger.debug(">> Response to server: %s", token) + tries += 1 + + if not complete: + raise InterfaceError( + f"Unable to fulfill server request after {tries} " + f"attempts. Last server response: {packet}" + ) + + logger.debug( + "Last response from server: %s length: %d", + packet, + len(packet), + ) + + # Receive OK packet from server. + packet = self._socket.recv() + logger.debug("<< Ok packet from server: %s", packet) + + return bytes(packet) + + def _get_connection(self) -> MySQLSocket: + """Get connection based on configuration + + This method will return the appropriated connection object using + the connection parameters. + + Returns subclass of MySQLBaseSocket. + """ + conn: Optional[MySQLSocket] = None + if self._unix_socket and os.name == "posix": + conn = MySQLUnixSocket(unix_socket=self.unix_socket) + else: + conn = MySQLTCPSocket( + host=self.server_host, + port=self.server_port, + force_ipv6=self._force_ipv6, + ) + + conn.set_connection_timeout(self._connection_timeout) + return conn + + def _open_connection(self) -> None: + """Open the connection to the MySQL server + + This method sets up and opens the connection to the MySQL server. + + Raises on errors. + """ + if self._auth_plugin == "authentication_kerberos_client" and not self._user: + cls = get_auth_plugin(self._auth_plugin, self._auth_plugin_class) + self._user = cls.get_user_from_credentials() + + self._protocol = MySQLProtocol() + self._socket = self._get_connection() + try: + self._socket.open_connection() + + # do initial handshake + self._do_handshake() + + # start authentication negotiation + self._do_auth( + self._user, + self._password, + self._database, + self._client_flags, + self._charset_id, + self._ssl, + self._conn_attrs, + ) + self.set_converter_class(self._converter_class) + + if self._client_flags & ClientFlag.COMPRESS: + # update the network layer accordingly + self._socket.switch_to_compressed_mode() + + self._socket.set_connection_timeout(None) + except Exception: + # close socket + self._socket.close_connection() + raise + + if ( + not self._ssl_disabled + and hasattr(self._socket.sock, "version") + and callable(self._socket.sock.version) + ): + # Raise a deprecation warning if TLSv1 or TLSv1.1 is being used + tls_version = self._socket.sock.version() + if tls_version in ("TLSv1", "TLSv1.1"): + warn_msg = ( + f"This connection is using {tls_version} which is now " + "deprecated and will be removed in a future release of " + "MySQL Connector/Python" + ) + warnings.warn(warn_msg, DeprecationWarning) + + def shutdown(self) -> None: + """Shut down connection to MySQL Server.""" + if not self._socket: + return + + try: + self._socket.shutdown() + except (AttributeError, Error): + pass # Getting an exception would mean we are disconnected. + + def close(self) -> None: + """Disconnect from the MySQL server""" + if self._span and self._span.is_recording(): + record_exception_event(self._span, sys.exc_info()[1]) + + if not self._socket: + return + + try: + self.cmd_quit() + except (AttributeError, Error): + pass # Getting an exception would mean we are disconnected. + + try: + self._socket.close_connection() + except Exception as err: + if OTEL_ENABLED: + record_exception_event(self._span, err) + raise + finally: + if OTEL_ENABLED: + end_span(self._span) + + self._handshake = None + + disconnect = close + + def _send_cmd( + self, + command: int, + argument: Optional[bytes] = None, + packet_number: int = 0, + packet: Optional[bytes] = None, + expect_response: bool = True, + compressed_packet_number: int = 0, + ) -> Optional[bytearray]: + """Send a command to the MySQL server + + This method sends a command with an optional argument. + If packet is not None, it will be sent and the argument will be + ignored. + + The packet_number is optional and should usually not be used. + + Some commands might not result in the MySQL server returning + a response. If a command does not return anything, you should + set expect_response to False. The _send_cmd method will then + return None instead of a MySQL packet. + + Returns a MySQL packet or None. + """ + self.handle_unread_result() + + try: + self._socket.send( + self._protocol.make_command(command, packet or argument), + packet_number, + compressed_packet_number, + ) + except AttributeError as err: + raise OperationalError("MySQL Connection not available") from err + + if not expect_response: + return None + return self._socket.recv() + + def _send_data(self, data_file: BinaryIO, send_empty_packet: bool = False) -> bytes: + """Send data to the MySQL server + + This method accepts a file-like object and sends its data + as is to the MySQL server. If the send_empty_packet is + True, it will send an extra empty package (for example + when using LOAD LOCAL DATA INFILE). + + Returns a MySQL packet. + """ + self.handle_unread_result() + + if not hasattr(data_file, "read"): + raise ValueError("expecting a file-like object") + + chunk_size = 131072 # 128 KB + try: + buf = data_file.read(chunk_size - 16) + while buf: + self._socket.send(buf) + buf = data_file.read(chunk_size - 16) + except AttributeError as err: + raise OperationalError("MySQL Connection not available") from err + + if send_empty_packet: + try: + self._socket.send(b"") + except AttributeError as err: + raise OperationalError("MySQL Connection not available") from err + + return bytes(self._socket.recv()) + + def _handle_server_status(self, flags: int) -> None: + """Handle the server flags found in MySQL packets + + This method handles the server flags send by MySQL OK and EOF + packets. It, for example, checks whether there exists more result + sets or whether there is an ongoing transaction. + """ + self._have_next_result = flag_is_set(ServerFlag.MORE_RESULTS_EXISTS, flags) + self._in_transaction = flag_is_set(ServerFlag.STATUS_IN_TRANS, flags) + + @property + def in_transaction(self) -> bool: + """MySQL session has started a transaction""" + return self._in_transaction + + def _handle_ok(self, packet: bytes) -> OkPacketType: + """Handle a MySQL OK packet + + This method handles a MySQL OK packet. When the packet is found to + be an Error packet, an error will be raised. If the packet is neither + an OK or an Error packet, InterfaceError will be raised. + + Returns a dict() + """ + if packet[4] == 0: + ok_pkt = self._protocol.parse_ok(packet) + self._handle_server_status(ok_pkt["status_flag"]) + return ok_pkt + if packet[4] == 255: + raise get_exception(packet) + raise InterfaceError("Expected OK packet") + + def _handle_eof(self, packet: bytes) -> EofPacketType: + """Handle a MySQL EOF packet + + This method handles a MySQL EOF packet. When the packet is found to + be an Error packet, an error will be raised. If the packet is neither + and OK or an Error packet, InterfaceError will be raised. + + Returns a dict() + """ + if packet[4] == 254: + eof = self._protocol.parse_eof(packet) + self._handle_server_status(eof["status_flag"]) + return eof + if packet[4] == 255: + raise get_exception(packet) + raise InterfaceError("Expected EOF packet") + + def _handle_load_data_infile(self, filename: str) -> OkPacketType: + """Handle a LOAD DATA INFILE LOCAL request""" + file_name = os.path.abspath(filename) + if os.path.islink(file_name): + raise OperationalError("Use of symbolic link is not allowed") + if not self._allow_local_infile and not self._allow_local_infile_in_path: + raise DatabaseError( + "LOAD DATA LOCAL INFILE file request rejected due to " + "restrictions on access." + ) + if not self._allow_local_infile and self._allow_local_infile_in_path: + # validate filename is inside of allow_local_infile_in_path path. + infile_path = os.path.abspath(self._allow_local_infile_in_path) + c_path = None + try: + c_path = os.path.commonpath([infile_path, file_name]) + except ValueError as err: + err_msg = ( + "{} while loading file `{}` and path `{}` given" + " in allow_local_infile_in_path" + ) + raise InterfaceError( + err_msg.format(str(err), file_name, infile_path) + ) from err + + if c_path != infile_path: + err_msg = ( + "The file `{}` is not found in the given " + "allow_local_infile_in_path {}" + ) + raise DatabaseError(err_msg.format(file_name, infile_path)) + + try: + data_file = open(file_name, "rb") # pylint: disable=consider-using-with + return self._handle_ok(self._send_data(data_file, send_empty_packet=True)) + except IOError: + # Send a empty packet to cancel the operation + try: + self._socket.send(b"") + except AttributeError as err: + raise OperationalError("MySQL Connection not available") from err + raise InterfaceError(f"File '{file_name}' could not be read") from None + finally: + try: + data_file.close() + except (IOError, NameError): + pass + + def _handle_result(self, packet: bytes) -> ResultType: + """Handle a MySQL Result + + This method handles a MySQL result, for example, after sending the + query command. OK and EOF packets will be handled and returned. If + the packet is an Error packet, an Error-exception will be + raised. + + The dictionary returned of: + - columns: column information + - eof: the EOF-packet information + + Returns a dict() + """ + if not packet or len(packet) < 4: + raise InterfaceError("Empty response") + if packet[4] == 0: + return self._handle_ok(packet) + if packet[4] == 251: + filename = packet[5:].decode() + return self._handle_load_data_infile(filename) + if packet[4] == 254: + return self._handle_eof(packet) + if packet[4] == 255: + raise get_exception(packet) + + # We have a text result set + column_count = self._protocol.parse_column_count(packet) + if not column_count or not isinstance(column_count, int): + raise InterfaceError("Illegal result set") + + self._columns_desc = [ + None, + ] * column_count + for i in range(0, column_count): + self._columns_desc[i] = self._protocol.parse_column( + self._socket.recv(), self.python_charset + ) + + eof = self._handle_eof(self._socket.recv()) + self.unread_result = True + return {"columns": self._columns_desc, "eof": eof} + + def get_row( + self, + binary: bool = False, + columns: Optional[List[DescriptionType]] = None, + raw: Optional[bool] = None, + ) -> Tuple[Optional[RowType], Optional[EofPacketType]]: + """Get the next rows returned by the MySQL server + + This method gets one row from the result set after sending, for + example, the query command. The result is a tuple consisting of the + row and the EOF packet. + If no row was available in the result set, the row data will be None. + + Returns a tuple. + """ + (rows, eof) = self.get_rows(count=1, binary=binary, columns=columns, raw=raw) + if rows: + return (rows[0], eof) + return (None, eof) + + def get_rows( + self, + count: Optional[int] = None, + binary: bool = False, + columns: Optional[List[DescriptionType]] = None, + raw: Optional[bool] = None, + prep_stmt: Any = None, + ) -> Tuple[List[RowType], Optional[EofPacketType]]: + """Get all rows returned by the MySQL server + + This method gets all rows returned by the MySQL server after sending, + for example, the query command. The result is a tuple consisting of + a list of rows and the EOF packet. + + Returns a tuple() + """ + if raw is None: + raw = self._raw + + if not self.unread_result: + raise InternalError("No result set available") + + rows: Tuple[List[Tuple[Any, ...]], Optional[EofPacketType]] = ([], None) + try: + if binary: + charset = self.charset + if charset == "utf8mb4": + charset = "utf8" + rows = self._protocol.read_binary_result( + self._socket, columns, count, charset + ) + else: + rows = self._protocol.read_text_result( + self._socket, self._server_version, count=count + ) + except Error as err: + self.unread_result = False + raise err + + rows, eof_p = rows + if ( + not (binary or raw) + and self._columns_desc is not None + and rows + and hasattr(self, "converter") + ): + row_to_python = self.converter.row_to_python + rows = [row_to_python(row, self._columns_desc) for row in rows] + + if eof_p is not None: + self._handle_server_status( + eof_p["status_flag"] + if "status_flag" in eof_p + else eof_p["server_status"] + ) + self.unread_result = False + + return rows, eof_p + + def consume_results(self) -> None: + """Consume results""" + if self.unread_result: + self.get_rows() + + def cmd_init_db(self, database: str) -> OkPacketType: + """Change the current database + + This method changes the current (default) database by sending the + INIT_DB command. The result is a dictionary containing the OK packet + information. + + Returns a dict() + """ + return self._handle_ok( + self._send_cmd(ServerCmd.INIT_DB, database.encode("utf-8")) + ) + + @with_context_propagation + def cmd_query( + self, + query: StrOrBytes, + raw: bool = False, + buffered: bool = False, + raw_as_string: bool = False, + ) -> ResultType: + """Send a query to the MySQL server + + This method send the query to the MySQL server and returns the result. + + If there was a text result, a tuple will be returned consisting of + the number of columns and a list containing information about these + columns. + + When the query doesn't return a text result, the OK or EOF packet + information as dictionary will be returned. In case the result was + an error, exception Error will be raised. + + Returns a tuple() + """ + if not isinstance(query, bytearray): + if isinstance(query, str): + query = query.encode("utf-8") + query = bytearray(query) + # Prepare query attrs + charset = self.charset if self.charset != "utf8mb4" else "utf8" + packet = bytearray() + if not self._query_attrs_supported and self._query_attrs: + warnings.warn( + "This version of the server does not support Query Attributes", + category=Warning, + ) + if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES: + names = [] + types = [] + values: List[bytes] = [] + null_bitmap = [0] * ((len(self._query_attrs) + 7) // 8) + for pos, attr_tuple in enumerate(self._query_attrs.items()): + value = attr_tuple[1] + flags = 0 + if value is None: + null_bitmap[(pos // 8)] |= 1 << (pos % 8) + types.append(int1store(FieldType.NULL) + int1store(flags)) + continue + if isinstance(value, int): + ( + packed, + field_type, + flags, + ) = self._protocol.prepare_binary_integer(value) + values.append(packed) + elif isinstance(value, str): + value = value.encode(charset) + values.append(lc_int(len(value)) + value) + field_type = FieldType.STRING + elif isinstance(value, bytes): + values.append(lc_int(len(value)) + value) + field_type = FieldType.STRING + elif isinstance(value, Decimal): + values.append( + lc_int(len(str(value).encode(charset))) + + str(value).encode(charset) + ) + field_type = FieldType.DECIMAL + elif isinstance(value, float): + values.append(struct.pack(" parameter_count Number of parameters + packet.extend(lc_int(len(self._query_attrs))) + # int parameter_set_count Number of parameter sets. + # Currently always 1 + packet.extend(lc_int(1)) + if values: + packet.extend( + b"".join([struct.pack("B", bit) for bit in null_bitmap]) + + int1store(1) + ) + for _type, name in zip(types, names): + packet.extend(_type) + packet.extend(name) + + for value in values: + packet.extend(value) + + packet.extend(query) + query = bytes(packet) + try: + result = self._handle_result(self._send_cmd(ServerCmd.QUERY, query)) + except ProgrammingError as err: + if err.errno == 3948 and "Loading local data is disabled" in err.msg: + err_msg = ( + "LOAD DATA LOCAL INFILE file request rejected due " + "to restrictions on access." + ) + raise DatabaseError(err_msg) from err + raise + if self._have_next_result: + raise InterfaceError( + "Use cmd_query_iter for statements with multiple queries." + ) + + return result + + def cmd_query_iter( + self, statements: StrOrBytes + ) -> Generator[ResultType, None, None]: + """Send one or more statements to the MySQL server + + Similar to the cmd_query method, but instead returns a generator + object to iterate through results. It sends the statements to the + MySQL server and through the iterator you can get the results. + + statement = 'SELECT 1; INSERT INTO t1 VALUES (); SELECT 2' + for result in cnx.cmd_query(statement, iterate=True): + if 'columns' in result: + columns = result['columns'] + rows = cnx.get_rows() + else: + # do something useful with INSERT result + + Returns a generator. + """ + packet = bytearray() + if not isinstance(statements, bytearray): + if isinstance(statements, str): + statements = statements.encode("utf8") + statements = bytearray(statements) + + if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES: + # int parameter_count Number of parameters + packet.extend(lc_int(0)) + # int parameter_set_count Number of parameter sets. + # Currently always 1 + packet.extend(lc_int(1)) + + packet.extend(statements) + query = bytes(packet) + # Handle the first query result + yield self._handle_result(self._send_cmd(ServerCmd.QUERY, query)) + + # Handle next results, if any + while self._have_next_result: + self.handle_unread_result() + yield self._handle_result(self._socket.recv()) + + def cmd_refresh(self, options: int) -> OkPacketType: + """Send the Refresh command to the MySQL server + + This method sends the Refresh command to the MySQL server. The options + argument should be a bitwise value using constants.RefreshOption. + Usage example: + RefreshOption = mysql.connector.RefreshOption + refresh = RefreshOption.LOG | RefreshOption.THREADS + cnx.cmd_refresh(refresh) + + The result is a dictionary with the OK packet information. + + Returns a dict() + """ + return self._handle_ok(self._send_cmd(ServerCmd.REFRESH, int4store(options))) + + def cmd_quit(self) -> bytes: + """Close the current connection with the server + + This method sends the QUIT command to the MySQL server, closing the + current connection. Since the no response can be returned to the + client, cmd_quit() will return the packet it send. + + Returns a str() + """ + self.handle_unread_result() + + packet = self._protocol.make_command(ServerCmd.QUIT) + self._socket.send(packet, 0, 0) + return bytes(packet) + + def cmd_shutdown(self, shutdown_type: Optional[int] = None) -> EofPacketType: + """Shut down the MySQL Server + + This method sends the SHUTDOWN command to the MySQL server and is only + possible if the current user has SUPER privileges. The result is a + dictionary containing the OK packet information. + + Note: Most applications and scripts do not the SUPER privilege. + + Returns a dict() + """ + if shutdown_type: + if not ShutdownType.get_info(shutdown_type): + raise InterfaceError("Invalid shutdown type") + atype = shutdown_type + else: + atype = ShutdownType.SHUTDOWN_DEFAULT + return self._handle_eof(self._send_cmd(ServerCmd.SHUTDOWN, int4store(atype))) + + def cmd_statistics(self) -> StatsPacketType: + """Send the statistics command to the MySQL Server + + This method sends the STATISTICS command to the MySQL server. The + result is a dictionary with various statistical information. + + Returns a dict() + """ + self.handle_unread_result() + + packet = self._protocol.make_command(ServerCmd.STATISTICS) + self._socket.send(packet, 0, 0) + return self._protocol.parse_statistics(self._socket.recv()) + + def cmd_process_kill(self, mysql_pid: int) -> OkPacketType: + """Kill a MySQL process + + This method send the PROCESS_KILL command to the server along with + the process ID. The result is a dictionary with the OK packet + information. + + Returns a dict() + """ + return self._handle_ok( + self._send_cmd(ServerCmd.PROCESS_KILL, int4store(mysql_pid)) + ) + + def cmd_debug(self) -> EofPacketType: + """Send the DEBUG command + + This method sends the DEBUG command to the MySQL server, which + requires the MySQL user to have SUPER privilege. The output will go + to the MySQL server error log and the result of this method is a + dictionary with EOF packet information. + + Returns a dict() + """ + return self._handle_eof(self._send_cmd(ServerCmd.DEBUG)) + + def cmd_ping(self) -> OkPacketType: + """Send the PING command + + This method sends the PING command to the MySQL server. It is used to + check if the the connection is still valid. The result of this + method is dictionary with OK packet information. + + Returns a dict() + """ + return self._handle_ok(self._send_cmd(ServerCmd.PING)) + + def cmd_change_user( + self, + username: str = "", + password: str = "", + database: str = "", + charset: int = 45, + password1: str = "", + password2: str = "", + password3: str = "", + oci_config_file: str = "", + oci_config_profile: str = "", + ) -> Optional[OkPacketType]: + """Change the current logged in user + + This method allows to change the current logged in user information. + The result is a dictionary with OK packet information. + + Returns a dict() + """ + if not isinstance(charset, int): + raise ValueError("charset must be an integer") + if charset < 0: + raise ValueError("charset should be either zero or a postive integer") + + self._mfa_nfactor = 1 + self._user = username + self._password = password + self._password1 = password1 + self._password2 = password2 + self._password3 = password3 + + if self._password1 and password != self._password1: + password = self._password1 + + self.handle_unread_result() + + if self._compress: + raise NotSupportedError("Change user is not supported with compression") + packet = self._protocol.make_change_user( + handshake=self._handshake, + username=username, + password=password, + database=database, + charset=charset, + client_flags=self._client_flags, + ssl_enabled=self._ssl_active, + auth_plugin=self._auth_plugin, + conn_attrs=self._conn_attrs, + ) + self._socket.send(packet, 0, 0) + + if oci_config_file: + self._oci_config_file = oci_config_file + + self._oci_config_profile = oci_config_profile + + ok_packet = self._auth_switch_request(username, password) + + if not (self._client_flags & ClientFlag.CONNECT_WITH_DB) and database: + self.cmd_init_db(database) + + self._charset_id = charset + self._post_connection() + + return ok_packet + + @property + def database(self) -> str: + """Get the current database""" + return self.info_query("SELECT DATABASE()")[0] # type: ignore[return-value] + + @database.setter + def database(self, value: str) -> None: + """Set the current database""" + self.cmd_init_db(value) + + def is_connected(self) -> bool: + """Reports whether the connection to MySQL Server is available + + This method checks whether the connection to MySQL is available. + It is similar to ping(), but unlike the ping()-method, either True + or False is returned and no exception is raised. + + Returns True or False. + """ + try: + self.cmd_ping() + except Error: + return False # This method does not raise + return True + + def set_allow_local_infile_in_path(self, path: str) -> None: + """Set the path that user can upload files. + + Args: + path (str): Path that user can upload files. + """ + self._allow_local_infile_in_path = path + + def reset_session( + self, + user_variables: Optional[Dict[str, Any]] = None, + session_variables: Optional[Dict[str, Any]] = None, + ) -> None: + """Clears the current active session + + This method resets the session state, if the MySQL server is 5.7.3 + or later active session will be reset without re-authenticating. + For other server versions session will be reset by re-authenticating. + + It is possible to provide a sequence of variables and their values to + be set after clearing the session. This is possible for both user + defined variables and session variables. + This method takes two arguments user_variables and session_variables + which are dictionaries. + + Raises OperationalError if not connected, InternalError if there are + unread results and InterfaceError on errors. + """ + if not self.is_connected(): + raise OperationalError("MySQL Connection not available.") + + if not self.cmd_reset_connection(): + try: + self.cmd_change_user( + self._user, + self._password, + self._database, + self._charset_id, + self._password1, + self._password2, + self._password3, + self._oci_config_file, + self._oci_config_profile, + ) + except ProgrammingError: + self.reconnect() + + cur = self.cursor() + if user_variables: + for key, value in user_variables.items(): + cur.execute(f"SET @`{key}` = %s", (value,)) + if session_variables: + for key, value in session_variables.items(): + cur.execute(f"SET SESSION `{key}` = %s", (value,)) + + def ping(self, reconnect: bool = False, attempts: int = 1, delay: int = 0) -> None: + """Check availability of the MySQL server + + When reconnect is set to True, one or more attempts are made to try + to reconnect to the MySQL server using the reconnect()-method. + + delay is the number of seconds to wait between each retry. + + When the connection is not available, an InterfaceError is raised. Use + the is_connected()-method if you just want to check the connection + without raising an error. + + Raises InterfaceError on errors. + """ + try: + self.cmd_ping() + except Error as err: + if reconnect: + self.reconnect(attempts=attempts, delay=delay) + else: + raise InterfaceError("Connection to MySQL is not available") from err + + @property + def connection_id(self) -> Optional[int]: + """MySQL connection ID""" + if self._handshake: + return self._handshake.get("server_threadid") # type: ignore[return-value] + return None + + def cursor( + self, + buffered: Optional[bool] = None, + raw: Optional[bool] = None, + prepared: Optional[bool] = None, + cursor_class: Optional[Type[MySQLCursor]] = None, + dictionary: Optional[bool] = None, + named_tuple: Optional[bool] = None, + ) -> MySQLCursor: + """Instantiates and returns a cursor + + By default, MySQLCursor is returned. Depending on the options + while connecting, a buffered and/or raw cursor is instantiated + instead. Also depending upon the cursor options, rows can be + returned as dictionary or named tuple. + + Dictionary and namedtuple based cursors are available with buffered + output but not raw. + + It is possible to also give a custom cursor through the + cursor_class parameter, but it needs to be a subclass of + mysql.connector.cursor.CursorBase. + + Raises ProgrammingError when cursor_class is not a subclass of + CursorBase. Raises ValueError when cursor is not available. + + Returns a cursor-object + """ + self.handle_unread_result() + + if not self.is_connected(): + raise OperationalError("MySQL Connection not available") + if cursor_class is not None: + if not issubclass(cursor_class, CursorBase): + raise ProgrammingError( + "Cursor class needs be to subclass of cursor.CursorBase" + ) + return (cursor_class)(self) + + buffered = buffered if buffered is not None else self._buffered + raw = raw if raw is not None else self._raw + + cursor_type = 0 + if buffered is True: + cursor_type |= 1 + if raw is True: + cursor_type |= 2 + if dictionary is True: + cursor_type |= 4 + if named_tuple is True: + cursor_type |= 8 + if prepared is True: + cursor_type |= 16 + + types = { + 0: MySQLCursor, # 0 + 1: MySQLCursorBuffered, + 2: MySQLCursorRaw, + 3: MySQLCursorBufferedRaw, + 4: MySQLCursorDict, + 5: MySQLCursorBufferedDict, + 8: MySQLCursorNamedTuple, + 9: MySQLCursorBufferedNamedTuple, + 16: MySQLCursorPrepared, + 18: MySQLCursorPreparedRaw, + 20: MySQLCursorPreparedDict, + 24: MySQLCursorPreparedNamedTuple, + } + try: + return (types[cursor_type])(self) + except KeyError: + args = ("buffered", "raw", "dictionary", "named_tuple", "prepared") + raise ValueError( + "Cursor not available with given criteria: " + + ", ".join([args[i] for i in range(5) if cursor_type & (1 << i) != 0]) + ) from None + + def commit(self) -> None: + """Commit current transaction""" + self._execute_query("COMMIT") + + def rollback(self) -> None: + """Rollback current transaction""" + if self.unread_result: + self.get_rows() + + self._execute_query("ROLLBACK") + + def _execute_query(self, query: StrOrBytes) -> None: + """Execute a query + + This method simply calls cmd_query() after checking for unread + result. If there are still unread result, an InterfaceError + is raised. Otherwise whatever cmd_query() returns is returned. + + Returns a dict() + """ + self.handle_unread_result() + self.cmd_query(query) + + def info_query(self, query: StrOrBytes) -> Optional[RowType]: + """Send a query which only returns 1 row""" + cursor = self.cursor(buffered=True) + cursor.execute(query) + return cursor.fetchone() + + def _handle_binary_ok(self, packet: bytes) -> Dict[str, int]: + """Handle a MySQL Binary Protocol OK packet + + This method handles a MySQL Binary Protocol OK packet. When the + packet is found to be an Error packet, an error will be raised. If + the packet is neither an OK or an Error packet, InterfaceError + will be raised. + + Returns a dict() + """ + if packet[4] == 0: + return self._protocol.parse_binary_prepare_ok(packet) + if packet[4] == 255: + raise get_exception(packet) + raise InterfaceError("Expected Binary OK packet") + + def _handle_binary_result( + self, packet: bytes + ) -> Union[OkPacketType, Tuple[int, List[DescriptionType], EofPacketType]]: + """Handle a MySQL Result + + This method handles a MySQL result, for example, after sending the + query command. OK and EOF packets will be handled and returned. If + the packet is an Error packet, an Error exception will be raised. + + The tuple returned by this method consist of: + - the number of columns in the result, + - a list of tuples with information about the columns, + - the EOF packet information as a dictionary. + + Returns tuple() or dict() + """ + if not packet or len(packet) < 4: + raise InterfaceError("Empty response") + if packet[4] == 0: + return self._handle_ok(packet) + if packet[4] == 254: + return self._handle_eof(packet) + if packet[4] == 255: + raise get_exception(packet) + + # We have a binary result set + column_count = self._protocol.parse_column_count(packet) + if not column_count or not isinstance(column_count, int): + raise InterfaceError("Illegal result set.") + + columns: List[DescriptionType] = [None] * column_count + for i in range(0, column_count): + columns[i] = self._protocol.parse_column( + self._socket.recv(), self.python_charset + ) + + eof = self._handle_eof(self._socket.recv()) + return (column_count, columns, eof) + + def cmd_stmt_fetch(self, statement_id: int, rows: int = 1) -> None: + """Fetch a MySQL statement Result Set + + This method will send the FETCH command to MySQL together with the + given statement id and the number of rows to fetch. + """ + packet = self._protocol.make_stmt_fetch(statement_id, rows) + self.unread_result = False + self._send_cmd(ServerCmd.STMT_FETCH, packet, expect_response=False) + self.unread_result = True + + def cmd_stmt_prepare( + self, statement: bytes + ) -> Mapping[str, Union[int, List[DescriptionType]]]: + """Prepare a MySQL statement + + This method will send the PREPARE command to MySQL together with the + given statement. + + Returns a dict() + """ + packet = self._send_cmd(ServerCmd.STMT_PREPARE, statement) + result = self._handle_binary_ok(packet) + + result["columns"] = [] + result["parameters"] = [] + if result["num_params"] > 0: + for _ in range(0, result["num_params"]): + result["parameters"].append( + self._protocol.parse_column( + self._socket.recv(), self.python_charset + ) + ) + self._handle_eof(self._socket.recv()) + if result["num_columns"] > 0: + for _ in range(0, result["num_columns"]): + result["columns"].append( + self._protocol.parse_column( + self._socket.recv(), self.python_charset + ) + ) + self._handle_eof(self._socket.recv()) + + return result + + @with_context_propagation + def cmd_stmt_execute( + self, + statement_id: int, + data: Sequence[SupportedMysqlBinaryProtocolTypes] = (), + parameters: Sequence[Any] = (), + flags: int = 0, + ) -> Union[OkPacketType, Tuple[int, List[DescriptionType], EofPacketType]]: + """Execute a prepared MySQL statement""" + parameters = list(parameters) + long_data_used = {} + + if data: + for param_id, _ in enumerate(parameters): + if isinstance(data[param_id], IOBase): + binary = True + try: + binary = "b" not in data[param_id].mode # type: ignore[union-attr] + except AttributeError: + pass + self.cmd_stmt_send_long_data(statement_id, param_id, data[param_id]) + long_data_used[param_id] = (binary,) + if not self._query_attrs_supported and self._query_attrs: + warnings.warn( + "This version of the server does not support Query Attributes", + category=Warning, + ) + if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES: + execute_packet = self._protocol.make_stmt_execute( + statement_id, + data, + tuple(parameters), + flags, + long_data_used, + self.charset, + self.query_attrs, + self._converter_str_fallback, + ) + else: + execute_packet = self._protocol.make_stmt_execute( + statement_id, + data, + tuple(parameters), + flags, + long_data_used, + self.charset, + converter_str_fallback=self._converter_str_fallback, + ) + packet = self._send_cmd(ServerCmd.STMT_EXECUTE, packet=execute_packet) + result = self._handle_binary_result(packet) + return result + + def cmd_stmt_close(self, statement_id: int) -> None: + """Deallocate a prepared MySQL statement + + This method deallocates the prepared statement using the + statement_id. Note that the MySQL server does not return + anything. + """ + self._send_cmd( + ServerCmd.STMT_CLOSE, + int4store(statement_id), + expect_response=False, + ) + + def cmd_stmt_send_long_data( + self, statement_id: int, param_id: int, data: BinaryIO + ) -> int: + """Send data for a column + + This methods send data for a column (for example BLOB) for statement + identified by statement_id. The param_id indicate which parameter + the data belongs too. + The data argument should be a file-like object. + + Since MySQL does not send anything back, no error is raised. When + the MySQL server is not reachable, an OperationalError is raised. + + cmd_stmt_send_long_data should be called before cmd_stmt_execute. + + The total bytes send is returned. + + Returns int. + """ + chunk_size = 131072 # 128 KB + total_sent = 0 + try: + buf = data.read(chunk_size) + while buf: + packet = self._protocol.prepare_stmt_send_long_data( + statement_id, param_id, buf + ) + self._send_cmd( + ServerCmd.STMT_SEND_LONG_DATA, + packet=packet, + expect_response=False, + ) + total_sent += len(buf) + buf = data.read(chunk_size) + except AttributeError as err: + raise OperationalError("MySQL Connection not available") from err + + return total_sent + + def cmd_stmt_reset(self, statement_id: int) -> None: + """Reset data for prepared statement sent as long data + + The result is a dictionary with OK packet information. + + Returns a dict() + """ + self._handle_ok(self._send_cmd(ServerCmd.STMT_RESET, int4store(statement_id))) + + def cmd_reset_connection(self) -> bool: + """Resets the session state without re-authenticating + + Reset command only works on MySQL server 5.7.3 or later. + The result is True for a successful reset otherwise False. + + Returns bool + """ + try: + self._handle_ok(self._send_cmd(ServerCmd.RESET_CONNECTION)) + self._post_connection() + return True + except (NotSupportedError, OperationalError): + return False + + def handle_unread_result(self) -> None: + """Check whether there is an unread result""" + if self.can_consume_results: + self.consume_results() + elif self.unread_result: + raise InternalError("Unread result found") diff --git a/mysql/connector/connection_cext.py b/mysql/connector/connection_cext.py new file mode 100644 index 0000000..34c618e --- /dev/null +++ b/mysql/connector/connection_cext.py @@ -0,0 +1,1004 @@ +# Copyright (c) 2014, 2023, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# mypy: disable-error-code="arg-type,index" + +"""Connection class using the C Extension.""" + +import os +import platform +import socket +import sys + +from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union + +from . import version +from .abstracts import MySQLConnectionAbstract +from .constants import CharacterSet, ClientFlag, FieldFlag, ServerFlag, ShutdownType +from .conversion import MySQLConverter +from .errors import ( + InterfaceError, + InternalError, + OperationalError, + ProgrammingError, + get_mysql_exception, +) +from .protocol import MySQLProtocol +from .types import ( + CextEofPacketType, + CextResultType, + DescriptionType, + ParamsSequenceOrDictType, + RowType, + StatsPacketType, + StrOrBytes, +) + +HAVE_CMYSQL = False + +try: + import _mysql_connector + + from _mysql_connector import MySQLInterfaceError, MySQLPrepStmt + + from .cursor_cext import ( + CMySQLCursor, + CMySQLCursorBuffered, + CMySQLCursorBufferedDict, + CMySQLCursorBufferedNamedTuple, + CMySQLCursorBufferedRaw, + CMySQLCursorDict, + CMySQLCursorNamedTuple, + CMySQLCursorPrepared, + CMySQLCursorPreparedDict, + CMySQLCursorPreparedNamedTuple, + CMySQLCursorPreparedRaw, + CMySQLCursorRaw, + ) + + HAVE_CMYSQL = True +except ImportError as exc: + raise ImportError( + f"MySQL Connector/Python C Extension not available ({exc})" + ) from exc + +from .opentelemetry.constants import OTEL_ENABLED +from .opentelemetry.context_propagation import with_context_propagation + +if OTEL_ENABLED: + from .opentelemetry.instrumentation import end_span, record_exception_event + + +class CMySQLConnection(MySQLConnectionAbstract): + """Class initiating a MySQL Connection using Connector/C.""" + + def __init__(self, **kwargs: Any) -> None: + """Initialization""" + if not HAVE_CMYSQL: + raise RuntimeError("MySQL Connector/Python C Extension not available") + self._cmysql: Optional[ + _mysql_connector.MySQL # pylint: disable=c-extension-no-member + ] = None + self._columns: List[DescriptionType] = [] + self._plugin_dir: str = os.path.join( + os.path.dirname(os.path.abspath(_mysql_connector.__file__)), + "mysql", + "vendor", + "plugin", + ) + if platform.system() == "Linux": + # Use the authentication plugins from system if they aren't bundled + if not os.path.exists(self._plugin_dir): + self._plugin_dir = ( + "/usr/lib64/mysql/plugin" + if os.path.exists("/usr/lib64/mysql/plugin") + else "/usr/lib/mysql/plugin" + ) + + self.converter: Optional[MySQLConverter] = None + super().__init__() + + if kwargs: + try: + self.connect(**kwargs) + except Exception: + self.close() + raise + + def _add_default_conn_attrs(self) -> None: + """Add default connection attributes""" + license_chunks = version.LICENSE.split(" ") + if license_chunks[0] == "GPLv2": + client_license = "GPL-2.0" + else: + client_license = "Commercial" + + self._conn_attrs.update( + { + "_connector_name": "mysql-connector-python", + "_connector_license": client_license, + "_connector_version": ".".join([str(x) for x in version.VERSION[0:3]]), + "_source_host": socket.gethostname(), + } + ) + + def _do_handshake(self) -> None: + """Gather information of the MySQL server before authentication""" + self._handshake = { + "protocol": self._cmysql.get_proto_info(), + "server_version_original": self._cmysql.get_server_info(), + "server_threadid": self._cmysql.thread_id(), + "charset": None, + "server_status": None, + "auth_plugin": None, + "auth_data": None, + "capabilities": self._cmysql.st_server_capabilities(), + } + + self._server_version = self._check_server_version( + self._handshake["server_version_original"] + ) + CharacterSet.set_mysql_version(self._server_version) + + @property + def _server_status(self) -> int: + """Returns the server status attribute of MYSQL structure""" + return self._cmysql.st_server_status() + + def set_allow_local_infile_in_path(self, path: str) -> None: + """set local_infile_in_path + + Set allow_local_infile_in_path. + """ + + if self._cmysql: + self._cmysql.set_load_data_local_infile_option(path) + + def set_unicode(self, value: bool = True) -> None: + """Toggle unicode mode + + Set whether we return string fields as unicode or not. + Default is True. + """ + self._use_unicode = value + if self._cmysql: + self._cmysql.use_unicode(value) + if self.converter: + self.converter.set_unicode(value) + + @property + def autocommit(self) -> bool: + """Get whether autocommit is on or off""" + value = self.info_query("SELECT @@session.autocommit")[0] + return value == 1 + + @autocommit.setter + def autocommit(self, value: bool) -> None: + """Toggle autocommit""" + try: + self._cmysql.autocommit(value) + self._autocommit = value + except MySQLInterfaceError as err: + raise get_mysql_exception( + msg=err.msg, errno=err.errno, sqlstate=err.sqlstate + ) from err + + @property + def database(self) -> str: + """Get the current database""" + return self.info_query("SELECT DATABASE()")[0] # type: ignore[return-value] + + @database.setter + def database(self, value: str) -> None: + """Set the current database""" + try: + self._cmysql.select_db(value) + except MySQLInterfaceError as err: + raise get_mysql_exception( + msg=err.msg, errno=err.errno, sqlstate=err.sqlstate + ) from err + + @property + def in_transaction(self) -> int: + """MySQL session has started a transaction""" + return self._server_status & ServerFlag.STATUS_IN_TRANS + + def _open_connection(self) -> None: + charset_name = CharacterSet.get_info(self._charset_id)[0] + # pylint: disable=c-extension-no-member + self._cmysql = _mysql_connector.MySQL( + buffered=self._buffered, + raw=self._raw, + charset_name=charset_name, + connection_timeout=(self._connection_timeout or 0), + use_unicode=self._use_unicode, + auth_plugin=self._auth_plugin, + plugin_dir=self._plugin_dir, + ) + # pylint: enable=c-extension-no-member + if not self.isset_client_flag(ClientFlag.CONNECT_ARGS): + self._conn_attrs = {} + cnx_kwargs = { + "host": self._host, + "user": self._user, + "password": self._password, + "password1": self._password1, + "password2": self._password2, + "password3": self._password3, + "database": self._database, + "port": self._port, + "client_flags": self._client_flags, + "unix_socket": self._unix_socket, + "compress": self._compress, + "ssl_disabled": True, + "conn_attrs": self._conn_attrs, + "local_infile": self._allow_local_infile, + "load_data_local_dir": self._allow_local_infile_in_path, + "oci_config_file": self._oci_config_file, + "oci_config_profile": self._oci_config_profile, + "fido_callback": self._fido_callback, + } + + tls_versions = self._ssl.get("tls_versions") + if tls_versions is not None: + tls_versions.sort(reverse=True) # type: ignore[union-attr] + tls_versions = ",".join(tls_versions) + if self._ssl.get("tls_ciphersuites") is not None: + ssl_ciphersuites = self._ssl.get("tls_ciphersuites")[0] + tls_ciphersuites = self._ssl.get("tls_ciphersuites")[1] + else: + ssl_ciphersuites = None + tls_ciphersuites = None + if ( + tls_versions is not None + and "TLSv1.3" in tls_versions + and not tls_ciphersuites + ): + tls_ciphersuites = "TLS_AES_256_GCM_SHA384" + if not self._ssl_disabled: + cnx_kwargs.update( + { + "ssl_ca": self._ssl.get("ca"), + "ssl_cert": self._ssl.get("cert"), + "ssl_key": self._ssl.get("key"), + "ssl_cipher_suites": ssl_ciphersuites, + "tls_versions": tls_versions, + "tls_cipher_suites": tls_ciphersuites, + "ssl_verify_cert": self._ssl.get("verify_cert") or False, + "ssl_verify_identity": self._ssl.get("verify_identity") or False, + "ssl_disabled": self._ssl_disabled, + } + ) + + if os.name == "nt" and self._auth_plugin_class == "MySQLKerberosAuthPlugin": + cnx_kwargs["use_kerberos_gssapi"] = True + + try: + self._cmysql.connect(**cnx_kwargs) + self._cmysql.converter_str_fallback = self._converter_str_fallback + if self.converter: + self.converter.str_fallback = self._converter_str_fallback + except MySQLInterfaceError as err: + raise get_mysql_exception( + msg=err.msg, errno=err.errno, sqlstate=err.sqlstate + ) from err + + self._do_handshake() + + def close(self) -> None: + """Disconnect from the MySQL server""" + if self._span and self._span.is_recording(): + record_exception_event(self._span, sys.exc_info()[1]) + + if not self._cmysql: + return + + try: + self.free_result() + self._cmysql.close() + except MySQLInterfaceError as err: + if OTEL_ENABLED: + record_exception_event(self._span, err) + raise get_mysql_exception( + msg=err.msg, errno=err.errno, sqlstate=err.sqlstate + ) from err + finally: + if OTEL_ENABLED: + end_span(self._span) + + disconnect = close + + def is_closed(self) -> bool: + """Return True if the connection to MySQL Server is closed.""" + return not self._cmysql.connected() + + def is_connected(self) -> bool: + """Reports whether the connection to MySQL Server is available""" + if self._cmysql: + self.handle_unread_result() + return self._cmysql.ping() + + return False + + def ping(self, reconnect: bool = False, attempts: int = 1, delay: int = 0) -> None: + """Check availability of the MySQL server + + When reconnect is set to True, one or more attempts are made to try + to reconnect to the MySQL server using the reconnect()-method. + + delay is the number of seconds to wait between each retry. + + When the connection is not available, an InterfaceError is raised. Use + the is_connected()-method if you just want to check the connection + without raising an error. + + Raises InterfaceError on errors. + """ + self.handle_unread_result() + + try: + connected = self._cmysql.ping() + except AttributeError: + pass # Raise or reconnect later + else: + if connected: + return + + if reconnect: + self.reconnect(attempts=attempts, delay=delay) + else: + raise InterfaceError("Connection to MySQL is not available") + + def set_character_set_name(self, charset: str) -> None: + """Sets the default character set name for current connection.""" + self._cmysql.set_character_set(charset) + + def info_query(self, query: StrOrBytes) -> Optional[RowType]: + """Send a query which only returns 1 row""" + first_row = () + try: + self._cmysql.query(query) + if self._cmysql.have_result_set: + first_row = self._cmysql.fetch_row() + if self._cmysql.fetch_row(): + self._cmysql.free_result() + raise InterfaceError("Query should not return more than 1 row") + self._cmysql.free_result() + except MySQLInterfaceError as err: + raise get_mysql_exception( + msg=err.msg, errno=err.errno, sqlstate=err.sqlstate + ) from err + + return first_row + + @property + def connection_id(self) -> Optional[int]: + """MySQL connection ID""" + try: + return self._cmysql.thread_id() + except MySQLInterfaceError: + pass # Just return None + + return None + + def get_rows( + self, + count: Optional[int] = None, + binary: bool = False, + columns: Optional[List[DescriptionType]] = None, + raw: Optional[bool] = None, + prep_stmt: Optional[MySQLPrepStmt] = None, + ) -> Tuple[List[RowType], Optional[CextEofPacketType]]: + """Get all or a subset of rows returned by the MySQL server""" + unread_result = prep_stmt.have_result_set if prep_stmt else self.unread_result + if not (self._cmysql and unread_result): + raise InternalError("No result set available") + + if raw is None: + raw = self._raw + + rows: List[Tuple[Any, ...]] = [] + if count is not None and count <= 0: + raise AttributeError("count should be 1 or higher, or None") + + counter = 0 + try: + fetch_row = prep_stmt.fetch_row if prep_stmt else self._cmysql.fetch_row + if self.converter: + # When using a converter class, the C extension should not + # convert the values. This can be accomplished by setting + # the raw option to True. + self._cmysql.raw(True) + row = fetch_row() + while row: + if not self._raw and self.converter: + row = list(row) + for i, _ in enumerate(row): + if not raw: + row[i] = self.converter.to_python(self._columns[i], row[i]) + row = tuple(row) + rows.append(row) + counter += 1 + if count and counter == count: + break + row = fetch_row() + if not row: + _eof: Optional[CextEofPacketType] = self.fetch_eof_columns(prep_stmt)[ + "eof" + ] # type: ignore[assignment] + if prep_stmt: + prep_stmt.free_result() + self._unread_result = False + else: + self.free_result() + else: + _eof = None + except MySQLInterfaceError as err: + if prep_stmt: + prep_stmt.free_result() + raise InterfaceError(str(err)) from err + self.free_result() + raise get_mysql_exception( + msg=err.msg, errno=err.errno, sqlstate=err.sqlstate + ) from err + + return rows, _eof + + def get_row( + self, + binary: bool = False, + columns: Optional[List[DescriptionType]] = None, + raw: Optional[bool] = None, + prep_stmt: Optional[MySQLPrepStmt] = None, + ) -> Tuple[Optional[RowType], CextEofPacketType]: + """Get the next rows returned by the MySQL server""" + try: + rows, eof = self.get_rows( + count=1, + binary=binary, + columns=columns, + raw=raw, + prep_stmt=prep_stmt, + ) + if rows: + return (rows[0], eof) + return (None, eof) + except IndexError: + # No row available + return (None, None) + + def next_result(self) -> Optional[bool]: + """Reads the next result""" + if self._cmysql: + self._cmysql.consume_result() + return self._cmysql.next_result() + return None + + def free_result(self) -> None: + """Frees the result""" + if self._cmysql: + self._cmysql.free_result() + + def commit(self) -> None: + """Commit current transaction""" + if self._cmysql: + self.handle_unread_result() + self._cmysql.commit() + + def rollback(self) -> None: + """Rollback current transaction""" + if self._cmysql: + self._cmysql.consume_result() + self._cmysql.rollback() + + def cmd_init_db(self, database: str) -> None: + """Change the current database""" + try: + self._cmysql.select_db(database) + except MySQLInterfaceError as err: + raise get_mysql_exception( + msg=err.msg, errno=err.errno, sqlstate=err.sqlstate + ) from err + + def fetch_eof_columns( + self, prep_stmt: Optional[MySQLPrepStmt] = None + ) -> CextResultType: + """Fetch EOF and column information""" + have_result_set = ( + prep_stmt.have_result_set if prep_stmt else self._cmysql.have_result_set + ) + if not have_result_set: + raise InterfaceError("No result set") + + fields = prep_stmt.fetch_fields() if prep_stmt else self._cmysql.fetch_fields() + self._columns = [] + for col in fields: + self._columns.append( + ( + col[4], + int(col[8]), + None, + None, + None, + None, + ~int(col[9]) & FieldFlag.NOT_NULL, + int(col[9]), + int(col[6]), + ) + ) + + return { + "eof": { + "status_flag": self._server_status, + "warning_count": self._cmysql.st_warning_count(), + }, + "columns": self._columns, + } + + def fetch_eof_status(self) -> Optional[CextEofPacketType]: + """Fetch EOF and status information""" + if self._cmysql: + return { + "warning_count": self._cmysql.st_warning_count(), + "field_count": self._cmysql.st_field_count(), + "insert_id": self._cmysql.insert_id(), + "affected_rows": self._cmysql.affected_rows(), + "server_status": self._server_status, + } + + return None + + def cmd_stmt_prepare(self, statement: bytes) -> MySQLPrepStmt: + """Prepares the SQL statement""" + if not self._cmysql: + raise OperationalError("MySQL Connection not available") + + try: + stmt = self._cmysql.stmt_prepare(statement) + stmt.converter_str_fallback = self._converter_str_fallback + return stmt + except MySQLInterfaceError as err: + raise InterfaceError(str(err)) from err + + def cmd_stmt_execute( + self, statement_id: MySQLPrepStmt, *args: Any + ) -> Optional[Union[CextEofPacketType, CextResultType]]: + """Executes the prepared statement""" + try: + statement_id.stmt_execute(*args) + except MySQLInterfaceError as err: + raise InterfaceError(str(err)) from err + + self._columns = [] + if not statement_id.have_result_set: + # No result + self._unread_result = False + return self.fetch_eof_status() + + self._unread_result = True + return self.fetch_eof_columns(statement_id) + + def cmd_stmt_close(self, statement_id: MySQLPrepStmt) -> None: + """Closes the prepared statement""" + if self._unread_result: + raise InternalError("Unread result found") + statement_id.stmt_close() + + def cmd_stmt_reset(self, statement_id: MySQLPrepStmt) -> None: + """Resets the prepared statement""" + if self._unread_result: + raise InternalError("Unread result found") + statement_id.stmt_reset() + + @with_context_propagation + def cmd_query( + self, + query: StrOrBytes, + raw: Optional[bool] = None, + buffered: bool = False, + raw_as_string: bool = False, + ) -> Optional[Union[CextEofPacketType, CextResultType]]: + """Send a query to the MySQL server""" + self.handle_unread_result() + if raw is None: + raw = self._raw + try: + if not isinstance(query, bytes): + query = query.encode("utf-8") + self._cmysql.query( + query, + raw=raw, + buffered=buffered, + raw_as_string=raw_as_string, + query_attrs=self.query_attrs, + ) + except MySQLInterfaceError as err: + raise get_mysql_exception( + err.errno, msg=err.msg, sqlstate=err.sqlstate + ) from err + except AttributeError as err: + addr = ( + self._unix_socket if self._unix_socket else f"{self._host}:{self._port}" + ) + raise OperationalError( + errno=2055, values=(addr, "Connection not available.") + ) from err + + self._columns = [] + if not self._cmysql.have_result_set: + # No result + return self.fetch_eof_status() + + return self.fetch_eof_columns() + + _execute_query = cmd_query + + def cursor( + self, + buffered: Optional[bool] = None, + raw: Optional[bool] = None, + prepared: Optional[bool] = None, + cursor_class: Optional[Type[CMySQLCursor]] = None, + dictionary: Optional[bool] = None, + named_tuple: Optional[bool] = None, + ) -> CMySQLCursor: + """Instantiates and returns a cursor using C Extension + + By default, CMySQLCursor is returned. Depending on the options + while connecting, a buffered and/or raw cursor is instantiated + instead. Also depending upon the cursor options, rows can be + returned as dictionary or named tuple. + + Dictionary and namedtuple based cursors are available with buffered + output but not raw. + + It is possible to also give a custom cursor through the + cursor_class parameter, but it needs to be a subclass of + mysql.connector.cursor_cext.CMySQLCursor. + + Raises ProgrammingError when cursor_class is not a subclass of + CursorBase. Raises ValueError when cursor is not available. + + Returns instance of CMySQLCursor or subclass. + + :param buffered: Return a buffering cursor + :param raw: Return a raw cursor + :param prepared: Return a cursor which uses prepared statements + :param cursor_class: Use a custom cursor class + :param dictionary: Rows are returned as dictionary + :param named_tuple: Rows are returned as named tuple + :return: Subclass of CMySQLCursor + :rtype: CMySQLCursor or subclass + """ + self.handle_unread_result(prepared) + if not self.is_connected(): + raise OperationalError("MySQL Connection not available.") + if cursor_class is not None: + if not issubclass(cursor_class, CMySQLCursor): + raise ProgrammingError( + "Cursor class needs be to subclass of cursor_cext.CMySQLCursor" + ) + return (cursor_class)(self) + + buffered = buffered or self._buffered + raw = raw or self._raw + + cursor_type = 0 + if buffered is True: + cursor_type |= 1 + if raw is True: + cursor_type |= 2 + if dictionary is True: + cursor_type |= 4 + if named_tuple is True: + cursor_type |= 8 + if prepared is True: + cursor_type |= 16 + + types = { + 0: CMySQLCursor, # 0 + 1: CMySQLCursorBuffered, + 2: CMySQLCursorRaw, + 3: CMySQLCursorBufferedRaw, + 4: CMySQLCursorDict, + 5: CMySQLCursorBufferedDict, + 8: CMySQLCursorNamedTuple, + 9: CMySQLCursorBufferedNamedTuple, + 16: CMySQLCursorPrepared, + 18: CMySQLCursorPreparedRaw, + 20: CMySQLCursorPreparedDict, + 24: CMySQLCursorPreparedNamedTuple, + } + try: + return (types[cursor_type])(self) + except KeyError: + args = ("buffered", "raw", "dictionary", "named_tuple", "prepared") + raise ValueError( + "Cursor not available with given criteria: " + + ", ".join([args[i] for i in range(5) if cursor_type & (1 << i) != 0]) + ) from None + + @property + def num_rows(self) -> int: + """Returns number of rows of current result set""" + if not self._cmysql.have_result_set: + raise InterfaceError("No result set") + + return self._cmysql.num_rows() + + @property + def warning_count(self) -> int: + """Returns number of warnings""" + if not self._cmysql: + return 0 + + return self._cmysql.warning_count() + + @property + def result_set_available(self) -> bool: + """Check if a result set is available""" + if not self._cmysql: + return False + + return self._cmysql.have_result_set + + @property # type: ignore[misc] + def unread_result(self) -> bool: + """Check if there are unread results or rows""" + return self.result_set_available + + @property + def more_results(self) -> bool: + """Check if there are more results""" + return self._cmysql.more_results() + + def prepare_for_mysql( + self, params: ParamsSequenceOrDictType + ) -> Union[Sequence[bytes], Dict[str, bytes],]: + """Prepare parameters for statements + + This method is use by cursors to prepared parameters found in the + list (or tuple) params. + + Returns dict. + """ + result: Union[List[Any], Dict[str, Any]] = [] + if isinstance(params, (list, tuple)): + if self.converter: + result = [ + self.converter.quote( + self.converter.escape( + self.converter.to_mysql(value), self._sql_mode + ) + ) + for value in params + ] + else: + result = self._cmysql.convert_to_mysql(*params) + elif isinstance(params, dict): + result = {} + if self.converter: + for key, value in params.items(): + result[key] = self.converter.quote( + self.converter.escape( + self.converter.to_mysql(value), self._sql_mode + ) + ) + else: + for key, value in params.items(): + result[key] = self._cmysql.convert_to_mysql(value)[0] + else: + raise ProgrammingError( + f"Could not process parameters: {type(params).__name__}({params})," + " it must be of type list, tuple or dict" + ) + + return result + + def consume_results(self) -> None: + """Consume the current result + + This method consume the result by reading (consuming) all rows. + """ + self._cmysql.consume_result() + + def cmd_change_user( + self, + username: str = "", + password: str = "", + database: str = "", + charset: int = 45, + password1: str = "", + password2: str = "", + password3: str = "", + oci_config_file: Optional[str] = None, + oci_config_profile: Optional[str] = None, + ) -> None: + """Change the current logged in user""" + try: + self._cmysql.change_user( + username, + password, + database, + password1, + password2, + password3, + oci_config_file, + oci_config_profile, + ) + + except MySQLInterfaceError as err: + raise get_mysql_exception( + msg=err.msg, errno=err.errno, sqlstate=err.sqlstate + ) from err + + self._charset_id = charset + self._user = username # updating user accordingly + self._post_connection() + + def cmd_reset_connection(self) -> bool: + """Resets the session state without re-authenticating + + Reset command only works on MySQL server 5.7.3 or later. + The result is True for a successful reset otherwise False. + + Returns bool + """ + res = self._cmysql.reset_connection() + if res: + self._post_connection() + return res + + def cmd_refresh(self, options: int) -> Optional[CextEofPacketType]: + """Send the Refresh command to the MySQL server""" + try: + self.handle_unread_result() + self._cmysql.refresh(options) + except MySQLInterfaceError as err: + raise get_mysql_exception( + msg=err.msg, errno=err.errno, sqlstate=err.sqlstate + ) from err + + return self.fetch_eof_status() + + def cmd_quit(self) -> None: + """Close the current connection with the server""" + self.close() + + def cmd_shutdown(self, shutdown_type: Optional[int] = None) -> None: + """Shut down the MySQL Server""" + if not self._cmysql: + raise OperationalError("MySQL Connection not available") + + if shutdown_type: + if not ShutdownType.get_info(shutdown_type): + raise InterfaceError("Invalid shutdown type") + level = shutdown_type + else: + level = ShutdownType.SHUTDOWN_DEFAULT + + try: + self._cmysql.shutdown(level) + except MySQLInterfaceError as err: + raise get_mysql_exception( + msg=err.msg, errno=err.errno, sqlstate=err.sqlstate + ) from err + self.close() + + def cmd_statistics(self) -> StatsPacketType: + """Return statistics from the MySQL server""" + self.handle_unread_result() + + try: + stat = self._cmysql.stat() + return MySQLProtocol().parse_statistics(stat, with_header=False) + except (MySQLInterfaceError, InterfaceError) as err: + raise get_mysql_exception( + msg=err.msg, errno=err.errno, sqlstate=err.sqlstate + ) from err + + def cmd_process_kill(self, mysql_pid: int) -> None: + """Kill a MySQL process""" + if not isinstance(mysql_pid, int): + raise ValueError("MySQL PID must be int") + self.info_query(f"KILL {mysql_pid}") + + def cmd_debug(self) -> Any: + """Send the DEBUG command""" + raise NotImplementedError + + def cmd_ping(self) -> Any: + """Send the PING command""" + raise NotImplementedError + + def cmd_query_iter(self, statements: Any) -> Any: + """Send one or more statements to the MySQL server""" + raise NotImplementedError + + def cmd_stmt_send_long_data( + self, statement_id: Any, param_id: Any, data: Any + ) -> Any: + """Send data for a column""" + raise NotImplementedError + + def handle_unread_result(self, prepared: bool = False) -> None: + """Check whether there is an unread result""" + unread_result = self._unread_result if prepared is True else self.unread_result + if self.can_consume_results: + self.consume_results() + elif unread_result: + raise InternalError("Unread result found") + + def reset_session( + self, + user_variables: Optional[Dict[str, Any]] = None, + session_variables: Optional[Dict[str, Any]] = None, + ) -> None: + """Clears the current active session + + This method resets the session state, if the MySQL server is 5.7.3 + or later active session will be reset without re-authenticating. + For other server versions session will be reset by re-authenticating. + + It is possible to provide a sequence of variables and their values to + be set after clearing the session. This is possible for both user + defined variables and session variables. + This method takes two arguments user_variables and session_variables + which are dictionaries. + + Raises OperationalError if not connected, InternalError if there are + unread results and InterfaceError on errors. + """ + if not self.is_connected(): + raise OperationalError("MySQL Connection not available.") + + if not self.cmd_reset_connection(): + try: + self.cmd_change_user( + self._user, + self._password, + self._database, + self._charset_id, + self._password1, + self._password2, + self._password3, + self._oci_config_file, + self._oci_config_profile, + ) + except ProgrammingError: + self.reconnect() + + if user_variables or session_variables: + cur = self.cursor() + if user_variables: + for key, value in user_variables.items(): + cur.execute(f"SET @`{key}` = %s", (value,)) + if session_variables: + for key, value in session_variables.items(): + cur.execute(f"SET SESSION `{key}` = %s", (value,)) + cur.close() diff --git a/mysql/connector/constants.py b/mysql/connector/constants.py new file mode 100644 index 0000000..f25da42 --- /dev/null +++ b/mysql/connector/constants.py @@ -0,0 +1,1148 @@ +# Copyright (c) 2009, 2023, Oracle and/or its affiliates. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Various MySQL constants and character sets.""" + +import warnings + +from abc import ABC, ABCMeta +from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, ValuesView + +from .charsets import MYSQL_CHARACTER_SETS, MYSQL_CHARACTER_SETS_57 +from .errors import ProgrammingError + +NET_BUFFER_LENGTH: int = 8192 +MAX_MYSQL_TABLE_COLUMNS: int = 4096 +# Flag used to send the Query Attributes with 0 (or more) parameters. +PARAMETER_COUNT_AVAILABLE: int = 8 + +DEFAULT_CONFIGURATION: Dict[str, Optional[Union[str, bool, int]]] = { + "database": None, + "user": "", + "password": "", + "password1": "", + "password2": "", + "password3": "", + "host": "127.0.0.1", + "port": 3306, + "unix_socket": None, + "use_unicode": True, + "charset": "utf8mb4", + "collation": None, + "converter_class": None, + "converter_str_fallback": False, + "autocommit": False, + "time_zone": None, + "sql_mode": None, + "get_warnings": False, + "raise_on_warnings": False, + "connection_timeout": None, + "client_flags": 0, + "compress": False, + "buffered": False, + "raw": False, + "ssl_ca": None, + "ssl_cert": None, + "ssl_key": None, + "ssl_verify_cert": False, + "ssl_verify_identity": False, + "ssl_cipher": None, + "tls_ciphersuites": None, + "ssl_disabled": False, + "tls_versions": None, + "passwd": None, + "db": None, + "connect_timeout": None, + "dsn": None, + "force_ipv6": False, + "auth_plugin": None, + "allow_local_infile": False, + "allow_local_infile_in_path": None, + "consume_results": False, + "conn_attrs": None, + "dns_srv": False, + "use_pure": False, + "krb_service_principal": None, + "oci_config_file": None, + "oci_config_profile": None, + "fido_callback": None, + "kerberos_auth_mode": None, + "init_command": None, +} + +CNX_POOL_ARGS: Tuple[str, str, str] = ("pool_name", "pool_size", "pool_reset_session") + +TLS_VERSIONS: List[str] = ["TLSv1.2", "TLSv1.3"] + +DEPRECATED_TLS_VERSIONS: List[str] = ["TLSv1", "TLSv1.1"] + + +def flag_is_set(flag: int, flags: int) -> bool: + """Checks if the flag is set + + Returns boolean""" + if (flags & flag) > 0: + return True + return False + + +def _obsolete_option(name: str, new_name: str, value: int) -> int: + """Raise a deprecation warning and advise a new option name. + + Args: + name (str): The name of the option. + new_name (str): The new option name. + value (int): The value of the option. + + Returns: + int: The value of the option. + """ + warnings.warn( + f"The option '{name}' has been deprecated, use '{new_name}' instead.", + category=DeprecationWarning, + ) + return value + + +class _Constants(ABC): + """Base class for constants.""" + + prefix: str = "" + desc: Dict[str, Tuple[int, str]] = {} + + @classmethod + def get_desc(cls, name: str) -> Optional[str]: + """Get description of given constant""" + try: + return cls.desc[name][1] + except (IndexError, KeyError): + return None + + @classmethod + def get_info(cls, setid: int) -> Union[Optional[str], Tuple[str, str]]: + """Get information about given constant""" + for name, info in cls.desc.items(): + if info[0] == setid: + return name + return None + + @classmethod + def get_full_info(cls) -> Union[str, Sequence[str]]: + """get full information about given constant""" + res: Union[str, List[str]] = [] + try: + res = [f"{k} : {v[1]}" for k, v in cls.desc.items()] + except (AttributeError, IndexError) as err: + res = f"No information found in constant class. {err}" + + return res + + +class _Flags(_Constants): + """Base class for classes describing flags""" + + @classmethod + def get_bit_info(cls, value: int) -> List[str]: + """Get the name of all bits set + + Returns a list of strings.""" + res = [] + for name, info in cls.desc.items(): + if value & info[0]: + res.append(name) + return res + + +class FieldType(_Constants): + """MySQL Field Types""" + + prefix: str = "FIELD_TYPE_" + DECIMAL: int = 0x00 + TINY: int = 0x01 + SHORT: int = 0x02 + LONG: int = 0x03 + FLOAT: int = 0x04 + DOUBLE: int = 0x05 + NULL: int = 0x06 + TIMESTAMP: int = 0x07 + LONGLONG: int = 0x08 + INT24: int = 0x09 + DATE: int = 0x0A + TIME: int = 0x0B + DATETIME: int = 0x0C + YEAR: int = 0x0D + NEWDATE: int = 0x0E + VARCHAR: int = 0x0F + BIT: int = 0x10 + JSON: int = 0xF5 + NEWDECIMAL: int = 0xF6 + ENUM: int = 0xF7 + SET: int = 0xF8 + TINY_BLOB: int = 0xF9 + MEDIUM_BLOB: int = 0xFA + LONG_BLOB: int = 0xFB + BLOB: int = 0xFC + VAR_STRING: int = 0xFD + STRING: int = 0xFE + GEOMETRY: int = 0xFF + + desc: Dict[str, Tuple[int, str]] = { + "DECIMAL": (0x00, "DECIMAL"), + "TINY": (0x01, "TINY"), + "SHORT": (0x02, "SHORT"), + "LONG": (0x03, "LONG"), + "FLOAT": (0x04, "FLOAT"), + "DOUBLE": (0x05, "DOUBLE"), + "NULL": (0x06, "NULL"), + "TIMESTAMP": (0x07, "TIMESTAMP"), + "LONGLONG": (0x08, "LONGLONG"), + "INT24": (0x09, "INT24"), + "DATE": (0x0A, "DATE"), + "TIME": (0x0B, "TIME"), + "DATETIME": (0x0C, "DATETIME"), + "YEAR": (0x0D, "YEAR"), + "NEWDATE": (0x0E, "NEWDATE"), + "VARCHAR": (0x0F, "VARCHAR"), + "BIT": (0x10, "BIT"), + "JSON": (0xF5, "JSON"), + "NEWDECIMAL": (0xF6, "NEWDECIMAL"), + "ENUM": (0xF7, "ENUM"), + "SET": (0xF8, "SET"), + "TINY_BLOB": (0xF9, "TINY_BLOB"), + "MEDIUM_BLOB": (0xFA, "MEDIUM_BLOB"), + "LONG_BLOB": (0xFB, "LONG_BLOB"), + "BLOB": (0xFC, "BLOB"), + "VAR_STRING": (0xFD, "VAR_STRING"), + "STRING": (0xFE, "STRING"), + "GEOMETRY": (0xFF, "GEOMETRY"), + } + + @classmethod + def get_string_types(cls) -> List[int]: + """Get the list of all string types""" + return [ + cls.VARCHAR, + cls.ENUM, + cls.VAR_STRING, + cls.STRING, + ] + + @classmethod + def get_binary_types(cls) -> List[int]: + """Get the list of all binary types""" + return [ + cls.TINY_BLOB, + cls.MEDIUM_BLOB, + cls.LONG_BLOB, + cls.BLOB, + ] + + @classmethod + def get_number_types(cls) -> List[int]: + """Get the list of all number types""" + return [ + cls.DECIMAL, + cls.NEWDECIMAL, + cls.TINY, + cls.SHORT, + cls.LONG, + cls.FLOAT, + cls.DOUBLE, + cls.LONGLONG, + cls.INT24, + cls.BIT, + cls.YEAR, + ] + + @classmethod + def get_timestamp_types(cls) -> List[int]: + """Get the list of all timestamp types""" + return [ + cls.DATETIME, + cls.TIMESTAMP, + ] + + +class FieldFlag(_Flags): + """MySQL Field Flags + + Field flags as found in MySQL sources mysql-src/include/mysql_com.h + """ + + _prefix: str = "" + NOT_NULL: int = 1 << 0 + PRI_KEY: int = 1 << 1 + UNIQUE_KEY: int = 1 << 2 + MULTIPLE_KEY: int = 1 << 3 + BLOB: int = 1 << 4 + UNSIGNED: int = 1 << 5 + ZEROFILL: int = 1 << 6 + BINARY: int = 1 << 7 + + ENUM: int = 1 << 8 + AUTO_INCREMENT: int = 1 << 9 + TIMESTAMP: int = 1 << 10 + SET: int = 1 << 11 + + NO_DEFAULT_VALUE: int = 1 << 12 + ON_UPDATE_NOW: int = 1 << 13 + NUM: int = 1 << 14 + PART_KEY: int = 1 << 15 + GROUP: int = 1 << 14 # SAME AS NUM !!!!!!!???? + UNIQUE: int = 1 << 16 + BINCMP: int = 1 << 17 + + GET_FIXED_FIELDS: int = 1 << 18 + FIELD_IN_PART_FUNC: int = 1 << 19 + FIELD_IN_ADD_INDEX: int = 1 << 20 + FIELD_IS_RENAMED: int = 1 << 21 + + desc: Dict[str, Tuple[int, str]] = { + "NOT_NULL": (1 << 0, "Field can't be NULL"), + "PRI_KEY": (1 << 1, "Field is part of a primary key"), + "UNIQUE_KEY": (1 << 2, "Field is part of a unique key"), + "MULTIPLE_KEY": (1 << 3, "Field is part of a key"), + "BLOB": (1 << 4, "Field is a blob"), + "UNSIGNED": (1 << 5, "Field is unsigned"), + "ZEROFILL": (1 << 6, "Field is zerofill"), + "BINARY": (1 << 7, "Field is binary "), + "ENUM": (1 << 8, "field is an enum"), + "AUTO_INCREMENT": (1 << 9, "field is a autoincrement field"), + "TIMESTAMP": (1 << 10, "Field is a timestamp"), + "SET": (1 << 11, "field is a set"), + "NO_DEFAULT_VALUE": (1 << 12, "Field doesn't have default value"), + "ON_UPDATE_NOW": (1 << 13, "Field is set to NOW on UPDATE"), + "NUM": (1 << 14, "Field is num (for clients)"), + "PART_KEY": (1 << 15, "Intern; Part of some key"), + "GROUP": (1 << 14, "Intern: Group field"), # Same as NUM + "UNIQUE": (1 << 16, "Intern: Used by sql_yacc"), + "BINCMP": (1 << 17, "Intern: Used by sql_yacc"), + "GET_FIXED_FIELDS": (1 << 18, "Used to get fields in item tree"), + "FIELD_IN_PART_FUNC": (1 << 19, "Field part of partition func"), + "FIELD_IN_ADD_INDEX": (1 << 20, "Intern: Field used in ADD INDEX"), + "FIELD_IS_RENAMED": (1 << 21, "Intern: Field is being renamed"), + } + + +class ServerCmdMeta(ABCMeta): + """ClientFlag Metaclass.""" + + def __getattribute__(cls, name: str) -> Any: + deprecated_options = ( + "FIELD_LIST", + "REFRESH", + "SHUTDOWN", + "PROCESS_INFO", + "PROCESS_KILL", + ) + if name in deprecated_options: + warnings.warn( + f"The option 'ServerCmd.{name}' is deprecated and will be removed in " + "a future release.", + category=DeprecationWarning, + ) + return super().__getattribute__(name) + + +class ServerCmd(_Constants, metaclass=ServerCmdMeta): + """MySQL Server Commands""" + + _prefix: str = "COM_" + SLEEP: int = 0 + QUIT: int = 1 + INIT_DB: int = 2 + QUERY: int = 3 + FIELD_LIST: int = 4 + CREATE_DB: int = 5 + DROP_DB: int = 6 + REFRESH: int = 7 + SHUTDOWN: int = 8 + STATISTICS: int = 9 + PROCESS_INFO: int = 10 + CONNECT: int = 11 + PROCESS_KILL: int = 12 + DEBUG: int = 13 + PING: int = 14 + TIME: int = 15 + DELAYED_INSERT: int = 16 + CHANGE_USER: int = 17 + BINLOG_DUMP: int = 18 + TABLE_DUMP: int = 19 + CONNECT_OUT: int = 20 + REGISTER_REPLICA: int = 21 + STMT_PREPARE: int = 22 + STMT_EXECUTE: int = 23 + STMT_SEND_LONG_DATA: int = 24 + STMT_CLOSE: int = 25 + STMT_RESET: int = 26 + SET_OPTION: int = 27 + STMT_FETCH: int = 28 + DAEMON: int = 29 + BINLOG_DUMP_GTID: int = 30 + RESET_CONNECTION: int = 31 + + desc: Dict[str, Tuple[int, str]] = { + "SLEEP": (0, "SLEEP"), + "QUIT": (1, "QUIT"), + "INIT_DB": (2, "INIT_DB"), + "QUERY": (3, "QUERY"), + "FIELD_LIST": (4, "FIELD_LIST"), + "CREATE_DB": (5, "CREATE_DB"), + "DROP_DB": (6, "DROP_DB"), + "REFRESH": (7, "REFRESH"), + "SHUTDOWN": (8, "SHUTDOWN"), + "STATISTICS": (9, "STATISTICS"), + "PROCESS_INFO": (10, "PROCESS_INFO"), + "CONNECT": (11, "CONNECT"), + "PROCESS_KILL": (12, "PROCESS_KILL"), + "DEBUG": (13, "DEBUG"), + "PING": (14, "PING"), + "TIME": (15, "TIME"), + "DELAYED_INSERT": (16, "DELAYED_INSERT"), + "CHANGE_USER": (17, "CHANGE_USER"), + "BINLOG_DUMP": (18, "BINLOG_DUMP"), + "TABLE_DUMP": (19, "TABLE_DUMP"), + "CONNECT_OUT": (20, "CONNECT_OUT"), + "REGISTER_REPLICA": (21, "REGISTER_REPLICA"), + "STMT_PREPARE": (22, "STMT_PREPARE"), + "STMT_EXECUTE": (23, "STMT_EXECUTE"), + "STMT_SEND_LONG_DATA": (24, "STMT_SEND_LONG_DATA"), + "STMT_CLOSE": (25, "STMT_CLOSE"), + "STMT_RESET": (26, "STMT_RESET"), + "SET_OPTION": (27, "SET_OPTION"), + "STMT_FETCH": (28, "STMT_FETCH"), + "DAEMON": (29, "DAEMON"), + "BINLOG_DUMP_GTID": (30, "BINLOG_DUMP_GTID"), + "RESET_CONNECTION": (31, "RESET_CONNECTION"), + } + + +class ClientFlag(_Flags): + """MySQL Client Flags + + Client options as found in the MySQL sources mysql-src/include/mysql_com.h + """ + + LONG_PASSWD: int = 1 << 0 + FOUND_ROWS: int = 1 << 1 + LONG_FLAG: int = 1 << 2 + CONNECT_WITH_DB: int = 1 << 3 + NO_SCHEMA: int = 1 << 4 + COMPRESS: int = 1 << 5 + ODBC: int = 1 << 6 + LOCAL_FILES: int = 1 << 7 + IGNORE_SPACE: int = 1 << 8 + PROTOCOL_41: int = 1 << 9 + INTERACTIVE: int = 1 << 10 + SSL: int = 1 << 11 + IGNORE_SIGPIPE: int = 1 << 12 + TRANSACTIONS: int = 1 << 13 + RESERVED: int = 1 << 14 + SECURE_CONNECTION: int = 1 << 15 + MULTI_STATEMENTS: int = 1 << 16 + MULTI_RESULTS: int = 1 << 17 + PS_MULTI_RESULTS: int = 1 << 18 + PLUGIN_AUTH: int = 1 << 19 + CONNECT_ARGS: int = 1 << 20 + PLUGIN_AUTH_LENENC_CLIENT_DATA: int = 1 << 21 + CAN_HANDLE_EXPIRED_PASSWORDS: int = 1 << 22 + SESION_TRACK: int = 1 << 23 # deprecated + SESSION_TRACK: int = 1 << 23 + DEPRECATE_EOF: int = 1 << 24 + CLIENT_QUERY_ATTRIBUTES: int = 1 << 27 + SSL_VERIFY_SERVER_CERT: int = 1 << 30 + REMEMBER_OPTIONS: int = 1 << 31 + MULTI_FACTOR_AUTHENTICATION: int = 1 << 28 + + desc: Dict[str, Tuple[int, str]] = { + "LONG_PASSWD": (1 << 0, "New more secure passwords"), + "FOUND_ROWS": (1 << 1, "Found instead of affected rows"), + "LONG_FLAG": (1 << 2, "Get all column flags"), + "CONNECT_WITH_DB": (1 << 3, "One can specify db on connect"), + "NO_SCHEMA": (1 << 4, "Don't allow database.table.column"), + "COMPRESS": (1 << 5, "Can use compression protocol"), + "ODBC": (1 << 6, "ODBC client"), + "LOCAL_FILES": (1 << 7, "Can use LOAD DATA LOCAL"), + "IGNORE_SPACE": (1 << 8, "Ignore spaces before ''"), + "PROTOCOL_41": (1 << 9, "New 4.1 protocol"), + "INTERACTIVE": (1 << 10, "This is an interactive client"), + "SSL": (1 << 11, "Switch to SSL after handshake"), + "IGNORE_SIGPIPE": (1 << 12, "IGNORE sigpipes"), + "TRANSACTIONS": (1 << 13, "Client knows about transactions"), + "RESERVED": (1 << 14, "Old flag for 4.1 protocol"), + "SECURE_CONNECTION": (1 << 15, "New 4.1 authentication"), + "MULTI_STATEMENTS": (1 << 16, "Enable/disable multi-stmt support"), + "MULTI_RESULTS": (1 << 17, "Enable/disable multi-results"), + "PS_MULTI_RESULTS": (1 << 18, "Multi-results in PS-protocol"), + "PLUGIN_AUTH": (1 << 19, "Client supports plugin authentication"), + "CONNECT_ARGS": (1 << 20, "Client supports connection attributes"), + "PLUGIN_AUTH_LENENC_CLIENT_DATA": ( + 1 << 21, + "Enable authentication response packet to be larger than 255 bytes", + ), + "CAN_HANDLE_EXPIRED_PASSWORDS": ( + 1 << 22, + "Don't close the connection for a connection with expired password", + ), + "SESION_TRACK": ( # deprecated + 1 << 23, + "Capable of handling server state change information", + ), + "SESSION_TRACK": ( + 1 << 23, + "Capable of handling server state change information", + ), + "DEPRECATE_EOF": (1 << 24, "Client no longer needs EOF packet"), + "CLIENT_QUERY_ATTRIBUTES": ( + 1 << 27, + "Support optional extension for query parameters", + ), + "SSL_VERIFY_SERVER_CERT": (1 << 30, ""), + "REMEMBER_OPTIONS": (1 << 31, ""), + } + + default: List[int] = [ + LONG_PASSWD, + LONG_FLAG, + CONNECT_WITH_DB, + PROTOCOL_41, + TRANSACTIONS, + SECURE_CONNECTION, + MULTI_STATEMENTS, + MULTI_RESULTS, + CONNECT_ARGS, + ] + + @classmethod + def get_default(cls) -> int: + """Get the default client options set + + Returns a flag with all the default client options set""" + flags = 0 + for option in cls.default: + flags |= option + return flags + + +class ServerFlag(_Flags): + """MySQL Server Flags + + Server flags as found in the MySQL sources mysql-src/include/mysql_com.h + """ + + _prefix: str = "SERVER_" + STATUS_IN_TRANS: int = 1 << 0 + STATUS_AUTOCOMMIT: int = 1 << 1 + MORE_RESULTS_EXISTS: int = 1 << 3 + QUERY_NO_GOOD_INDEX_USED: int = 1 << 4 + QUERY_NO_INDEX_USED: int = 1 << 5 + STATUS_CURSOR_EXISTS: int = 1 << 6 + STATUS_LAST_ROW_SENT: int = 1 << 7 + STATUS_DB_DROPPED: int = 1 << 8 + STATUS_NO_BACKSLASH_ESCAPES: int = 1 << 9 + SERVER_STATUS_METADATA_CHANGED: int = 1 << 10 + SERVER_QUERY_WAS_SLOW: int = 1 << 11 + SERVER_PS_OUT_PARAMS: int = 1 << 12 + SERVER_STATUS_IN_TRANS_READONLY: int = 1 << 13 + SERVER_SESSION_STATE_CHANGED: int = 1 << 14 + + desc: Dict[str, Tuple[int, str]] = { + "SERVER_STATUS_IN_TRANS": (1 << 0, "Transaction has started"), + "SERVER_STATUS_AUTOCOMMIT": (1 << 1, "Server in auto_commit mode"), + "SERVER_MORE_RESULTS_EXISTS": ( + 1 << 3, + "Multi query - next query exists", + ), + "SERVER_QUERY_NO_GOOD_INDEX_USED": (1 << 4, ""), + "SERVER_QUERY_NO_INDEX_USED": (1 << 5, ""), + "SERVER_STATUS_CURSOR_EXISTS": ( + 1 << 6, + "Set when server opened a read-only non-scrollable cursor for a query.", + ), + "SERVER_STATUS_LAST_ROW_SENT": ( + 1 << 7, + "Set when a read-only cursor is exhausted", + ), + "SERVER_STATUS_DB_DROPPED": (1 << 8, "A database was dropped"), + "SERVER_STATUS_NO_BACKSLASH_ESCAPES": (1 << 9, ""), + "SERVER_STATUS_METADATA_CHANGED": ( + 1024, + "Set if after a prepared statement " + "reprepare we discovered that the " + "new statement returns a different " + "number of result set columns.", + ), + "SERVER_QUERY_WAS_SLOW": (2048, ""), + "SERVER_PS_OUT_PARAMS": ( + 4096, + "To mark ResultSet containing output parameter values.", + ), + "SERVER_STATUS_IN_TRANS_READONLY": ( + 8192, + "Set if multi-statement transaction is a read-only transaction.", + ), + "SERVER_SESSION_STATE_CHANGED": ( + 1 << 14, + "Session state has changed on the " + "server because of the execution of " + "the last statement", + ), + } + + +class RefreshOptionMeta(ABCMeta): + """RefreshOption Metaclass.""" + + @property + def SLAVE(self) -> int: # pylint: disable=bad-mcs-method-argument,invalid-name + """Return the deprecated alias of RefreshOption.REPLICA. + + Raises a warning about this attribute deprecation. + """ + return _obsolete_option( + "RefreshOption.SLAVE", + "RefreshOption.REPLICA", + RefreshOption.REPLICA, + ) + + +class RefreshOption(_Constants, metaclass=RefreshOptionMeta): + """MySQL Refresh command options. + + Options used when sending the COM_REFRESH server command. + """ + + _prefix: str = "REFRESH_" + GRANT: int = 1 << 0 + LOG: int = 1 << 1 + TABLES: int = 1 << 2 + HOST: int = 1 << 3 + STATUS: int = 1 << 4 + THREADS: int = 1 << 5 + REPLICA: int = 1 << 6 + + desc: Dict[str, Tuple[int, str]] = { + "GRANT": (1 << 0, "Refresh grant tables"), + "LOG": (1 << 1, "Start on new log file"), + "TABLES": (1 << 2, "close all tables"), + "HOST": (1 << 3, "Flush host cache"), + "STATUS": (1 << 4, "Flush status variables"), + "THREADS": (1 << 5, "Flush thread cache"), + "REPLICA": (1 << 6, "Reset source info and restart replica thread"), + "SLAVE": (1 << 6, "Deprecated option; use REPLICA instead."), + } + + +class ShutdownType(_Constants): + """MySQL Shutdown types + + Shutdown types used by the COM_SHUTDOWN server command. + """ + + _prefix: str = "" + SHUTDOWN_DEFAULT: int = 0 + SHUTDOWN_WAIT_CONNECTIONS: int = 1 + SHUTDOWN_WAIT_TRANSACTIONS: int = 2 + SHUTDOWN_WAIT_UPDATES: int = 8 + SHUTDOWN_WAIT_ALL_BUFFERS: int = 16 + SHUTDOWN_WAIT_CRITICAL_BUFFERS: int = 17 + KILL_QUERY: int = 254 + KILL_CONNECTION: int = 255 + + desc: Dict[str, Tuple[int, str]] = { + "SHUTDOWN_DEFAULT": ( + SHUTDOWN_DEFAULT, + "defaults to SHUTDOWN_WAIT_ALL_BUFFERS", + ), + "SHUTDOWN_WAIT_CONNECTIONS": ( + SHUTDOWN_WAIT_CONNECTIONS, + "wait for existing connections to finish", + ), + "SHUTDOWN_WAIT_TRANSACTIONS": ( + SHUTDOWN_WAIT_TRANSACTIONS, + "wait for existing trans to finish", + ), + "SHUTDOWN_WAIT_UPDATES": ( + SHUTDOWN_WAIT_UPDATES, + "wait for existing updates to finish", + ), + "SHUTDOWN_WAIT_ALL_BUFFERS": ( + SHUTDOWN_WAIT_ALL_BUFFERS, + "flush InnoDB and other storage engine buffers", + ), + "SHUTDOWN_WAIT_CRITICAL_BUFFERS": ( + SHUTDOWN_WAIT_CRITICAL_BUFFERS, + "don't flush InnoDB buffers, flush other storage engines' buffers", + ), + "KILL_QUERY": (KILL_QUERY, "(no description)"), + "KILL_CONNECTION": (KILL_CONNECTION, "(no description)"), + } + + +class CharacterSet(_Constants): + """MySQL supported character sets and collations + + List of character sets with their collations supported by MySQL. This + maps to the character set we get from the server within the handshake + packet. + + The list is hardcode so we avoid a database query when getting the + name of the used character set or collation. + """ + + # Use LTS character set as default + desc: List[ + Optional[Tuple[str, str, bool]] + ] = MYSQL_CHARACTER_SETS_57 # type: ignore[assignment] + mysql_version: Tuple[int, ...] = (5, 7) + + # Multi-byte character sets which use 5c (backslash) in characters + slash_charsets: Tuple[int, ...] = (1, 13, 28, 84, 87, 88) + + @classmethod + def set_mysql_version(cls, version: Tuple[int, ...]) -> None: + """Set the MySQL major version and change the charset mapping if is 5.7. + + Args: + version (tuple): MySQL version tuple. + """ + cls.mysql_version = version[:2] + if cls.mysql_version >= (8, 0): + cls.desc = MYSQL_CHARACTER_SETS + + @classmethod + def get_info(cls, setid: int) -> Tuple[str, str]: + """Retrieves character set information as tuple using an ID + + Retrieves character set and collation information based on the + given MySQL ID. + + Raises ProgrammingError when character set is not supported. + + Returns a tuple. + """ + try: + return cls.desc[setid][0:2] + except IndexError: + raise ProgrammingError(f"Character set '{setid}' unsupported") from None + + @classmethod + def get_desc(cls, name: int) -> str: # type: ignore[override] + """Retrieves character set information as string using an ID + + Retrieves character set and collation information based on the + given MySQL ID. + + Returns a tuple. + """ + charset, collation = cls.get_info(name) + return f"{charset}/{collation}" + + @classmethod + def get_default_collation(cls, charset: Union[int, str]) -> Tuple[str, str, int]: + """Retrieves the default collation for given character set + + Raises ProgrammingError when character set is not supported. + + Returns list (collation, charset, index) + """ + if isinstance(charset, int): + try: + info = cls.desc[charset] + return info[1], info[0], charset + except (IndexError, KeyError) as err: + raise ProgrammingError( + f"Character set ID '{charset}' unsupported" + ) from err + + for cid, info in enumerate(cls.desc): + if info is None: + continue + if info[0] == charset and info[2] is True: + return info[1], info[0], cid + + raise ProgrammingError(f"Character set '{charset}' unsupported") + + @classmethod + def get_charset_info( + cls, charset: Optional[Union[int, str]] = None, collation: Optional[str] = None + ) -> Tuple[int, str, str]: + """Get character set information using charset name and/or collation + + Retrieves character set and collation information given character + set name and/or a collation name. + If charset is an integer, it will look up the character set based + on the MySQL's ID. + For example: + get_charset_info('utf8',None) + get_charset_info(collation='utf8_general_ci') + get_charset_info(47) + + Raises ProgrammingError when character set is not supported. + + Returns a tuple with (id, characterset name, collation) + """ + info: Optional[Union[Tuple[str, str, bool], Tuple[str, str, int]]] = None + if isinstance(charset, int): + try: + info = cls.desc[charset] + return (charset, info[0], info[1]) + except IndexError as err: + raise ProgrammingError(f"Character set ID {charset} unknown") from err + + if charset in ("utf8", "utf-8") and cls.mysql_version >= (8, 0): + charset = "utf8mb4" + if charset is not None and collation is None: + info = cls.get_default_collation(charset) + return (info[2], info[1], info[0]) + if charset is None and collation is not None: + for cid, info in enumerate(cls.desc): + if info is None: + continue + if collation == info[1]: + return (cid, info[0], info[1]) + raise ProgrammingError(f"Collation '{collation}' unknown") + for cid, info in enumerate(cls.desc): + if info is None: + continue + if info[0] == charset and info[1] == collation: + return (cid, info[0], info[1]) + _ = cls.get_default_collation(charset) + raise ProgrammingError(f"Collation '{collation}' unknown") + + @classmethod + def get_supported(cls) -> Tuple[str, ...]: + """Retrieves a list with names of all supproted character sets + + Returns a tuple. + """ + res = [] + for info in cls.desc: + if info and info[0] not in res: + res.append(info[0]) + return tuple(res) + + +class SQLMode(_Constants): + """MySQL SQL Modes + + The numeric values of SQL Modes are not interesting, only the names + are used when setting the SQL_MODE system variable using the MySQL + SET command. + + See http://dev.mysql.com/doc/refman/5.6/en/server-sql-mode.html + """ + + _prefix: str = "MODE_" + REAL_AS_FLOAT: str = "REAL_AS_FLOAT" + PIPES_AS_CONCAT: str = "PIPES_AS_CONCAT" + ANSI_QUOTES: str = "ANSI_QUOTES" + IGNORE_SPACE: str = "IGNORE_SPACE" + NOT_USED: str = "NOT_USED" + ONLY_FULL_GROUP_BY: str = "ONLY_FULL_GROUP_BY" + NO_UNSIGNED_SUBTRACTION: str = "NO_UNSIGNED_SUBTRACTION" + NO_DIR_IN_CREATE: str = "NO_DIR_IN_CREATE" + POSTGRESQL: str = "POSTGRESQL" + ORACLE: str = "ORACLE" + MSSQL: str = "MSSQL" + DB2: str = "DB2" + MAXDB: str = "MAXDB" + NO_KEY_OPTIONS: str = "NO_KEY_OPTIONS" + NO_TABLE_OPTIONS: str = "NO_TABLE_OPTIONS" + NO_FIELD_OPTIONS: str = "NO_FIELD_OPTIONS" + MYSQL323: str = "MYSQL323" + MYSQL40: str = "MYSQL40" + ANSI: str = "ANSI" + NO_AUTO_VALUE_ON_ZERO: str = "NO_AUTO_VALUE_ON_ZERO" + NO_BACKSLASH_ESCAPES: str = "NO_BACKSLASH_ESCAPES" + STRICT_TRANS_TABLES: str = "STRICT_TRANS_TABLES" + STRICT_ALL_TABLES: str = "STRICT_ALL_TABLES" + NO_ZERO_IN_DATE: str = "NO_ZERO_IN_DATE" + NO_ZERO_DATE: str = "NO_ZERO_DATE" + INVALID_DATES: str = "INVALID_DATES" + ERROR_FOR_DIVISION_BY_ZERO: str = "ERROR_FOR_DIVISION_BY_ZERO" + TRADITIONAL: str = "TRADITIONAL" + NO_AUTO_CREATE_USER: str = "NO_AUTO_CREATE_USER" + HIGH_NOT_PRECEDENCE: str = "HIGH_NOT_PRECEDENCE" + NO_ENGINE_SUBSTITUTION: str = "NO_ENGINE_SUBSTITUTION" + PAD_CHAR_TO_FULL_LENGTH: str = "PAD_CHAR_TO_FULL_LENGTH" + + @classmethod + def get_desc(cls, name: str) -> Optional[str]: + raise NotImplementedError + + @classmethod + def get_info(cls, setid: int) -> Optional[str]: + raise NotImplementedError + + @classmethod + def get_full_info(cls) -> Tuple[str, ...]: + """Returns a sequence of all available SQL Modes + + This class method returns a tuple containing all SQL Mode names. The + names will be alphabetically sorted. + + Returns a tuple. + """ + res = [] + for key in vars(cls).keys(): + if not key.startswith("_") and not hasattr(getattr(cls, key), "__call__"): + res.append(key) + return tuple(sorted(res)) + + +CONN_ATTRS_DN: List[str] = [ + "_pid", + "_platform", + "_source_host", + "_client_name", + "_client_license", + "_client_version", + "_os", + "_connector_name", + "_connector_license", + "_connector_version", +] + +# TLS v1.0 cipher suites IANI to OpenSSL name translation +TLSV1_CIPHER_SUITES: Dict[str, str] = { + "TLS_RSA_WITH_NULL_MD5": "NULL-MD5", + "TLS_RSA_WITH_NULL_SHA": "NULL-SHA", + "TLS_RSA_WITH_RC4_128_MD5": "RC4-MD5", + "TLS_RSA_WITH_RC4_128_SHA": "RC4-SHA", + "TLS_RSA_WITH_IDEA_CBC_SHA": "IDEA-CBC-SHA", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA": "DES-CBC3-SHA", + "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA": "Not implemented.", + "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA": "Not implemented.", + "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA": "DHE-DSS-DES-CBC3-SHA", + "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA": "DHE-RSA-DES-CBC3-SHA", + "TLS_DH_anon_WITH_RC4_128_MD5": "ADH-RC4-MD5", + "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA": "ADH-DES-CBC3-SHA", + # AES cipher suites from RFC3268, extending TLS v1.0 + "TLS_RSA_WITH_AES_128_CBC_SHA": "AES128-SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA": "AES256-SHA", + "TLS_DH_DSS_WITH_AES_128_CBC_SHA": "DH-DSS-AES128-SHA", + "TLS_DH_DSS_WITH_AES_256_CBC_SHA": "DH-DSS-AES256-SHA", + "TLS_DH_RSA_WITH_AES_128_CBC_SHA": "DH-RSA-AES128-SHA", + "TLS_DH_RSA_WITH_AES_256_CBC_SHA": "DH-RSA-AES256-SHA", + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA": "DHE-DSS-AES128-SHA", + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA": "DHE-DSS-AES256-SHA", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA": "DHE-RSA-AES128-SHA", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA": "DHE-RSA-AES256-SHA", + "TLS_DH_anon_WITH_AES_128_CBC_SHA": "ADH-AES128-SHA", + "TLS_DH_anon_WITH_AES_256_CBC_SHA": "ADH-AES256-SHA", + # Camellia cipher suites from RFC4132, extending TLS v1.0 + "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA": "CAMELLIA128-SHA", + "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA": "CAMELLIA256-SHA", + "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA": "DH-DSS-CAMELLIA128-SHA", + "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA": "DH-DSS-CAMELLIA256-SHA", + "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA": "DH-RSA-CAMELLIA128-SHA", + "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA": "DH-RSA-CAMELLIA256-SHA", + "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA": "DHE-DSS-CAMELLIA128-SHA", + "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA": "DHE-DSS-CAMELLIA256-SHA", + "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA": "DHE-RSA-CAMELLIA128-SHA", + "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA": "DHE-RSA-CAMELLIA256-SHA", + "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA": "ADH-CAMELLIA128-SHA", + "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA": "ADH-CAMELLIA256-SHA", + # SEED cipher suites from RFC4162, extending TLS v1.0 + "TLS_RSA_WITH_SEED_CBC_SHA": "SEED-SHA", + "TLS_DH_DSS_WITH_SEED_CBC_SHA": "DH-DSS-SEED-SHA", + "TLS_DH_RSA_WITH_SEED_CBC_SHA": "DH-RSA-SEED-SHA", + "TLS_DHE_DSS_WITH_SEED_CBC_SHA": "DHE-DSS-SEED-SHA", + "TLS_DHE_RSA_WITH_SEED_CBC_SHA": "DHE-RSA-SEED-SHA", + "TLS_DH_anon_WITH_SEED_CBC_SHA": "ADH-SEED-SHA", + # GOST cipher suites from draft-chudov-cryptopro-cptls, extending TLS v1.0 + "TLS_GOSTR341094_WITH_28147_CNT_IMIT": "GOST94-GOST89-GOST89", + "TLS_GOSTR341001_WITH_28147_CNT_IMIT": "GOST2001-GOST89-GOST89", + "TLS_GOSTR341094_WITH_NULL_GOSTR3411": "GOST94-NULL-GOST94", + "TLS_GOSTR341001_WITH_NULL_GOSTR3411": "GOST2001-NULL-GOST94", +} + +# TLS v1.1 cipher suites IANI to OpenSSL name translation +TLSV1_1_CIPHER_SUITES: Dict[str, str] = TLSV1_CIPHER_SUITES + +# TLS v1.2 cipher suites IANI to OpenSSL name translation +TLSV1_2_CIPHER_SUITES: Dict[str, str] = { + "TLS_RSA_WITH_NULL_SHA256": "NULL-SHA256", + "TLS_RSA_WITH_AES_128_CBC_SHA256": "AES128-SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA256": "AES256-SHA256", + "TLS_RSA_WITH_AES_128_GCM_SHA256": "AES128-GCM-SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384": "AES256-GCM-SHA384", + "TLS_DH_RSA_WITH_AES_128_CBC_SHA256": "DH-RSA-AES128-SHA256", + "TLS_DH_RSA_WITH_AES_256_CBC_SHA256": "DH-RSA-AES256-SHA256", + "TLS_DH_RSA_WITH_AES_128_GCM_SHA256": "DH-RSA-AES128-GCM-SHA256", + "TLS_DH_RSA_WITH_AES_256_GCM_SHA384": "DH-RSA-AES256-GCM-SHA384", + "TLS_DH_DSS_WITH_AES_128_CBC_SHA256": "DH-DSS-AES128-SHA256", + "TLS_DH_DSS_WITH_AES_256_CBC_SHA256": "DH-DSS-AES256-SHA256", + "TLS_DH_DSS_WITH_AES_128_GCM_SHA256": "DH-DSS-AES128-GCM-SHA256", + "TLS_DH_DSS_WITH_AES_256_GCM_SHA384": "DH-DSS-AES256-GCM-SHA384", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256": "DHE-RSA-AES128-SHA256", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256": "DHE-RSA-AES256-SHA256", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256": "DHE-RSA-AES128-GCM-SHA256", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384": "DHE-RSA-AES256-GCM-SHA384", + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256": "DHE-DSS-AES128-SHA256", + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256": "DHE-DSS-AES256-SHA256", + "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256": "DHE-DSS-AES128-GCM-SHA256", + "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384": "DHE-DSS-AES256-GCM-SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": "ECDHE-RSA-AES128-SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384": "ECDHE-RSA-AES256-SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": "ECDHE-RSA-AES128-GCM-SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": "ECDHE-RSA-AES256-GCM-SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": "ECDHE-ECDSA-AES128-SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384": "ECDHE-ECDSA-AES256-SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": "ECDHE-ECDSA-AES128-GCM-SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": "ECDHE-ECDSA-AES256-GCM-SHA384", + "TLS_DH_anon_WITH_AES_128_CBC_SHA256": "ADH-AES128-SHA256", + "TLS_DH_anon_WITH_AES_256_CBC_SHA256": "ADH-AES256-SHA256", + "TLS_DH_anon_WITH_AES_128_GCM_SHA256": "ADH-AES128-GCM-SHA256", + "TLS_DH_anon_WITH_AES_256_GCM_SHA384": "ADH-AES256-GCM-SHA384", + "RSA_WITH_AES_128_CCM": "AES128-CCM", + "RSA_WITH_AES_256_CCM": "AES256-CCM", + "DHE_RSA_WITH_AES_128_CCM": "DHE-RSA-AES128-CCM", + "DHE_RSA_WITH_AES_256_CCM": "DHE-RSA-AES256-CCM", + "RSA_WITH_AES_128_CCM_8": "AES128-CCM8", + "RSA_WITH_AES_256_CCM_8": "AES256-CCM8", + "DHE_RSA_WITH_AES_128_CCM_8": "DHE-RSA-AES128-CCM8", + "DHE_RSA_WITH_AES_256_CCM_8": "DHE-RSA-AES256-CCM8", + "ECDHE_ECDSA_WITH_AES_128_CCM": "ECDHE-ECDSA-AES128-CCM", + "ECDHE_ECDSA_WITH_AES_256_CCM": "ECDHE-ECDSA-AES256-CCM", + "ECDHE_ECDSA_WITH_AES_128_CCM_8": "ECDHE-ECDSA-AES128-CCM8", + "ECDHE_ECDSA_WITH_AES_256_CCM_8": "ECDHE-ECDSA-AES256-CCM8", + # ARIA cipher suites from RFC6209, extending TLS v1.2 + "TLS_RSA_WITH_ARIA_128_GCM_SHA256": "ARIA128-GCM-SHA256", + "TLS_RSA_WITH_ARIA_256_GCM_SHA384": "ARIA256-GCM-SHA384", + "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256": "DHE-RSA-ARIA128-GCM-SHA256", + "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384": "DHE-RSA-ARIA256-GCM-SHA384", + "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256": "DHE-DSS-ARIA128-GCM-SHA256", + "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384": "DHE-DSS-ARIA256-GCM-SHA384", + "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256": "ECDHE-ECDSA-ARIA128-GCM-SHA256", + "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384": "ECDHE-ECDSA-ARIA256-GCM-SHA384", + "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256": "ECDHE-ARIA128-GCM-SHA256", + "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384": "ECDHE-ARIA256-GCM-SHA384", + "TLS_PSK_WITH_ARIA_128_GCM_SHA256": "PSK-ARIA128-GCM-SHA256", + "TLS_PSK_WITH_ARIA_256_GCM_SHA384": "PSK-ARIA256-GCM-SHA384", + "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256": "DHE-PSK-ARIA128-GCM-SHA256", + "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384": "DHE-PSK-ARIA256-GCM-SHA384", + "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256": "RSA-PSK-ARIA128-GCM-SHA256", + "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384": "RSA-PSK-ARIA256-GCM-SHA384", + # Camellia HMAC-Based cipher suites from RFC6367, extending TLS v1.2 + "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-ECDSA-CAMELLIA128-SHA256", + "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-ECDSA-CAMELLIA256-SHA384", + "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-RSA-CAMELLIA128-SHA256", + "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-RSA-CAMELLIA256-SHA384", + # Pre-shared keying (PSK) cipher suites", + "PSK_WITH_NULL_SHA": "PSK-NULL-SHA", + "DHE_PSK_WITH_NULL_SHA": "DHE-PSK-NULL-SHA", + "RSA_PSK_WITH_NULL_SHA": "RSA-PSK-NULL-SHA", + "PSK_WITH_RC4_128_SHA": "PSK-RC4-SHA", + "PSK_WITH_3DES_EDE_CBC_SHA": "PSK-3DES-EDE-CBC-SHA", + "PSK_WITH_AES_128_CBC_SHA": "PSK-AES128-CBC-SHA", + "PSK_WITH_AES_256_CBC_SHA": "PSK-AES256-CBC-SHA", + "DHE_PSK_WITH_RC4_128_SHA": "DHE-PSK-RC4-SHA", + "DHE_PSK_WITH_3DES_EDE_CBC_SHA": "DHE-PSK-3DES-EDE-CBC-SHA", + "DHE_PSK_WITH_AES_128_CBC_SHA": "DHE-PSK-AES128-CBC-SHA", + "DHE_PSK_WITH_AES_256_CBC_SHA": "DHE-PSK-AES256-CBC-SHA", + "RSA_PSK_WITH_RC4_128_SHA": "RSA-PSK-RC4-SHA", + "RSA_PSK_WITH_3DES_EDE_CBC_SHA": "RSA-PSK-3DES-EDE-CBC-SHA", + "RSA_PSK_WITH_AES_128_CBC_SHA": "RSA-PSK-AES128-CBC-SHA", + "RSA_PSK_WITH_AES_256_CBC_SHA": "RSA-PSK-AES256-CBC-SHA", + "PSK_WITH_AES_128_GCM_SHA256": "PSK-AES128-GCM-SHA256", + "PSK_WITH_AES_256_GCM_SHA384": "PSK-AES256-GCM-SHA384", + "DHE_PSK_WITH_AES_128_GCM_SHA256": "DHE-PSK-AES128-GCM-SHA256", + "DHE_PSK_WITH_AES_256_GCM_SHA384": "DHE-PSK-AES256-GCM-SHA384", + "RSA_PSK_WITH_AES_128_GCM_SHA256": "RSA-PSK-AES128-GCM-SHA256", + "RSA_PSK_WITH_AES_256_GCM_SHA384": "RSA-PSK-AES256-GCM-SHA384", + "PSK_WITH_AES_128_CBC_SHA256": "PSK-AES128-CBC-SHA256", + "PSK_WITH_AES_256_CBC_SHA384": "PSK-AES256-CBC-SHA384", + "PSK_WITH_NULL_SHA256": "PSK-NULL-SHA256", + "PSK_WITH_NULL_SHA384": "PSK-NULL-SHA384", + "DHE_PSK_WITH_AES_128_CBC_SHA256": "DHE-PSK-AES128-CBC-SHA256", + "DHE_PSK_WITH_AES_256_CBC_SHA384": "DHE-PSK-AES256-CBC-SHA384", + "DHE_PSK_WITH_NULL_SHA256": "DHE-PSK-NULL-SHA256", + "DHE_PSK_WITH_NULL_SHA384": "DHE-PSK-NULL-SHA384", + "RSA_PSK_WITH_AES_128_CBC_SHA256": "RSA-PSK-AES128-CBC-SHA256", + "RSA_PSK_WITH_AES_256_CBC_SHA384": "RSA-PSK-AES256-CBC-SHA384", + "RSA_PSK_WITH_NULL_SHA256": "RSA-PSK-NULL-SHA256", + "RSA_PSK_WITH_NULL_SHA384": "RSA-PSK-NULL-SHA384", + "ECDHE_PSK_WITH_RC4_128_SHA": "ECDHE-PSK-RC4-SHA", + "ECDHE_PSK_WITH_3DES_EDE_CBC_SHA": "ECDHE-PSK-3DES-EDE-CBC-SHA", + "ECDHE_PSK_WITH_AES_128_CBC_SHA": "ECDHE-PSK-AES128-CBC-SHA", + "ECDHE_PSK_WITH_AES_256_CBC_SHA": "ECDHE-PSK-AES256-CBC-SHA", + "ECDHE_PSK_WITH_AES_128_CBC_SHA256": "ECDHE-PSK-AES128-CBC-SHA256", + "ECDHE_PSK_WITH_AES_256_CBC_SHA384": "ECDHE-PSK-AES256-CBC-SHA384", + "ECDHE_PSK_WITH_NULL_SHA": "ECDHE-PSK-NULL-SHA", + "ECDHE_PSK_WITH_NULL_SHA256": "ECDHE-PSK-NULL-SHA256", + "ECDHE_PSK_WITH_NULL_SHA384": "ECDHE-PSK-NULL-SHA384", + "PSK_WITH_CAMELLIA_128_CBC_SHA256": "PSK-CAMELLIA128-SHA256", + "PSK_WITH_CAMELLIA_256_CBC_SHA384": "PSK-CAMELLIA256-SHA384", + "DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": "DHE-PSK-CAMELLIA128-SHA256", + "DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": "DHE-PSK-CAMELLIA256-SHA384", + "RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256": "RSA-PSK-CAMELLIA128-SHA256", + "RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384": "RSA-PSK-CAMELLIA256-SHA384", + "ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-PSK-CAMELLIA128-SHA256", + "ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-PSK-CAMELLIA256-SHA384", + "PSK_WITH_AES_128_CCM": "PSK-AES128-CCM", + "PSK_WITH_AES_256_CCM": "PSK-AES256-CCM", + "DHE_PSK_WITH_AES_128_CCM": "DHE-PSK-AES128-CCM", + "DHE_PSK_WITH_AES_256_CCM": "DHE-PSK-AES256-CCM", + "PSK_WITH_AES_128_CCM_8": "PSK-AES128-CCM8", + "PSK_WITH_AES_256_CCM_8": "PSK-AES256-CCM8", + "DHE_PSK_WITH_AES_128_CCM_8": "DHE-PSK-AES128-CCM8", + "DHE_PSK_WITH_AES_256_CCM_8": "DHE-PSK-AES256-CCM8", + # ChaCha20-Poly1305 cipher suites, extending TLS v1.2 + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-RSA-CHACHA20-POLY1305", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-ECDSA-CHACHA20-POLY1305", + "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256": "DHE-RSA-CHACHA20-POLY1305", + "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256": "PSK-CHACHA20-POLY1305", + "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-PSK-CHACHA20-POLY1305", + "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256": "DHE-PSK-CHACHA20-POLY1305", + "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256": "RSA-PSK-CHACHA20-POLY1305", +} + +# TLS v1.3 cipher suites IANI to OpenSSL name translation +TLSV1_3_CIPHER_SUITES: Dict[str, str] = { + "TLS_AES_128_GCM_SHA256": "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384": "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256": "TLS_CHACHA20_POLY1305_SHA256", + "TLS_AES_128_CCM_SHA256": "TLS_AES_128_CCM_SHA256", + "TLS_AES_128_CCM_8_SHA256": "TLS_AES_128_CCM_8_SHA256", +} + +TLS_CIPHER_SUITES: Dict[str, Dict[str, str]] = { + "TLSv1": TLSV1_CIPHER_SUITES, + "TLSv1.1": TLSV1_1_CIPHER_SUITES, + "TLSv1.2": TLSV1_2_CIPHER_SUITES, + "TLSv1.3": TLSV1_3_CIPHER_SUITES, +} + +OPENSSL_CS_NAMES: Dict[str, ValuesView[str]] = { + "TLSv1": TLSV1_CIPHER_SUITES.values(), + "TLSv1.1": TLSV1_1_CIPHER_SUITES.values(), + "TLSv1.2": TLSV1_2_CIPHER_SUITES.values(), + "TLSv1.3": TLSV1_3_CIPHER_SUITES.values(), +} diff --git a/mysql/connector/conversion.py b/mysql/connector/conversion.py new file mode 100644 index 0000000..dc0886b --- /dev/null +++ b/mysql/connector/conversion.py @@ -0,0 +1,740 @@ +# Copyright (c) 2009, 2023, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Converting MySQL and Python types +""" + +import datetime +import math +import struct +import time + +from decimal import Decimal +from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union + +from .constants import CharacterSet, FieldFlag, FieldType +from .custom_types import HexLiteral +from .types import ( + DescriptionType, + RowType, + StrOrBytes, + ToMysqlInputTypes, + ToMysqlOutputTypes, + ToPythonOutputTypes, +) +from .utils import NUMERIC_TYPES + +CONVERT_ERROR = "Could not convert '{value}' to python {pytype}" + + +class MySQLConverterBase: + """Base class for conversion classes + + All class dealing with converting to and from MySQL data types must + be a subclass of this class. + """ + + def __init__( + self, + charset: Optional[str] = "utf8", + use_unicode: bool = True, + str_fallback: bool = False, + ) -> None: + self.python_types: Optional[Tuple[Any, ...]] = None + self.mysql_types: Optional[Tuple[Any, ...]] = None + self.charset: Optional[str] = None + self.charset_id: int = 0 + self.set_charset(charset) + self.use_unicode: bool = use_unicode + self.str_fallback: bool = str_fallback + self._cache_field_types: Dict[ + int, + Callable[[bytes, DescriptionType], ToPythonOutputTypes], + ] = {} + + def set_charset(self, charset: Optional[str]) -> None: + """Set character set""" + if charset in ("utf8mb4", "utf8mb3"): + charset = "utf8" + if charset is not None: + self.charset = charset + else: + # default to utf8 + self.charset = "utf8" + self.charset_id = CharacterSet.get_charset_info(self.charset)[0] + + def set_unicode(self, value: bool = True) -> None: + """Set whether to use Unicode""" + self.use_unicode = value + + def to_mysql( + self, value: ToMysqlInputTypes + ) -> Union[ToMysqlInputTypes, HexLiteral]: + """Convert Python data type to MySQL""" + type_name = value.__class__.__name__.lower() + try: + converted: ToMysqlOutputTypes = getattr(self, f"_{type_name}_to_mysql")( + value + ) + return converted + except AttributeError: + return value + + def to_python( + self, vtype: DescriptionType, value: Optional[bytes] + ) -> ToPythonOutputTypes: + """Convert MySQL data type to Python""" + + if (value == b"\x00" or value is None) and vtype[1] != FieldType.BIT: + # Don't go further when we hit a NULL value + return None + + if not self._cache_field_types: + self._cache_field_types = {} + for name, info in FieldType.desc.items(): + try: + self._cache_field_types[info[0]] = getattr( + self, f"_{name.lower()}_to_python" + ) + except AttributeError: + # We ignore field types which has no method + pass + if value is None: + return None + try: + return self._cache_field_types[vtype[1]](value, vtype) + except KeyError: + return value + + @staticmethod + def escape( + value: Any, + sql_mode: Optional[str] = None, # pylint: disable=unused-argument + ) -> Any: + """Escape buffer for sending to MySQL""" + return value + + @staticmethod + def quote(buf: Any) -> StrOrBytes: + """Quote buffer for sending to MySQL""" + return str(buf) + + +class MySQLConverter(MySQLConverterBase): + """Default conversion class for MySQL Connector/Python. + + o escape method: for escaping values send to MySQL + o quoting method: for quoting values send to MySQL in statements + o conversion mapping: maps Python and MySQL data types to + function for converting them. + + Whenever one needs to convert values differently, a converter_class + argument can be given while instantiating a new connection like + cnx.connect(converter_class=CustomMySQLConverterClass). + + """ + + def __init__( + self, + charset: Optional[str] = None, + use_unicode: bool = True, + str_fallback: bool = False, + ) -> None: + MySQLConverterBase.__init__(self, charset, use_unicode, str_fallback) + self._cache_field_types: Dict[ + int, + Callable[[bytes, DescriptionType], ToPythonOutputTypes], + ] = {} + + @staticmethod + def escape(value: Any, sql_mode: Optional[str] = None) -> Any: + """ + Escapes special characters as they are expected to by when MySQL + receives them. + As found in MySQL source mysys/charset.c + + Returns the value if not a string, or the escaped string. + """ + if isinstance(value, (bytes, bytearray)): + if sql_mode == "NO_BACKSLASH_ESCAPES": + return value.replace(b"'", b"''") + value = value.replace(b"\\", b"\\\\") + value = value.replace(b"\n", b"\\n") + value = value.replace(b"\r", b"\\r") + value = value.replace(b"\047", b"\134\047") # single quotes + value = value.replace(b"\042", b"\134\042") # double quotes + value = value.replace(b"\032", b"\134\032") # for Win32 + elif isinstance(value, str) and not isinstance(value, HexLiteral): + if sql_mode == "NO_BACKSLASH_ESCAPES": + return value.replace("'", "''") + value = value.replace("\\", "\\\\") + value = value.replace("\n", "\\n") + value = value.replace("\r", "\\r") + value = value.replace("\047", "\134\047") # single quotes + value = value.replace("\042", "\134\042") # double quotes + value = value.replace("\032", "\134\032") # for Win32 + return value + + @staticmethod + def quote(buf: Optional[Union[float, int, Decimal, HexLiteral, bytes]]) -> bytes: + """ + Quote the parameters for commands. General rules: + o numbers are returns as bytes using ascii codec + o None is returned as bytearray(b'NULL') + o Everything else is single quoted '' + + Returns a bytearray object. + """ + if isinstance(buf, NUMERIC_TYPES): + return str(buf).encode("ascii") + if isinstance(buf, type(None)): + return bytearray(b"NULL") + return bytearray(b"'" + buf + b"'") # type: ignore[operator] + + def to_mysql(self, value: ToMysqlInputTypes) -> ToMysqlOutputTypes: + """Convert Python data type to MySQL""" + type_name = value.__class__.__name__.lower() + try: + converted: ToMysqlOutputTypes = getattr(self, f"_{type_name}_to_mysql")( + value + ) + return converted + except AttributeError: + if self.str_fallback: + return str(value).encode() + raise TypeError( + f"Python '{type_name}' cannot be converted to a MySQL type" + ) from None + + def to_python( + self, + vtype: DescriptionType, + value: Optional[bytes], + ) -> ToPythonOutputTypes: + """Convert MySQL data type to Python""" + # \x00 + if value == 0 and vtype[1] != FieldType.BIT: + # Don't go further when we hit a NULL value + return None + if value is None: + return None + + if not self._cache_field_types: + self._cache_field_types = {} + for name, info in FieldType.desc.items(): + try: + self._cache_field_types[info[0]] = getattr( + self, f"_{name.lower()}_to_python" + ) + except AttributeError: + # We ignore field types which has no method + pass + + try: + return self._cache_field_types[vtype[1]](value, vtype) + except KeyError: + # If one type is not defined, we just return the value as str + try: + return value.decode("utf-8") + except UnicodeDecodeError: + return value + except ValueError as err: + raise ValueError(f"{err} (field {vtype[0]})") from err + except TypeError as err: + raise TypeError(f"{err} (field {vtype[0]})") from err + + @staticmethod + def _int_to_mysql(value: int) -> int: + """Convert value to int""" + return int(value) + + @staticmethod + def _long_to_mysql(value: int) -> int: + """Convert value to int + + Note: There is no type "long" in Python 3 since integers are of unlimited size. + Since Python 2 is no longer supported, this method should be deprecated. + """ + return int(value) + + @staticmethod + def _float_to_mysql(value: float) -> Optional[float]: + """Convert value to float""" + if math.isnan(value): + return None + return float(value) + + def _str_to_mysql(self, value: str) -> Union[bytes, HexLiteral]: + """Convert value to string""" + return self._unicode_to_mysql(value) + + def _unicode_to_mysql(self, value: str) -> Union[bytes, HexLiteral]: + """Convert unicode""" + charset = self.charset + charset_id = self.charset_id + if charset == "binary": + charset = "utf8" + charset_id = CharacterSet.get_charset_info(charset)[0] + encoded = value.encode(charset) + if charset_id in CharacterSet.slash_charsets: + if b"\x5c" in encoded: + return HexLiteral(value, charset) + return encoded + + @staticmethod + def _bytes_to_mysql(value: bytes) -> bytes: + """Convert value to bytes""" + return value + + @staticmethod + def _bytearray_to_mysql(value: bytearray) -> bytes: + """Convert value to bytes""" + return bytes(value) + + @staticmethod + def _bool_to_mysql(value: bool) -> int: + """Convert value to boolean""" + return 1 if value else 0 + + @staticmethod + def _nonetype_to_mysql(value: None) -> None: # pylint: disable=unused-argument + """ + This would return what None would be in MySQL, but instead we + leave it None and return it right away. The actual conversion + from None to NULL happens in the quoting functionality. + + Return None. + """ + return None + + @staticmethod + def _datetime_to_mysql(value: datetime.datetime) -> bytes: + """ + Converts a datetime instance to a string suitable for MySQL. + The returned string has format: %Y-%m-%d %H:%M:%S[.%f] + + If the instance isn't a datetime.datetime type, it return None. + + Returns a bytes. + """ + if value.microsecond: + fmt = "{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:06d}" + return fmt.format( + value.year, + value.month, + value.day, + value.hour, + value.minute, + value.second, + value.microsecond, + ).encode("ascii") + + fmt = "{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}" + return fmt.format( + value.year, + value.month, + value.day, + value.hour, + value.minute, + value.second, + ).encode("ascii") + + @staticmethod + def _date_to_mysql(value: datetime.date) -> bytes: + """ + Converts a date instance to a string suitable for MySQL. + The returned string has format: %Y-%m-%d + + If the instance isn't a datetime.date type, it return None. + + Returns a bytes. + """ + return f"{value.year:04d}-{value.month:02d}-{value.day:02d}".encode("ascii") + + @staticmethod + def _time_to_mysql(value: datetime.time) -> bytes: + """ + Converts a time instance to a string suitable for MySQL. + The returned string has format: %H:%M:%S[.%f] + + If the instance isn't a datetime.time type, it return None. + + Returns a bytes. + """ + if value.microsecond: + return value.strftime("%H:%M:%S.%f").encode("ascii") + return value.strftime("%H:%M:%S").encode("ascii") + + @staticmethod + def _struct_time_to_mysql(value: time.struct_time) -> bytes: + """ + Converts a time.struct_time sequence to a string suitable + for MySQL. + The returned string has format: %Y-%m-%d %H:%M:%S + + Returns a bytes or None when not valid. + """ + return time.strftime("%Y-%m-%d %H:%M:%S", value).encode("ascii") + + @staticmethod + def _timedelta_to_mysql(value: datetime.timedelta) -> bytes: + """ + Converts a timedelta instance to a string suitable for MySQL. + The returned string has format: %H:%M:%S + + Returns a bytes. + """ + seconds = abs(value.days * 86400 + value.seconds) + + if value.microseconds: + fmt = "{0:02d}:{1:02d}:{2:02d}.{3:06d}" + if value.days < 0: + mcs = 1000000 - value.microseconds + seconds -= 1 + else: + mcs = value.microseconds + else: + fmt = "{0:02d}:{1:02d}:{2:02d}" + + if value.days < 0: + fmt = "-" + fmt + + (hours, remainder) = divmod(seconds, 3600) + (mins, secs) = divmod(remainder, 60) + + if value.microseconds: + result = fmt.format(hours, mins, secs, mcs) + else: + result = fmt.format(hours, mins, secs) + + return result.encode("ascii") + + @staticmethod + def _decimal_to_mysql(value: Decimal) -> Optional[bytes]: + """ + Converts a decimal.Decimal instance to a string suitable for + MySQL. + + Returns a bytes or None when not valid. + """ + if isinstance(value, Decimal): + return str(value).encode("ascii") + + return None + + def row_to_python( + self, row: Tuple[bytes, ...], fields: List[DescriptionType] + ) -> RowType: + """Convert a MySQL text result row to Python types + + The row argument is a sequence containing text result returned + by a MySQL server. Each value of the row is converted to the + using the field type information in the fields argument. + + Returns a tuple. + """ + i = 0 + result: List[ToPythonOutputTypes] = [None] * len(fields) + + if not self._cache_field_types: + self._cache_field_types = {} + for name, info in FieldType.desc.items(): + try: + self._cache_field_types[info[0]] = getattr( + self, f"_{name.lower()}_to_python" + ) + except AttributeError: + # We ignore field types which has no method + pass + + for field in fields: + field_type = field[1] + + if (row[i] == 0 and field_type != FieldType.BIT) or row[i] is None: + # Don't convert NULL value + i += 1 + continue + + try: + result[i] = self._cache_field_types[field_type](row[i], field) + except KeyError: + # If one type is not defined, we just return the value as str + try: + result[i] = row[i].decode("utf-8") + except UnicodeDecodeError: + result[i] = row[i] + except (ValueError, TypeError) as err: + # Item "ValueError" of "Union[ValueError, TypeError]" has no attribute "message" + err.message = f"{err} (field {field[0]})" # type: ignore[union-attr] + raise + + i += 1 + + return tuple(result) + + # pylint: disable=unused-argument + @staticmethod + def _float_to_python(value: bytes, desc: Optional[DescriptionType] = None) -> float: + """ + Returns value as float type. + """ + return float(value) + + _double_to_python = _float_to_python + + @staticmethod + def _int_to_python(value: bytes, desc: Optional[DescriptionType] = None) -> int: + """ + Returns value as int type. + """ + return int(value) + + _tiny_to_python = _int_to_python + _short_to_python = _int_to_python + _int24_to_python = _int_to_python + _long_to_python = _int_to_python + _longlong_to_python = _int_to_python + + def _decimal_to_python( + self, value: bytes, desc: Optional[DescriptionType] = None + ) -> Decimal: + """ + Returns value as a decimal.Decimal. + """ + val = value.decode(self.charset) + return Decimal(val) + + _newdecimal_to_python = _decimal_to_python + + @staticmethod + def _str(value: bytes, desc: Optional[DescriptionType] = None) -> str: + """ + Returns value as str type. + """ + return str(value) + + @staticmethod + def _bit_to_python(value: bytes, dsc: Optional[DescriptionType] = None) -> int: + """Returns BIT columntype as integer""" + int_val = value + if len(int_val) < 8: + int_val = b"\x00" * (8 - len(int_val)) + int_val + return int(struct.unpack(">Q", int_val)[0]) + + @staticmethod + def _date_to_python( + value: bytes, dsc: Optional[DescriptionType] = None + ) -> Optional[datetime.date]: + """Converts TIME column MySQL to a python datetime.datetime type. + + Raises ValueError if the value can not be converted. + + Returns DATE column type as datetime.date type. + """ + if isinstance(value, datetime.date): + return value + try: + parts = value.split(b"-") + if len(parts) != 3: + raise ValueError(f"invalid datetime format: {parts} len: {len(parts)}") + try: + return datetime.date(int(parts[0]), int(parts[1]), int(parts[2])) + except ValueError: + return None + except (IndexError, ValueError): + raise ValueError( + f"Could not convert {repr(value)} to python datetime.timedelta" + ) from None + + _NEWDATE_to_python = _date_to_python + + @staticmethod + def _time_to_python( + value: bytes, dsc: Optional[DescriptionType] = None + ) -> datetime.timedelta: + """Converts TIME column value to python datetime.time value type. + + Converts the TIME column MySQL type passed as bytes to a python + datetime.datetime type. + + Raises ValueError if the value can not be converted. + + Returns datetime.timedelta type. + """ + mcs: Optional[Union[int, bytes]] = None + try: + (hms, mcs) = value.split(b".") + mcs = int(mcs.ljust(6, b"0")) + except (TypeError, ValueError): + hms = value + mcs = 0 + try: + (hours, mins, secs) = [int(d) for d in hms.split(b":")] + if value[0] == 45 or value[0] == "-": + mins, secs, mcs = ( + -mins, + -secs, + -mcs, # pylint: disable=invalid-unary-operand-type + ) + return datetime.timedelta( + hours=hours, minutes=mins, seconds=secs, microseconds=mcs + ) + except (IndexError, TypeError, ValueError): + raise ValueError( + CONVERT_ERROR.format(value=value, pytype="datetime.timedelta") + ) from None + + @staticmethod + def _datetime_to_python( + value: bytes, dsc: Optional[DescriptionType] = None + ) -> Optional[datetime.datetime]: + """Converts DATETIME column value to python datetime.time value type. + + Converts the DATETIME column MySQL type passed as bytes to a python + datetime.datetime type. + + Returns: datetime.datetime type. + """ + if isinstance(value, datetime.datetime): + return value + datetime_val = None + mcs: Optional[Union[int, bytes]] = None + try: + (date_, time_) = value.split(b" ") + if len(time_) > 8: + (hms, mcs) = time_.split(b".") + mcs = int(mcs.ljust(6, b"0")) + else: + hms = time_ + mcs = 0 + dtval = ( + [int(i) for i in date_.split(b"-")] + + [int(i) for i in hms.split(b":")] + + [ + mcs, + ] + ) + if len(dtval) < 6: + raise ValueError(f"invalid datetime format: {dtval} len: {len(dtval)}") + # Note that by default MySQL accepts invalid timestamps + # (this is also backward compatibility). + # Traditionaly C/py returns None for this well formed but + # invalid datetime for python like '0000-00-00 HH:MM:SS'. + try: + datetime_val = datetime.datetime(*dtval) # type: ignore[arg-type] + except ValueError: + return None + except (IndexError, TypeError): + raise ValueError( + CONVERT_ERROR.format(value=value, pytype="datetime.timedelta") + ) from None + + return datetime_val + + _timestamp_to_python = _datetime_to_python + + @staticmethod + def _year_to_python(value: bytes, dsc: Optional[DescriptionType] = None) -> int: + """Returns YEAR column type as integer""" + try: + year = int(value) + except ValueError as err: + raise ValueError(f"Failed converting YEAR to int ({repr(value)})") from err + + return year + + def _set_to_python( + self, value: bytes, dsc: Optional[DescriptionType] = None + ) -> Set[str]: + """Returns SET column type as set + + Actually, MySQL protocol sees a SET as a string type field. So this + code isn't called directly, but used by STRING_to_python() method. + + Returns SET column type as a set. + """ + set_type = None + val = value.decode(self.charset) + if not val: + return set() + try: + set_type = set(val.split(",")) + except ValueError as err: + raise ValueError( + f"Could not convert set {repr(value)} to a sequence" + ) from err + return set_type + + def _string_to_python( + self, value: bytes, dsc: Optional[DescriptionType] = None + ) -> Union[StrOrBytes, Set[str]]: + """ + Note that a SET is a string too, but using the FieldFlag we can see + whether we have to split it. + + Returns string typed columns as string type. + """ + if self.charset == "binary": + return value + if dsc is not None: + if dsc[1] == FieldType.JSON and self.use_unicode: + return value.decode(self.charset) + if dsc[7] & FieldFlag.SET: + return self._set_to_python(value, dsc) + # 'binary' charset + if dsc[8] == 63: + return value + if isinstance(value, (bytes, bytearray)) and self.use_unicode: + try: + return value.decode(self.charset) + except UnicodeDecodeError: + return value + + return value + + _var_string_to_python = _string_to_python + _json_to_python = _string_to_python + + def _blob_to_python( + self, value: bytes, dsc: Optional[DescriptionType] = None + ) -> Union[StrOrBytes, Set[str]]: + """Convert BLOB data type to Python.""" + if dsc is not None: + if ( + dsc[7] & FieldFlag.BLOB + and dsc[7] & FieldFlag.BINARY + # 'binary' charset + and dsc[8] == 63 + ): + return bytes(value) + return self._string_to_python(value, dsc) + + _long_blob_to_python = _blob_to_python + _medium_blob_to_python = _blob_to_python + _tiny_blob_to_python = _blob_to_python + # pylint: enable=unused-argument diff --git a/mysql/connector/cursor.py b/mysql/connector/cursor.py new file mode 100644 index 0000000..0248615 --- /dev/null +++ b/mysql/connector/cursor.py @@ -0,0 +1,1756 @@ +# Copyright (c) 2009, 2023, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# mypy: disable-error-code="assignment,arg-type,attr-defined,index,override,call-overload" + +"""Cursor classes.""" +from __future__ import annotations + +import re +import warnings +import weakref + +from collections import namedtuple +from decimal import Decimal +from typing import ( + Any, + Dict, + Generator, + Iterator, + List, + NoReturn, + Optional, + Sequence, + Tuple, + Type, + Union, +) +from weakref import CallableProxyType + +from .abstracts import NAMED_TUPLE_CACHE, MySQLConnectionAbstract, MySQLCursorAbstract +from .constants import ServerFlag +from .errors import ( + Error, + InterfaceError, + NotSupportedError, + ProgrammingError, + get_mysql_exception, +) +from .types import ( + DescriptionType, + EofPacketType, + ParamsDictType, + ParamsSequenceOrDictType, + ParamsSequenceType, + ResultType, + RowType, + StrOrBytes, + ToPythonOutputTypes, + WarningType, +) + +SQL_COMMENT = r"\/\*.*?\*\/" +RE_SQL_COMMENT = re.compile( + rf"""({SQL_COMMENT})|(["'`][^"'`]*?({SQL_COMMENT})[^"'`]*?["'`])""", + re.I | re.M | re.S, +) +RE_SQL_ON_DUPLICATE = re.compile( + r"""\s*ON\s+DUPLICATE\s+KEY(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$""", + re.I | re.M | re.S, +) +RE_SQL_INSERT_STMT = re.compile( + rf"({SQL_COMMENT}|\s)*INSERT({SQL_COMMENT}|\s)" + r"*(?:IGNORE\s+)?INTO\s+[`'\"]?.+[`'\"]?(?:\.[`'\"]?.+[`'\"]?)" + r"{0,2}\s+VALUES\s*(\(.+\)).*", + re.I | re.M | re.S, +) +RE_SQL_INSERT_VALUES = re.compile(r".*VALUES\s*(\(.+\)).*", re.I | re.M | re.S) +RE_PY_PARAM = re.compile(b"(%s)") +RE_PY_MAPPING_PARAM = re.compile( + rb""" + % + \((?P[^)]+)\) + (?P[diouxXeEfFgGcrs%]) + """, + re.X, +) +RE_SQL_SPLIT_STMTS = re.compile( + b""";(?=(?:[^"'`]*(?:"[^"]*"|'[^']*'|`[^`]*`))*[^"'`]*$)""" +) +RE_SQL_FIND_PARAM = re.compile(b"""%s(?=(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$)""") +RE_SQL_PYTHON_REPLACE_PARAM = re.compile(r"%\(.*?\)s") +RE_SQL_PYTHON_CAPTURE_PARAM_NAME = re.compile(r"%\((.*?)\)s") + +ERR_NO_RESULT_TO_FETCH = "No result set to fetch from" + +MAX_RESULTS = 4294967295 + + +class _ParamSubstitutor: + """ + Substitutes parameters into SQL statement. + """ + + def __init__(self, params: Sequence[bytes]) -> None: + self.params: Sequence[bytes] = params + self.index: int = 0 + + def __call__(self, matchobj: re.Match) -> bytes: + index = self.index + self.index += 1 + try: + return bytes(self.params[index]) + except IndexError: + raise ProgrammingError( + "Not enough parameters for the SQL statement" + ) from None + + @property + def remaining(self) -> int: + """Returns number of parameters remaining to be substituted""" + return len(self.params) - self.index + + +def _bytestr_format_dict(bytestr: bytes, value_dict: Dict[bytes, bytes]) -> bytes: + """ + >>> _bytestr_format_dict(b'%(a)s', {b'a': b'foobar'}) + b'foobar + >>> _bytestr_format_dict(b'%%(a)s', {b'a': b'foobar'}) + b'%%(a)s' + >>> _bytestr_format_dict(b'%%%(a)s', {b'a': b'foobar'}) + b'%%foobar' + >>> _bytestr_format_dict(b'%(x)s %(y)s', + ... {b'x': b'x=%(y)s', b'y': b'y=%(x)s'}) + b'x=%(y)s y=%(x)s' + """ + + def replace(matchobj: re.Match) -> bytes: + """Replace pattern.""" + value: Optional[bytes] = None + groups = matchobj.groupdict() + if groups["conversion_type"] == b"%": + value = b"%" + if groups["conversion_type"] == b"s": + key = groups["mapping_key"] + value = value_dict[key] + if value is None: + raise ValueError( + f"Unsupported conversion_type: {groups['conversion_type']}" + ) + return value + + stmt = RE_PY_MAPPING_PARAM.sub(replace, bytestr) + return stmt + + +class CursorBase(MySQLCursorAbstract): + """ + Base for defining MySQLCursor. This class is a skeleton and defines + methods and members as required for the Python Database API + Specification v2.0. + + It's better to inherite from MySQLCursor. + """ + + _raw: bool = False + + def __init__(self) -> None: + self._description: Optional[List[DescriptionType]] = None + self._rowcount: int = -1 + self.arraysize: int = 1 + super().__init__() + + def callproc(self, procname: str, args: Sequence[Any] = ()) -> Any: + """Calls a stored procedue with the given arguments + + The arguments will be set during this session, meaning + they will be called like ___arg where + is an enumeration (+1) of the arguments. + + Coding Example: + 1) Definining the Stored Routine in MySQL: + CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT) + BEGIN + SET pProd := pFac1 * pFac2; + END + + 2) Executing in Python: + args = (5,5,0) # 0 is to hold pprod + cursor.callproc('multiply', args) + print(cursor.fetchone()) + + Does not return a value, but a result set will be + available when the CALL-statement execute successfully. + Raises exceptions when something is wrong. + """ + + def close(self) -> Any: + """Close the cursor.""" + + def execute( + self, + operation: Any, + params: Union[Sequence[Any], Dict[str, Any]] = (), + multi: bool = False, + ) -> Any: + """Executes the given operation + + Executes the given operation substituting any markers with + the given parameters. + + For example, getting all rows where id is 5: + cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,)) + + The multi argument should be set to True when executing multiple + statements in one operation. If not set and multiple results are + found, an InterfaceError will be raised. + + If warnings where generated, and connection.get_warnings is True, then + self._warnings will be a list containing these warnings. + + Returns an iterator when multi is True, otherwise None. + """ + + def executemany( + self, operation: Any, seq_params: Sequence[Union[Sequence[Any], Dict[str, Any]]] + ) -> Any: + """Execute the given operation multiple times + + The executemany() method will execute the operation iterating + over the list of parameters in seq_params. + + Example: Inserting 3 new employees and their phone number + + data = [ + ('Jane','555-001'), + ('Joe', '555-001'), + ('John', '555-003') + ] + stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s')" + cursor.executemany(stmt, data) + + INSERT statements are optimized by batching the data, that is + using the MySQL multiple rows syntax. + + Results are discarded. If they are needed, consider looping over + data using the execute() method. + """ + + def fetchone(self) -> Optional[Sequence[Any]]: + """Return next row of a query result set. + + Returns: + tuple or None: A row from query result set. + """ + + def fetchmany(self, size: int = 1) -> List[Sequence[Any]]: + """Return the next set of rows of a query result set. + + When no more rows are available, it returns an empty list. + The number of rows returned can be specified using the size argument, + which defaults to one. + + Returns: + list: The next set of rows of a query result set. + """ + + def fetchall(self) -> List[Sequence[Any]]: + """Return all rows of a query result set. + + Returns: + list: A list of tuples with all rows of a query result set. + """ + + def nextset(self) -> Any: + """Not Implemented.""" + + def setinputsizes(self, sizes: Any) -> Any: + """Not Implemented.""" + + def setoutputsize(self, size: Any, column: Any = None) -> Any: + """Not Implemented.""" + + def reset(self, free: bool = True) -> Any: + """Reset the cursor to default""" + + @property + def description(self) -> Optional[List[DescriptionType]]: + """Returns description of columns in a result + + This property returns a list of tuples describing the columns in + in a result set. A tuple is described as follows:: + + (column_name, + type, + None, + None, + None, + None, + null_ok, + column_flags) # Addition to PEP-249 specs + + Returns a list of tuples. + """ + return self._description + + @property + def rowcount(self) -> int: + """Returns the number of rows produced or affected + + This property returns the number of rows produced by queries + such as a SELECT, or affected rows when executing DML statements + like INSERT or UPDATE. + + Note that for non-buffered cursors it is impossible to know the + number of rows produced before having fetched them all. For those, + the number of rows will be -1 right after execution, and + incremented when fetching rows. + + Returns an integer. + """ + return self._rowcount + + +class MySQLCursor(CursorBase): + """Default cursor for interacting with MySQL + + This cursor will execute statements and handle the result. It will + not automatically fetch all rows. + + MySQLCursor should be inherited whenever other functionallity is + required. An example would to change the fetch* member functions + to return dictionaries instead of lists of values. + + Implements the Python Database API Specification v2.0 (PEP-249) + """ + + def __init__( + self, connection: Optional[Type[MySQLConnectionAbstract]] = None + ) -> None: + CursorBase.__init__(self) + self._connection: CallableProxyType[Type[MySQLConnectionAbstract]] = None + self._nextrow: Tuple[Optional[RowType], Optional[EofPacketType]] = ( + None, + None, + ) + self._binary: bool = False + + if connection is not None: + self._set_connection(connection) + + def __iter__(self) -> Iterator[RowType]: + """ + Iteration over the result set which calls self.fetchone() + and returns the next row. + """ + return iter(self.fetchone, None) + + def _set_connection(self, connection: Type[MySQLConnectionAbstract]) -> None: + """Set the connection""" + try: + self._connection = weakref.proxy(connection) + self._connection.is_connected() + except (AttributeError, TypeError): + raise InterfaceError(errno=2048) from None + + def _reset_result(self) -> None: + """Reset the cursor to default""" + self._rowcount: int = -1 + self._nextrow = (None, None) + self._stored_results: List[MySQLCursor] = [] + self._warnings: Optional[List[WarningType]] = None + self._warning_count: int = 0 + self._description: Optional[List[DescriptionType]] = None + self._executed: Optional[StrOrBytes] = None + self._executed_list: List[StrOrBytes] = [] + self.reset() + + def _have_unread_result(self) -> bool: + """Check whether there is an unread result""" + try: + return self._connection.unread_result + except AttributeError: + return False + + def _check_executed(self) -> None: + """Check if the statement has been executed. + + Raises an error if the statement has not been executed. + """ + if self._executed is None: + raise InterfaceError(ERR_NO_RESULT_TO_FETCH) + + def __next__(self) -> RowType: + """ + Used for iterating over the result set. Calles self.fetchone() + to get the next row. + """ + try: + row = self.fetchone() + except InterfaceError: + raise StopIteration from None + if not row: + raise StopIteration + return row + + def close(self) -> bool: + """Close the cursor + + Returns True when successful, otherwise False. + """ + if self._connection is None: + return False + + self._connection.handle_unread_result() + self._reset_result() + self._connection = None + + return True + + def _process_params_dict( + self, params: ParamsDictType + ) -> Dict[bytes, Union[bytes, Decimal]]: + """Process query parameters given as dictionary""" + res: Dict[bytes, Any] = {} + try: + sql_mode = self._connection.sql_mode + to_mysql = self._connection.converter.to_mysql + escape = self._connection.converter.escape + quote = self._connection.converter.quote + for key, value in params.items(): + conv = value + conv = to_mysql(conv) + conv = escape(conv, sql_mode) + if not isinstance(value, Decimal): + conv = quote(conv) + res[key.encode()] = conv + except Exception as err: + raise ProgrammingError( + f"Failed processing pyformat-parameters; {err}" + ) from err + return res + + def _process_params( + self, params: ParamsSequenceType + ) -> Tuple[Union[bytes, Decimal], ...]: + """Process query parameters.""" + res = params[:] + try: + sql_mode = self._connection.sql_mode + to_mysql = self._connection.converter.to_mysql + escape = self._connection.converter.escape + quote = self._connection.converter.quote + res = [to_mysql(value) for value in res] + res = [escape(value, sql_mode) for value in res] + res = [ + quote(value) if not isinstance(params[i], Decimal) else value + for i, value in enumerate(res) + ] + except Exception as err: + raise ProgrammingError( + f"Failed processing format-parameters; {err}" + ) from err + return tuple(res) + + def _handle_noresultset(self, res: ResultType) -> None: + """Handles result of execute() when there is no result set""" + try: + self._rowcount = res["affected_rows"] + self._last_insert_id = res["insert_id"] + self._warning_count = res["warning_count"] + except (KeyError, TypeError) as err: + raise ProgrammingError(f"Failed handling non-resultset; {err}") from None + + self._handle_warnings() + + def _handle_resultset(self) -> None: + """Handles result set + + This method handles the result set and is called after reading + and storing column information in _handle_result(). For non-buffering + cursors, this method is usually doing nothing. + """ + + def _handle_result(self, result: ResultType) -> None: + """ + Handle the result after a command was send. The result can be either + an OK-packet or a dictionary containing column/eof information. + + Raises InterfaceError when result is not a dict() or result is + invalid. + """ + if not isinstance(result, dict): + raise InterfaceError("Result was not a dict()") + + if "columns" in result: + # Weak test, must be column/eof information + self._description = result["columns"] + self._connection.unread_result = True + self._handle_resultset() + elif "affected_rows" in result: + # Weak test, must be an OK-packet + self._connection.unread_result = False + self._handle_noresultset(result) + else: + raise InterfaceError("Invalid result") + + def _execute_iter( + self, query_iter: Generator[ResultType, None, None] + ) -> Generator[MySQLCursor, None, None]: + """Generator returns MySQLCursor objects for multiple statements + + This method is only used when multiple statements are executed + by the execute() method. It uses zip() to make an iterator from the + given query_iter (result of MySQLConnection.cmd_query_iter()) and + the list of statements that were executed. + """ + executed_list = RE_SQL_SPLIT_STMTS.split(self._executed) + + i = 0 + while True: + try: + result = next(query_iter) + self._reset_result() + self._handle_result(result) + try: + self._executed = executed_list[i].strip() + i += 1 + except IndexError: + self._executed = executed_list[0] + + yield self + except StopIteration: + return + + def execute( + self, + operation: StrOrBytes, + params: Optional[ParamsSequenceOrDictType] = None, + multi: bool = False, + ) -> Optional[Generator[MySQLCursor, None, None]]: + """Executes the given operation + + Executes the given operation substituting any markers with + the given parameters. + + For example, getting all rows where id is 5: + cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,)) + + The multi argument should be set to True when executing multiple + statements in one operation. If not set and multiple results are + found, an InterfaceError will be raised. + + If warnings where generated, and connection.get_warnings is True, then + self._warnings will be a list containing these warnings. + + Returns an iterator when multi is True, otherwise None. + """ + if not operation: + return None + + try: + if not self._connection: + raise ProgrammingError + except (ProgrammingError, ReferenceError) as err: + raise ProgrammingError("Cursor is not connected") from err + + self._connection.handle_unread_result() + + self._reset_result() + stmt: StrOrBytes = "" + + try: + if not isinstance(operation, (bytes, bytearray)): + stmt = operation.encode(self._connection.python_charset) + else: + stmt = operation + except (UnicodeDecodeError, UnicodeEncodeError) as err: + raise ProgrammingError(str(err)) from err + + if params: + if isinstance(params, dict): + stmt = _bytestr_format_dict(stmt, self._process_params_dict(params)) + elif isinstance(params, (list, tuple)): + psub = _ParamSubstitutor(self._process_params(params)) + stmt = RE_PY_PARAM.sub(psub, stmt) + if psub.remaining != 0: + raise ProgrammingError( + "Not all parameters were used in the SQL statement" + ) + else: + raise ProgrammingError( + f"Could not process parameters: {type(params).__name__}({params})," + " it must be of type list, tuple or dict" + ) + + self._executed = stmt + if multi: + self._executed_list = [] + return self._execute_iter(self._connection.cmd_query_iter(stmt)) + + try: + self._handle_result(self._connection.cmd_query(stmt)) + except InterfaceError as err: + if self._connection.have_next_result: + raise InterfaceError( + "Use multi=True when executing multiple statements" + ) from err + raise + return None + + def _batch_insert( + self, operation: str, seq_params: Sequence[ParamsSequenceOrDictType] + ) -> Optional[bytes]: + """Implements multi row insert""" + + def remove_comments(match: re.Match) -> str: + """Remove comments from INSERT statements. + + This function is used while removing comments from INSERT + statements. If the matched string is a comment not enclosed + by quotes, it returns an empty string, else the string itself. + """ + if match.group(1): + return "" + return match.group(2) + + tmp = re.sub( + RE_SQL_ON_DUPLICATE, + "", + re.sub(RE_SQL_COMMENT, remove_comments, operation), + ) + + matches = re.search(RE_SQL_INSERT_VALUES, tmp) + if not matches: + raise InterfaceError( + "Failed rewriting statement for multi-row INSERT. Check SQL syntax" + ) + fmt = matches.group(1).encode(self._connection.python_charset) + values = [] + + try: + stmt = operation.encode(self._connection.python_charset) + for params in seq_params: + tmp = fmt + if isinstance(params, dict): + tmp = _bytestr_format_dict(tmp, self._process_params_dict(params)) + else: + psub = _ParamSubstitutor(self._process_params(params)) + tmp = RE_PY_PARAM.sub(psub, tmp) + if psub.remaining != 0: + raise ProgrammingError( + "Not all parameters were used in the SQL statement" + ) + values.append(tmp) + if fmt in stmt: + stmt = stmt.replace(fmt, b",".join(values), 1) + self._executed = stmt + return stmt + return None + except (UnicodeDecodeError, UnicodeEncodeError) as err: + raise ProgrammingError(str(err)) from err + except Error: + raise + except Exception as err: + raise InterfaceError(f"Failed executing the operation; {err}") from None + + def executemany( + self, operation: str, seq_params: Sequence[ParamsSequenceOrDictType] + ) -> Optional[Generator[MySQLCursor, None, None]]: + """Execute the given operation multiple times + + The executemany() method will execute the operation iterating + over the list of parameters in seq_params. + + Example: Inserting 3 new employees and their phone number + + data = [ + ('Jane','555-001'), + ('Joe', '555-001'), + ('John', '555-003') + ] + stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s)" + cursor.executemany(stmt, data) + + INSERT statements are optimized by batching the data, that is + using the MySQL multiple rows syntax. + + Results are discarded. If they are needed, consider looping over + data using the execute() method. + """ + if not operation or not seq_params: + return None + self._connection.handle_unread_result() + + try: + _ = iter(seq_params) + except TypeError as err: + raise ProgrammingError("Parameters for query must be an Iterable") from err + + # Optimize INSERTs by batching them + if re.match(RE_SQL_INSERT_STMT, operation): + if not seq_params: + self._rowcount = 0 + return None + stmt = self._batch_insert(operation, seq_params) + if stmt is not None: + self._executed = stmt + return self.execute(stmt) + + rowcnt = 0 + try: + for params in seq_params: + self.execute(operation, params) + if self.with_rows and self._have_unread_result(): + self.fetchall() + rowcnt += self._rowcount + except (ValueError, TypeError) as err: + raise InterfaceError(f"Failed executing the operation; {err}") from None + self._rowcount = rowcnt + return None + + def stored_results(self) -> Iterator[MySQLCursor]: + """Returns an iterator for stored results + + This method returns an iterator over results which are stored when + callproc() is called. The iterator will provide MySQLCursorBuffered + instances. + + Returns a iterator. + """ + return iter(self._stored_results) + + def callproc( + self, + procname: str, + args: Sequence[Any] = (), + ) -> Optional[Union[Dict[str, ToPythonOutputTypes], RowType]]: + """Calls a stored procedure with the given arguments + + The arguments will be set during this session, meaning + they will be called like ___arg where + is an enumeration (+1) of the arguments. + + Coding Example: + 1) Defining the Stored Routine in MySQL: + CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT) + BEGIN + SET pProd := pFac1 * pFac2; + END + + 2) Executing in Python: + args = (5, 5, 0) # 0 is to hold pprod + cursor.callproc('multiply', args) + print(cursor.fetchone()) + + For OUT and INOUT parameters the user should provide the + type of the parameter as well. The argument should be a + tuple with first item as the value of the parameter to pass + and second argument the type of the argument. + + In the above example, one can call callproc method like: + args = (5, 5, (0, 'INT')) + cursor.callproc('multiply', args) + + The type of the argument given in the tuple will be used by + the MySQL CAST function to convert the values in the corresponding + MySQL type (See CAST in MySQL Reference for more information) + + Does not return a value, but a result set will be + available when the CALL-statement execute successfully. + Raises exceptions when something is wrong. + """ + if not procname or not isinstance(procname, str): + raise ValueError("procname must be a string") + + if not isinstance(args, (tuple, list)): + raise ValueError("args must be a sequence") + + argfmt = "@_{name}_arg{index}" + self._stored_results = [] + + results = [] + try: + argnames = [] + argtypes = [] + + # MySQL itself does support calling procedures with their full + # name .. It's necessary to split + # by '.' and grab the procedure name from procname. + procname_abs = procname.split(".")[-1] + if args: + argvalues = [] + for idx, arg in enumerate(args): + argname = argfmt.format(name=procname_abs, index=idx + 1) + argnames.append(argname) + if isinstance(arg, tuple): + argtypes.append(f" CAST({argname} AS {arg[1]})") + argvalues.append(arg[0]) + else: + argtypes.append(argname) + argvalues.append(arg) + + placeholders = ",".join(f"{arg}=%s" for arg in argnames) + self.execute(f"SET {placeholders}", argvalues) + + call = f"CALL {procname}({','.join(argnames)})" + + # We disable consuming results temporary to make sure we + # getting all results + can_consume_results = self._connection.can_consume_results + for result in self._connection.cmd_query_iter(call): + self._connection.can_consume_results = False + if isinstance(self, (MySQLCursorDict, MySQLCursorBufferedDict)): + cursor_class = MySQLCursorBufferedDict + elif isinstance( + self, + (MySQLCursorNamedTuple, MySQLCursorBufferedNamedTuple), + ): + cursor_class = MySQLCursorBufferedNamedTuple + elif self._raw: + cursor_class = MySQLCursorBufferedRaw + else: + cursor_class = MySQLCursorBuffered + # pylint: disable=protected-access + cur = cursor_class(self._connection.get_self()) + cur._executed = f"(a result of {call})" + cur._handle_result(result) + # pylint: enable=protected-access + if cur.warnings is not None: + self._warnings = cur.warnings + if "columns" in result: + results.append(cur) + self._connection.can_consume_results = can_consume_results + + if argnames: + # Create names aliases to be compatible with namedtuples + args = [ + f"{name} AS {alias}" + for name, alias in zip( + argtypes, [arg.lstrip("@_") for arg in argnames] + ) + ] + select = f"SELECT {','.join(args)}" + self.execute(select) + self._stored_results = results + return self.fetchone() + + self._stored_results = results + return tuple() + + except Error: + raise + except Exception as err: + raise InterfaceError(f"Failed calling stored routine; {err}") from None + + def getlastrowid(self) -> Optional[int]: + """Returns the value generated for an AUTO_INCREMENT column + + Returns the value generated for an AUTO_INCREMENT column by + the previous INSERT or UPDATE statement. + + Returns a long value or None. + """ + return self._last_insert_id + + def _fetch_warnings(self) -> Optional[List[WarningType]]: + """ + Fetch warnings doing a SHOW WARNINGS. Can be called after getting + the result. + + Returns a result set or None when there were no warnings. + """ + res = [] + try: + cur = self._connection.cursor(raw=False) + cur.execute("SHOW WARNINGS") + res = cur.fetchall() + cur.close() + except Exception as err: + raise InterfaceError(f"Failed getting warnings; {err}") from None + + if res: + return res + + return None + + def _handle_warnings(self) -> None: + """Handle possible warnings after all results are consumed. + + Raises: + Error: Also raises exceptions if raise_on_warnings is set. + """ + if self._connection.get_warnings and self._warning_count: + self._warnings = self._fetch_warnings() + + if not self._warnings: + return + + err = get_mysql_exception( + self._warnings[0][1], + self._warnings[0][2], + warning=not self._connection.raise_on_warnings, + ) + + if self._connection.raise_on_warnings: + raise err + + warnings.warn(err, stacklevel=4) + + def _handle_eof(self, eof: EofPacketType) -> None: + """Handle EOF packet""" + self._connection.unread_result = False + self._nextrow = (None, None) + self._warning_count = eof["warning_count"] + self._handle_warnings() + + def _fetch_row(self, raw: bool = False) -> Optional[RowType]: + """Returns the next row in the result set + + Returns a tuple or None. + """ + if not self._have_unread_result(): + return None + row = None + + if self._nextrow == (None, None): + (row, eof) = self._connection.get_row( + binary=self._binary, columns=self.description, raw=raw + ) + else: + (row, eof) = self._nextrow + + if row: + self._nextrow = self._connection.get_row( + binary=self._binary, columns=self.description, raw=raw + ) + eof = self._nextrow[1] + if eof is not None: + self._handle_eof(eof) + if self._rowcount == -1: + self._rowcount = 1 + else: + self._rowcount += 1 + if eof: + self._handle_eof(eof) + + return row + + def fetchone(self) -> Optional[RowType]: + """Return next row of a query result set. + + Returns: + tuple or None: A row from query result set. + """ + self._check_executed() + return self._fetch_row() + + def fetchmany(self, size: Optional[int] = None) -> List[RowType]: + """Return the next set of rows of a query result set. + + When no more rows are available, it returns an empty list. + The number of rows returned can be specified using the size argument, + which defaults to one. + + Returns: + list: The next set of rows of a query result set. + """ + self._check_executed() + res = [] + cnt = size or self.arraysize + while cnt > 0 and self._have_unread_result(): + cnt -= 1 + row = self.fetchone() + if row: + res.append(row) + return res + + def fetchall(self) -> List[RowType]: + """Return all rows of a query result set. + + Returns: + list: A list of tuples with all rows of a query result set. + """ + self._check_executed() + if not self._have_unread_result(): + return [] + + (rows, eof) = self._connection.get_rows() + if self._nextrow[0]: + rows.insert(0, self._nextrow[0]) + + self._handle_eof(eof) + rowcount = len(rows) + if rowcount >= 0 and self._rowcount == -1: + self._rowcount = 0 + self._rowcount += rowcount + return rows + + @property + def column_names(self) -> Tuple[str, ...]: + """Returns column names + + This property returns the columns names as a tuple. + + Returns a tuple. + """ + if not self.description: + return tuple() + return tuple(d[0] for d in self.description) + + @property + def statement(self) -> Optional[str]: + """Returns the executed statement + + This property returns the executed statement. When multiple + statements were executed, the current statement in the iterator + will be returned. + """ + if self._executed is None: + return None + try: + return self._executed.strip().decode("utf-8") # type: ignore[union-attr] + except (AttributeError, UnicodeDecodeError): + return self._executed.strip() # type: ignore[return-value] + + @property + def with_rows(self) -> bool: + """Returns whether the cursor could have rows returned + + This property returns True when column descriptions are available + and possibly also rows, which will need to be fetched. + + Returns True or False. + """ + if not self.description: + return False + return True + + def __str__(self) -> str: + fmt = "{class_name}: {stmt}" + if self._executed: + try: + executed = self._executed.decode("utf-8") # type: ignore[union-attr] + except AttributeError: + executed = self._executed + if len(executed) > 40: + executed = executed[:40] + ".." + else: + executed = "(Nothing executed yet)" + return fmt.format(class_name=self.__class__.__name__, stmt=executed) + + +class MySQLCursorBuffered(MySQLCursor): + """Cursor which fetches rows within execute()""" + + def __init__( + self, connection: Optional[Type[MySQLConnectionAbstract]] = None + ) -> None: + super().__init__(connection) + self._rows: Optional[List[RowType]] = None + self._next_row: int = 0 + + def _handle_resultset(self) -> None: + (self._rows, eof) = self._connection.get_rows() + self._rowcount = len(self._rows) + self._handle_eof(eof) + self._next_row = 0 + try: + self._connection.unread_result = False + except AttributeError: + pass + + def reset(self, free: bool = True) -> None: + self._rows = None + + def _fetch_row(self, raw: bool = False) -> Optional[RowType]: + row = None + try: + row = self._rows[self._next_row] + except (IndexError, TypeError): + return None + self._next_row += 1 + return row + + def fetchone(self) -> Optional[RowType]: + """Return next row of a query result set. + + Returns: + tuple or None: A row from query result set. + """ + self._check_executed() + return self._fetch_row() + + def fetchall(self) -> List[RowType]: + """Return all rows of a query result set. + + Returns: + list: A list of tuples with all rows of a query result set. + """ + if self._executed is None or self._rows is None: + raise InterfaceError(ERR_NO_RESULT_TO_FETCH) + res = [] + res = self._rows[self._next_row :] + self._next_row = len(self._rows) + return res + + def fetchmany(self, size: Optional[int] = None) -> List[RowType]: + """Return the next set of rows of a query result set. + + When no more rows are available, it returns an empty list. + The number of rows returned can be specified using the size argument, + which defaults to one. + + Returns: + list: The next set of rows of a query result set. + """ + self._check_executed() + res = [] + cnt = size or self.arraysize + while cnt > 0: + cnt -= 1 + row = self.fetchone() + if row: + res.append(row) + + return res + + @property + def with_rows(self) -> bool: + return self._rows is not None + + +class MySQLCursorRaw(MySQLCursor): + """ + Skips conversion from MySQL datatypes to Python types when fetching rows. + """ + + _raw: bool = True + + def fetchone(self) -> Optional[RowType]: + """Return next row of a query result set. + + Returns: + tuple or None: A row from query result set. + """ + self._check_executed() + return self._fetch_row(raw=True) + + def fetchall(self) -> List[RowType]: + """Return all rows of a query result set. + + Returns: + list: A list of tuples with all rows of a query result set. + """ + self._check_executed() + if not self._have_unread_result(): + return [] + (rows, eof) = self._connection.get_rows(raw=True) + if self._nextrow[0]: + rows.insert(0, self._nextrow[0]) + self._handle_eof(eof) + rowcount = len(rows) + if rowcount >= 0 and self._rowcount == -1: + self._rowcount = 0 + self._rowcount += rowcount + return rows + + +class MySQLCursorBufferedRaw(MySQLCursorBuffered): + """ + Cursor which skips conversion from MySQL datatypes to Python types when + fetching rows and fetches rows within execute(). + """ + + _raw: bool = True + + def _handle_resultset(self) -> None: + (self._rows, eof) = self._connection.get_rows(raw=self._raw) + self._rowcount = len(self._rows) + self._handle_eof(eof) + self._next_row = 0 + try: + self._connection.unread_result = False + except AttributeError: + pass + + def fetchone(self) -> Optional[RowType]: + """Return next row of a query result set. + + Returns: + tuple or None: A row from query result set. + """ + self._check_executed() + return self._fetch_row() + + def fetchall(self) -> List[RowType]: + """Return all rows of a query result set. + + Returns: + list: A list of tuples with all rows of a query result set. + """ + self._check_executed() + return list(self._rows[self._next_row :]) + + @property + def with_rows(self) -> bool: + return self._rows is not None + + +class MySQLCursorPrepared(MySQLCursor): + """Cursor using MySQL Prepared Statements""" + + def __init__(self, connection: Optional[Type[MySQLConnectionAbstract]] = None): + super().__init__(connection) + self._rows: Optional[List[RowType]] = None + self._next_row: int = 0 + self._prepared: Optional[Dict[str, Union[int, List[DescriptionType]]]] = None + self._binary: bool = True + self._have_result: Optional[bool] = None + self._last_row_sent: bool = False + self._cursor_exists: bool = False + + def reset(self, free: bool = True) -> None: + if self._prepared: + try: + self._connection.cmd_stmt_close(self._prepared["statement_id"]) + except Error: + # We tried to deallocate, but it's OK when we fail. + pass + self._prepared = None + self._last_row_sent = False + self._cursor_exists = False + + def _handle_noresultset(self, res: ResultType) -> None: + self._handle_server_status(res.get("status_flag", res.get("server_status", 0))) + super()._handle_noresultset(res) + + def _handle_server_status(self, flags: int) -> None: + """Check for SERVER_STATUS_CURSOR_EXISTS and + SERVER_STATUS_LAST_ROW_SENT flags set by the server. + """ + self._cursor_exists = flags & ServerFlag.STATUS_CURSOR_EXISTS != 0 + self._last_row_sent = flags & ServerFlag.STATUS_LAST_ROW_SENT != 0 + + def _handle_eof(self, eof: EofPacketType) -> None: + self._handle_server_status(eof.get("status_flag", eof.get("server_status", 0))) + super()._handle_eof(eof) + + def callproc(self, procname: Any, args: Any = ()) -> NoReturn: + """Calls a stored procedue + + Not supported with MySQLCursorPrepared. + """ + raise NotSupportedError() + + def close(self) -> None: + """Close the cursor + + This method will try to deallocate the prepared statement and close + the cursor. + """ + self.reset() + super().close() + + def _row_to_python(self, rowdata: Any, desc: Any = None) -> Any: + """Convert row data from MySQL to Python types + + The conversion is done while reading binary data in the + protocol module. + """ + + def _handle_result(self, result: ResultType) -> None: + """Handle result after execution""" + if isinstance(result, dict): + self._connection.unread_result = False + self._have_result = False + self._handle_noresultset(result) + else: + self._description = result[1] + self._connection.unread_result = True + self._have_result = True + + if "status_flag" in result[2]: # type: ignore[operator] + self._handle_server_status(result[2]["status_flag"]) + elif "server_status" in result[2]: # type: ignore[operator] + self._handle_server_status(result[2]["server_status"]) + + def execute( + self, + operation: StrOrBytes, + params: Optional[ParamsSequenceOrDictType] = None, + multi: bool = False, + ) -> None: # multi is unused + """Prepare and execute a MySQL Prepared Statement + + This method will prepare the given operation and execute it using + the optionally given parameters. + + If the cursor instance already had a prepared statement, it is + first closed. + + Note: argument "multi" is unused. + """ + charset = self._connection.charset + if charset == "utf8mb4": + charset = "utf8" + + if not isinstance(operation, str): + try: + operation = operation.decode(charset) + except UnicodeDecodeError as err: + raise ProgrammingError(str(err)) from err + + if isinstance(params, dict): + replacement_keys = re.findall(RE_SQL_PYTHON_CAPTURE_PARAM_NAME, operation) + try: + # Replace params dict with params tuple in correct order. + params = tuple(params[key] for key in replacement_keys) + except KeyError as err: + raise ProgrammingError( + "Not all placeholders were found in the parameters dict" + ) from err + # Convert %(name)s to ? before sending it to MySQL + operation = re.sub(RE_SQL_PYTHON_REPLACE_PARAM, "?", operation) + + if operation is not self._executed: + if self._prepared: + self._connection.cmd_stmt_close(self._prepared["statement_id"]) + self._executed = operation + + try: + operation = operation.encode(charset) + except UnicodeEncodeError as err: + raise ProgrammingError(str(err)) from err + + if b"%s" in operation: + # Convert %s to ? before sending it to MySQL + operation = re.sub(RE_SQL_FIND_PARAM, b"?", operation) + + try: + self._prepared = self._connection.cmd_stmt_prepare(operation) + except Error: + self._executed = None + raise + + self._connection.cmd_stmt_reset(self._prepared["statement_id"]) + + if self._prepared["parameters"] and not params: + return + if params: + if not isinstance(params, (tuple, list)): + raise ProgrammingError( + errno=1210, + msg=f"Incorrect type of argument: {type(params).__name__}({params})" + ", it must be of type tuple or list the argument given to " + "the prepared statement", + ) + if len(self._prepared["parameters"]) != len(params): + raise ProgrammingError( + errno=1210, + msg="Incorrect number of arguments executing prepared statement", + ) + + if params is None: + params = () + res = self._connection.cmd_stmt_execute( + self._prepared["statement_id"], + data=params, + parameters=self._prepared["parameters"], + ) + self._handle_result(res) + + def executemany( + self, + operation: str, + seq_params: Sequence[ParamsSequenceType], + ) -> None: + """Prepare and execute a MySQL Prepared Statement many times + + This method will prepare the given operation and execute with each + tuple found the list seq_params. + + If the cursor instance already had a prepared statement, it is + first closed. + + executemany() simply calls execute(). + """ + rowcnt = 0 + try: + for params in seq_params: + self.execute(operation, params) + if self.with_rows and self._have_unread_result(): + self.fetchall() + rowcnt += self._rowcount + except (ValueError, TypeError) as err: + raise InterfaceError(f"Failed executing the operation; {err}") from None + self._rowcount = rowcnt + + def fetchone(self) -> Optional[RowType]: + """Return next row of a query result set. + + Returns: + tuple or None: A row from query result set. + """ + self._check_executed() + if self._cursor_exists: + self._connection.cmd_stmt_fetch(self._prepared["statement_id"]) + return self._fetch_row() or None + + def fetchmany(self, size: Optional[int] = None) -> List[RowType]: + """Return the next set of rows of a query result set. + + When no more rows are available, it returns an empty list. + The number of rows returned can be specified using the size argument, + which defaults to one. + + Returns: + list: The next set of rows of a query result set. + """ + self._check_executed() + res = [] + cnt = size or self.arraysize + while cnt > 0 and self._have_unread_result(): + cnt -= 1 + row = self._fetch_row() + if row: + res.append(row) + return res + + def fetchall(self) -> List[RowType]: + """Return all rows of a query result set. + + Returns: + list: A list of tuples with all rows of a query result set. + """ + self._check_executed() + rows = [] + if self._nextrow[0]: + rows.append(self._nextrow[0]) + while self._have_unread_result(): + if self._cursor_exists: + self._connection.cmd_stmt_fetch( + self._prepared["statement_id"], MAX_RESULTS + ) + (tmp, eof) = self._connection.get_rows( + binary=self._binary, columns=self.description + ) + rows.extend(tmp) + self._handle_eof(eof) + self._rowcount = len(rows) + return rows + + +class MySQLCursorDict(MySQLCursor): + """ + Cursor fetching rows as dictionaries. + + The fetch methods of this class will return dictionaries instead of tuples. + Each row is a dictionary that looks like: + row = { + "col1": value1, + "col2": value2 + } + """ + + def _row_to_python( + self, + rowdata: RowType, + desc: Optional[List[DescriptionType]] = None, # pylint: disable=unused-argument + ) -> Optional[Dict[str, ToPythonOutputTypes]]: + """Convert a MySQL text result row to Python types + + Returns a dictionary. + """ + return dict(zip(self.column_names, rowdata)) if rowdata else None + + def fetchone(self) -> Optional[Dict[str, ToPythonOutputTypes]]: + """Return next row of a query result set. + + Returns: + dict or None: A dict from query result set. + """ + return self._row_to_python(super().fetchone(), self.description) + + def fetchall(self) -> List[Optional[Dict[str, ToPythonOutputTypes]]]: + """Return all rows of a query result set. + + Returns: + list: A list of dictionaries with all rows of a query + result set where column names are used as keys. + """ + return [ + self._row_to_python(row, self.description) + for row in super().fetchall() + if row + ] + + +class MySQLCursorNamedTuple(MySQLCursor): + """ + Cursor fetching rows as named tuple. + + The fetch methods of this class will return namedtuples instead of tuples. + Each row is returned as a namedtuple and the values can be accessed as: + row.col1, row.col2 + """ + + def _row_to_python( + self, + rowdata: RowType, + desc: Optional[List[DescriptionType]] = None, # pylint: disable=unused-argument + ) -> Optional[RowType]: + """Convert a MySQL text result row to Python types + + Returns a named tuple. + """ + row = rowdata + + if row: + columns = tuple(self.column_names) + try: + named_tuple = NAMED_TUPLE_CACHE[columns] + except KeyError: + named_tuple = namedtuple("Row", columns) # type:ignore[no-redef, misc] + NAMED_TUPLE_CACHE[columns] = named_tuple + return named_tuple(*row) + return None + + def fetchone(self) -> Optional[RowType]: + """Return next row of a query result set. + + Returns: + tuple or None: A row from query result set. + """ + row = super().fetchone() + if not row: + return None + return ( + self._row_to_python(row, self.description) + if hasattr(self._connection, "converter") + else row + ) + + def fetchall(self) -> List[Optional[RowType]]: + """Return all rows of a query result set. + + Returns: + list: A list of tuples with all rows of a query result set. + """ + return [ + self._row_to_python(row, self.description) + for row in super().fetchall() + if row + ] + + +class MySQLCursorBufferedDict(MySQLCursorDict, MySQLCursorBuffered): + """ + Buffered Cursor fetching rows as dictionaries. + """ + + def fetchone(self) -> Optional[Dict[str, ToPythonOutputTypes]]: + """Return next row of a query result set. + + Returns: + tuple or None: A row from query result set. + """ + self._check_executed() + row = self._fetch_row() + if row: + return self._row_to_python(row, self.description) + return None + + def fetchall(self) -> List[Optional[Dict[str, ToPythonOutputTypes]]]: + """Return all rows of a query result set. + + Returns: + list: A list of tuples with all rows of a query result set. + """ + if self._executed is None or self._rows is None: + raise InterfaceError(ERR_NO_RESULT_TO_FETCH) + res = [] + for row in self._rows[self._next_row :]: + res.append(self._row_to_python(row, self.description)) + self._next_row = len(self._rows) + return res + + +class MySQLCursorBufferedNamedTuple(MySQLCursorNamedTuple, MySQLCursorBuffered): + """ + Buffered Cursor fetching rows as named tuple. + """ + + def fetchone(self) -> Optional[RowType]: + """Return next row of a query result set. + + Returns: + tuple or None: A row from query result set. + """ + self._check_executed() + row = self._fetch_row() + if row: + return self._row_to_python(row, self.description) + return None + + def fetchall(self) -> List[Optional[RowType]]: + """Return all rows of a query result set. + + Returns: + list: A list of tuples with all rows of a query result set. + """ + if self._executed is None or self._rows is None: + raise InterfaceError(ERR_NO_RESULT_TO_FETCH) + res = [] + for row in self._rows[self._next_row :]: + res.append(self._row_to_python(row, self.description)) + self._next_row = len(self._rows) + return res + + +class MySQLCursorPreparedDict(MySQLCursorDict, MySQLCursorPrepared): # type: ignore[misc] + """ + This class is a blend of features from MySQLCursorDict and MySQLCursorPrepared + + Multiple inheritance in python is allowed but care must be taken + when assuming methods resolution. In the case of multiple + inheritance, a given attribute is first searched in the current + class if it's not found then it's searched in the parent classes. + The parent classes are searched in a left-right fashion and each + class is searched once. + Based on python's attribute resolution, in this case, attributes + are searched as follows: + 1. MySQLCursorPreparedDict (current class) + 2. MySQLCursorDict (left parent class) + 3. MySQLCursorPrepared (right parent class) + 4. MySQLCursor (base class) + """ + + def fetchmany( + self, size: Optional[int] = None + ) -> List[Dict[str, ToPythonOutputTypes]]: + """Return the next set of rows of a query result set. + + When no more rows are available, it returns an empty list. + The number of rows returned can be specified using the size argument, + which defaults to one. + + Returns: + list: The next set of rows of a query result set represented + as a list of dictionaries where column names are used as keys. + """ + return [ + self._row_to_python(row, self.description) + for row in super().fetchmany(size=size) + if row + ] + + +class MySQLCursorPreparedNamedTuple(MySQLCursorNamedTuple, MySQLCursorPrepared): + """ + This class is a blend of features from MySQLCursorNamedTuple and MySQLCursorPrepared + """ + + def fetchmany(self, size: Optional[int] = None) -> List[RowType]: + """Return the next set of rows of a query result set. + + When no more rows are available, it returns an empty list. + The number of rows returned can be specified using the size argument, + which defaults to one. + + Returns: + list: The next set of rows of a query result set represented + as a list of named tuples where column names are used as names. + """ + return [ + self._row_to_python(row, self.description) + for row in super().fetchmany(size=size) + if row + ] + + +class MySQLCursorPreparedRaw(MySQLCursorPrepared): + """ + This class is a blend of features from MySQLCursorRaw and MySQLCursorPrepared + """ + + _raw: bool = True + + def fetchone(self) -> Optional[RowType]: + """Return next row of a query result set. + + Returns: + tuple or None: A row from query result set. + """ + self._check_executed() + if self._cursor_exists: + self._connection.cmd_stmt_fetch(self._prepared["statement_id"]) + return self._fetch_row(raw=self._raw) or None + + def fetchmany(self, size: Optional[int] = None) -> List[RowType]: + """Return the next set of rows of a query result set. + + When no more rows are available, it returns an empty list. + The number of rows returned can be specified using the size argument, + which defaults to one. + + Returns: + list: The next set of rows of a query result set. + """ + self._check_executed() + res = [] + cnt = size or self.arraysize + while cnt > 0 and self._have_unread_result(): + cnt -= 1 + row = self._fetch_row(raw=self._raw) + if row: + res.append(row) + return res + + def fetchall(self) -> List[RowType]: + """Return all rows of a query result set. + + Returns: + list: A list of tuples with all rows of a query result set. + """ + self._check_executed() + rows = [] + if self._nextrow[0]: + rows.append(self._nextrow[0]) + while self._have_unread_result(): + if self._cursor_exists: + self._connection.cmd_stmt_fetch( + self._prepared["statement_id"], MAX_RESULTS + ) + (tmp, eof) = self._connection.get_rows( + raw=self._raw, binary=self._binary, columns=self.description + ) + rows.extend(tmp) + self._handle_eof(eof) + self._rowcount = len(rows) + return rows diff --git a/mysql/connector/cursor_cext.py b/mysql/connector/cursor_cext.py new file mode 100644 index 0000000..33295cf --- /dev/null +++ b/mysql/connector/cursor_cext.py @@ -0,0 +1,1288 @@ +# Copyright (c) 2014, 2023, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# mypy: disable-error-code="assignment,arg-type,override,union-attr" + +"""Cursor classes using the C Extension.""" +from __future__ import annotations + +import re +import warnings +import weakref + +from collections import namedtuple +from typing import ( + Any, + Dict, + Generator, + Iterator, + List, + NoReturn, + Optional, + Sequence, + Tuple, + Type, + Union, +) +from weakref import CallableProxyType + +# pylint: disable=import-error,no-name-in-module +from _mysql_connector import MySQLInterfaceError, MySQLPrepStmt + +from .types import ( + CextEofPacketType, + CextResultType, + DescriptionType, + ParamsSequenceOrDictType, + ParamsSequenceType, + RowType, + StrOrBytes, + ToPythonOutputTypes, + WarningType, +) + +# pylint: enable=import-error,no-name-in-module +# isort: split + +from .abstracts import NAMED_TUPLE_CACHE, MySQLConnectionAbstract, MySQLCursorAbstract +from .cursor import ( + RE_PY_PARAM, + RE_SQL_COMMENT, + RE_SQL_FIND_PARAM, + RE_SQL_INSERT_STMT, + RE_SQL_INSERT_VALUES, + RE_SQL_ON_DUPLICATE, + RE_SQL_PYTHON_CAPTURE_PARAM_NAME, + RE_SQL_PYTHON_REPLACE_PARAM, + RE_SQL_SPLIT_STMTS, +) +from .errorcode import CR_NO_RESULT_SET +from .errors import ( + Error, + InterfaceError, + NotSupportedError, + ProgrammingError, + get_mysql_exception, +) + +ERR_NO_RESULT_TO_FETCH = "No result set to fetch from" + + +class _ParamSubstitutor: + + """ + Substitutes parameters into SQL statement. + """ + + def __init__(self, params: Sequence[bytes]) -> None: + self.params: Sequence[bytes] = params + self.index: int = 0 + + def __call__(self, matchobj: object) -> bytes: + index = self.index + self.index += 1 + try: + return self.params[index] + except IndexError: + raise ProgrammingError( + "Not enough parameters for the SQL statement" + ) from None + + @property + def remaining(self) -> int: + """Returns number of parameters remaining to be substituted""" + return len(self.params) - self.index + + +class CMySQLCursor(MySQLCursorAbstract): + + """Default cursor for interacting with MySQL using C Extension""" + + _raw: bool = False + _buffered: bool = False + _raw_as_string: bool = False + + def __init__(self, connection: Type[MySQLConnectionAbstract]) -> None: + """Initialize""" + MySQLCursorAbstract.__init__(self) + + self._affected_rows: int = -1 + self._rowcount: int = -1 + self._nextrow: Tuple[Optional[RowType], Optional[CextEofPacketType]] = ( + None, + None, + ) + + if not isinstance(connection, MySQLConnectionAbstract): + raise InterfaceError(errno=2048) + self._cnx: CallableProxyType[Type[MySQLConnectionAbstract]] = weakref.proxy( + connection + ) + + def reset(self, free: bool = True) -> None: + """Reset the cursor + + When free is True (default) the result will be freed. + """ + self._rowcount = -1 + self._nextrow = None + self._affected_rows = -1 + self._last_insert_id: int = 0 + self._warning_count: int = 0 + self._warnings: Optional[List[WarningType]] = None + self._warnings = None + self._warning_count = 0 + self._description: Optional[List[DescriptionType]] = None + self._executed_list: List[StrOrBytes] = [] + if free and self._cnx: + self._cnx.free_result() + super().reset() + + def _check_executed(self) -> None: + """Check if the statement has been executed. + + Raises an error if the statement has not been executed. + """ + if self._executed is None: + raise InterfaceError(ERR_NO_RESULT_TO_FETCH) + + def _fetch_warnings(self) -> Optional[List[WarningType]]: + """Fetch warnings + + Fetch warnings doing a SHOW WARNINGS. Can be called after getting + the result. + + Returns a result set or None when there were no warnings. + + Raises Error (or subclass) on errors. + + Returns list of tuples or None. + """ + warns = [] + try: + # force freeing result + self._cnx.consume_results() + _ = self._cnx.cmd_query("SHOW WARNINGS") + warns = self._cnx.get_rows()[0] + self._cnx.consume_results() + except MySQLInterfaceError as err: + raise get_mysql_exception( + msg=err.msg, errno=err.errno, sqlstate=err.sqlstate + ) from err + except Exception as err: + raise InterfaceError(f"Failed getting warnings; {err}") from None + + if warns: + return warns + + return None + + def _handle_warnings(self) -> None: + """Handle possible warnings after all results are consumed. + + Raises: + Error: Also raises exceptions if raise_on_warnings is set. + """ + if self._cnx.get_warnings and self._warning_count: + self._warnings = self._fetch_warnings() + + if not self._warnings: + return + + err = get_mysql_exception( + *self._warnings[0][1:3], warning=not self._cnx.raise_on_warnings + ) + if self._cnx.raise_on_warnings: + raise err + + warnings.warn(str(err), stacklevel=4) + + def _handle_result(self, result: Union[CextEofPacketType, CextResultType]) -> None: + """Handles the result after statement execution""" + if "columns" in result: + self._description = result["columns"] + self._rowcount = 0 + self._handle_resultset() + else: + self._last_insert_id = result["insert_id"] + self._warning_count = result["warning_count"] + self._affected_rows = result["affected_rows"] + self._rowcount = -1 + self._handle_warnings() + + def _handle_resultset(self) -> None: + """Handle a result set""" + + def _handle_eof(self) -> None: + """Handle end of reading the result + + Raises an Error on errors. + """ + self._warning_count = self._cnx.warning_count + self._handle_warnings() + if not self._cnx.more_results: + self._cnx.free_result() + + def _execute_iter(self) -> Generator[CMySQLCursor, None, None]: + """Generator returns MySQLCursor objects for multiple statements + + Deprecated: use nextset() method directly. + + This method is only used when multiple statements are executed + by the execute() method. It uses zip() to make an iterator from the + given query_iter (result of MySQLConnection.cmd_query_iter()) and + the list of statements that were executed. + """ + executed_list = RE_SQL_SPLIT_STMTS.split(self._executed) + i = 0 + self._executed = executed_list[i] + yield self + + while True: + try: + if not self.nextset(): + raise StopIteration + except InterfaceError as err: + # Result without result set + if err.errno != CR_NO_RESULT_SET: + raise + except StopIteration: + return + i += 1 + try: + self._executed = executed_list[i].strip() + except IndexError: + self._executed = executed_list[0] + yield self + return + + def execute( + self, + operation: StrOrBytes, + params: ParamsSequenceOrDictType = (), + multi: bool = False, + ) -> Optional[Generator[CMySQLCursor, None, None]]: + """Execute given statement using given parameters + + Deprecated: The multi argument is not needed and nextset() should + be used to handle multiple result sets. + """ + if not operation: + return None + + try: + if not self._cnx or self._cnx.is_closed(): + raise ProgrammingError + except (ProgrammingError, ReferenceError) as err: + raise ProgrammingError("Cursor is not connected", 2055) from err + self._cnx.handle_unread_result() + + stmt = "" + self.reset() + + try: + if isinstance(operation, str): + stmt = operation.encode(self._cnx.python_charset) + else: + stmt = operation + except (UnicodeDecodeError, UnicodeEncodeError) as err: + raise ProgrammingError(str(err)) from err + + if params: + prepared = self._cnx.prepare_for_mysql(params) + if isinstance(prepared, dict): + for key, value in prepared.items(): + stmt = stmt.replace(f"%({key})s".encode(), value) + elif isinstance(prepared, (list, tuple)): + psub = _ParamSubstitutor(prepared) + stmt = RE_PY_PARAM.sub(psub, stmt) # type: ignore[call-overload] + if psub.remaining != 0: + raise ProgrammingError( + "Not all parameters were used in the SQL statement" + ) + + try: + result = self._cnx.cmd_query( + stmt, + raw=self._raw, + buffered=self._buffered, + raw_as_string=self._raw_as_string, + ) + except MySQLInterfaceError as err: + raise get_mysql_exception( + msg=err.msg, errno=err.errno, sqlstate=err.sqlstate + ) from err + + self._executed = stmt + self._handle_result(result) + + if multi: + return self._execute_iter() + + return None + + def _batch_insert( + self, + operation: str, + seq_params: Sequence[ParamsSequenceOrDictType], + ) -> Optional[bytes]: + """Implements multi row insert""" + + def remove_comments(match: re.Match) -> str: + """Remove comments from INSERT statements. + + This function is used while removing comments from INSERT + statements. If the matched string is a comment not enclosed + by quotes, it returns an empty string, else the string itself. + """ + if match.group(1): + return "" + return match.group(2) + + tmp = re.sub( + RE_SQL_ON_DUPLICATE, + "", + re.sub(RE_SQL_COMMENT, remove_comments, operation), + ) + + matches = re.search(RE_SQL_INSERT_VALUES, tmp) + if not matches: + raise InterfaceError( + "Failed rewriting statement for multi-row INSERT. Check SQL syntax" + ) + fmt = matches.group(1).encode(self._cnx.python_charset) + values = [] + + try: + stmt = operation.encode(self._cnx.python_charset) + for params in seq_params: + tmp = fmt + prepared = self._cnx.prepare_for_mysql(params) + if isinstance(prepared, dict): + for key, value in prepared.items(): + tmp = tmp.replace(f"%({key})s".encode(), value) + elif isinstance(prepared, (list, tuple)): + psub = _ParamSubstitutor(prepared) + tmp = RE_PY_PARAM.sub(psub, tmp) # type: ignore[call-overload] + if psub.remaining != 0: + raise ProgrammingError( + "Not all parameters were used in the SQL statement" + ) + values.append(tmp) + + if fmt in stmt: + stmt = stmt.replace(fmt, b",".join(values), 1) + self._executed = stmt + return stmt + return None + except (UnicodeDecodeError, UnicodeEncodeError) as err: + raise ProgrammingError(str(err)) from err + except Exception as err: + raise InterfaceError(f"Failed executing the operation; {err}") from None + + def executemany( + self, + operation: str, + seq_params: Sequence[ParamsSequenceOrDictType], + ) -> Optional[Generator[CMySQLCursor, None, None]]: + """Execute the given operation multiple times + + The executemany() method will execute the operation iterating + over the list of parameters in seq_params. + + Example: Inserting 3 new employees and their phone number + + data = [ + ('Jane','555-001'), + ('Joe', '555-001'), + ('John', '555-003') + ] + stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s)" + cursor.executemany(stmt, data) + + INSERT statements are optimized by batching the data, that is + using the MySQL multiple rows syntax. + + Results are discarded! If they are needed, consider looping over + data using the execute() method. + """ + if not operation or not seq_params: + return None + + try: + if not self._cnx: + raise ProgrammingError + except (ProgrammingError, ReferenceError) as err: + raise ProgrammingError("Cursor is not connected") from err + self._cnx.handle_unread_result() + + if not isinstance(seq_params, (list, tuple)): + raise ProgrammingError("Parameters for query must be list or tuple.") + + # Optimize INSERTs by batching them + if re.match(RE_SQL_INSERT_STMT, operation): + if not seq_params: + self._rowcount = 0 + return None + stmt = self._batch_insert(operation, seq_params) + if stmt is not None: + self._executed = stmt + return self.execute(stmt) + + rowcnt = 0 + try: + # When processing read ops (e.g., SELECT), rowcnt is updated + # based on self._rowcount. For write ops (e.g., INSERT) is + # updated based on self._affected_rows. + # The variable self._description is None for write ops, that's + # why we use it as indicator for updating rowcnt. + for params in seq_params: + self.execute(operation, params) + if self.with_rows and self._cnx.unread_result: + self.fetchall() + rowcnt += self._rowcount if self.description else self._affected_rows + except (ValueError, TypeError) as err: + raise InterfaceError(f"Failed executing the operation; {err}") from None + + self._rowcount = rowcnt + return None + + @property + def description(self) -> Optional[List[DescriptionType]]: + """Returns description of columns in a result""" + return self._description + + @property + def rowcount(self) -> int: + """Returns the number of rows produced or affected""" + if self._rowcount == -1: + return self._affected_rows + return self._rowcount + + def close(self) -> bool: + """Close the cursor + + The result will be freed. + """ + if not self._cnx: + return False + + self._cnx.handle_unread_result() + self._warnings = None + self._cnx = None + return True + + def callproc( + self, + procname: str, + args: Sequence[Any] = (), + ) -> Optional[Union[Dict[str, ToPythonOutputTypes], RowType]]: + """Calls a stored procedure with the given arguments""" + if not procname or not isinstance(procname, str): + raise ValueError("procname must be a string") + + if not isinstance(args, (tuple, list)): + raise ValueError("args must be a sequence") + + argfmt = "@_{name}_arg{index}" + self._stored_results = [] + + try: + argnames = [] + argtypes = [] + + # MySQL itself does support calling procedures with their full + # name .. It's necessary to split + # by '.' and grab the procedure name from procname. + procname_abs = procname.split(".")[-1] + if args: + argvalues = [] + for idx, arg in enumerate(args): + argname = argfmt.format(name=procname_abs, index=idx + 1) + argnames.append(argname) + if isinstance(arg, tuple): + argtypes.append(f" CAST({argname} AS {arg[1]})") + argvalues.append(arg[0]) + else: + argtypes.append(argname) + argvalues.append(arg) + + placeholders = ",".join(f"{arg}=%s" for arg in argnames) + self.execute(f"SET {placeholders}", argvalues) + + call = f"CALL {procname}({','.join(argnames)})" + + result = self._cnx.cmd_query( + call, raw=self._raw, raw_as_string=self._raw_as_string + ) + + results = [] + while self._cnx.result_set_available: + result = self._cnx.fetch_eof_columns() + if isinstance(self, (CMySQLCursorDict, CMySQLCursorBufferedDict)): + cursor_class = CMySQLCursorBufferedDict + elif isinstance( + self, + (CMySQLCursorNamedTuple, CMySQLCursorBufferedNamedTuple), + ): + cursor_class = CMySQLCursorBufferedNamedTuple + elif self._raw: + cursor_class = CMySQLCursorBufferedRaw + else: + cursor_class = CMySQLCursorBuffered + # pylint: disable=protected-access + cur = cursor_class(self._cnx.get_self()) + cur._executed = f"(a result of {call})" + cur._handle_result(result) + # pylint: enable=protected-access + results.append(cur) + self._cnx.next_result() + self._stored_results = results + self._handle_eof() + + if argnames: + self.reset() + # Create names aliases to be compatible with namedtuples + args = [ + f"{name} AS {alias}" + for name, alias in zip( + argtypes, [arg.lstrip("@_") for arg in argnames] + ) + ] + select = f"SELECT {','.join(args)}" + self.execute(select) + + return self.fetchone() + return tuple() + + except Error: + raise + except Exception as err: + raise InterfaceError(f"Failed calling stored routine; {err}") from None + + def nextset(self) -> Optional[bool]: + """Skip to the next available result set""" + if not self._cnx.next_result(): + self.reset(free=True) + return None + self.reset(free=False) + + if not self._cnx.result_set_available: + eof = self._cnx.fetch_eof_status() + self._handle_result(eof) + raise InterfaceError(errno=CR_NO_RESULT_SET) + + self._handle_result(self._cnx.fetch_eof_columns()) + return True + + def fetchall(self) -> List[RowType]: + """Return all rows of a query result set. + + Returns: + list: A list of tuples with all rows of a query result set. + """ + self._check_executed() + if not self._cnx.unread_result: + return [] + + rows: Tuple[List[RowType], Optional[CextEofPacketType]] = self._cnx.get_rows() + if self._nextrow and self._nextrow[0]: + rows[0].insert(0, self._nextrow[0]) + + if not rows[0]: + self._handle_eof() + return [] + + self._rowcount += len(rows[0]) + self._handle_eof() + # self._cnx.handle_unread_result() + return rows[0] + + def fetchmany(self, size: int = 1) -> List[RowType]: + """Return the next set of rows of a query result set. + + When no more rows are available, it returns an empty list. + The number of rows returned can be specified using the size argument, + which defaults to one. + + Returns: + list: The next set of rows of a query result set. + """ + self._check_executed() + if self._nextrow and self._nextrow[0]: + rows = [self._nextrow[0]] + size -= 1 + else: + rows = [] + + if size and self._cnx.unread_result: + rows.extend(self._cnx.get_rows(size)[0]) + + if size: + if self._cnx.unread_result: + self._nextrow = self._cnx.get_row() + if ( + self._nextrow + and not self._nextrow[0] + and not self._cnx.more_results + ): + self._cnx.free_result() + else: + self._nextrow = (None, None) + + if not rows: + self._handle_eof() + return [] + + self._rowcount += len(rows) + return rows + + def fetchone(self) -> Optional[RowType]: + """Return next row of a query result set. + + Returns: + tuple or None: A row from query result set. + """ + self._check_executed() + row = self._nextrow + if not row and self._cnx.unread_result: + row = self._cnx.get_row() + + if row and row[0]: + self._nextrow = self._cnx.get_row() + if not self._nextrow[0] and not self._cnx.more_results: + self._cnx.free_result() + else: + self._handle_eof() + return None + self._rowcount += 1 + return row[0] + + def __iter__(self) -> Iterator[RowType]: + """Iteration over the result set + + Iteration over the result set which calls self.fetchone() + and returns the next row. + """ + return iter(self.fetchone, None) + + def stored_results(self) -> Generator[CMySQLCursor, None, None]: + """Returns an iterator for stored results + + This method returns an iterator over results which are stored when + callproc() is called. The iterator will provide MySQLCursorBuffered + instances. + + Returns a iterator. + """ + for result in self._stored_results: + yield result + self._stored_results = [] + + def __next__(self) -> RowType: + """Iteration over the result set + Used for iterating over the result set. Calls self.fetchone() + to get the next row. + + Raises StopIteration when no more rows are available. + """ + try: + row = self.fetchone() + except InterfaceError: + raise StopIteration from None + if not row: + raise StopIteration from None + return row + + @property + def column_names(self) -> Tuple[str, ...]: + """Returns column names + + This property returns the columns names as a tuple. + + Returns a tuple. + """ + if not self.description: + return () + return tuple(d[0] for d in self.description) + + @property + def statement(self) -> str: + """Returns the executed statement + + This property returns the executed statement. When multiple + statements were executed, the current statement in the iterator + will be returned. + """ + try: + return self._executed.strip().decode("utf8") + except AttributeError: + return self._executed.strip() # type: ignore[return-value] + + @property + def with_rows(self) -> bool: + """Returns whether the cursor could have rows returned + + This property returns True when column descriptions are available + and possibly also rows, which will need to be fetched. + + Returns True or False. + """ + if self.description: + return True + return False + + def __str__(self) -> str: + fmt = "{class_name}: {stmt}" + if self._executed: + try: + executed = self._executed.decode("utf-8") + except AttributeError: + executed = self._executed + if len(executed) > 40: + executed = executed[:40] + ".." + else: + executed = "(Nothing executed yet)" + + return fmt.format(class_name=self.__class__.__name__, stmt=executed) + + +class CMySQLCursorBuffered(CMySQLCursor): + + """Cursor using C Extension buffering results""" + + def __init__(self, connection: Type[MySQLConnectionAbstract]): + """Initialize""" + super().__init__(connection) + + self._rows: Optional[List[RowType]] = None + self._next_row: int = 0 + + def _handle_resultset(self) -> None: + """Handle a result set""" + self._rows = self._cnx.get_rows()[0] + self._next_row = 0 + self._rowcount: int = len(self._rows) + self._handle_eof() + + def reset(self, free: bool = True) -> None: + """Reset the cursor to default""" + self._rows = None + self._next_row = 0 + super().reset(free=free) + + def _fetch_row(self) -> Optional[RowType]: + """Returns the next row in the result set + + Returns a tuple or None. + """ + row = None + try: + row = self._rows[self._next_row] + except IndexError: + return None + self._next_row += 1 + return row + + def fetchall(self) -> List[RowType]: + """Return all rows of a query result set. + + Returns: + list: A list of tuples with all rows of a query result set. + """ + self._check_executed() + res = self._rows[self._next_row :] + self._next_row = len(self._rows) + return res + + def fetchmany(self, size: int = 1) -> List[RowType]: + """Return the next set of rows of a query result set. + + When no more rows are available, it returns an empty list. + The number of rows returned can be specified using the size argument, + which defaults to one. + + Returns: + list: The next set of rows of a query result set. + """ + self._check_executed() + res = [] + cnt = size or self.arraysize + while cnt > 0: + cnt -= 1 + row = self._fetch_row() + if row: + res.append(row) + else: + break + return res + + def fetchone(self) -> Optional[RowType]: + """Return next row of a query result set. + + Returns: + tuple or None: A row from query result set. + """ + self._check_executed() + return self._fetch_row() + + @property + def with_rows(self) -> bool: + """Returns whether the cursor could have rows returned + + This property returns True when rows are available, + which will need to be fetched. + + Returns True or False. + """ + return self._rows is not None + + +class CMySQLCursorRaw(CMySQLCursor): + """Cursor using C Extension return raw results""" + + _raw: bool = True + + +class CMySQLCursorBufferedRaw(CMySQLCursorBuffered): + """Cursor using C Extension buffering raw results""" + + _raw: bool = True + + +class CMySQLCursorDict(CMySQLCursor): + """Cursor using C Extension returning rows as dictionaries""" + + _raw: bool = False + + def fetchone(self) -> Optional[Dict[str, ToPythonOutputTypes]]: + """Return next row of a query result set. + + Returns: + dict or None: A dict from query result set. + """ + row = super().fetchone() + return dict(zip(self.column_names, row)) if row else None + + def fetchmany(self, size: int = 1) -> List[Dict[str, ToPythonOutputTypes]]: + """Return the next set of rows of a query result set. + + When no more rows are available, it returns an empty list. + The number of rows returned can be specified using the size argument, + which defaults to one. + + Returns: + list: The next set of rows of a query result set represented + as a list of dictionaries where column names are used as keys. + """ + res = super().fetchmany(size=size) + return [dict(zip(self.column_names, row)) for row in res] + + def fetchall(self) -> List[Dict[str, ToPythonOutputTypes]]: + """Return all rows of a query result set. + + Returns: + list: A list of dictionaries with all rows of a query + result set where column names are used as keys. + """ + res = super().fetchall() + return [dict(zip(self.column_names, row)) for row in res] + + +class CMySQLCursorBufferedDict(CMySQLCursorBuffered): + """Cursor using C Extension buffering and returning rows as dictionaries""" + + _raw = False + + def _fetch_row(self) -> Optional[Dict[str, ToPythonOutputTypes]]: + row = super()._fetch_row() + if row: + return dict(zip(self.column_names, row)) + return None + + def fetchall(self) -> List[Dict[str, ToPythonOutputTypes]]: + """Return all rows of a query result set. + + Returns: + list: A list of tuples with all rows of a query result set. + """ + res = super().fetchall() + return [dict(zip(self.column_names, row)) for row in res] + + +class CMySQLCursorNamedTuple(CMySQLCursor): + """Cursor using C Extension returning rows as named tuples""" + + named_tuple: Any = None + + def _handle_resultset(self) -> None: + """Handle a result set""" + super()._handle_resultset() + columns = tuple(self.column_names) + try: + self.named_tuple = NAMED_TUPLE_CACHE[columns] + except KeyError: + self.named_tuple = namedtuple("Row", columns) # type: ignore[misc] + NAMED_TUPLE_CACHE[columns] = self.named_tuple + + def fetchone(self) -> Optional[RowType]: + """Return next row of a query result set. + + Returns: + tuple or None: A row from query result set. + """ + row = super().fetchone() + if row: + return self.named_tuple(*row) + return None + + def fetchmany(self, size: int = 1) -> List[RowType]: + """Return the next set of rows of a query result set. + + When no more rows are available, it returns an empty list. + The number of rows returned can be specified using the size argument, + which defaults to one. + + Returns: + list: The next set of rows of a query result set. + """ + res = super().fetchmany(size=size) + if not res: + return [] + return [self.named_tuple(*row) for row in res] + + def fetchall(self) -> List[RowType]: + """Return all rows of a query result set. + + Returns: + list: A list of tuples with all rows of a query result set. + """ + res = super().fetchall() + return [self.named_tuple(*row) for row in res] + + +class CMySQLCursorBufferedNamedTuple(CMySQLCursorBuffered): + """Cursor using C Extension buffering and returning rows as named tuples""" + + named_tuple: Any = None + + def _handle_resultset(self) -> None: + super()._handle_resultset() + self.named_tuple = namedtuple("Row", self.column_names) # type: ignore[misc] + + def _fetch_row(self) -> Optional[RowType]: + row = super()._fetch_row() + if row: + return self.named_tuple(*row) + return None + + def fetchall(self) -> List[RowType]: + """Return all rows of a query result set. + + Returns: + list: A list of tuples with all rows of a query result set. + """ + res = super().fetchall() + return [self.named_tuple(*row) for row in res] + + +class CMySQLCursorPrepared(CMySQLCursor): + """Cursor using MySQL Prepared Statements""" + + def __init__(self, connection: Type[MySQLConnectionAbstract]): + super().__init__(connection) + self._rows: Optional[List[RowType]] = None + self._rowcount: int = 0 + self._next_row: int = 0 + self._binary: bool = True + self._stmt: Optional[MySQLPrepStmt] = None + + def _handle_eof(self) -> None: + """Handle EOF packet""" + self._nextrow = (None, None) + self._handle_warnings() + + def _fetch_row(self, raw: bool = False) -> Optional[RowType]: + """Returns the next row in the result set + + Returns a tuple or None. + """ + if not self._stmt or not self._stmt.have_result_set: + return None + row = None + + if self._nextrow == (None, None): + (row, eof) = self._cnx.get_row( + binary=self._binary, + columns=self.description, + raw=raw, + prep_stmt=self._stmt, + ) + else: + (row, eof) = self._nextrow + + if row: + self._nextrow = self._cnx.get_row( + binary=self._binary, + columns=self.description, + raw=raw, + prep_stmt=self._stmt, + ) + eof = self._nextrow[1] + if eof is not None: + self._warning_count = eof["warning_count"] + self._handle_eof() + if self._rowcount == -1: + self._rowcount = 1 + else: + self._rowcount += 1 + if eof: + self._warning_count = eof["warning_count"] + self._handle_eof() + + return row + + def callproc(self, procname: Any, args: Any = None) -> NoReturn: + """Calls a stored procedue + + Not supported with CMySQLCursorPrepared. + """ + raise NotSupportedError() + + def close(self) -> None: + """Close the cursor + + This method will try to deallocate the prepared statement and close + the cursor. + """ + if self._stmt: + self.reset() + self._cnx.cmd_stmt_close(self._stmt) + self._stmt = None + super().close() + + def reset(self, free: bool = True) -> None: + """Resets the prepared statement.""" + if self._stmt: + self._cnx.cmd_stmt_reset(self._stmt) + super().reset(free=free) + + def execute( + self, + operation: StrOrBytes, + params: Optional[ParamsSequenceOrDictType] = None, + multi: bool = False, + ) -> None: # multi is unused + """Prepare and execute a MySQL Prepared Statement + + This method will prepare the given operation and execute it using + the given parameters. + + If the cursor instance already had a prepared statement, it is + first closed. + + Note: argument "multi" is unused. + """ + if not operation: + return + + try: + if not self._cnx or self._cnx.is_closed(): + raise ProgrammingError + except (ProgrammingError, ReferenceError) as err: + raise ProgrammingError("Cursor is not connected", 2055) from err + + self._cnx.handle_unread_result(prepared=True) + + charset = self._cnx.charset + if charset == "utf8mb4": + charset = "utf8" + + if not isinstance(operation, str): + try: + operation = operation.decode(charset) + except UnicodeDecodeError as err: + raise ProgrammingError(str(err)) from err + + if isinstance(params, dict): + replacement_keys = re.findall(RE_SQL_PYTHON_CAPTURE_PARAM_NAME, operation) + try: + # Replace params dict with params tuple in correct order. + params = tuple(params[key] for key in replacement_keys) + except KeyError as err: + raise ProgrammingError( + "Not all placeholders were found in the parameters dict" + ) from err + # Convert %(name)s to ? before sending it to MySQL + operation = re.sub(RE_SQL_PYTHON_REPLACE_PARAM, "?", operation) + + if operation is not self._executed: + if self._stmt: + self._cnx.cmd_stmt_close(self._stmt) + self._executed = operation + + try: + operation = operation.encode(charset) + except UnicodeEncodeError as err: + raise ProgrammingError(str(err)) from err + + if b"%s" in operation: + # Convert %s to ? before sending it to MySQL + operation = re.sub(RE_SQL_FIND_PARAM, b"?", operation) + + try: + self._stmt = self._cnx.cmd_stmt_prepare(operation) + except Error: + self._executed = None + self._stmt = None + raise + + self._cnx.cmd_stmt_reset(self._stmt) + + if self._stmt.param_count > 0 and not params: + return + if params: + if not isinstance(params, (tuple, list)): + raise ProgrammingError( + errno=1210, + msg=f"Incorrect type of argument: {type(params).__name__}({params})" + ", it must be of type tuple or list the argument given to " + "the prepared statement", + ) + if self._stmt.param_count != len(params): + raise ProgrammingError( + errno=1210, + msg="Incorrect number of arguments executing prepared statement", + ) + + if params is None: + params = () + res = self._cnx.cmd_stmt_execute(self._stmt, *params) + if res: + self._handle_result(res) + + def executemany( + self, operation: str, seq_params: Sequence[ParamsSequenceType] + ) -> None: + """Prepare and execute a MySQL Prepared Statement many times + + This method will prepare the given operation and execute with each + tuple found the list seq_params. + + If the cursor instance already had a prepared statement, it is + first closed. + """ + rowcnt = 0 + try: + for params in seq_params: + self.execute(operation, params) + if self.with_rows: + self.fetchall() + rowcnt += self._rowcount + except (ValueError, TypeError) as err: + raise InterfaceError(f"Failed executing the operation; {err}") from err + self._rowcount = rowcnt + + def fetchone(self) -> Optional[RowType]: + """Return next row of a query result set. + + Returns: + tuple or None: A row from query result set. + """ + self._check_executed() + return self._fetch_row() or None + + def fetchmany(self, size: Optional[int] = None) -> List[RowType]: + """Return the next set of rows of a query result set. + + When no more rows are available, it returns an empty list. + The number of rows returned can be specified using the size argument, + which defaults to one. + + Returns: + list: The next set of rows of a query result set. + """ + self._check_executed() + res = [] + cnt = size or self.arraysize + while cnt > 0 and self._stmt.have_result_set: + cnt -= 1 + row = self._fetch_row() + if row: + res.append(row) + return res + + def fetchall(self) -> List[RowType]: + """Return all rows of a query result set. + + Returns: + list: A list of tuples with all rows of a query result set. + """ + self._check_executed() + if not self._stmt.have_result_set: + return [] + + rows = self._cnx.get_rows(prep_stmt=self._stmt) + if self._nextrow and self._nextrow[0]: + rows[0].insert(0, self._nextrow[0]) + + if not rows[0]: + self._handle_eof() + return [] + + self._rowcount += len(rows[0]) + self._handle_eof() + return rows[0] + + +class CMySQLCursorPreparedDict(CMySQLCursorDict, CMySQLCursorPrepared): # type: ignore[misc] + """This class is a blend of features from CMySQLCursorDict and CMySQLCursorPrepared + + Multiple inheritance in python is allowed but care must be taken + when assuming methods resolution. In the case of multiple + inheritance, a given attribute is first searched in the current + class if it's not found then it's searched in the parent classes. + The parent classes are searched in a left-right fashion and each + class is searched once. + Based on python's attribute resolution, in this case, attributes + are searched as follows: + 1. CMySQLCursorPreparedDict (current class) + 2. CMySQLCursorDict (left parent class) + 3. CMySQLCursorPrepared (right parent class) + 4. CMySQLCursor (base class) + """ + + +class CMySQLCursorPreparedNamedTuple(CMySQLCursorNamedTuple, CMySQLCursorPrepared): + """This class is a blend of features from CMySQLCursorNamedTuple and CMySQLCursorPrepared""" + + +class CMySQLCursorPreparedRaw(CMySQLCursorPrepared): + """This class is a blend of features from CMySQLCursorRaw and CMySQLCursorPrepared""" + + _raw: bool = True diff --git a/mysql/connector/custom_types.py b/mysql/connector/custom_types.py new file mode 100644 index 0000000..7fcacbd --- /dev/null +++ b/mysql/connector/custom_types.py @@ -0,0 +1,50 @@ +# Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Custom Python types used by MySQL Connector/Python""" +from __future__ import annotations + +from typing import Type + + +class HexLiteral(str): + + """Class holding MySQL hex literals""" + + charset: str = "" + original: str = "" + + def __new__(cls: Type[HexLiteral], str_: str, charset: str = "utf8") -> HexLiteral: + hexed = [f"{i:02x}" for i in str_.encode(charset)] + obj = str.__new__(cls, "".join(hexed)) + obj.charset = charset + obj.original = str_ + return obj + + def __str__(self) -> str: + return "0x" + self diff --git a/mysql/connector/dbapi.py b/mysql/connector/dbapi.py new file mode 100644 index 0000000..f827aa1 --- /dev/null +++ b/mysql/connector/dbapi.py @@ -0,0 +1,85 @@ +# Copyright (c) 2009, 2022, Oracle and/or its affiliates. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +""" +This module implements some constructors and singletons as required by the +DB API v2.0 (PEP-249). +""" + +# Python Db API v2 +# pylint: disable=invalid-name +apilevel: str = "2.0" +threadsafety: int = 1 +paramstyle: str = "pyformat" + +import datetime +import time + +from typing import Tuple + +from . import constants + + +class _DBAPITypeObject: + def __init__(self, *values: int) -> None: + self.values: Tuple[int, ...] = values + + def __eq__(self, other: object) -> bool: + return other in self.values + + def __ne__(self, other: object) -> bool: + return other not in self.values + + +Date = datetime.date +Time = datetime.time +Timestamp = datetime.datetime + + +def DateFromTicks(ticks: int) -> datetime.date: + """Construct an object holding a date value from the given ticks value.""" + return Date(*time.localtime(ticks)[:3]) + + +def TimeFromTicks(ticks: int) -> datetime.time: + """Construct an object holding a time value from the given ticks value.""" + return Time(*time.localtime(ticks)[3:6]) + + +def TimestampFromTicks(ticks: int) -> datetime.datetime: + """Construct an object holding a time stamp from the given ticks value.""" + return Timestamp(*time.localtime(ticks)[:6]) + + +Binary = bytes + +STRING = _DBAPITypeObject(*constants.FieldType.get_string_types()) +BINARY = _DBAPITypeObject(*constants.FieldType.get_binary_types()) +NUMBER = _DBAPITypeObject(*constants.FieldType.get_number_types()) +DATETIME = _DBAPITypeObject(*constants.FieldType.get_timestamp_types()) +ROWID = _DBAPITypeObject() diff --git a/mysql/connector/django/__init__.py b/mysql/connector/django/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mysql/connector/django/base.py b/mysql/connector/django/base.py new file mode 100644 index 0000000..0d10728 --- /dev/null +++ b/mysql/connector/django/base.py @@ -0,0 +1,636 @@ +# Copyright (c) 2020, 2023, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# mypy: disable-error-code="override" + +"""Django database Backend using MySQL Connector/Python. + +This Django database backend is heavily based on the MySQL backend from Django. + +Changes include: +* Support for microseconds (MySQL 5.6.3 and later) +* Using INFORMATION_SCHEMA where possible +* Using new defaults for, for example SQL_AUTO_IS_NULL + +Requires and comes with MySQL Connector/Python v8.0.22 and later: + http://dev.mysql.com/downloads/connector/python/ +""" + +import warnings + +from datetime import datetime, time +from typing import Any, Dict, Generator, Iterator, List, Optional, Set, Tuple, Union + +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.db import IntegrityError +from django.db.backends.base.base import BaseDatabaseWrapper +from django.utils import dateparse, timezone +from django.utils.functional import cached_property + +try: + import mysql.connector + + from mysql.connector.connection import MySQLConnection + from mysql.connector.connection_cext import CMySQLConnection + from mysql.connector.conversion import MySQLConverter + from mysql.connector.cursor import MySQLCursor + from mysql.connector.cursor_cext import CMySQLCursor + from mysql.connector.custom_types import HexLiteral + from mysql.connector.pooling import PooledMySQLConnection + from mysql.connector.types import ( + ParamsDictType, + ParamsSequenceOrDictType, + ParamsSequenceType, + RowType, + StrOrBytes, + ) +except ImportError as err: + raise ImproperlyConfigured(f"Error loading mysql.connector module: {err}") from err + +try: + from _mysql_connector import datetime_to_mysql +except ImportError: + HAVE_CEXT = False +else: + HAVE_CEXT = True + +from .client import DatabaseClient +from .creation import DatabaseCreation +from .features import DatabaseFeatures +from .introspection import DatabaseIntrospection +from .operations import DatabaseOperations +from .schema import DatabaseSchemaEditor +from .validation import DatabaseValidation + +Error = mysql.connector.Error +DatabaseError = mysql.connector.DatabaseError +NotSupportedError = mysql.connector.NotSupportedError +OperationalError = mysql.connector.OperationalError +ProgrammingError = mysql.connector.ProgrammingError + + +def adapt_datetime_with_timezone_support(value: datetime) -> StrOrBytes: + """Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL.""" + if settings.USE_TZ: + if timezone.is_naive(value): + warnings.warn( + f"MySQL received a naive datetime ({value})" + " while time zone support is active.", + RuntimeWarning, + ) + default_timezone = timezone.get_default_timezone() + value = timezone.make_aware(value, default_timezone) + value = value.astimezone(timezone.utc).replace(tzinfo=None) + if HAVE_CEXT: + mysql_datetime: bytes = datetime_to_mysql(value) + return mysql_datetime + return value.strftime("%Y-%m-%d %H:%M:%S.%f") + + +class CursorWrapper: + """Wrapper around MySQL Connector/Python's cursor class. + + The cursor class is defined by the options passed to MySQL + Connector/Python. If buffered option is True in those options, + MySQLCursorBuffered will be used. + """ + + codes_for_integrityerror = ( + 1048, # Column cannot be null + 1690, # BIGINT UNSIGNED value is out of range + 3819, # CHECK constraint is violated + 4025, # CHECK constraint failed + ) + + def __init__(self, cursor: Union[MySQLCursor, CMySQLCursor]) -> None: + self.cursor: Union[MySQLCursor, CMySQLCursor] = cursor + + @staticmethod + def _adapt_execute_args_dict(args: ParamsDictType) -> ParamsDictType: + if not args: + return args + new_args = dict(args) + for key, value in args.items(): + if isinstance(value, datetime): + new_args[key] = adapt_datetime_with_timezone_support(value) + + return new_args + + @staticmethod + def _adapt_execute_args( + args: Optional[ParamsSequenceType], + ) -> Optional[ParamsSequenceType]: + if not args: + return args + new_args = list(args) + for i, arg in enumerate(args): + if isinstance(arg, datetime): + new_args[i] = adapt_datetime_with_timezone_support(arg) + + return tuple(new_args) + + def execute( + self, query: str, args: Optional[ParamsSequenceOrDictType] = None + ) -> Optional[Generator[Union[MySQLCursor, CMySQLCursor], None, None]]: + """Executes the given operation + + This wrapper method around the execute()-method of the cursor is + mainly needed to re-raise using different exceptions. + """ + new_args: Optional[ParamsSequenceOrDictType] = None + if isinstance(args, dict): + new_args = self._adapt_execute_args_dict(args) + else: + new_args = self._adapt_execute_args(args) + try: + return self.cursor.execute(query, new_args) + except mysql.connector.OperationalError as exc: + if exc.args[0] in self.codes_for_integrityerror: + raise IntegrityError(*tuple(exc.args)) from None + raise + + def executemany( + self, + query: str, + args: Union[ + Tuple[ParamsSequenceOrDictType, ...], + List[ParamsSequenceOrDictType], + ], + ) -> Optional[Generator[Union[MySQLCursor, CMySQLCursor], None, None]]: + """Executes the given operation + + This wrapper method around the executemany()-method of the cursor is + mainly needed to re-raise using different exceptions. + """ + try: + return self.cursor.executemany(query, args) + except mysql.connector.OperationalError as exc: + if exc.args[0] in self.codes_for_integrityerror: + raise IntegrityError(*tuple(exc.args)) from None + raise + + def __getattr__(self, attr: Any) -> Any: + """Return an attribute of wrapped cursor""" + return getattr(self.cursor, attr) + + def __iter__(self) -> Iterator[RowType]: + """Return an iterator over wrapped cursor""" + return iter(self.cursor) + + +class DatabaseWrapper(BaseDatabaseWrapper): # pylint: disable=abstract-method + """Represent a database connection.""" + + vendor = "mysql" + # This dictionary maps Field objects to their associated MySQL column + # types, as strings. Column-type strings can contain format strings; they'll + # be interpolated against the values of Field.__dict__ before being output. + # If a column type is set to None, it won't be included in the output. + data_types = { + "AutoField": "integer AUTO_INCREMENT", + "BigAutoField": "bigint AUTO_INCREMENT", + "BinaryField": "longblob", + "BooleanField": "bool", + "CharField": "varchar(%(max_length)s)", + "DateField": "date", + "DateTimeField": "datetime(6)", + "DecimalField": "numeric(%(max_digits)s, %(decimal_places)s)", + "DurationField": "bigint", + "FileField": "varchar(%(max_length)s)", + "FilePathField": "varchar(%(max_length)s)", + "FloatField": "double precision", + "IntegerField": "integer", + "BigIntegerField": "bigint", + "IPAddressField": "char(15)", + "GenericIPAddressField": "char(39)", + "JSONField": "json", + "NullBooleanField": "bool", + "OneToOneField": "integer", + "PositiveBigIntegerField": "bigint UNSIGNED", + "PositiveIntegerField": "integer UNSIGNED", + "PositiveSmallIntegerField": "smallint UNSIGNED", + "SlugField": "varchar(%(max_length)s)", + "SmallAutoField": "smallint AUTO_INCREMENT", + "SmallIntegerField": "smallint", + "TextField": "longtext", + "TimeField": "time(6)", + "UUIDField": "char(32)", + } + + # For these data types: + # - MySQL < 8.0.13 doesn't accept default values and + # implicitly treat them as nullable + # - all versions of MySQL doesn't support full width database + # indexes + _limited_data_types = ( + "tinyblob", + "blob", + "mediumblob", + "longblob", + "tinytext", + "text", + "mediumtext", + "longtext", + "json", + ) + + operators = { + "exact": "= %s", + "iexact": "LIKE %s", + "contains": "LIKE BINARY %s", + "icontains": "LIKE %s", + "regex": "REGEXP BINARY %s", + "iregex": "REGEXP %s", + "gt": "> %s", + "gte": ">= %s", + "lt": "< %s", + "lte": "<= %s", + "startswith": "LIKE BINARY %s", + "endswith": "LIKE BINARY %s", + "istartswith": "LIKE %s", + "iendswith": "LIKE %s", + } + + # The patterns below are used to generate SQL pattern lookup clauses when + # the right-hand side of the lookup isn't a raw string (it might be an expression + # or the result of a bilateral transformation). + # In those cases, special characters for LIKE operators (e.g. \, *, _) should be + # escaped on database side. + # + # Note: we use str.format() here for readability as '%' is used as a wildcard for + # the LIKE operator. + pattern_esc = r"REPLACE(REPLACE(REPLACE({}, '\\', '\\\\'), '%%', '\%%'), '_', '\_')" + pattern_ops = { + "contains": "LIKE BINARY CONCAT('%%', {}, '%%')", + "icontains": "LIKE CONCAT('%%', {}, '%%')", + "startswith": "LIKE BINARY CONCAT({}, '%%')", + "istartswith": "LIKE CONCAT({}, '%%')", + "endswith": "LIKE BINARY CONCAT('%%', {})", + "iendswith": "LIKE CONCAT('%%', {})", + } + + isolation_level: Optional[str] = None + isolation_levels = { + "read uncommitted", + "read committed", + "repeatable read", + "serializable", + } + + Database = mysql.connector + SchemaEditorClass = DatabaseSchemaEditor + # Classes instantiated in __init__(). + client_class = DatabaseClient + creation_class = DatabaseCreation + features_class = DatabaseFeatures + introspection_class = DatabaseIntrospection + ops_class = DatabaseOperations + validation_class = DatabaseValidation + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + options = self.settings_dict.get("OPTIONS") + if options: + self._use_pure = options.get("use_pure", not HAVE_CEXT) + converter_class = options.get( + "converter_class", + DjangoMySQLConverter, + ) + if not issubclass(converter_class, DjangoMySQLConverter): + raise ProgrammingError( + "Converter class should be a subclass of " + "mysql.connector.django.base.DjangoMySQLConverter" + ) + self.converter = converter_class() + else: + self.converter = DjangoMySQLConverter() + self._use_pure = not HAVE_CEXT + + def __getattr__(self, attr: str) -> bool: + if attr.startswith("mysql_is"): + return False + raise AttributeError + + def get_connection_params(self) -> Dict[str, Any]: + kwargs = { + "charset": "utf8", + "use_unicode": True, + "buffered": False, + "consume_results": True, + } + + settings_dict = self.settings_dict + + if settings_dict["USER"]: + kwargs["user"] = settings_dict["USER"] + if settings_dict["NAME"]: + kwargs["database"] = settings_dict["NAME"] + if settings_dict["PASSWORD"]: + kwargs["passwd"] = settings_dict["PASSWORD"] + if settings_dict["HOST"].startswith("/"): + kwargs["unix_socket"] = settings_dict["HOST"] + elif settings_dict["HOST"]: + kwargs["host"] = settings_dict["HOST"] + if settings_dict["PORT"]: + kwargs["port"] = int(settings_dict["PORT"]) + if settings_dict.get("OPTIONS", {}).get("init_command"): + kwargs["init_command"] = settings_dict["OPTIONS"]["init_command"] + + # Raise exceptions for database warnings if DEBUG is on + kwargs["raise_on_warnings"] = settings.DEBUG + + kwargs["client_flags"] = [ + # Need potentially affected rows on UPDATE + mysql.connector.constants.ClientFlag.FOUND_ROWS, + ] + + try: + options = settings_dict["OPTIONS"].copy() + isolation_level = options.pop("isolation_level") + if isolation_level: + isolation_level = isolation_level.lower() + if isolation_level not in self.isolation_levels: + valid_levels = ", ".join( + f"'{level}'" for level in sorted(self.isolation_levels) + ) + raise ImproperlyConfigured( + f"Invalid transaction isolation level '{isolation_level}' " + f"specified.\nUse one of {valid_levels}, or None." + ) + self.isolation_level = isolation_level + kwargs.update(options) + except KeyError: + # OPTIONS missing is OK + pass + return kwargs + + def get_new_connection( + self, conn_params: Dict[str, Any] + ) -> Union[PooledMySQLConnection, MySQLConnection, CMySQLConnection]: + if "converter_class" not in conn_params: + conn_params["converter_class"] = DjangoMySQLConverter + cnx = mysql.connector.connect(**conn_params) + + return cnx + + def init_connection_state(self) -> None: + assignments = [] + if self.features.is_sql_auto_is_null_enabled: # type: ignore[attr-defined] + # SQL_AUTO_IS_NULL controls whether an AUTO_INCREMENT column on + # a recently inserted row will return when the field is tested + # for NULL. Disabling this brings this aspect of MySQL in line + # with SQL standards. + assignments.append("SET SQL_AUTO_IS_NULL = 0") + + if self.isolation_level: + assignments.append( + "SET SESSION TRANSACTION ISOLATION LEVEL " + f"{self.isolation_level.upper()}" + ) + + if assignments: + with self.cursor() as cursor: + cursor.execute("; ".join(assignments)) + + if "AUTOCOMMIT" in self.settings_dict: + try: + self.set_autocommit(self.settings_dict["AUTOCOMMIT"]) + except AttributeError: + self._set_autocommit(self.settings_dict["AUTOCOMMIT"]) + + def create_cursor(self, name: Any = None) -> CursorWrapper: + cursor = self.connection.cursor() + return CursorWrapper(cursor) + + def _rollback(self) -> None: + try: + BaseDatabaseWrapper._rollback(self) # type: ignore[attr-defined] + except NotSupportedError: + pass + + def _set_autocommit(self, autocommit: bool) -> None: + with self.wrap_database_errors: + self.connection.autocommit = autocommit + + def disable_constraint_checking(self) -> bool: + """ + Disable foreign key checks, primarily for use in adding rows with + forward references. Always return True to indicate constraint checks + need to be re-enabled. + """ + with self.cursor() as cursor: + cursor.execute("SET foreign_key_checks=0") + return True + + def enable_constraint_checking(self) -> None: + """ + Re-enable foreign key checks after they have been disabled. + """ + # Override needs_rollback in case constraint_checks_disabled is + # nested inside transaction.atomic. + self.needs_rollback, needs_rollback = False, self.needs_rollback + try: + with self.cursor() as cursor: + cursor.execute("SET foreign_key_checks=1") + finally: + self.needs_rollback = needs_rollback + + def check_constraints(self, table_names: Optional[List[str]] = None) -> None: + """ + Check each table name in `table_names` for rows with invalid foreign + key references. This method is intended to be used in conjunction with + `disable_constraint_checking()` and `enable_constraint_checking()`, to + determine if rows with invalid references were entered while constraint + checks were off. + """ + with self.cursor() as cursor: + if table_names is None: + table_names = self.introspection.table_names(cursor) + for table_name in table_names: + primary_key_column_name = self.introspection.get_primary_key_column( + cursor, table_name + ) + if not primary_key_column_name: + continue + key_columns = self.introspection.get_key_columns(cursor, table_name) + for ( + column_name, + referenced_table_name, + referenced_column_name, + ) in key_columns: + cursor.execute( + f""" + SELECT REFERRING.`{primary_key_column_name}`, + REFERRING.`{column_name}` + FROM `{table_name}` as REFERRING + LEFT JOIN `{referenced_table_name}` as REFERRED + ON ( + REFERRING.`{column_name}` = + REFERRED.`{referenced_column_name}` + ) + WHERE REFERRING.`{column_name}` IS NOT NULL + AND REFERRED.`{referenced_column_name}` IS NULL + """ + ) + for bad_row in cursor.fetchall(): + raise IntegrityError( + f"The row in table '{table_name}' with primary " + f"key '{bad_row[0]}' has an invalid foreign key: " + f"{table_name}.{column_name} contains a value " + f"'{bad_row[1]}' that does not have a " + f"corresponding value in " + f"{referenced_table_name}." + f"{referenced_column_name}." + ) + + def is_usable(self) -> bool: + try: + self.connection.ping() + except Error: + return False + return True + + @cached_property + @staticmethod + def display_name() -> str: + """Display name.""" + return "MySQL" + + @cached_property + def data_type_check_constraints(self) -> Dict[str, str]: + """Mapping of Field objects to their SQL for CHECK constraints.""" + if self.features.supports_column_check_constraints: + check_constraints = { + "PositiveBigIntegerField": "`%(column)s` >= 0", + "PositiveIntegerField": "`%(column)s` >= 0", + "PositiveSmallIntegerField": "`%(column)s` >= 0", + } + return check_constraints + return {} + + @cached_property + def mysql_server_data(self) -> Dict[str, Any]: + """Return MySQL server data.""" + with self.temporary_connection() as cursor: + # Select some server variables and test if the time zone + # definitions are installed. CONVERT_TZ returns NULL if 'UTC' + # timezone isn't loaded into the mysql.time_zone table. + cursor.execute( + """ + SELECT VERSION(), + @@sql_mode, + @@default_storage_engine, + @@sql_auto_is_null, + @@lower_case_table_names, + CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL + """ + ) + row = cursor.fetchone() + return { + "version": row[0], + "sql_mode": row[1], + "default_storage_engine": row[2], + "sql_auto_is_null": bool(row[3]), + "lower_case_table_names": bool(row[4]), + "has_zoneinfo_database": bool(row[5]), + } + + @cached_property + def mysql_server_info(self) -> Any: + """Return MySQL version.""" + with self.temporary_connection() as cursor: + cursor.execute("SELECT VERSION()") + return cursor.fetchone()[0] + + @cached_property + def mysql_version(self) -> Tuple[int, ...]: + """Return MySQL version.""" + config = self.get_connection_params() + with mysql.connector.connect(**config) as conn: + server_version: Tuple[int, ...] = conn.get_server_version() + return server_version + + @cached_property + def sql_mode(self) -> Set[str]: + """Return SQL mode.""" + with self.cursor() as cursor: + cursor.execute("SELECT @@sql_mode") + sql_mode = cursor.fetchone() + return set(sql_mode[0].split(",") if sql_mode else ()) + + @property + def use_pure(self) -> bool: + """Return True if pure Python version is being used.""" + ans: bool = self._use_pure + return ans + + +class DjangoMySQLConverter(MySQLConverter): + """Custom converter for Django.""" + + # pylint: disable=unused-argument + + @staticmethod + def _time_to_python(value: bytes, dsc: Any = None) -> Optional[time]: + """Return MySQL TIME data type as datetime.time() + + Returns datetime.time() + """ + return dateparse.parse_time(value.decode("utf-8")) + + @staticmethod + def _datetime_to_python(value: bytes, dsc: Any = None) -> Optional[datetime]: + """Connector/Python always returns naive datetime.datetime + + Connector/Python always returns naive timestamps since MySQL has + no time zone support. + + - A naive datetime is a datetime that doesn't know its own timezone. + + Django needs a non-naive datetime, but in this method we don't need + to make a datetime value time zone aware since Django itself at some + point will make it aware (at least in versions 3.2.16 and 4.1.2) when + USE_TZ=True. This may change in a future release, we need to keep an + eye on this behaviour. + + Returns datetime.datetime() + """ + return MySQLConverter._datetime_to_python(value) if value else None + + # pylint: enable=unused-argument + + def _safestring_to_mysql(self, value: str) -> Union[bytes, HexLiteral]: + return self._str_to_mysql(value) + + def _safetext_to_mysql(self, value: str) -> Union[bytes, HexLiteral]: + return self._str_to_mysql(value) + + def _safebytes_to_mysql(self, value: bytes) -> bytes: + return self._bytes_to_mysql(value) diff --git a/mysql/connector/django/client.py b/mysql/connector/django/client.py new file mode 100644 index 0000000..090ccd5 --- /dev/null +++ b/mysql/connector/django/client.py @@ -0,0 +1,106 @@ +# Copyright (c) 2020, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Database Client.""" + +import os +import subprocess + +from typing import Any, Dict, Iterable, List, Optional, Tuple + +from django.db.backends.base.client import BaseDatabaseClient + + +class DatabaseClient(BaseDatabaseClient): + """Encapsulate backend-specific methods for opening a client shell.""" + + executable_name = "mysql" + + @classmethod + def settings_to_cmd_args_env( + cls, settings_dict: Dict[str, Any], parameters: Optional[Iterable[str]] = None + ) -> Tuple[List[str], Optional[Dict[str, Any]]]: + args = [cls.executable_name] + + db = settings_dict["OPTIONS"].get("database", settings_dict["NAME"]) + user = settings_dict["OPTIONS"].get("user", settings_dict["USER"]) + passwd = settings_dict["OPTIONS"].get("password", settings_dict["PASSWORD"]) + host = settings_dict["OPTIONS"].get("host", settings_dict["HOST"]) + port = settings_dict["OPTIONS"].get("port", settings_dict["PORT"]) + ssl_ca = settings_dict["OPTIONS"].get("ssl_ca") + ssl_cert = settings_dict["OPTIONS"].get("ssl_cert") + ssl_key = settings_dict["OPTIONS"].get("ssl_key") + defaults_file = settings_dict["OPTIONS"].get("read_default_file") + charset = settings_dict["OPTIONS"].get("charset") + + # --defaults-file should always be the first option + if defaults_file: + args.append(f"--defaults-file={defaults_file}") + + # Load any custom init_commands. We always force SQL_MODE to TRADITIONAL + init_command = settings_dict["OPTIONS"].get("init_command", "") + args.append(f"--init-command=SET @@session.SQL_MODE=TRADITIONAL;{init_command}") + + if user: + args.append(f"--user={user}") + if passwd: + args.append(f"--password={passwd}") + + if host: + if "/" in host: + args.append(f"--socket={host}") + else: + args.append(f"--host={host}") + + if port: + args.append(f"--port={port}") + + if db: + args.append(f"--database={db}") + + if ssl_ca: + args.append(f"--ssl-ca={ssl_ca}") + if ssl_cert: + args.append(f"--ssl-cert={ssl_cert}") + if ssl_key: + args.append(f"--ssl-key={ssl_key}") + + if charset: + args.append(f"--default-character-set={charset}") + + if parameters: + args.extend(parameters) + + return args, None + + def runshell(self, parameters: Optional[Iterable[str]] = None) -> None: + args, env = self.settings_to_cmd_args_env( + self.connection.settings_dict, parameters + ) + env = {**os.environ, **env} if env else None + subprocess.run(args, env=env, check=True) diff --git a/mysql/connector/django/compiler.py b/mysql/connector/django/compiler.py new file mode 100644 index 0000000..1ee7871 --- /dev/null +++ b/mysql/connector/django/compiler.py @@ -0,0 +1,45 @@ +# Copyright (c) 2020, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""SQL Compiler classes.""" + +from django.db.backends.mysql.compiler import ( + SQLAggregateCompiler, + SQLCompiler, + SQLDeleteCompiler, + SQLInsertCompiler, + SQLUpdateCompiler, +) + +__all__ = [ + "SQLAggregateCompiler", + "SQLCompiler", + "SQLDeleteCompiler", + "SQLInsertCompiler", + "SQLUpdateCompiler", +] diff --git a/mysql/connector/django/creation.py b/mysql/connector/django/creation.py new file mode 100644 index 0000000..82f0853 --- /dev/null +++ b/mysql/connector/django/creation.py @@ -0,0 +1,33 @@ +# Copyright (c) 2020, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Backend specific database creation.""" + +from django.db.backends.mysql.creation import DatabaseCreation + +__all__ = ["DatabaseCreation"] diff --git a/mysql/connector/django/features.py b/mysql/connector/django/features.py new file mode 100644 index 0000000..e8debb8 --- /dev/null +++ b/mysql/connector/django/features.py @@ -0,0 +1,50 @@ +# Copyright (c) 2020, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Database Features.""" + +from typing import Any, List + +from django.db.backends.mysql.features import DatabaseFeatures as MySQLDatabaseFeatures +from django.utils.functional import cached_property + + +class DatabaseFeatures(MySQLDatabaseFeatures): + """Database Features Specification class.""" + + empty_fetchmany_value: List[Any] = [] + + @cached_property + def can_introspect_check_constraints(self) -> bool: # type: ignore[override] + """Check if backend support introspection CHECK of constraints.""" + return self.connection.mysql_version >= (8, 0, 16) + + @cached_property + def supports_microsecond_precision(self) -> bool: + """Check if backend support microsecond precision.""" + return self.connection.mysql_version >= (5, 6, 3) diff --git a/mysql/connector/django/introspection.py b/mysql/connector/django/introspection.py new file mode 100644 index 0000000..304a0ec --- /dev/null +++ b/mysql/connector/django/introspection.py @@ -0,0 +1,461 @@ +# Copyright (c) 2020, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# mypy: disable-error-code="override,attr-defined,call-arg" + +"""Database Introspection.""" + +from collections import namedtuple +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple + +import sqlparse + +from django import VERSION as DJANGO_VERSION +from django.db.backends.base.introspection import ( + BaseDatabaseIntrospection, + FieldInfo as BaseFieldInfo, + TableInfo, +) +from django.db.models import Index +from django.utils.datastructures import OrderedSet + +from mysql.connector.constants import FieldType + +# from .base import CursorWrapper produces a circular import error, +# avoiding importing CursorWrapper explicitly, using a documented +# trick; write the imports inside if TYPE_CHECKING: so that they +# are not executed at runtime. +# Ref: https://buildmedia.readthedocs.org/media/pdf/mypy/stable/mypy.pdf [page 42] +if TYPE_CHECKING: + # CursorWraper is used exclusively for type hinting + from mysql.connector.django.base import CursorWrapper + +# Based on my investigation, named tuples to +# comply with mypy need to define a static list or tuple +# for field_names (second argument). In this case, the field +# names are created dynamically for FieldInfo which triggers +# a mypy error. The solution is not straightforward since +# FieldInfo attributes are Django version dependent. Code +# refactory is needed to fix this issue. +FieldInfo = namedtuple( # type: ignore[misc] + "FieldInfo", + BaseFieldInfo._fields + ("extra", "is_unsigned", "has_json_constraint"), +) +if DJANGO_VERSION < (3, 2, 0): + InfoLine = namedtuple( + "InfoLine", + "col_name data_type max_len num_prec num_scale extra column_default " + "is_unsigned", + ) +else: + InfoLine = namedtuple( # type: ignore[no-redef] + "InfoLine", + "col_name data_type max_len num_prec num_scale extra column_default " + "collation is_unsigned", + ) + + +class DatabaseIntrospection(BaseDatabaseIntrospection): + """Encapsulate backend-specific introspection utilities.""" + + data_types_reverse = { + FieldType.BLOB: "TextField", + FieldType.DECIMAL: "DecimalField", + FieldType.NEWDECIMAL: "DecimalField", + FieldType.DATE: "DateField", + FieldType.DATETIME: "DateTimeField", + FieldType.DOUBLE: "FloatField", + FieldType.FLOAT: "FloatField", + FieldType.INT24: "IntegerField", + FieldType.LONG: "IntegerField", + FieldType.LONGLONG: "BigIntegerField", + FieldType.SHORT: "SmallIntegerField", + FieldType.STRING: "CharField", + FieldType.TIME: "TimeField", + FieldType.TIMESTAMP: "DateTimeField", + FieldType.TINY: "IntegerField", + FieldType.TINY_BLOB: "TextField", + FieldType.MEDIUM_BLOB: "TextField", + FieldType.LONG_BLOB: "TextField", + FieldType.VAR_STRING: "CharField", + } + + def get_field_type(self, data_type: str, description: FieldInfo) -> str: + field_type = super().get_field_type(data_type, description) # type: ignore[arg-type] + if "auto_increment" in description.extra: + if field_type == "IntegerField": + return "AutoField" + if field_type == "BigIntegerField": + return "BigAutoField" + if field_type == "SmallIntegerField": + return "SmallAutoField" + if description.is_unsigned: + if field_type == "BigIntegerField": + return "PositiveBigIntegerField" + if field_type == "IntegerField": + return "PositiveIntegerField" + if field_type == "SmallIntegerField": + return "PositiveSmallIntegerField" + # JSON data type is an alias for LONGTEXT in MariaDB, use check + # constraints clauses to introspect JSONField. + if description.has_json_constraint: + return "JSONField" + return field_type + + def get_table_list(self, cursor: "CursorWrapper") -> List[TableInfo]: + """Return a list of table and view names in the current database.""" + cursor.execute("SHOW FULL TABLES") + return [ + TableInfo(row[0], {"BASE TABLE": "t", "VIEW": "v"}.get(row[1])) + for row in cursor.fetchall() + ] + + def get_table_description( + self, cursor: "CursorWrapper", table_name: str + ) -> List[FieldInfo]: + """ + Return a description of the table with the DB-API cursor.description + interface." + """ + json_constraints: Dict[Any, Any] = {} + # A default collation for the given table. + cursor.execute( + """ + SELECT table_collation + FROM information_schema.tables + WHERE table_schema = DATABASE() + AND table_name = %s + """, + [table_name], + ) + row = cursor.fetchone() + default_column_collation = row[0] if row else "" + # information_schema database gives more accurate results for some figures: + # - varchar length returned by cursor.description is an internal length, + # not visible length (#5725) + # - precision and scale (for decimal fields) (#5014) + # - auto_increment is not available in cursor.description + if DJANGO_VERSION < (3, 2, 0): + cursor.execute( + """ + SELECT + column_name, data_type, character_maximum_length, + numeric_precision, numeric_scale, extra, column_default, + CASE + WHEN column_type LIKE '%% unsigned' THEN 1 + ELSE 0 + END AS is_unsigned + FROM information_schema.columns + WHERE table_name = %s AND table_schema = DATABASE() + """, + [table_name], + ) + else: + cursor.execute( + """ + SELECT + column_name, data_type, character_maximum_length, + numeric_precision, numeric_scale, extra, column_default, + CASE + WHEN collation_name = %s THEN NULL + ELSE collation_name + END AS collation_name, + CASE + WHEN column_type LIKE '%% unsigned' THEN 1 + ELSE 0 + END AS is_unsigned + FROM information_schema.columns + WHERE table_name = %s AND table_schema = DATABASE() + """, + [default_column_collation, table_name], + ) + field_info = {line[0]: InfoLine(*line) for line in cursor.fetchall()} + + cursor.execute( + f"SELECT * FROM {self.connection.ops.quote_name(table_name)} LIMIT 1" + ) + + def to_int(i: Any) -> Optional[int]: + return int(i) if i is not None else i + + fields = [] + for line in cursor.description: + info = field_info[line[0]] + if DJANGO_VERSION < (3, 2, 0): + fields.append( + FieldInfo( + *line[:3], + to_int(info.max_len) or line[3], + to_int(info.num_prec) or line[4], + to_int(info.num_scale) or line[5], + line[6], + info.column_default, + info.extra, + info.is_unsigned, + line[0] in json_constraints, + ) + ) + else: + fields.append( + FieldInfo( + *line[:3], + to_int(info.max_len) or line[3], + to_int(info.num_prec) or line[4], + to_int(info.num_scale) or line[5], + line[6], + info.column_default, + info.collation, + info.extra, + info.is_unsigned, + line[0] in json_constraints, + ) + ) + return fields + + def get_indexes( + self, cursor: "CursorWrapper", table_name: str + ) -> Dict[int, Dict[str, bool]]: + """Return indexes from table.""" + cursor.execute(f"SHOW INDEX FROM {self.connection.ops.quote_name(table_name)}") + # Do a two-pass search for indexes: on first pass check which indexes + # are multicolumn, on second pass check which single-column indexes + # are present. + rows = list(cursor.fetchall()) + multicol_indexes = set() + for row in rows: + if row[3] > 1: + multicol_indexes.add(row[2]) + indexes: Dict[int, Dict[str, bool]] = {} + for row in rows: + if row[2] in multicol_indexes: + continue + if row[4] not in indexes: + indexes[row[4]] = {"primary_key": False, "unique": False} + # It's possible to have the unique and PK constraints in + # separate indexes. + if row[2] == "PRIMARY": + indexes[row[4]]["primary_key"] = True + if not row[1]: + indexes[row[4]]["unique"] = True + return indexes + + def get_primary_key_column( + self, cursor: "CursorWrapper", table_name: str + ) -> Optional[int]: + """ + Returns the name of the primary key column for the given table + """ + for column in self.get_indexes(cursor, table_name).items(): + if column[1]["primary_key"]: + return column[0] + return None + + def get_sequences( + self, cursor: "CursorWrapper", table_name: str, table_fields: Any = () + ) -> List[Dict[str, str]]: + for field_info in self.get_table_description(cursor, table_name): + if "auto_increment" in field_info.extra: + # MySQL allows only one auto-increment column per table. + return [{"table": table_name, "column": field_info.name}] + return [] + + def get_relations( + self, cursor: "CursorWrapper", table_name: str + ) -> Dict[str, Tuple[str, str]]: + """ + Return a dictionary of {field_name: (field_name_other_table, other_table)} + representing all relationships to the given table. + """ + constraints = self.get_key_columns(cursor, table_name) + relations = {} + for my_fieldname, other_table, other_field in constraints: + relations[my_fieldname] = (other_field, other_table) + return relations + + def get_key_columns( + self, cursor: "CursorWrapper", table_name: str + ) -> List[Tuple[str, str, str]]: + """ + Return a list of (column_name, referenced_table_name, referenced_column_name) + for all key columns in the given table. + """ + key_columns: List[Any] = [] + cursor.execute( + """ + SELECT column_name, referenced_table_name, referenced_column_name + FROM information_schema.key_column_usage + WHERE table_name = %s + AND table_schema = DATABASE() + AND referenced_table_name IS NOT NULL + AND referenced_column_name IS NOT NULL""", + [table_name], + ) + key_columns.extend(cursor.fetchall()) + return key_columns + + def get_storage_engine(self, cursor: "CursorWrapper", table_name: str) -> str: + """ + Retrieve the storage engine for a given table. Return the default + storage engine if the table doesn't exist. + """ + cursor.execute( + "SELECT engine FROM information_schema.tables WHERE table_name = %s", + [table_name], + ) + result = cursor.fetchone() + # pylint: disable=protected-access + if not result: + return self.connection.features._mysql_storage_engine + # pylint: enable=protected-access + return result[0] + + def _parse_constraint_columns( + self, check_clause: Any, columns: Set[str] + ) -> OrderedSet: + check_columns: OrderedSet = OrderedSet() + statement = sqlparse.parse(check_clause)[0] + tokens = (token for token in statement.flatten() if not token.is_whitespace) + for token in tokens: + if ( + token.ttype == sqlparse.tokens.Name + and self.connection.ops.quote_name(token.value) == token.value + and token.value[1:-1] in columns + ): + check_columns.add(token.value[1:-1]) + return check_columns + + def get_constraints( + self, cursor: "CursorWrapper", table_name: str + ) -> Dict[str, Any]: + """ + Retrieve any constraints or keys (unique, pk, fk, check, index) across + one or more columns. + """ + constraints: Dict[str, Any] = {} + # Get the actual constraint names and columns + name_query = """ + SELECT kc.`constraint_name`, kc.`column_name`, + kc.`referenced_table_name`, kc.`referenced_column_name` + FROM information_schema.key_column_usage AS kc + WHERE + kc.table_schema = DATABASE() AND + kc.table_name = %s + ORDER BY kc.`ordinal_position` + """ + cursor.execute(name_query, [table_name]) + for constraint, column, ref_table, ref_column in cursor.fetchall(): + if constraint not in constraints: + constraints[constraint] = { + "columns": OrderedSet(), + "primary_key": False, + "unique": False, + "index": False, + "check": False, + "foreign_key": (ref_table, ref_column) if ref_column else None, + } + if self.connection.features.supports_index_column_ordering: + constraints[constraint]["orders"] = [] + constraints[constraint]["columns"].add(column) + # Now get the constraint types + type_query = """ + SELECT c.constraint_name, c.constraint_type + FROM information_schema.table_constraints AS c + WHERE + c.table_schema = DATABASE() AND + c.table_name = %s + """ + cursor.execute(type_query, [table_name]) + for constraint, kind in cursor.fetchall(): + if kind.lower() == "primary key": + constraints[constraint]["primary_key"] = True + constraints[constraint]["unique"] = True + elif kind.lower() == "unique": + constraints[constraint]["unique"] = True + # Add check constraints. + if self.connection.features.can_introspect_check_constraints: + unnamed_constraints_index = 0 + columns = { + info.name for info in self.get_table_description(cursor, table_name) + } + type_query = """ + SELECT cc.constraint_name, cc.check_clause + FROM + information_schema.check_constraints AS cc, + information_schema.table_constraints AS tc + WHERE + cc.constraint_schema = DATABASE() AND + tc.table_schema = cc.constraint_schema AND + cc.constraint_name = tc.constraint_name AND + tc.constraint_type = 'CHECK' AND + tc.table_name = %s + """ + cursor.execute(type_query, [table_name]) + for constraint, check_clause in cursor.fetchall(): + constraint_columns = self._parse_constraint_columns( + check_clause, columns + ) + # Ensure uniqueness of unnamed constraints. Unnamed unique + # and check columns constraints have the same name as + # a column. + if set(constraint_columns) == {constraint}: + unnamed_constraints_index += 1 + constraint = f"__unnamed_constraint_{unnamed_constraints_index}__" + constraints[constraint] = { + "columns": constraint_columns, + "primary_key": False, + "unique": False, + "index": False, + "check": True, + "foreign_key": None, + } + # Now add in the indexes + cursor.execute(f"SHOW INDEX FROM {self.connection.ops.quote_name(table_name)}") + for _, _, index, _, column, order, type_ in [ + x[:6] + (x[10],) for x in cursor.fetchall() + ]: + if index not in constraints: + constraints[index] = { + "columns": OrderedSet(), + "primary_key": False, + "unique": False, + "check": False, + "foreign_key": None, + } + if self.connection.features.supports_index_column_ordering: + constraints[index]["orders"] = [] + constraints[index]["index"] = True + constraints[index]["type"] = ( + Index.suffix if type_ == "BTREE" else type_.lower() + ) + constraints[index]["columns"].add(column) + if self.connection.features.supports_index_column_ordering: + constraints[index]["orders"].append("DESC" if order == "D" else "ASC") + # Convert the sorted sets to lists + for constraint in constraints.values(): + constraint["columns"] = list(constraint["columns"]) + return constraints diff --git a/mysql/connector/django/operations.py b/mysql/connector/django/operations.py new file mode 100644 index 0000000..d010b0b --- /dev/null +++ b/mysql/connector/django/operations.py @@ -0,0 +1,104 @@ +# Copyright (c) 2020, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# mypy: disable-error-code="override,attr-defined" + +"""Database Operations.""" + +from datetime import datetime, time +from typing import Optional + +from django.conf import settings +from django.db.backends.mysql.operations import ( + DatabaseOperations as MySQLDatabaseOperations, +) +from django.utils import timezone + +try: + from _mysql_connector import datetime_to_mysql, time_to_mysql +except ImportError: + HAVE_CEXT = False +else: + HAVE_CEXT = True + + +class DatabaseOperations(MySQLDatabaseOperations): + """Database Operations class.""" + + compiler_module = "mysql.connector.django.compiler" + + def regex_lookup(self, lookup_type: str) -> str: + """Return the string to use in a query when performing regular + expression lookup.""" + if self.connection.mysql_version < (8, 0, 0): + if lookup_type == "regex": + return "%s REGEXP BINARY %s" + return "%s REGEXP %s" + + match_option = "c" if lookup_type == "regex" else "i" + return f"REGEXP_LIKE(%s, %s, '{match_option}')" + + def adapt_datetimefield_value(self, value: Optional[datetime]) -> Optional[bytes]: + """Transform a datetime value to an object compatible with what is + expected by the backend driver for datetime columns.""" + return self.value_to_db_datetime(value) + + def value_to_db_datetime(self, value: Optional[datetime]) -> Optional[bytes]: + """Convert value to MySQL DATETIME.""" + ans: Optional[bytes] = None + if value is None: + return ans + # MySQL doesn't support tz-aware times + if timezone.is_aware(value): + if settings.USE_TZ: + value = value.astimezone(timezone.utc).replace(tzinfo=None) + else: + raise ValueError("MySQL backend does not support timezone-aware times") + if not self.connection.features.supports_microsecond_precision: + value = value.replace(microsecond=0) + if not self.connection.use_pure: + return datetime_to_mysql(value) + return self.connection.converter.to_mysql(value) + + def adapt_timefield_value(self, value: Optional[time]) -> Optional[bytes]: + """Transform a time value to an object compatible with what is expected + by the backend driver for time columns.""" + return self.value_to_db_time(value) + + def value_to_db_time(self, value: Optional[time]) -> Optional[bytes]: + """Convert value to MySQL TIME.""" + if value is None: + return None + + # MySQL doesn't support tz-aware times + if timezone.is_aware(value): + raise ValueError("MySQL backend does not support timezone-aware times") + + if not self.connection.use_pure: + return time_to_mysql(value) + return self.connection.converter.to_mysql(value) diff --git a/mysql/connector/django/schema.py b/mysql/connector/django/schema.py new file mode 100644 index 0000000..4d4f454 --- /dev/null +++ b/mysql/connector/django/schema.py @@ -0,0 +1,59 @@ +# Copyright (c) 2020, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# mypy: disable-error-code="override" + +"""Database schema editor.""" +from typing import Any + +from django.db.backends.mysql.schema import ( + DatabaseSchemaEditor as MySQLDatabaseSchemaEditor, +) + + +class DatabaseSchemaEditor(MySQLDatabaseSchemaEditor): + """This class is responsible for emitting schema-changing statements to the + databases. + """ + + def quote_value(self, value: Any) -> Any: + """Quote value.""" + self.connection.ensure_connection() + if isinstance(value, str): + value = value.replace("%", "%%") + quoted = self.connection.connection.converter.escape(value) + if isinstance(value, str) and isinstance(quoted, bytes): + quoted = quoted.decode() + return quoted + + def prepare_default(self, value: Any) -> Any: + """Implement the required abstract method. + + MySQL has requires_literal_defaults=False, therefore return the value. + """ + return value diff --git a/mysql/connector/django/validation.py b/mysql/connector/django/validation.py new file mode 100644 index 0000000..9096e1c --- /dev/null +++ b/mysql/connector/django/validation.py @@ -0,0 +1,33 @@ +# Copyright (c) 2020, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Backend specific database validation.""" + +from django.db.backends.mysql.validation import DatabaseValidation + +__all__ = ["DatabaseValidation"] diff --git a/mysql/connector/errorcode.py b/mysql/connector/errorcode.py new file mode 100644 index 0000000..39fdb1b --- /dev/null +++ b/mysql/connector/errorcode.py @@ -0,0 +1,1877 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""This module contains the MySQL Server and Client error codes.""" + +# This file was auto-generated. +_GENERATED_ON = "2021-08-11" +_MYSQL_VERSION = (8, 0, 27) + +# Start MySQL Errors +OBSOLETE_ER_HASHCHK = 1000 +OBSOLETE_ER_NISAMCHK = 1001 +ER_NO = 1002 +ER_YES = 1003 +ER_CANT_CREATE_FILE = 1004 +ER_CANT_CREATE_TABLE = 1005 +ER_CANT_CREATE_DB = 1006 +ER_DB_CREATE_EXISTS = 1007 +ER_DB_DROP_EXISTS = 1008 +OBSOLETE_ER_DB_DROP_DELETE = 1009 +ER_DB_DROP_RMDIR = 1010 +OBSOLETE_ER_CANT_DELETE_FILE = 1011 +ER_CANT_FIND_SYSTEM_REC = 1012 +ER_CANT_GET_STAT = 1013 +OBSOLETE_ER_CANT_GET_WD = 1014 +ER_CANT_LOCK = 1015 +ER_CANT_OPEN_FILE = 1016 +ER_FILE_NOT_FOUND = 1017 +ER_CANT_READ_DIR = 1018 +OBSOLETE_ER_CANT_SET_WD = 1019 +ER_CHECKREAD = 1020 +OBSOLETE_ER_DISK_FULL = 1021 +ER_DUP_KEY = 1022 +OBSOLETE_ER_ERROR_ON_CLOSE = 1023 +ER_ERROR_ON_READ = 1024 +ER_ERROR_ON_RENAME = 1025 +ER_ERROR_ON_WRITE = 1026 +ER_FILE_USED = 1027 +OBSOLETE_ER_FILSORT_ABORT = 1028 +OBSOLETE_ER_FORM_NOT_FOUND = 1029 +ER_GET_ERRNO = 1030 +ER_ILLEGAL_HA = 1031 +ER_KEY_NOT_FOUND = 1032 +ER_NOT_FORM_FILE = 1033 +ER_NOT_KEYFILE = 1034 +ER_OLD_KEYFILE = 1035 +ER_OPEN_AS_READONLY = 1036 +ER_OUTOFMEMORY = 1037 +ER_OUT_OF_SORTMEMORY = 1038 +OBSOLETE_ER_UNEXPECTED_EOF = 1039 +ER_CON_COUNT_ERROR = 1040 +ER_OUT_OF_RESOURCES = 1041 +ER_BAD_HOST_ERROR = 1042 +ER_HANDSHAKE_ERROR = 1043 +ER_DBACCESS_DENIED_ERROR = 1044 +ER_ACCESS_DENIED_ERROR = 1045 +ER_NO_DB_ERROR = 1046 +ER_UNKNOWN_COM_ERROR = 1047 +ER_BAD_NULL_ERROR = 1048 +ER_BAD_DB_ERROR = 1049 +ER_TABLE_EXISTS_ERROR = 1050 +ER_BAD_TABLE_ERROR = 1051 +ER_NON_UNIQ_ERROR = 1052 +ER_SERVER_SHUTDOWN = 1053 +ER_BAD_FIELD_ERROR = 1054 +ER_WRONG_FIELD_WITH_GROUP = 1055 +ER_WRONG_GROUP_FIELD = 1056 +ER_WRONG_SUM_SELECT = 1057 +ER_WRONG_VALUE_COUNT = 1058 +ER_TOO_LONG_IDENT = 1059 +ER_DUP_FIELDNAME = 1060 +ER_DUP_KEYNAME = 1061 +ER_DUP_ENTRY = 1062 +ER_WRONG_FIELD_SPEC = 1063 +ER_PARSE_ERROR = 1064 +ER_EMPTY_QUERY = 1065 +ER_NONUNIQ_TABLE = 1066 +ER_INVALID_DEFAULT = 1067 +ER_MULTIPLE_PRI_KEY = 1068 +ER_TOO_MANY_KEYS = 1069 +ER_TOO_MANY_KEY_PARTS = 1070 +ER_TOO_LONG_KEY = 1071 +ER_KEY_COLUMN_DOES_NOT_EXITS = 1072 +ER_BLOB_USED_AS_KEY = 1073 +ER_TOO_BIG_FIELDLENGTH = 1074 +ER_WRONG_AUTO_KEY = 1075 +ER_READY = 1076 +OBSOLETE_ER_NORMAL_SHUTDOWN = 1077 +OBSOLETE_ER_GOT_SIGNAL = 1078 +ER_SHUTDOWN_COMPLETE = 1079 +ER_FORCING_CLOSE = 1080 +ER_IPSOCK_ERROR = 1081 +ER_NO_SUCH_INDEX = 1082 +ER_WRONG_FIELD_TERMINATORS = 1083 +ER_BLOBS_AND_NO_TERMINATED = 1084 +ER_TEXTFILE_NOT_READABLE = 1085 +ER_FILE_EXISTS_ERROR = 1086 +ER_LOAD_INFO = 1087 +ER_ALTER_INFO = 1088 +ER_WRONG_SUB_KEY = 1089 +ER_CANT_REMOVE_ALL_FIELDS = 1090 +ER_CANT_DROP_FIELD_OR_KEY = 1091 +ER_INSERT_INFO = 1092 +ER_UPDATE_TABLE_USED = 1093 +ER_NO_SUCH_THREAD = 1094 +ER_KILL_DENIED_ERROR = 1095 +ER_NO_TABLES_USED = 1096 +ER_TOO_BIG_SET = 1097 +ER_NO_UNIQUE_LOGFILE = 1098 +ER_TABLE_NOT_LOCKED_FOR_WRITE = 1099 +ER_TABLE_NOT_LOCKED = 1100 +ER_BLOB_CANT_HAVE_DEFAULT = 1101 +ER_WRONG_DB_NAME = 1102 +ER_WRONG_TABLE_NAME = 1103 +ER_TOO_BIG_SELECT = 1104 +ER_UNKNOWN_ERROR = 1105 +ER_UNKNOWN_PROCEDURE = 1106 +ER_WRONG_PARAMCOUNT_TO_PROCEDURE = 1107 +ER_WRONG_PARAMETERS_TO_PROCEDURE = 1108 +ER_UNKNOWN_TABLE = 1109 +ER_FIELD_SPECIFIED_TWICE = 1110 +ER_INVALID_GROUP_FUNC_USE = 1111 +ER_UNSUPPORTED_EXTENSION = 1112 +ER_TABLE_MUST_HAVE_COLUMNS = 1113 +ER_RECORD_FILE_FULL = 1114 +ER_UNKNOWN_CHARACTER_SET = 1115 +ER_TOO_MANY_TABLES = 1116 +ER_TOO_MANY_FIELDS = 1117 +ER_TOO_BIG_ROWSIZE = 1118 +ER_STACK_OVERRUN = 1119 +ER_WRONG_OUTER_JOIN_UNUSED = 1120 +ER_NULL_COLUMN_IN_INDEX = 1121 +ER_CANT_FIND_UDF = 1122 +ER_CANT_INITIALIZE_UDF = 1123 +ER_UDF_NO_PATHS = 1124 +ER_UDF_EXISTS = 1125 +ER_CANT_OPEN_LIBRARY = 1126 +ER_CANT_FIND_DL_ENTRY = 1127 +ER_FUNCTION_NOT_DEFINED = 1128 +ER_HOST_IS_BLOCKED = 1129 +ER_HOST_NOT_PRIVILEGED = 1130 +ER_PASSWORD_ANONYMOUS_USER = 1131 +ER_PASSWORD_NOT_ALLOWED = 1132 +ER_PASSWORD_NO_MATCH = 1133 +ER_UPDATE_INFO = 1134 +ER_CANT_CREATE_THREAD = 1135 +ER_WRONG_VALUE_COUNT_ON_ROW = 1136 +ER_CANT_REOPEN_TABLE = 1137 +ER_INVALID_USE_OF_NULL = 1138 +ER_REGEXP_ERROR = 1139 +ER_MIX_OF_GROUP_FUNC_AND_FIELDS = 1140 +ER_NONEXISTING_GRANT = 1141 +ER_TABLEACCESS_DENIED_ERROR = 1142 +ER_COLUMNACCESS_DENIED_ERROR = 1143 +ER_ILLEGAL_GRANT_FOR_TABLE = 1144 +ER_GRANT_WRONG_HOST_OR_USER = 1145 +ER_NO_SUCH_TABLE = 1146 +ER_NONEXISTING_TABLE_GRANT = 1147 +ER_NOT_ALLOWED_COMMAND = 1148 +ER_SYNTAX_ERROR = 1149 +OBSOLETE_ER_UNUSED1 = 1150 +OBSOLETE_ER_UNUSED2 = 1151 +ER_ABORTING_CONNECTION = 1152 +ER_NET_PACKET_TOO_LARGE = 1153 +ER_NET_READ_ERROR_FROM_PIPE = 1154 +ER_NET_FCNTL_ERROR = 1155 +ER_NET_PACKETS_OUT_OF_ORDER = 1156 +ER_NET_UNCOMPRESS_ERROR = 1157 +ER_NET_READ_ERROR = 1158 +ER_NET_READ_INTERRUPTED = 1159 +ER_NET_ERROR_ON_WRITE = 1160 +ER_NET_WRITE_INTERRUPTED = 1161 +ER_TOO_LONG_STRING = 1162 +ER_TABLE_CANT_HANDLE_BLOB = 1163 +ER_TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164 +OBSOLETE_ER_UNUSED3 = 1165 +ER_WRONG_COLUMN_NAME = 1166 +ER_WRONG_KEY_COLUMN = 1167 +ER_WRONG_MRG_TABLE = 1168 +ER_DUP_UNIQUE = 1169 +ER_BLOB_KEY_WITHOUT_LENGTH = 1170 +ER_PRIMARY_CANT_HAVE_NULL = 1171 +ER_TOO_MANY_ROWS = 1172 +ER_REQUIRES_PRIMARY_KEY = 1173 +OBSOLETE_ER_NO_RAID_COMPILED = 1174 +ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175 +ER_KEY_DOES_NOT_EXITS = 1176 +ER_CHECK_NO_SUCH_TABLE = 1177 +ER_CHECK_NOT_IMPLEMENTED = 1178 +ER_CANT_DO_THIS_DURING_AN_TRANSACTION = 1179 +ER_ERROR_DURING_COMMIT = 1180 +ER_ERROR_DURING_ROLLBACK = 1181 +ER_ERROR_DURING_FLUSH_LOGS = 1182 +OBSOLETE_ER_ERROR_DURING_CHECKPOINT = 1183 +ER_NEW_ABORTING_CONNECTION = 1184 +OBSOLETE_ER_DUMP_NOT_IMPLEMENTED = 1185 +OBSOLETE_ER_FLUSH_MASTER_BINLOG_CLOSED = 1186 +OBSOLETE_ER_INDEX_REBUILD = 1187 +ER_MASTER = 1188 +ER_MASTER_NET_READ = 1189 +ER_MASTER_NET_WRITE = 1190 +ER_FT_MATCHING_KEY_NOT_FOUND = 1191 +ER_LOCK_OR_ACTIVE_TRANSACTION = 1192 +ER_UNKNOWN_SYSTEM_VARIABLE = 1193 +ER_CRASHED_ON_USAGE = 1194 +ER_CRASHED_ON_REPAIR = 1195 +ER_WARNING_NOT_COMPLETE_ROLLBACK = 1196 +ER_TRANS_CACHE_FULL = 1197 +OBSOLETE_ER_SLAVE_MUST_STOP = 1198 +ER_SLAVE_NOT_RUNNING = 1199 +ER_BAD_SLAVE = 1200 +ER_MASTER_INFO = 1201 +ER_SLAVE_THREAD = 1202 +ER_TOO_MANY_USER_CONNECTIONS = 1203 +ER_SET_CONSTANTS_ONLY = 1204 +ER_LOCK_WAIT_TIMEOUT = 1205 +ER_LOCK_TABLE_FULL = 1206 +ER_READ_ONLY_TRANSACTION = 1207 +OBSOLETE_ER_DROP_DB_WITH_READ_LOCK = 1208 +OBSOLETE_ER_CREATE_DB_WITH_READ_LOCK = 1209 +ER_WRONG_ARGUMENTS = 1210 +ER_NO_PERMISSION_TO_CREATE_USER = 1211 +OBSOLETE_ER_UNION_TABLES_IN_DIFFERENT_DIR = 1212 +ER_LOCK_DEADLOCK = 1213 +ER_TABLE_CANT_HANDLE_FT = 1214 +ER_CANNOT_ADD_FOREIGN = 1215 +ER_NO_REFERENCED_ROW = 1216 +ER_ROW_IS_REFERENCED = 1217 +ER_CONNECT_TO_MASTER = 1218 +OBSOLETE_ER_QUERY_ON_MASTER = 1219 +ER_ERROR_WHEN_EXECUTING_COMMAND = 1220 +ER_WRONG_USAGE = 1221 +ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222 +ER_CANT_UPDATE_WITH_READLOCK = 1223 +ER_MIXING_NOT_ALLOWED = 1224 +ER_DUP_ARGUMENT = 1225 +ER_USER_LIMIT_REACHED = 1226 +ER_SPECIFIC_ACCESS_DENIED_ERROR = 1227 +ER_LOCAL_VARIABLE = 1228 +ER_GLOBAL_VARIABLE = 1229 +ER_NO_DEFAULT = 1230 +ER_WRONG_VALUE_FOR_VAR = 1231 +ER_WRONG_TYPE_FOR_VAR = 1232 +ER_VAR_CANT_BE_READ = 1233 +ER_CANT_USE_OPTION_HERE = 1234 +ER_NOT_SUPPORTED_YET = 1235 +ER_MASTER_FATAL_ERROR_READING_BINLOG = 1236 +ER_SLAVE_IGNORED_TABLE = 1237 +ER_INCORRECT_GLOBAL_LOCAL_VAR = 1238 +ER_WRONG_FK_DEF = 1239 +ER_KEY_REF_DO_NOT_MATCH_TABLE_REF = 1240 +ER_OPERAND_COLUMNS = 1241 +ER_SUBQUERY_NO_1_ROW = 1242 +ER_UNKNOWN_STMT_HANDLER = 1243 +ER_CORRUPT_HELP_DB = 1244 +OBSOLETE_ER_CYCLIC_REFERENCE = 1245 +ER_AUTO_CONVERT = 1246 +ER_ILLEGAL_REFERENCE = 1247 +ER_DERIVED_MUST_HAVE_ALIAS = 1248 +ER_SELECT_REDUCED = 1249 +ER_TABLENAME_NOT_ALLOWED_HERE = 1250 +ER_NOT_SUPPORTED_AUTH_MODE = 1251 +ER_SPATIAL_CANT_HAVE_NULL = 1252 +ER_COLLATION_CHARSET_MISMATCH = 1253 +OBSOLETE_ER_SLAVE_WAS_RUNNING = 1254 +OBSOLETE_ER_SLAVE_WAS_NOT_RUNNING = 1255 +ER_TOO_BIG_FOR_UNCOMPRESS = 1256 +ER_ZLIB_Z_MEM_ERROR = 1257 +ER_ZLIB_Z_BUF_ERROR = 1258 +ER_ZLIB_Z_DATA_ERROR = 1259 +ER_CUT_VALUE_GROUP_CONCAT = 1260 +ER_WARN_TOO_FEW_RECORDS = 1261 +ER_WARN_TOO_MANY_RECORDS = 1262 +ER_WARN_NULL_TO_NOTNULL = 1263 +ER_WARN_DATA_OUT_OF_RANGE = 1264 +WARN_DATA_TRUNCATED = 1265 +ER_WARN_USING_OTHER_HANDLER = 1266 +ER_CANT_AGGREGATE_2COLLATIONS = 1267 +OBSOLETE_ER_DROP_USER = 1268 +ER_REVOKE_GRANTS = 1269 +ER_CANT_AGGREGATE_3COLLATIONS = 1270 +ER_CANT_AGGREGATE_NCOLLATIONS = 1271 +ER_VARIABLE_IS_NOT_STRUCT = 1272 +ER_UNKNOWN_COLLATION = 1273 +ER_SLAVE_IGNORED_SSL_PARAMS = 1274 +OBSOLETE_ER_SERVER_IS_IN_SECURE_AUTH_MODE = 1275 +ER_WARN_FIELD_RESOLVED = 1276 +ER_BAD_SLAVE_UNTIL_COND = 1277 +ER_MISSING_SKIP_SLAVE = 1278 +ER_UNTIL_COND_IGNORED = 1279 +ER_WRONG_NAME_FOR_INDEX = 1280 +ER_WRONG_NAME_FOR_CATALOG = 1281 +OBSOLETE_ER_WARN_QC_RESIZE = 1282 +ER_BAD_FT_COLUMN = 1283 +ER_UNKNOWN_KEY_CACHE = 1284 +ER_WARN_HOSTNAME_WONT_WORK = 1285 +ER_UNKNOWN_STORAGE_ENGINE = 1286 +ER_WARN_DEPRECATED_SYNTAX = 1287 +ER_NON_UPDATABLE_TABLE = 1288 +ER_FEATURE_DISABLED = 1289 +ER_OPTION_PREVENTS_STATEMENT = 1290 +ER_DUPLICATED_VALUE_IN_TYPE = 1291 +ER_TRUNCATED_WRONG_VALUE = 1292 +OBSOLETE_ER_TOO_MUCH_AUTO_TIMESTAMP_COLS = 1293 +ER_INVALID_ON_UPDATE = 1294 +ER_UNSUPPORTED_PS = 1295 +ER_GET_ERRMSG = 1296 +ER_GET_TEMPORARY_ERRMSG = 1297 +ER_UNKNOWN_TIME_ZONE = 1298 +ER_WARN_INVALID_TIMESTAMP = 1299 +ER_INVALID_CHARACTER_STRING = 1300 +ER_WARN_ALLOWED_PACKET_OVERFLOWED = 1301 +ER_CONFLICTING_DECLARATIONS = 1302 +ER_SP_NO_RECURSIVE_CREATE = 1303 +ER_SP_ALREADY_EXISTS = 1304 +ER_SP_DOES_NOT_EXIST = 1305 +ER_SP_DROP_FAILED = 1306 +ER_SP_STORE_FAILED = 1307 +ER_SP_LILABEL_MISMATCH = 1308 +ER_SP_LABEL_REDEFINE = 1309 +ER_SP_LABEL_MISMATCH = 1310 +ER_SP_UNINIT_VAR = 1311 +ER_SP_BADSELECT = 1312 +ER_SP_BADRETURN = 1313 +ER_SP_BADSTATEMENT = 1314 +ER_UPDATE_LOG_DEPRECATED_IGNORED = 1315 +ER_UPDATE_LOG_DEPRECATED_TRANSLATED = 1316 +ER_QUERY_INTERRUPTED = 1317 +ER_SP_WRONG_NO_OF_ARGS = 1318 +ER_SP_COND_MISMATCH = 1319 +ER_SP_NORETURN = 1320 +ER_SP_NORETURNEND = 1321 +ER_SP_BAD_CURSOR_QUERY = 1322 +ER_SP_BAD_CURSOR_SELECT = 1323 +ER_SP_CURSOR_MISMATCH = 1324 +ER_SP_CURSOR_ALREADY_OPEN = 1325 +ER_SP_CURSOR_NOT_OPEN = 1326 +ER_SP_UNDECLARED_VAR = 1327 +ER_SP_WRONG_NO_OF_FETCH_ARGS = 1328 +ER_SP_FETCH_NO_DATA = 1329 +ER_SP_DUP_PARAM = 1330 +ER_SP_DUP_VAR = 1331 +ER_SP_DUP_COND = 1332 +ER_SP_DUP_CURS = 1333 +ER_SP_CANT_ALTER = 1334 +ER_SP_SUBSELECT_NYI = 1335 +ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG = 1336 +ER_SP_VARCOND_AFTER_CURSHNDLR = 1337 +ER_SP_CURSOR_AFTER_HANDLER = 1338 +ER_SP_CASE_NOT_FOUND = 1339 +ER_FPARSER_TOO_BIG_FILE = 1340 +ER_FPARSER_BAD_HEADER = 1341 +ER_FPARSER_EOF_IN_COMMENT = 1342 +ER_FPARSER_ERROR_IN_PARAMETER = 1343 +ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344 +ER_VIEW_NO_EXPLAIN = 1345 +OBSOLETE_ER_FRM_UNKNOWN_TYPE = 1346 +ER_WRONG_OBJECT = 1347 +ER_NONUPDATEABLE_COLUMN = 1348 +OBSOLETE_ER_VIEW_SELECT_DERIVED_UNUSED = 1349 +ER_VIEW_SELECT_CLAUSE = 1350 +ER_VIEW_SELECT_VARIABLE = 1351 +ER_VIEW_SELECT_TMPTABLE = 1352 +ER_VIEW_WRONG_LIST = 1353 +ER_WARN_VIEW_MERGE = 1354 +ER_WARN_VIEW_WITHOUT_KEY = 1355 +ER_VIEW_INVALID = 1356 +ER_SP_NO_DROP_SP = 1357 +OBSOLETE_ER_SP_GOTO_IN_HNDLR = 1358 +ER_TRG_ALREADY_EXISTS = 1359 +ER_TRG_DOES_NOT_EXIST = 1360 +ER_TRG_ON_VIEW_OR_TEMP_TABLE = 1361 +ER_TRG_CANT_CHANGE_ROW = 1362 +ER_TRG_NO_SUCH_ROW_IN_TRG = 1363 +ER_NO_DEFAULT_FOR_FIELD = 1364 +ER_DIVISION_BY_ZERO = 1365 +ER_TRUNCATED_WRONG_VALUE_FOR_FIELD = 1366 +ER_ILLEGAL_VALUE_FOR_TYPE = 1367 +ER_VIEW_NONUPD_CHECK = 1368 +ER_VIEW_CHECK_FAILED = 1369 +ER_PROCACCESS_DENIED_ERROR = 1370 +ER_RELAY_LOG_FAIL = 1371 +OBSOLETE_ER_PASSWD_LENGTH = 1372 +ER_UNKNOWN_TARGET_BINLOG = 1373 +ER_IO_ERR_LOG_INDEX_READ = 1374 +ER_BINLOG_PURGE_PROHIBITED = 1375 +ER_FSEEK_FAIL = 1376 +ER_BINLOG_PURGE_FATAL_ERR = 1377 +ER_LOG_IN_USE = 1378 +ER_LOG_PURGE_UNKNOWN_ERR = 1379 +ER_RELAY_LOG_INIT = 1380 +ER_NO_BINARY_LOGGING = 1381 +ER_RESERVED_SYNTAX = 1382 +OBSOLETE_ER_WSAS_FAILED = 1383 +OBSOLETE_ER_DIFF_GROUPS_PROC = 1384 +OBSOLETE_ER_NO_GROUP_FOR_PROC = 1385 +OBSOLETE_ER_ORDER_WITH_PROC = 1386 +OBSOLETE_ER_LOGGING_PROHIBIT_CHANGING_OF = 1387 +OBSOLETE_ER_NO_FILE_MAPPING = 1388 +OBSOLETE_ER_WRONG_MAGIC = 1389 +ER_PS_MANY_PARAM = 1390 +ER_KEY_PART_0 = 1391 +ER_VIEW_CHECKSUM = 1392 +ER_VIEW_MULTIUPDATE = 1393 +ER_VIEW_NO_INSERT_FIELD_LIST = 1394 +ER_VIEW_DELETE_MERGE_VIEW = 1395 +ER_CANNOT_USER = 1396 +ER_XAER_NOTA = 1397 +ER_XAER_INVAL = 1398 +ER_XAER_RMFAIL = 1399 +ER_XAER_OUTSIDE = 1400 +ER_XAER_RMERR = 1401 +ER_XA_RBROLLBACK = 1402 +ER_NONEXISTING_PROC_GRANT = 1403 +ER_PROC_AUTO_GRANT_FAIL = 1404 +ER_PROC_AUTO_REVOKE_FAIL = 1405 +ER_DATA_TOO_LONG = 1406 +ER_SP_BAD_SQLSTATE = 1407 +ER_STARTUP = 1408 +ER_LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR = 1409 +ER_CANT_CREATE_USER_WITH_GRANT = 1410 +ER_WRONG_VALUE_FOR_TYPE = 1411 +ER_TABLE_DEF_CHANGED = 1412 +ER_SP_DUP_HANDLER = 1413 +ER_SP_NOT_VAR_ARG = 1414 +ER_SP_NO_RETSET = 1415 +ER_CANT_CREATE_GEOMETRY_OBJECT = 1416 +OBSOLETE_ER_FAILED_ROUTINE_BREAK_BINLOG = 1417 +ER_BINLOG_UNSAFE_ROUTINE = 1418 +ER_BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419 +OBSOLETE_ER_EXEC_STMT_WITH_OPEN_CURSOR = 1420 +ER_STMT_HAS_NO_OPEN_CURSOR = 1421 +ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422 +ER_NO_DEFAULT_FOR_VIEW_FIELD = 1423 +ER_SP_NO_RECURSION = 1424 +ER_TOO_BIG_SCALE = 1425 +ER_TOO_BIG_PRECISION = 1426 +ER_M_BIGGER_THAN_D = 1427 +ER_WRONG_LOCK_OF_SYSTEM_TABLE = 1428 +ER_CONNECT_TO_FOREIGN_DATA_SOURCE = 1429 +ER_QUERY_ON_FOREIGN_DATA_SOURCE = 1430 +ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431 +ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432 +ER_FOREIGN_DATA_STRING_INVALID = 1433 +OBSOLETE_ER_CANT_CREATE_FEDERATED_TABLE = 1434 +ER_TRG_IN_WRONG_SCHEMA = 1435 +ER_STACK_OVERRUN_NEED_MORE = 1436 +ER_TOO_LONG_BODY = 1437 +ER_WARN_CANT_DROP_DEFAULT_KEYCACHE = 1438 +ER_TOO_BIG_DISPLAYWIDTH = 1439 +ER_XAER_DUPID = 1440 +ER_DATETIME_FUNCTION_OVERFLOW = 1441 +ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG = 1442 +ER_VIEW_PREVENT_UPDATE = 1443 +ER_PS_NO_RECURSION = 1444 +ER_SP_CANT_SET_AUTOCOMMIT = 1445 +OBSOLETE_ER_MALFORMED_DEFINER = 1446 +ER_VIEW_FRM_NO_USER = 1447 +ER_VIEW_OTHER_USER = 1448 +ER_NO_SUCH_USER = 1449 +ER_FORBID_SCHEMA_CHANGE = 1450 +ER_ROW_IS_REFERENCED_2 = 1451 +ER_NO_REFERENCED_ROW_2 = 1452 +ER_SP_BAD_VAR_SHADOW = 1453 +ER_TRG_NO_DEFINER = 1454 +ER_OLD_FILE_FORMAT = 1455 +ER_SP_RECURSION_LIMIT = 1456 +OBSOLETE_ER_SP_PROC_TABLE_CORRUPT = 1457 +ER_SP_WRONG_NAME = 1458 +ER_TABLE_NEEDS_UPGRADE = 1459 +ER_SP_NO_AGGREGATE = 1460 +ER_MAX_PREPARED_STMT_COUNT_REACHED = 1461 +ER_VIEW_RECURSIVE = 1462 +ER_NON_GROUPING_FIELD_USED = 1463 +ER_TABLE_CANT_HANDLE_SPKEYS = 1464 +ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA = 1465 +ER_REMOVED_SPACES = 1466 +ER_AUTOINC_READ_FAILED = 1467 +ER_USERNAME = 1468 +ER_HOSTNAME = 1469 +ER_WRONG_STRING_LENGTH = 1470 +ER_NON_INSERTABLE_TABLE = 1471 +ER_ADMIN_WRONG_MRG_TABLE = 1472 +ER_TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT = 1473 +ER_NAME_BECOMES_EMPTY = 1474 +ER_AMBIGUOUS_FIELD_TERM = 1475 +ER_FOREIGN_SERVER_EXISTS = 1476 +ER_FOREIGN_SERVER_DOESNT_EXIST = 1477 +ER_ILLEGAL_HA_CREATE_OPTION = 1478 +ER_PARTITION_REQUIRES_VALUES_ERROR = 1479 +ER_PARTITION_WRONG_VALUES_ERROR = 1480 +ER_PARTITION_MAXVALUE_ERROR = 1481 +OBSOLETE_ER_PARTITION_SUBPARTITION_ERROR = 1482 +OBSOLETE_ER_PARTITION_SUBPART_MIX_ERROR = 1483 +ER_PARTITION_WRONG_NO_PART_ERROR = 1484 +ER_PARTITION_WRONG_NO_SUBPART_ERROR = 1485 +ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR = 1486 +OBSOLETE_ER_NO_CONST_EXPR_IN_RANGE_OR_LIST_ERROR = 1487 +ER_FIELD_NOT_FOUND_PART_ERROR = 1488 +OBSOLETE_ER_LIST_OF_FIELDS_ONLY_IN_HASH_ERROR = 1489 +ER_INCONSISTENT_PARTITION_INFO_ERROR = 1490 +ER_PARTITION_FUNC_NOT_ALLOWED_ERROR = 1491 +ER_PARTITIONS_MUST_BE_DEFINED_ERROR = 1492 +ER_RANGE_NOT_INCREASING_ERROR = 1493 +ER_INCONSISTENT_TYPE_OF_FUNCTIONS_ERROR = 1494 +ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR = 1495 +ER_PARTITION_ENTRY_ERROR = 1496 +ER_MIX_HANDLER_ERROR = 1497 +ER_PARTITION_NOT_DEFINED_ERROR = 1498 +ER_TOO_MANY_PARTITIONS_ERROR = 1499 +ER_SUBPARTITION_ERROR = 1500 +ER_CANT_CREATE_HANDLER_FILE = 1501 +ER_BLOB_FIELD_IN_PART_FUNC_ERROR = 1502 +ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF = 1503 +ER_NO_PARTS_ERROR = 1504 +ER_PARTITION_MGMT_ON_NONPARTITIONED = 1505 +ER_FOREIGN_KEY_ON_PARTITIONED = 1506 +ER_DROP_PARTITION_NON_EXISTENT = 1507 +ER_DROP_LAST_PARTITION = 1508 +ER_COALESCE_ONLY_ON_HASH_PARTITION = 1509 +ER_REORG_HASH_ONLY_ON_SAME_NO = 1510 +ER_REORG_NO_PARAM_ERROR = 1511 +ER_ONLY_ON_RANGE_LIST_PARTITION = 1512 +ER_ADD_PARTITION_SUBPART_ERROR = 1513 +ER_ADD_PARTITION_NO_NEW_PARTITION = 1514 +ER_COALESCE_PARTITION_NO_PARTITION = 1515 +ER_REORG_PARTITION_NOT_EXIST = 1516 +ER_SAME_NAME_PARTITION = 1517 +ER_NO_BINLOG_ERROR = 1518 +ER_CONSECUTIVE_REORG_PARTITIONS = 1519 +ER_REORG_OUTSIDE_RANGE = 1520 +ER_PARTITION_FUNCTION_FAILURE = 1521 +OBSOLETE_ER_PART_STATE_ERROR = 1522 +ER_LIMITED_PART_RANGE = 1523 +ER_PLUGIN_IS_NOT_LOADED = 1524 +ER_WRONG_VALUE = 1525 +ER_NO_PARTITION_FOR_GIVEN_VALUE = 1526 +ER_FILEGROUP_OPTION_ONLY_ONCE = 1527 +ER_CREATE_FILEGROUP_FAILED = 1528 +ER_DROP_FILEGROUP_FAILED = 1529 +ER_TABLESPACE_AUTO_EXTEND_ERROR = 1530 +ER_WRONG_SIZE_NUMBER = 1531 +ER_SIZE_OVERFLOW_ERROR = 1532 +ER_ALTER_FILEGROUP_FAILED = 1533 +ER_BINLOG_ROW_LOGGING_FAILED = 1534 +OBSOLETE_ER_BINLOG_ROW_WRONG_TABLE_DEF = 1535 +OBSOLETE_ER_BINLOG_ROW_RBR_TO_SBR = 1536 +ER_EVENT_ALREADY_EXISTS = 1537 +OBSOLETE_ER_EVENT_STORE_FAILED = 1538 +ER_EVENT_DOES_NOT_EXIST = 1539 +OBSOLETE_ER_EVENT_CANT_ALTER = 1540 +OBSOLETE_ER_EVENT_DROP_FAILED = 1541 +ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG = 1542 +ER_EVENT_ENDS_BEFORE_STARTS = 1543 +ER_EVENT_EXEC_TIME_IN_THE_PAST = 1544 +OBSOLETE_ER_EVENT_OPEN_TABLE_FAILED = 1545 +OBSOLETE_ER_EVENT_NEITHER_M_EXPR_NOR_M_AT = 1546 +OBSOLETE_ER_COL_COUNT_DOESNT_MATCH_CORRUPTED = 1547 +OBSOLETE_ER_CANNOT_LOAD_FROM_TABLE = 1548 +OBSOLETE_ER_EVENT_CANNOT_DELETE = 1549 +OBSOLETE_ER_EVENT_COMPILE_ERROR = 1550 +ER_EVENT_SAME_NAME = 1551 +OBSOLETE_ER_EVENT_DATA_TOO_LONG = 1552 +ER_DROP_INDEX_FK = 1553 +ER_WARN_DEPRECATED_SYNTAX_WITH_VER = 1554 +OBSOLETE_ER_CANT_WRITE_LOCK_LOG_TABLE = 1555 +ER_CANT_LOCK_LOG_TABLE = 1556 +ER_FOREIGN_DUPLICATE_KEY_OLD_UNUSED = 1557 +ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE = 1558 +OBSOLETE_ER_TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR = 1559 +ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1560 +OBSOLETE_ER_NDB_CANT_SWITCH_BINLOG_FORMAT = 1561 +ER_PARTITION_NO_TEMPORARY = 1562 +ER_PARTITION_CONST_DOMAIN_ERROR = 1563 +ER_PARTITION_FUNCTION_IS_NOT_ALLOWED = 1564 +OBSOLETE_ER_DDL_LOG_ERROR_UNUSED = 1565 +ER_NULL_IN_VALUES_LESS_THAN = 1566 +ER_WRONG_PARTITION_NAME = 1567 +ER_CANT_CHANGE_TX_CHARACTERISTICS = 1568 +ER_DUP_ENTRY_AUTOINCREMENT_CASE = 1569 +OBSOLETE_ER_EVENT_MODIFY_QUEUE_ERROR = 1570 +ER_EVENT_SET_VAR_ERROR = 1571 +ER_PARTITION_MERGE_ERROR = 1572 +OBSOLETE_ER_CANT_ACTIVATE_LOG = 1573 +OBSOLETE_ER_RBR_NOT_AVAILABLE = 1574 +ER_BASE64_DECODE_ERROR = 1575 +ER_EVENT_RECURSION_FORBIDDEN = 1576 +OBSOLETE_ER_EVENTS_DB_ERROR = 1577 +ER_ONLY_INTEGERS_ALLOWED = 1578 +ER_UNSUPORTED_LOG_ENGINE = 1579 +ER_BAD_LOG_STATEMENT = 1580 +ER_CANT_RENAME_LOG_TABLE = 1581 +ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT = 1582 +ER_WRONG_PARAMETERS_TO_NATIVE_FCT = 1583 +ER_WRONG_PARAMETERS_TO_STORED_FCT = 1584 +ER_NATIVE_FCT_NAME_COLLISION = 1585 +ER_DUP_ENTRY_WITH_KEY_NAME = 1586 +ER_BINLOG_PURGE_EMFILE = 1587 +ER_EVENT_CANNOT_CREATE_IN_THE_PAST = 1588 +ER_EVENT_CANNOT_ALTER_IN_THE_PAST = 1589 +OBSOLETE_ER_SLAVE_INCIDENT = 1590 +ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT = 1591 +ER_BINLOG_UNSAFE_STATEMENT = 1592 +ER_BINLOG_FATAL_ERROR = 1593 +OBSOLETE_ER_SLAVE_RELAY_LOG_READ_FAILURE = 1594 +OBSOLETE_ER_SLAVE_RELAY_LOG_WRITE_FAILURE = 1595 +OBSOLETE_ER_SLAVE_CREATE_EVENT_FAILURE = 1596 +OBSOLETE_ER_SLAVE_MASTER_COM_FAILURE = 1597 +ER_BINLOG_LOGGING_IMPOSSIBLE = 1598 +ER_VIEW_NO_CREATION_CTX = 1599 +ER_VIEW_INVALID_CREATION_CTX = 1600 +OBSOLETE_ER_SR_INVALID_CREATION_CTX = 1601 +ER_TRG_CORRUPTED_FILE = 1602 +ER_TRG_NO_CREATION_CTX = 1603 +ER_TRG_INVALID_CREATION_CTX = 1604 +ER_EVENT_INVALID_CREATION_CTX = 1605 +ER_TRG_CANT_OPEN_TABLE = 1606 +OBSOLETE_ER_CANT_CREATE_SROUTINE = 1607 +OBSOLETE_ER_NEVER_USED = 1608 +ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT = 1609 +ER_SLAVE_CORRUPT_EVENT = 1610 +OBSOLETE_ER_LOAD_DATA_INVALID_COLUMN_UNUSED = 1611 +ER_LOG_PURGE_NO_FILE = 1612 +ER_XA_RBTIMEOUT = 1613 +ER_XA_RBDEADLOCK = 1614 +ER_NEED_REPREPARE = 1615 +OBSOLETE_ER_DELAYED_NOT_SUPPORTED = 1616 +WARN_NO_MASTER_INFO = 1617 +WARN_OPTION_IGNORED = 1618 +ER_PLUGIN_DELETE_BUILTIN = 1619 +WARN_PLUGIN_BUSY = 1620 +ER_VARIABLE_IS_READONLY = 1621 +ER_WARN_ENGINE_TRANSACTION_ROLLBACK = 1622 +OBSOLETE_ER_SLAVE_HEARTBEAT_FAILURE = 1623 +ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE = 1624 +ER_NDB_REPLICATION_SCHEMA_ERROR = 1625 +ER_CONFLICT_FN_PARSE_ERROR = 1626 +ER_EXCEPTIONS_WRITE_ERROR = 1627 +ER_TOO_LONG_TABLE_COMMENT = 1628 +ER_TOO_LONG_FIELD_COMMENT = 1629 +ER_FUNC_INEXISTENT_NAME_COLLISION = 1630 +ER_DATABASE_NAME = 1631 +ER_TABLE_NAME = 1632 +ER_PARTITION_NAME = 1633 +ER_SUBPARTITION_NAME = 1634 +ER_TEMPORARY_NAME = 1635 +ER_RENAMED_NAME = 1636 +ER_TOO_MANY_CONCURRENT_TRXS = 1637 +WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED = 1638 +ER_DEBUG_SYNC_TIMEOUT = 1639 +ER_DEBUG_SYNC_HIT_LIMIT = 1640 +ER_DUP_SIGNAL_SET = 1641 +ER_SIGNAL_WARN = 1642 +ER_SIGNAL_NOT_FOUND = 1643 +ER_SIGNAL_EXCEPTION = 1644 +ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER = 1645 +ER_SIGNAL_BAD_CONDITION_TYPE = 1646 +WARN_COND_ITEM_TRUNCATED = 1647 +ER_COND_ITEM_TOO_LONG = 1648 +ER_UNKNOWN_LOCALE = 1649 +ER_SLAVE_IGNORE_SERVER_IDS = 1650 +OBSOLETE_ER_QUERY_CACHE_DISABLED = 1651 +ER_SAME_NAME_PARTITION_FIELD = 1652 +ER_PARTITION_COLUMN_LIST_ERROR = 1653 +ER_WRONG_TYPE_COLUMN_VALUE_ERROR = 1654 +ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR = 1655 +ER_MAXVALUE_IN_VALUES_IN = 1656 +ER_TOO_MANY_VALUES_ERROR = 1657 +ER_ROW_SINGLE_PARTITION_FIELD_ERROR = 1658 +ER_FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD = 1659 +ER_PARTITION_FIELDS_TOO_LONG = 1660 +ER_BINLOG_ROW_ENGINE_AND_STMT_ENGINE = 1661 +ER_BINLOG_ROW_MODE_AND_STMT_ENGINE = 1662 +ER_BINLOG_UNSAFE_AND_STMT_ENGINE = 1663 +ER_BINLOG_ROW_INJECTION_AND_STMT_ENGINE = 1664 +ER_BINLOG_STMT_MODE_AND_ROW_ENGINE = 1665 +ER_BINLOG_ROW_INJECTION_AND_STMT_MODE = 1666 +ER_BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1667 +ER_BINLOG_UNSAFE_LIMIT = 1668 +OBSOLETE_ER_UNUSED4 = 1669 +ER_BINLOG_UNSAFE_SYSTEM_TABLE = 1670 +ER_BINLOG_UNSAFE_AUTOINC_COLUMNS = 1671 +ER_BINLOG_UNSAFE_UDF = 1672 +ER_BINLOG_UNSAFE_SYSTEM_VARIABLE = 1673 +ER_BINLOG_UNSAFE_SYSTEM_FUNCTION = 1674 +ER_BINLOG_UNSAFE_NONTRANS_AFTER_TRANS = 1675 +ER_MESSAGE_AND_STATEMENT = 1676 +OBSOLETE_ER_SLAVE_CONVERSION_FAILED = 1677 +ER_SLAVE_CANT_CREATE_CONVERSION = 1678 +ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1679 +ER_PATH_LENGTH = 1680 +ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT = 1681 +ER_WRONG_NATIVE_TABLE_STRUCTURE = 1682 +ER_WRONG_PERFSCHEMA_USAGE = 1683 +ER_WARN_I_S_SKIPPED_TABLE = 1684 +ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1685 +ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1686 +ER_SPATIAL_MUST_HAVE_GEOM_COL = 1687 +ER_TOO_LONG_INDEX_COMMENT = 1688 +ER_LOCK_ABORTED = 1689 +ER_DATA_OUT_OF_RANGE = 1690 +OBSOLETE_ER_WRONG_SPVAR_TYPE_IN_LIMIT = 1691 +ER_BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1692 +ER_BINLOG_UNSAFE_MIXED_STATEMENT = 1693 +ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1694 +ER_STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1695 +ER_FAILED_READ_FROM_PAR_FILE = 1696 +ER_VALUES_IS_NOT_INT_TYPE_ERROR = 1697 +ER_ACCESS_DENIED_NO_PASSWORD_ERROR = 1698 +ER_SET_PASSWORD_AUTH_PLUGIN = 1699 +OBSOLETE_ER_GRANT_PLUGIN_USER_EXISTS = 1700 +ER_TRUNCATE_ILLEGAL_FK = 1701 +ER_PLUGIN_IS_PERMANENT = 1702 +ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN = 1703 +ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX = 1704 +ER_STMT_CACHE_FULL = 1705 +ER_MULTI_UPDATE_KEY_CONFLICT = 1706 +ER_TABLE_NEEDS_REBUILD = 1707 +WARN_OPTION_BELOW_LIMIT = 1708 +ER_INDEX_COLUMN_TOO_LONG = 1709 +ER_ERROR_IN_TRIGGER_BODY = 1710 +ER_ERROR_IN_UNKNOWN_TRIGGER_BODY = 1711 +ER_INDEX_CORRUPT = 1712 +ER_UNDO_RECORD_TOO_BIG = 1713 +ER_BINLOG_UNSAFE_INSERT_IGNORE_SELECT = 1714 +ER_BINLOG_UNSAFE_INSERT_SELECT_UPDATE = 1715 +ER_BINLOG_UNSAFE_REPLACE_SELECT = 1716 +ER_BINLOG_UNSAFE_CREATE_IGNORE_SELECT = 1717 +ER_BINLOG_UNSAFE_CREATE_REPLACE_SELECT = 1718 +ER_BINLOG_UNSAFE_UPDATE_IGNORE = 1719 +ER_PLUGIN_NO_UNINSTALL = 1720 +ER_PLUGIN_NO_INSTALL = 1721 +ER_BINLOG_UNSAFE_WRITE_AUTOINC_SELECT = 1722 +ER_BINLOG_UNSAFE_CREATE_SELECT_AUTOINC = 1723 +ER_BINLOG_UNSAFE_INSERT_TWO_KEYS = 1724 +ER_TABLE_IN_FK_CHECK = 1725 +ER_UNSUPPORTED_ENGINE = 1726 +ER_BINLOG_UNSAFE_AUTOINC_NOT_FIRST = 1727 +ER_CANNOT_LOAD_FROM_TABLE_V2 = 1728 +ER_MASTER_DELAY_VALUE_OUT_OF_RANGE = 1729 +ER_ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT = 1730 +ER_PARTITION_EXCHANGE_DIFFERENT_OPTION = 1731 +ER_PARTITION_EXCHANGE_PART_TABLE = 1732 +ER_PARTITION_EXCHANGE_TEMP_TABLE = 1733 +ER_PARTITION_INSTEAD_OF_SUBPARTITION = 1734 +ER_UNKNOWN_PARTITION = 1735 +ER_TABLES_DIFFERENT_METADATA = 1736 +ER_ROW_DOES_NOT_MATCH_PARTITION = 1737 +ER_BINLOG_CACHE_SIZE_GREATER_THAN_MAX = 1738 +ER_WARN_INDEX_NOT_APPLICABLE = 1739 +ER_PARTITION_EXCHANGE_FOREIGN_KEY = 1740 +OBSOLETE_ER_NO_SUCH_KEY_VALUE = 1741 +ER_RPL_INFO_DATA_TOO_LONG = 1742 +OBSOLETE_ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE = 1743 +OBSOLETE_ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE = 1744 +ER_BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX = 1745 +ER_CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT = 1746 +ER_PARTITION_CLAUSE_ON_NONPARTITIONED = 1747 +ER_ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET = 1748 +OBSOLETE_ER_NO_SUCH_PARTITION__UNUSED = 1749 +ER_CHANGE_RPL_INFO_REPOSITORY_FAILURE = 1750 +ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE = 1751 +ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE = 1752 +ER_MTS_FEATURE_IS_NOT_SUPPORTED = 1753 +ER_MTS_UPDATED_DBS_GREATER_MAX = 1754 +ER_MTS_CANT_PARALLEL = 1755 +ER_MTS_INCONSISTENT_DATA = 1756 +ER_FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING = 1757 +ER_DA_INVALID_CONDITION_NUMBER = 1758 +ER_INSECURE_PLAIN_TEXT = 1759 +ER_INSECURE_CHANGE_MASTER = 1760 +ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO = 1761 +ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO = 1762 +ER_SQLTHREAD_WITH_SECURE_SLAVE = 1763 +ER_TABLE_HAS_NO_FT = 1764 +ER_VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER = 1765 +ER_VARIABLE_NOT_SETTABLE_IN_TRANSACTION = 1766 +OBSOLETE_ER_GTID_NEXT_IS_NOT_IN_GTID_NEXT_LIST = 1767 +OBSOLETE_ER_CANT_CHANGE_GTID_NEXT_IN_TRANSACTION = 1768 +ER_SET_STATEMENT_CANNOT_INVOKE_FUNCTION = 1769 +ER_GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL = 1770 +OBSOLETE_ER_SKIPPING_LOGGED_TRANSACTION = 1771 +ER_MALFORMED_GTID_SET_SPECIFICATION = 1772 +ER_MALFORMED_GTID_SET_ENCODING = 1773 +ER_MALFORMED_GTID_SPECIFICATION = 1774 +ER_GNO_EXHAUSTED = 1775 +ER_BAD_SLAVE_AUTO_POSITION = 1776 +ER_AUTO_POSITION_REQUIRES_GTID_MODE_NOT_OFF = 1777 +ER_CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET = 1778 +ER_GTID_MODE_ON_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON = 1779 +OBSOLETE_ER_GTID_MODE_REQUIRES_BINLOG = 1780 +ER_CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF = 1781 +ER_CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON = 1782 +ER_CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF = 1783 +OBSOLETE_ER_FOUND_GTID_EVENT_WHEN_GTID_MODE_IS_OFF__UNUSED = 1784 +ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE = 1785 +ER_GTID_UNSAFE_CREATE_SELECT = 1786 +OBSOLETE_ER_GTID_UNSAFE_CREATE_DROP_TEMP_TABLE_IN_TRANSACTION = 1787 +ER_GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME = 1788 +ER_MASTER_HAS_PURGED_REQUIRED_GTIDS = 1789 +ER_CANT_SET_GTID_NEXT_WHEN_OWNING_GTID = 1790 +ER_UNKNOWN_EXPLAIN_FORMAT = 1791 +ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION = 1792 +ER_TOO_LONG_TABLE_PARTITION_COMMENT = 1793 +ER_SLAVE_CONFIGURATION = 1794 +ER_INNODB_FT_LIMIT = 1795 +ER_INNODB_NO_FT_TEMP_TABLE = 1796 +ER_INNODB_FT_WRONG_DOCID_COLUMN = 1797 +ER_INNODB_FT_WRONG_DOCID_INDEX = 1798 +ER_INNODB_ONLINE_LOG_TOO_BIG = 1799 +ER_UNKNOWN_ALTER_ALGORITHM = 1800 +ER_UNKNOWN_ALTER_LOCK = 1801 +ER_MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS = 1802 +ER_MTS_RECOVERY_FAILURE = 1803 +ER_MTS_RESET_WORKERS = 1804 +ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2 = 1805 +ER_SLAVE_SILENT_RETRY_TRANSACTION = 1806 +ER_DISCARD_FK_CHECKS_RUNNING = 1807 +ER_TABLE_SCHEMA_MISMATCH = 1808 +ER_TABLE_IN_SYSTEM_TABLESPACE = 1809 +ER_IO_READ_ERROR = 1810 +ER_IO_WRITE_ERROR = 1811 +ER_TABLESPACE_MISSING = 1812 +ER_TABLESPACE_EXISTS = 1813 +ER_TABLESPACE_DISCARDED = 1814 +ER_INTERNAL_ERROR = 1815 +ER_INNODB_IMPORT_ERROR = 1816 +ER_INNODB_INDEX_CORRUPT = 1817 +ER_INVALID_YEAR_COLUMN_LENGTH = 1818 +ER_NOT_VALID_PASSWORD = 1819 +ER_MUST_CHANGE_PASSWORD = 1820 +ER_FK_NO_INDEX_CHILD = 1821 +ER_FK_NO_INDEX_PARENT = 1822 +ER_FK_FAIL_ADD_SYSTEM = 1823 +ER_FK_CANNOT_OPEN_PARENT = 1824 +ER_FK_INCORRECT_OPTION = 1825 +ER_FK_DUP_NAME = 1826 +ER_PASSWORD_FORMAT = 1827 +ER_FK_COLUMN_CANNOT_DROP = 1828 +ER_FK_COLUMN_CANNOT_DROP_CHILD = 1829 +ER_FK_COLUMN_NOT_NULL = 1830 +ER_DUP_INDEX = 1831 +ER_FK_COLUMN_CANNOT_CHANGE = 1832 +ER_FK_COLUMN_CANNOT_CHANGE_CHILD = 1833 +OBSOLETE_ER_UNUSED5 = 1834 +ER_MALFORMED_PACKET = 1835 +ER_READ_ONLY_MODE = 1836 +ER_GTID_NEXT_TYPE_UNDEFINED_GTID = 1837 +ER_VARIABLE_NOT_SETTABLE_IN_SP = 1838 +OBSOLETE_ER_CANT_SET_GTID_PURGED_WHEN_GTID_MODE_IS_OFF = 1839 +ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY = 1840 +ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY = 1841 +ER_GTID_PURGED_WAS_CHANGED = 1842 +ER_GTID_EXECUTED_WAS_CHANGED = 1843 +ER_BINLOG_STMT_MODE_AND_NO_REPL_TABLES = 1844 +ER_ALTER_OPERATION_NOT_SUPPORTED = 1845 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON = 1846 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY = 1847 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION = 1848 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME = 1849 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE = 1850 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK = 1851 +OBSOLETE_ER_UNUSED6 = 1852 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK = 1853 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC = 1854 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS = 1855 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS = 1856 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS = 1857 +OBSOLETE_ER_SQL_REPLICA_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE = 1858 +ER_DUP_UNKNOWN_IN_INDEX = 1859 +ER_IDENT_CAUSES_TOO_LONG_PATH = 1860 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL = 1861 +ER_MUST_CHANGE_PASSWORD_LOGIN = 1862 +ER_ROW_IN_WRONG_PARTITION = 1863 +ER_MTS_EVENT_BIGGER_PENDING_JOBS_SIZE_MAX = 1864 +OBSOLETE_ER_INNODB_NO_FT_USES_PARSER = 1865 +ER_BINLOG_LOGICAL_CORRUPTION = 1866 +ER_WARN_PURGE_LOG_IN_USE = 1867 +ER_WARN_PURGE_LOG_IS_ACTIVE = 1868 +ER_AUTO_INCREMENT_CONFLICT = 1869 +WARN_ON_BLOCKHOLE_IN_RBR = 1870 +ER_SLAVE_MI_INIT_REPOSITORY = 1871 +ER_SLAVE_RLI_INIT_REPOSITORY = 1872 +ER_ACCESS_DENIED_CHANGE_USER_ERROR = 1873 +ER_INNODB_READ_ONLY = 1874 +ER_STOP_SLAVE_SQL_THREAD_TIMEOUT = 1875 +ER_STOP_SLAVE_IO_THREAD_TIMEOUT = 1876 +ER_TABLE_CORRUPT = 1877 +ER_TEMP_FILE_WRITE_FAILURE = 1878 +ER_INNODB_FT_AUX_NOT_HEX_ID = 1879 +ER_OLD_TEMPORALS_UPGRADED = 1880 +ER_INNODB_FORCED_RECOVERY = 1881 +ER_AES_INVALID_IV = 1882 +ER_PLUGIN_CANNOT_BE_UNINSTALLED = 1883 +ER_GTID_UNSAFE_BINLOG_SPLITTABLE_STATEMENT_AND_ASSIGNED_GTID = 1884 +ER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER = 1885 +ER_MISSING_KEY = 1886 +WARN_NAMED_PIPE_ACCESS_EVERYONE = 1887 +ER_FILE_CORRUPT = 3000 +ER_ERROR_ON_MASTER = 3001 +OBSOLETE_ER_INCONSISTENT_ERROR = 3002 +ER_STORAGE_ENGINE_NOT_LOADED = 3003 +ER_GET_STACKED_DA_WITHOUT_ACTIVE_HANDLER = 3004 +ER_WARN_LEGACY_SYNTAX_CONVERTED = 3005 +ER_BINLOG_UNSAFE_FULLTEXT_PLUGIN = 3006 +ER_CANNOT_DISCARD_TEMPORARY_TABLE = 3007 +ER_FK_DEPTH_EXCEEDED = 3008 +ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE_V2 = 3009 +ER_WARN_TRIGGER_DOESNT_HAVE_CREATED = 3010 +ER_REFERENCED_TRG_DOES_NOT_EXIST = 3011 +ER_EXPLAIN_NOT_SUPPORTED = 3012 +ER_INVALID_FIELD_SIZE = 3013 +ER_MISSING_HA_CREATE_OPTION = 3014 +ER_ENGINE_OUT_OF_MEMORY = 3015 +ER_PASSWORD_EXPIRE_ANONYMOUS_USER = 3016 +ER_SLAVE_SQL_THREAD_MUST_STOP = 3017 +ER_NO_FT_MATERIALIZED_SUBQUERY = 3018 +ER_INNODB_UNDO_LOG_FULL = 3019 +ER_INVALID_ARGUMENT_FOR_LOGARITHM = 3020 +ER_SLAVE_CHANNEL_IO_THREAD_MUST_STOP = 3021 +ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO = 3022 +ER_WARN_ONLY_MASTER_LOG_FILE_NO_POS = 3023 +ER_QUERY_TIMEOUT = 3024 +ER_NON_RO_SELECT_DISABLE_TIMER = 3025 +ER_DUP_LIST_ENTRY = 3026 +OBSOLETE_ER_SQL_MODE_NO_EFFECT = 3027 +ER_AGGREGATE_ORDER_FOR_UNION = 3028 +ER_AGGREGATE_ORDER_NON_AGG_QUERY = 3029 +ER_SLAVE_WORKER_STOPPED_PREVIOUS_THD_ERROR = 3030 +ER_DONT_SUPPORT_REPLICA_PRESERVE_COMMIT_ORDER = 3031 +ER_SERVER_OFFLINE_MODE = 3032 +ER_GIS_DIFFERENT_SRIDS = 3033 +ER_GIS_UNSUPPORTED_ARGUMENT = 3034 +ER_GIS_UNKNOWN_ERROR = 3035 +ER_GIS_UNKNOWN_EXCEPTION = 3036 +ER_GIS_INVALID_DATA = 3037 +ER_BOOST_GEOMETRY_EMPTY_INPUT_EXCEPTION = 3038 +ER_BOOST_GEOMETRY_CENTROID_EXCEPTION = 3039 +ER_BOOST_GEOMETRY_OVERLAY_INVALID_INPUT_EXCEPTION = 3040 +ER_BOOST_GEOMETRY_TURN_INFO_EXCEPTION = 3041 +ER_BOOST_GEOMETRY_SELF_INTERSECTION_POINT_EXCEPTION = 3042 +ER_BOOST_GEOMETRY_UNKNOWN_EXCEPTION = 3043 +ER_STD_BAD_ALLOC_ERROR = 3044 +ER_STD_DOMAIN_ERROR = 3045 +ER_STD_LENGTH_ERROR = 3046 +ER_STD_INVALID_ARGUMENT = 3047 +ER_STD_OUT_OF_RANGE_ERROR = 3048 +ER_STD_OVERFLOW_ERROR = 3049 +ER_STD_RANGE_ERROR = 3050 +ER_STD_UNDERFLOW_ERROR = 3051 +ER_STD_LOGIC_ERROR = 3052 +ER_STD_RUNTIME_ERROR = 3053 +ER_STD_UNKNOWN_EXCEPTION = 3054 +ER_GIS_DATA_WRONG_ENDIANESS = 3055 +ER_CHANGE_MASTER_PASSWORD_LENGTH = 3056 +ER_USER_LOCK_WRONG_NAME = 3057 +ER_USER_LOCK_DEADLOCK = 3058 +ER_REPLACE_INACCESSIBLE_ROWS = 3059 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS = 3060 +ER_ILLEGAL_USER_VAR = 3061 +ER_GTID_MODE_OFF = 3062 +OBSOLETE_ER_UNSUPPORTED_BY_REPLICATION_THREAD = 3063 +ER_INCORRECT_TYPE = 3064 +ER_FIELD_IN_ORDER_NOT_SELECT = 3065 +ER_AGGREGATE_IN_ORDER_NOT_SELECT = 3066 +ER_INVALID_RPL_WILD_TABLE_FILTER_PATTERN = 3067 +ER_NET_OK_PACKET_TOO_LARGE = 3068 +ER_INVALID_JSON_DATA = 3069 +ER_INVALID_GEOJSON_MISSING_MEMBER = 3070 +ER_INVALID_GEOJSON_WRONG_TYPE = 3071 +ER_INVALID_GEOJSON_UNSPECIFIED = 3072 +ER_DIMENSION_UNSUPPORTED = 3073 +ER_SLAVE_CHANNEL_DOES_NOT_EXIST = 3074 +OBSOLETE_ER_SLAVE_MULTIPLE_CHANNELS_HOST_PORT = 3075 +ER_SLAVE_CHANNEL_NAME_INVALID_OR_TOO_LONG = 3076 +ER_SLAVE_NEW_CHANNEL_WRONG_REPOSITORY = 3077 +OBSOLETE_ER_SLAVE_CHANNEL_DELETE = 3078 +ER_SLAVE_MULTIPLE_CHANNELS_CMD = 3079 +ER_SLAVE_MAX_CHANNELS_EXCEEDED = 3080 +ER_SLAVE_CHANNEL_MUST_STOP = 3081 +ER_SLAVE_CHANNEL_NOT_RUNNING = 3082 +ER_SLAVE_CHANNEL_WAS_RUNNING = 3083 +ER_SLAVE_CHANNEL_WAS_NOT_RUNNING = 3084 +ER_SLAVE_CHANNEL_SQL_THREAD_MUST_STOP = 3085 +ER_SLAVE_CHANNEL_SQL_SKIP_COUNTER = 3086 +ER_WRONG_FIELD_WITH_GROUP_V2 = 3087 +ER_MIX_OF_GROUP_FUNC_AND_FIELDS_V2 = 3088 +ER_WARN_DEPRECATED_SYSVAR_UPDATE = 3089 +ER_WARN_DEPRECATED_SQLMODE = 3090 +ER_CANNOT_LOG_PARTIAL_DROP_DATABASE_WITH_GTID = 3091 +ER_GROUP_REPLICATION_CONFIGURATION = 3092 +ER_GROUP_REPLICATION_RUNNING = 3093 +ER_GROUP_REPLICATION_APPLIER_INIT_ERROR = 3094 +ER_GROUP_REPLICATION_STOP_APPLIER_THREAD_TIMEOUT = 3095 +ER_GROUP_REPLICATION_COMMUNICATION_LAYER_SESSION_ERROR = 3096 +ER_GROUP_REPLICATION_COMMUNICATION_LAYER_JOIN_ERROR = 3097 +ER_BEFORE_DML_VALIDATION_ERROR = 3098 +ER_PREVENTS_VARIABLE_WITHOUT_RBR = 3099 +ER_RUN_HOOK_ERROR = 3100 +ER_TRANSACTION_ROLLBACK_DURING_COMMIT = 3101 +ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED = 3102 +ER_UNSUPPORTED_ALTER_INPLACE_ON_VIRTUAL_COLUMN = 3103 +ER_WRONG_FK_OPTION_FOR_GENERATED_COLUMN = 3104 +ER_NON_DEFAULT_VALUE_FOR_GENERATED_COLUMN = 3105 +ER_UNSUPPORTED_ACTION_ON_GENERATED_COLUMN = 3106 +ER_GENERATED_COLUMN_NON_PRIOR = 3107 +ER_DEPENDENT_BY_GENERATED_COLUMN = 3108 +ER_GENERATED_COLUMN_REF_AUTO_INC = 3109 +ER_FEATURE_NOT_AVAILABLE = 3110 +ER_CANT_SET_GTID_MODE = 3111 +ER_CANT_USE_AUTO_POSITION_WITH_GTID_MODE_OFF = 3112 +OBSOLETE_ER_CANT_REPLICATE_ANONYMOUS_WITH_AUTO_POSITION = 3113 +OBSOLETE_ER_CANT_REPLICATE_ANONYMOUS_WITH_GTID_MODE_ON = 3114 +OBSOLETE_ER_CANT_REPLICATE_GTID_WITH_GTID_MODE_OFF = 3115 +ER_CANT_ENFORCE_GTID_CONSISTENCY_WITH_ONGOING_GTID_VIOLATING_TX = 3116 +ER_ENFORCE_GTID_CONSISTENCY_WARN_WITH_ONGOING_GTID_VIOLATING_TX = 3117 +ER_ACCOUNT_HAS_BEEN_LOCKED = 3118 +ER_WRONG_TABLESPACE_NAME = 3119 +ER_TABLESPACE_IS_NOT_EMPTY = 3120 +ER_WRONG_FILE_NAME = 3121 +ER_BOOST_GEOMETRY_INCONSISTENT_TURNS_EXCEPTION = 3122 +ER_WARN_OPTIMIZER_HINT_SYNTAX_ERROR = 3123 +ER_WARN_BAD_MAX_EXECUTION_TIME = 3124 +ER_WARN_UNSUPPORTED_MAX_EXECUTION_TIME = 3125 +ER_WARN_CONFLICTING_HINT = 3126 +ER_WARN_UNKNOWN_QB_NAME = 3127 +ER_UNRESOLVED_HINT_NAME = 3128 +ER_WARN_ON_MODIFYING_GTID_EXECUTED_TABLE = 3129 +ER_PLUGGABLE_PROTOCOL_COMMAND_NOT_SUPPORTED = 3130 +ER_LOCKING_SERVICE_WRONG_NAME = 3131 +ER_LOCKING_SERVICE_DEADLOCK = 3132 +ER_LOCKING_SERVICE_TIMEOUT = 3133 +ER_GIS_MAX_POINTS_IN_GEOMETRY_OVERFLOWED = 3134 +ER_SQL_MODE_MERGED = 3135 +ER_VTOKEN_PLUGIN_TOKEN_MISMATCH = 3136 +ER_VTOKEN_PLUGIN_TOKEN_NOT_FOUND = 3137 +ER_CANT_SET_VARIABLE_WHEN_OWNING_GTID = 3138 +ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED = 3139 +ER_INVALID_JSON_TEXT = 3140 +ER_INVALID_JSON_TEXT_IN_PARAM = 3141 +ER_INVALID_JSON_BINARY_DATA = 3142 +ER_INVALID_JSON_PATH = 3143 +ER_INVALID_JSON_CHARSET = 3144 +ER_INVALID_JSON_CHARSET_IN_FUNCTION = 3145 +ER_INVALID_TYPE_FOR_JSON = 3146 +ER_INVALID_CAST_TO_JSON = 3147 +ER_INVALID_JSON_PATH_CHARSET = 3148 +ER_INVALID_JSON_PATH_WILDCARD = 3149 +ER_JSON_VALUE_TOO_BIG = 3150 +ER_JSON_KEY_TOO_BIG = 3151 +ER_JSON_USED_AS_KEY = 3152 +ER_JSON_VACUOUS_PATH = 3153 +ER_JSON_BAD_ONE_OR_ALL_ARG = 3154 +ER_NUMERIC_JSON_VALUE_OUT_OF_RANGE = 3155 +ER_INVALID_JSON_VALUE_FOR_CAST = 3156 +ER_JSON_DOCUMENT_TOO_DEEP = 3157 +ER_JSON_DOCUMENT_NULL_KEY = 3158 +ER_SECURE_TRANSPORT_REQUIRED = 3159 +ER_NO_SECURE_TRANSPORTS_CONFIGURED = 3160 +ER_DISABLED_STORAGE_ENGINE = 3161 +ER_USER_DOES_NOT_EXIST = 3162 +ER_USER_ALREADY_EXISTS = 3163 +ER_AUDIT_API_ABORT = 3164 +ER_INVALID_JSON_PATH_ARRAY_CELL = 3165 +ER_BUFPOOL_RESIZE_INPROGRESS = 3166 +ER_FEATURE_DISABLED_SEE_DOC = 3167 +ER_SERVER_ISNT_AVAILABLE = 3168 +ER_SESSION_WAS_KILLED = 3169 +ER_CAPACITY_EXCEEDED = 3170 +ER_CAPACITY_EXCEEDED_IN_RANGE_OPTIMIZER = 3171 +OBSOLETE_ER_TABLE_NEEDS_UPG_PART = 3172 +ER_CANT_WAIT_FOR_EXECUTED_GTID_SET_WHILE_OWNING_A_GTID = 3173 +ER_CANNOT_ADD_FOREIGN_BASE_COL_VIRTUAL = 3174 +ER_CANNOT_CREATE_VIRTUAL_INDEX_CONSTRAINT = 3175 +ER_ERROR_ON_MODIFYING_GTID_EXECUTED_TABLE = 3176 +ER_LOCK_REFUSED_BY_ENGINE = 3177 +ER_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN = 3178 +ER_MASTER_KEY_ROTATION_NOT_SUPPORTED_BY_SE = 3179 +OBSOLETE_ER_MASTER_KEY_ROTATION_ERROR_BY_SE = 3180 +ER_MASTER_KEY_ROTATION_BINLOG_FAILED = 3181 +ER_MASTER_KEY_ROTATION_SE_UNAVAILABLE = 3182 +ER_TABLESPACE_CANNOT_ENCRYPT = 3183 +ER_INVALID_ENCRYPTION_OPTION = 3184 +ER_CANNOT_FIND_KEY_IN_KEYRING = 3185 +ER_CAPACITY_EXCEEDED_IN_PARSER = 3186 +ER_UNSUPPORTED_ALTER_ENCRYPTION_INPLACE = 3187 +ER_KEYRING_UDF_KEYRING_SERVICE_ERROR = 3188 +ER_USER_COLUMN_OLD_LENGTH = 3189 +ER_CANT_RESET_MASTER = 3190 +ER_GROUP_REPLICATION_MAX_GROUP_SIZE = 3191 +ER_CANNOT_ADD_FOREIGN_BASE_COL_STORED = 3192 +ER_TABLE_REFERENCED = 3193 +OBSOLETE_ER_PARTITION_ENGINE_DEPRECATED_FOR_TABLE = 3194 +OBSOLETE_ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID_ZERO = 3195 +OBSOLETE_ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID = 3196 +ER_XA_RETRY = 3197 +ER_KEYRING_AWS_UDF_AWS_KMS_ERROR = 3198 +ER_BINLOG_UNSAFE_XA = 3199 +ER_UDF_ERROR = 3200 +ER_KEYRING_MIGRATION_FAILURE = 3201 +ER_KEYRING_ACCESS_DENIED_ERROR = 3202 +ER_KEYRING_MIGRATION_STATUS = 3203 +OBSOLETE_ER_PLUGIN_FAILED_TO_OPEN_TABLES = 3204 +OBSOLETE_ER_PLUGIN_FAILED_TO_OPEN_TABLE = 3205 +OBSOLETE_ER_AUDIT_LOG_NO_KEYRING_PLUGIN_INSTALLED = 3206 +OBSOLETE_ER_AUDIT_LOG_ENCRYPTION_PASSWORD_HAS_NOT_BEEN_SET = 3207 +OBSOLETE_ER_AUDIT_LOG_COULD_NOT_CREATE_AES_KEY = 3208 +OBSOLETE_ER_AUDIT_LOG_ENCRYPTION_PASSWORD_CANNOT_BE_FETCHED = 3209 +OBSOLETE_ER_AUDIT_LOG_JSON_FILTERING_NOT_ENABLED = 3210 +OBSOLETE_ER_AUDIT_LOG_UDF_INSUFFICIENT_PRIVILEGE = 3211 +OBSOLETE_ER_AUDIT_LOG_SUPER_PRIVILEGE_REQUIRED = 3212 +OBSOLETE_ER_COULD_NOT_REINITIALIZE_AUDIT_LOG_FILTERS = 3213 +OBSOLETE_ER_AUDIT_LOG_UDF_INVALID_ARGUMENT_TYPE = 3214 +OBSOLETE_ER_AUDIT_LOG_UDF_INVALID_ARGUMENT_COUNT = 3215 +OBSOLETE_ER_AUDIT_LOG_HAS_NOT_BEEN_INSTALLED = 3216 +OBSOLETE_ER_AUDIT_LOG_UDF_READ_INVALID_MAX_ARRAY_LENGTH_ARG_TYPE = 3217 +ER_AUDIT_LOG_UDF_READ_INVALID_MAX_ARRAY_LENGTH_ARG_VALUE = 3218 +OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_PARSING_ERROR = 3219 +OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_NAME_CANNOT_BE_EMPTY = 3220 +OBSOLETE_ER_AUDIT_LOG_JSON_USER_NAME_CANNOT_BE_EMPTY = 3221 +OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_DOES_NOT_EXISTS = 3222 +OBSOLETE_ER_AUDIT_LOG_USER_FIRST_CHARACTER_MUST_BE_ALPHANUMERIC = 3223 +OBSOLETE_ER_AUDIT_LOG_USER_NAME_INVALID_CHARACTER = 3224 +OBSOLETE_ER_AUDIT_LOG_HOST_NAME_INVALID_CHARACTER = 3225 +OBSOLETE_ER_XA_REPLICATION_FILTERS = 3226 +OBSOLETE_ER_CANT_OPEN_ERROR_LOG = 3227 +OBSOLETE_ER_GROUPING_ON_TIMESTAMP_IN_DST = 3228 +OBSOLETE_ER_CANT_START_SERVER_NAMED_PIPE = 3229 +ER_WRITE_SET_EXCEEDS_LIMIT = 3230 +ER_UNSUPPORT_COMPRESSED_TEMPORARY_TABLE = 3500 +ER_ACL_OPERATION_FAILED = 3501 +ER_UNSUPPORTED_INDEX_ALGORITHM = 3502 +ER_NO_SUCH_DB = 3503 +ER_TOO_BIG_ENUM = 3504 +ER_TOO_LONG_SET_ENUM_VALUE = 3505 +ER_INVALID_DD_OBJECT = 3506 +ER_UPDATING_DD_TABLE = 3507 +ER_INVALID_DD_OBJECT_ID = 3508 +ER_INVALID_DD_OBJECT_NAME = 3509 +ER_TABLESPACE_MISSING_WITH_NAME = 3510 +ER_TOO_LONG_ROUTINE_COMMENT = 3511 +ER_SP_LOAD_FAILED = 3512 +ER_INVALID_BITWISE_OPERANDS_SIZE = 3513 +ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE = 3514 +ER_WARN_UNSUPPORTED_HINT = 3515 +ER_UNEXPECTED_GEOMETRY_TYPE = 3516 +ER_SRS_PARSE_ERROR = 3517 +ER_SRS_PROJ_PARAMETER_MISSING = 3518 +ER_WARN_SRS_NOT_FOUND = 3519 +ER_SRS_NOT_CARTESIAN = 3520 +ER_SRS_NOT_CARTESIAN_UNDEFINED = 3521 +ER_PK_INDEX_CANT_BE_INVISIBLE = 3522 +ER_UNKNOWN_AUTHID = 3523 +ER_FAILED_ROLE_GRANT = 3524 +ER_OPEN_ROLE_TABLES = 3525 +ER_FAILED_DEFAULT_ROLES = 3526 +ER_COMPONENTS_NO_SCHEME = 3527 +ER_COMPONENTS_NO_SCHEME_SERVICE = 3528 +ER_COMPONENTS_CANT_LOAD = 3529 +ER_ROLE_NOT_GRANTED = 3530 +ER_FAILED_REVOKE_ROLE = 3531 +ER_RENAME_ROLE = 3532 +ER_COMPONENTS_CANT_ACQUIRE_SERVICE_IMPLEMENTATION = 3533 +ER_COMPONENTS_CANT_SATISFY_DEPENDENCY = 3534 +ER_COMPONENTS_LOAD_CANT_REGISTER_SERVICE_IMPLEMENTATION = 3535 +ER_COMPONENTS_LOAD_CANT_INITIALIZE = 3536 +ER_COMPONENTS_UNLOAD_NOT_LOADED = 3537 +ER_COMPONENTS_UNLOAD_CANT_DEINITIALIZE = 3538 +ER_COMPONENTS_CANT_RELEASE_SERVICE = 3539 +ER_COMPONENTS_UNLOAD_CANT_UNREGISTER_SERVICE = 3540 +ER_COMPONENTS_CANT_UNLOAD = 3541 +ER_WARN_UNLOAD_THE_NOT_PERSISTED = 3542 +ER_COMPONENT_TABLE_INCORRECT = 3543 +ER_COMPONENT_MANIPULATE_ROW_FAILED = 3544 +ER_COMPONENTS_UNLOAD_DUPLICATE_IN_GROUP = 3545 +ER_CANT_SET_GTID_PURGED_DUE_SETS_CONSTRAINTS = 3546 +ER_CANNOT_LOCK_USER_MANAGEMENT_CACHES = 3547 +ER_SRS_NOT_FOUND = 3548 +ER_VARIABLE_NOT_PERSISTED = 3549 +ER_IS_QUERY_INVALID_CLAUSE = 3550 +ER_UNABLE_TO_STORE_STATISTICS = 3551 +ER_NO_SYSTEM_SCHEMA_ACCESS = 3552 +ER_NO_SYSTEM_TABLESPACE_ACCESS = 3553 +ER_NO_SYSTEM_TABLE_ACCESS = 3554 +ER_NO_SYSTEM_TABLE_ACCESS_FOR_DICTIONARY_TABLE = 3555 +ER_NO_SYSTEM_TABLE_ACCESS_FOR_SYSTEM_TABLE = 3556 +ER_NO_SYSTEM_TABLE_ACCESS_FOR_TABLE = 3557 +ER_INVALID_OPTION_KEY = 3558 +ER_INVALID_OPTION_VALUE = 3559 +ER_INVALID_OPTION_KEY_VALUE_PAIR = 3560 +ER_INVALID_OPTION_START_CHARACTER = 3561 +ER_INVALID_OPTION_END_CHARACTER = 3562 +ER_INVALID_OPTION_CHARACTERS = 3563 +ER_DUPLICATE_OPTION_KEY = 3564 +ER_WARN_SRS_NOT_FOUND_AXIS_ORDER = 3565 +ER_NO_ACCESS_TO_NATIVE_FCT = 3566 +ER_RESET_MASTER_TO_VALUE_OUT_OF_RANGE = 3567 +ER_UNRESOLVED_TABLE_LOCK = 3568 +ER_DUPLICATE_TABLE_LOCK = 3569 +ER_BINLOG_UNSAFE_SKIP_LOCKED = 3570 +ER_BINLOG_UNSAFE_NOWAIT = 3571 +ER_LOCK_NOWAIT = 3572 +ER_CTE_RECURSIVE_REQUIRES_UNION = 3573 +ER_CTE_RECURSIVE_REQUIRES_NONRECURSIVE_FIRST = 3574 +ER_CTE_RECURSIVE_FORBIDS_AGGREGATION = 3575 +ER_CTE_RECURSIVE_FORBIDDEN_JOIN_ORDER = 3576 +ER_CTE_RECURSIVE_REQUIRES_SINGLE_REFERENCE = 3577 +ER_SWITCH_TMP_ENGINE = 3578 +ER_WINDOW_NO_SUCH_WINDOW = 3579 +ER_WINDOW_CIRCULARITY_IN_WINDOW_GRAPH = 3580 +ER_WINDOW_NO_CHILD_PARTITIONING = 3581 +ER_WINDOW_NO_INHERIT_FRAME = 3582 +ER_WINDOW_NO_REDEFINE_ORDER_BY = 3583 +ER_WINDOW_FRAME_START_ILLEGAL = 3584 +ER_WINDOW_FRAME_END_ILLEGAL = 3585 +ER_WINDOW_FRAME_ILLEGAL = 3586 +ER_WINDOW_RANGE_FRAME_ORDER_TYPE = 3587 +ER_WINDOW_RANGE_FRAME_TEMPORAL_TYPE = 3588 +ER_WINDOW_RANGE_FRAME_NUMERIC_TYPE = 3589 +ER_WINDOW_RANGE_BOUND_NOT_CONSTANT = 3590 +ER_WINDOW_DUPLICATE_NAME = 3591 +ER_WINDOW_ILLEGAL_ORDER_BY = 3592 +ER_WINDOW_INVALID_WINDOW_FUNC_USE = 3593 +ER_WINDOW_INVALID_WINDOW_FUNC_ALIAS_USE = 3594 +ER_WINDOW_NESTED_WINDOW_FUNC_USE_IN_WINDOW_SPEC = 3595 +ER_WINDOW_ROWS_INTERVAL_USE = 3596 +ER_WINDOW_NO_GROUP_ORDER_UNUSED = 3597 +ER_WINDOW_EXPLAIN_JSON = 3598 +ER_WINDOW_FUNCTION_IGNORES_FRAME = 3599 +ER_WL9236_NOW_UNUSED = 3600 +ER_INVALID_NO_OF_ARGS = 3601 +ER_FIELD_IN_GROUPING_NOT_GROUP_BY = 3602 +ER_TOO_LONG_TABLESPACE_COMMENT = 3603 +ER_ENGINE_CANT_DROP_TABLE = 3604 +ER_ENGINE_CANT_DROP_MISSING_TABLE = 3605 +ER_TABLESPACE_DUP_FILENAME = 3606 +ER_DB_DROP_RMDIR2 = 3607 +ER_IMP_NO_FILES_MATCHED = 3608 +ER_IMP_SCHEMA_DOES_NOT_EXIST = 3609 +ER_IMP_TABLE_ALREADY_EXISTS = 3610 +ER_IMP_INCOMPATIBLE_MYSQLD_VERSION = 3611 +ER_IMP_INCOMPATIBLE_DD_VERSION = 3612 +ER_IMP_INCOMPATIBLE_SDI_VERSION = 3613 +ER_WARN_INVALID_HINT = 3614 +ER_VAR_DOES_NOT_EXIST = 3615 +ER_LONGITUDE_OUT_OF_RANGE = 3616 +ER_LATITUDE_OUT_OF_RANGE = 3617 +ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS = 3618 +ER_ILLEGAL_PRIVILEGE_LEVEL = 3619 +ER_NO_SYSTEM_VIEW_ACCESS = 3620 +ER_COMPONENT_FILTER_FLABBERGASTED = 3621 +ER_PART_EXPR_TOO_LONG = 3622 +ER_UDF_DROP_DYNAMICALLY_REGISTERED = 3623 +ER_UNABLE_TO_STORE_COLUMN_STATISTICS = 3624 +ER_UNABLE_TO_UPDATE_COLUMN_STATISTICS = 3625 +ER_UNABLE_TO_DROP_COLUMN_STATISTICS = 3626 +ER_UNABLE_TO_BUILD_HISTOGRAM = 3627 +ER_MANDATORY_ROLE = 3628 +ER_MISSING_TABLESPACE_FILE = 3629 +ER_PERSIST_ONLY_ACCESS_DENIED_ERROR = 3630 +ER_CMD_NEED_SUPER = 3631 +ER_PATH_IN_DATADIR = 3632 +ER_CLONE_DDL_IN_PROGRESS = 3633 +ER_CLONE_TOO_MANY_CONCURRENT_CLONES = 3634 +ER_APPLIER_LOG_EVENT_VALIDATION_ERROR = 3635 +ER_CTE_MAX_RECURSION_DEPTH = 3636 +ER_NOT_HINT_UPDATABLE_VARIABLE = 3637 +ER_CREDENTIALS_CONTRADICT_TO_HISTORY = 3638 +ER_WARNING_PASSWORD_HISTORY_CLAUSES_VOID = 3639 +ER_CLIENT_DOES_NOT_SUPPORT = 3640 +ER_I_S_SKIPPED_TABLESPACE = 3641 +ER_TABLESPACE_ENGINE_MISMATCH = 3642 +ER_WRONG_SRID_FOR_COLUMN = 3643 +ER_CANNOT_ALTER_SRID_DUE_TO_INDEX = 3644 +ER_WARN_BINLOG_PARTIAL_UPDATES_DISABLED = 3645 +ER_WARN_BINLOG_V1_ROW_EVENTS_DISABLED = 3646 +ER_WARN_BINLOG_PARTIAL_UPDATES_SUGGESTS_PARTIAL_IMAGES = 3647 +ER_COULD_NOT_APPLY_JSON_DIFF = 3648 +ER_CORRUPTED_JSON_DIFF = 3649 +ER_RESOURCE_GROUP_EXISTS = 3650 +ER_RESOURCE_GROUP_NOT_EXISTS = 3651 +ER_INVALID_VCPU_ID = 3652 +ER_INVALID_VCPU_RANGE = 3653 +ER_INVALID_THREAD_PRIORITY = 3654 +ER_DISALLOWED_OPERATION = 3655 +ER_RESOURCE_GROUP_BUSY = 3656 +ER_RESOURCE_GROUP_DISABLED = 3657 +ER_FEATURE_UNSUPPORTED = 3658 +ER_ATTRIBUTE_IGNORED = 3659 +ER_INVALID_THREAD_ID = 3660 +ER_RESOURCE_GROUP_BIND_FAILED = 3661 +ER_INVALID_USE_OF_FORCE_OPTION = 3662 +ER_GROUP_REPLICATION_COMMAND_FAILURE = 3663 +ER_SDI_OPERATION_FAILED = 3664 +ER_MISSING_JSON_TABLE_VALUE = 3665 +ER_WRONG_JSON_TABLE_VALUE = 3666 +ER_TF_MUST_HAVE_ALIAS = 3667 +ER_TF_FORBIDDEN_JOIN_TYPE = 3668 +ER_JT_VALUE_OUT_OF_RANGE = 3669 +ER_JT_MAX_NESTED_PATH = 3670 +ER_PASSWORD_EXPIRATION_NOT_SUPPORTED_BY_AUTH_METHOD = 3671 +ER_INVALID_GEOJSON_CRS_NOT_TOP_LEVEL = 3672 +ER_BAD_NULL_ERROR_NOT_IGNORED = 3673 +WARN_USELESS_SPATIAL_INDEX = 3674 +ER_DISK_FULL_NOWAIT = 3675 +ER_PARSE_ERROR_IN_DIGEST_FN = 3676 +ER_UNDISCLOSED_PARSE_ERROR_IN_DIGEST_FN = 3677 +ER_SCHEMA_DIR_EXISTS = 3678 +ER_SCHEMA_DIR_MISSING = 3679 +ER_SCHEMA_DIR_CREATE_FAILED = 3680 +ER_SCHEMA_DIR_UNKNOWN = 3681 +ER_ONLY_IMPLEMENTED_FOR_SRID_0_AND_4326 = 3682 +ER_BINLOG_EXPIRE_LOG_DAYS_AND_SECS_USED_TOGETHER = 3683 +ER_REGEXP_BUFFER_OVERFLOW = 3684 +ER_REGEXP_ILLEGAL_ARGUMENT = 3685 +ER_REGEXP_INDEX_OUTOFBOUNDS_ERROR = 3686 +ER_REGEXP_INTERNAL_ERROR = 3687 +ER_REGEXP_RULE_SYNTAX = 3688 +ER_REGEXP_BAD_ESCAPE_SEQUENCE = 3689 +ER_REGEXP_UNIMPLEMENTED = 3690 +ER_REGEXP_MISMATCHED_PAREN = 3691 +ER_REGEXP_BAD_INTERVAL = 3692 +ER_REGEXP_MAX_LT_MIN = 3693 +ER_REGEXP_INVALID_BACK_REF = 3694 +ER_REGEXP_LOOK_BEHIND_LIMIT = 3695 +ER_REGEXP_MISSING_CLOSE_BRACKET = 3696 +ER_REGEXP_INVALID_RANGE = 3697 +ER_REGEXP_STACK_OVERFLOW = 3698 +ER_REGEXP_TIME_OUT = 3699 +ER_REGEXP_PATTERN_TOO_BIG = 3700 +ER_CANT_SET_ERROR_LOG_SERVICE = 3701 +ER_EMPTY_PIPELINE_FOR_ERROR_LOG_SERVICE = 3702 +ER_COMPONENT_FILTER_DIAGNOSTICS = 3703 +ER_NOT_IMPLEMENTED_FOR_CARTESIAN_SRS = 3704 +ER_NOT_IMPLEMENTED_FOR_PROJECTED_SRS = 3705 +ER_NONPOSITIVE_RADIUS = 3706 +ER_RESTART_SERVER_FAILED = 3707 +ER_SRS_MISSING_MANDATORY_ATTRIBUTE = 3708 +ER_SRS_MULTIPLE_ATTRIBUTE_DEFINITIONS = 3709 +ER_SRS_NAME_CANT_BE_EMPTY_OR_WHITESPACE = 3710 +ER_SRS_ORGANIZATION_CANT_BE_EMPTY_OR_WHITESPACE = 3711 +ER_SRS_ID_ALREADY_EXISTS = 3712 +ER_WARN_SRS_ID_ALREADY_EXISTS = 3713 +ER_CANT_MODIFY_SRID_0 = 3714 +ER_WARN_RESERVED_SRID_RANGE = 3715 +ER_CANT_MODIFY_SRS_USED_BY_COLUMN = 3716 +ER_SRS_INVALID_CHARACTER_IN_ATTRIBUTE = 3717 +ER_SRS_ATTRIBUTE_STRING_TOO_LONG = 3718 +ER_DEPRECATED_UTF8_ALIAS = 3719 +ER_DEPRECATED_NATIONAL = 3720 +ER_INVALID_DEFAULT_UTF8MB4_COLLATION = 3721 +ER_UNABLE_TO_COLLECT_LOG_STATUS = 3722 +ER_RESERVED_TABLESPACE_NAME = 3723 +ER_UNABLE_TO_SET_OPTION = 3724 +ER_SLAVE_POSSIBLY_DIVERGED_AFTER_DDL = 3725 +ER_SRS_NOT_GEOGRAPHIC = 3726 +ER_POLYGON_TOO_LARGE = 3727 +ER_SPATIAL_UNIQUE_INDEX = 3728 +ER_INDEX_TYPE_NOT_SUPPORTED_FOR_SPATIAL_INDEX = 3729 +ER_FK_CANNOT_DROP_PARENT = 3730 +ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE = 3731 +ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE = 3732 +ER_FK_CANNOT_USE_VIRTUAL_COLUMN = 3733 +ER_FK_NO_COLUMN_PARENT = 3734 +ER_CANT_SET_ERROR_SUPPRESSION_LIST = 3735 +ER_SRS_GEOGCS_INVALID_AXES = 3736 +ER_SRS_INVALID_SEMI_MAJOR_AXIS = 3737 +ER_SRS_INVALID_INVERSE_FLATTENING = 3738 +ER_SRS_INVALID_ANGULAR_UNIT = 3739 +ER_SRS_INVALID_PRIME_MERIDIAN = 3740 +ER_TRANSFORM_SOURCE_SRS_NOT_SUPPORTED = 3741 +ER_TRANSFORM_TARGET_SRS_NOT_SUPPORTED = 3742 +ER_TRANSFORM_SOURCE_SRS_MISSING_TOWGS84 = 3743 +ER_TRANSFORM_TARGET_SRS_MISSING_TOWGS84 = 3744 +ER_TEMP_TABLE_PREVENTS_SWITCH_SESSION_BINLOG_FORMAT = 3745 +ER_TEMP_TABLE_PREVENTS_SWITCH_GLOBAL_BINLOG_FORMAT = 3746 +ER_RUNNING_APPLIER_PREVENTS_SWITCH_GLOBAL_BINLOG_FORMAT = 3747 +ER_CLIENT_GTID_UNSAFE_CREATE_DROP_TEMP_TABLE_IN_TRX_IN_SBR = 3748 +OBSOLETE_ER_XA_CANT_CREATE_MDL_BACKUP = 3749 +ER_TABLE_WITHOUT_PK = 3750 +ER_WARN_DATA_TRUNCATED_FUNCTIONAL_INDEX = 3751 +ER_WARN_DATA_OUT_OF_RANGE_FUNCTIONAL_INDEX = 3752 +ER_FUNCTIONAL_INDEX_ON_JSON_OR_GEOMETRY_FUNCTION = 3753 +ER_FUNCTIONAL_INDEX_REF_AUTO_INCREMENT = 3754 +ER_CANNOT_DROP_COLUMN_FUNCTIONAL_INDEX = 3755 +ER_FUNCTIONAL_INDEX_PRIMARY_KEY = 3756 +ER_FUNCTIONAL_INDEX_ON_LOB = 3757 +ER_FUNCTIONAL_INDEX_FUNCTION_IS_NOT_ALLOWED = 3758 +ER_FULLTEXT_FUNCTIONAL_INDEX = 3759 +ER_SPATIAL_FUNCTIONAL_INDEX = 3760 +ER_WRONG_KEY_COLUMN_FUNCTIONAL_INDEX = 3761 +ER_FUNCTIONAL_INDEX_ON_FIELD = 3762 +ER_GENERATED_COLUMN_NAMED_FUNCTION_IS_NOT_ALLOWED = 3763 +ER_GENERATED_COLUMN_ROW_VALUE = 3764 +ER_GENERATED_COLUMN_VARIABLES = 3765 +ER_DEPENDENT_BY_DEFAULT_GENERATED_VALUE = 3766 +ER_DEFAULT_VAL_GENERATED_NON_PRIOR = 3767 +ER_DEFAULT_VAL_GENERATED_REF_AUTO_INC = 3768 +ER_DEFAULT_VAL_GENERATED_FUNCTION_IS_NOT_ALLOWED = 3769 +ER_DEFAULT_VAL_GENERATED_NAMED_FUNCTION_IS_NOT_ALLOWED = 3770 +ER_DEFAULT_VAL_GENERATED_ROW_VALUE = 3771 +ER_DEFAULT_VAL_GENERATED_VARIABLES = 3772 +ER_DEFAULT_AS_VAL_GENERATED = 3773 +ER_UNSUPPORTED_ACTION_ON_DEFAULT_VAL_GENERATED = 3774 +ER_GTID_UNSAFE_ALTER_ADD_COL_WITH_DEFAULT_EXPRESSION = 3775 +ER_FK_CANNOT_CHANGE_ENGINE = 3776 +ER_WARN_DEPRECATED_USER_SET_EXPR = 3777 +ER_WARN_DEPRECATED_UTF8MB3_COLLATION = 3778 +ER_WARN_DEPRECATED_NESTED_COMMENT_SYNTAX = 3779 +ER_FK_INCOMPATIBLE_COLUMNS = 3780 +ER_GR_HOLD_WAIT_TIMEOUT = 3781 +ER_GR_HOLD_KILLED = 3782 +ER_GR_HOLD_MEMBER_STATUS_ERROR = 3783 +ER_RPL_ENCRYPTION_FAILED_TO_FETCH_KEY = 3784 +ER_RPL_ENCRYPTION_KEY_NOT_FOUND = 3785 +ER_RPL_ENCRYPTION_KEYRING_INVALID_KEY = 3786 +ER_RPL_ENCRYPTION_HEADER_ERROR = 3787 +ER_RPL_ENCRYPTION_FAILED_TO_ROTATE_LOGS = 3788 +ER_RPL_ENCRYPTION_KEY_EXISTS_UNEXPECTED = 3789 +ER_RPL_ENCRYPTION_FAILED_TO_GENERATE_KEY = 3790 +ER_RPL_ENCRYPTION_FAILED_TO_STORE_KEY = 3791 +ER_RPL_ENCRYPTION_FAILED_TO_REMOVE_KEY = 3792 +ER_RPL_ENCRYPTION_UNABLE_TO_CHANGE_OPTION = 3793 +ER_RPL_ENCRYPTION_MASTER_KEY_RECOVERY_FAILED = 3794 +ER_SLOW_LOG_MODE_IGNORED_WHEN_NOT_LOGGING_TO_FILE = 3795 +ER_GRP_TRX_CONSISTENCY_NOT_ALLOWED = 3796 +ER_GRP_TRX_CONSISTENCY_BEFORE = 3797 +ER_GRP_TRX_CONSISTENCY_AFTER_ON_TRX_BEGIN = 3798 +ER_GRP_TRX_CONSISTENCY_BEGIN_NOT_ALLOWED = 3799 +ER_FUNCTIONAL_INDEX_ROW_VALUE_IS_NOT_ALLOWED = 3800 +ER_RPL_ENCRYPTION_FAILED_TO_ENCRYPT = 3801 +ER_PAGE_TRACKING_NOT_STARTED = 3802 +ER_PAGE_TRACKING_RANGE_NOT_TRACKED = 3803 +ER_PAGE_TRACKING_CANNOT_PURGE = 3804 +ER_RPL_ENCRYPTION_CANNOT_ROTATE_BINLOG_MASTER_KEY = 3805 +ER_BINLOG_MASTER_KEY_RECOVERY_OUT_OF_COMBINATION = 3806 +ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_OPERATE_KEY = 3807 +ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_ROTATE_LOGS = 3808 +ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_REENCRYPT_LOG = 3809 +ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_CLEANUP_UNUSED_KEYS = 3810 +ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_CLEANUP_AUX_KEY = 3811 +ER_NON_BOOLEAN_EXPR_FOR_CHECK_CONSTRAINT = 3812 +ER_COLUMN_CHECK_CONSTRAINT_REFERENCES_OTHER_COLUMN = 3813 +ER_CHECK_CONSTRAINT_NAMED_FUNCTION_IS_NOT_ALLOWED = 3814 +ER_CHECK_CONSTRAINT_FUNCTION_IS_NOT_ALLOWED = 3815 +ER_CHECK_CONSTRAINT_VARIABLES = 3816 +ER_CHECK_CONSTRAINT_ROW_VALUE = 3817 +ER_CHECK_CONSTRAINT_REFERS_AUTO_INCREMENT_COLUMN = 3818 +ER_CHECK_CONSTRAINT_VIOLATED = 3819 +ER_CHECK_CONSTRAINT_REFERS_UNKNOWN_COLUMN = 3820 +ER_CHECK_CONSTRAINT_NOT_FOUND = 3821 +ER_CHECK_CONSTRAINT_DUP_NAME = 3822 +ER_CHECK_CONSTRAINT_CLAUSE_USING_FK_REFER_ACTION_COLUMN = 3823 +WARN_UNENCRYPTED_TABLE_IN_ENCRYPTED_DB = 3824 +ER_INVALID_ENCRYPTION_REQUEST = 3825 +ER_CANNOT_SET_TABLE_ENCRYPTION = 3826 +ER_CANNOT_SET_DATABASE_ENCRYPTION = 3827 +ER_CANNOT_SET_TABLESPACE_ENCRYPTION = 3828 +ER_TABLESPACE_CANNOT_BE_ENCRYPTED = 3829 +ER_TABLESPACE_CANNOT_BE_DECRYPTED = 3830 +ER_TABLESPACE_TYPE_UNKNOWN = 3831 +ER_TARGET_TABLESPACE_UNENCRYPTED = 3832 +ER_CANNOT_USE_ENCRYPTION_CLAUSE = 3833 +ER_INVALID_MULTIPLE_CLAUSES = 3834 +ER_UNSUPPORTED_USE_OF_GRANT_AS = 3835 +ER_UKNOWN_AUTH_ID_OR_ACCESS_DENIED_FOR_GRANT_AS = 3836 +ER_DEPENDENT_BY_FUNCTIONAL_INDEX = 3837 +ER_PLUGIN_NOT_EARLY = 3838 +ER_INNODB_REDO_LOG_ARCHIVE_START_SUBDIR_PATH = 3839 +ER_INNODB_REDO_LOG_ARCHIVE_START_TIMEOUT = 3840 +ER_INNODB_REDO_LOG_ARCHIVE_DIRS_INVALID = 3841 +ER_INNODB_REDO_LOG_ARCHIVE_LABEL_NOT_FOUND = 3842 +ER_INNODB_REDO_LOG_ARCHIVE_DIR_EMPTY = 3843 +ER_INNODB_REDO_LOG_ARCHIVE_NO_SUCH_DIR = 3844 +ER_INNODB_REDO_LOG_ARCHIVE_DIR_CLASH = 3845 +ER_INNODB_REDO_LOG_ARCHIVE_DIR_PERMISSIONS = 3846 +ER_INNODB_REDO_LOG_ARCHIVE_FILE_CREATE = 3847 +ER_INNODB_REDO_LOG_ARCHIVE_ACTIVE = 3848 +ER_INNODB_REDO_LOG_ARCHIVE_INACTIVE = 3849 +ER_INNODB_REDO_LOG_ARCHIVE_FAILED = 3850 +ER_INNODB_REDO_LOG_ARCHIVE_SESSION = 3851 +ER_STD_REGEX_ERROR = 3852 +ER_INVALID_JSON_TYPE = 3853 +ER_CANNOT_CONVERT_STRING = 3854 +ER_DEPENDENT_BY_PARTITION_FUNC = 3855 +ER_WARN_DEPRECATED_FLOAT_AUTO_INCREMENT = 3856 +ER_RPL_CANT_STOP_SLAVE_WHILE_LOCKED_BACKUP = 3857 +ER_WARN_DEPRECATED_FLOAT_DIGITS = 3858 +ER_WARN_DEPRECATED_FLOAT_UNSIGNED = 3859 +ER_WARN_DEPRECATED_INTEGER_DISPLAY_WIDTH = 3860 +ER_WARN_DEPRECATED_ZEROFILL = 3861 +ER_CLONE_DONOR = 3862 +ER_CLONE_PROTOCOL = 3863 +ER_CLONE_DONOR_VERSION = 3864 +ER_CLONE_OS = 3865 +ER_CLONE_PLATFORM = 3866 +ER_CLONE_CHARSET = 3867 +ER_CLONE_CONFIG = 3868 +ER_CLONE_SYS_CONFIG = 3869 +ER_CLONE_PLUGIN_MATCH = 3870 +ER_CLONE_LOOPBACK = 3871 +ER_CLONE_ENCRYPTION = 3872 +ER_CLONE_DISK_SPACE = 3873 +ER_CLONE_IN_PROGRESS = 3874 +ER_CLONE_DISALLOWED = 3875 +ER_CANNOT_GRANT_ROLES_TO_ANONYMOUS_USER = 3876 +ER_SECONDARY_ENGINE_PLUGIN = 3877 +ER_SECOND_PASSWORD_CANNOT_BE_EMPTY = 3878 +ER_DB_ACCESS_DENIED = 3879 +ER_DA_AUTH_ID_WITH_SYSTEM_USER_PRIV_IN_MANDATORY_ROLES = 3880 +ER_DA_RPL_GTID_TABLE_CANNOT_OPEN = 3881 +ER_GEOMETRY_IN_UNKNOWN_LENGTH_UNIT = 3882 +ER_DA_PLUGIN_INSTALL_ERROR = 3883 +ER_NO_SESSION_TEMP = 3884 +ER_DA_UNKNOWN_ERROR_NUMBER = 3885 +ER_COLUMN_CHANGE_SIZE = 3886 +ER_REGEXP_INVALID_CAPTURE_GROUP_NAME = 3887 +ER_DA_SSL_LIBRARY_ERROR = 3888 +ER_SECONDARY_ENGINE = 3889 +ER_SECONDARY_ENGINE_DDL = 3890 +ER_INCORRECT_CURRENT_PASSWORD = 3891 +ER_MISSING_CURRENT_PASSWORD = 3892 +ER_CURRENT_PASSWORD_NOT_REQUIRED = 3893 +ER_PASSWORD_CANNOT_BE_RETAINED_ON_PLUGIN_CHANGE = 3894 +ER_CURRENT_PASSWORD_CANNOT_BE_RETAINED = 3895 +ER_PARTIAL_REVOKES_EXIST = 3896 +ER_CANNOT_GRANT_SYSTEM_PRIV_TO_MANDATORY_ROLE = 3897 +ER_XA_REPLICATION_FILTERS = 3898 +ER_UNSUPPORTED_SQL_MODE = 3899 +ER_REGEXP_INVALID_FLAG = 3900 +ER_PARTIAL_REVOKE_AND_DB_GRANT_BOTH_EXISTS = 3901 +ER_UNIT_NOT_FOUND = 3902 +ER_INVALID_JSON_VALUE_FOR_FUNC_INDEX = 3903 +ER_JSON_VALUE_OUT_OF_RANGE_FOR_FUNC_INDEX = 3904 +ER_EXCEEDED_MV_KEYS_NUM = 3905 +ER_EXCEEDED_MV_KEYS_SPACE = 3906 +ER_FUNCTIONAL_INDEX_DATA_IS_TOO_LONG = 3907 +ER_WRONG_MVI_VALUE = 3908 +ER_WARN_FUNC_INDEX_NOT_APPLICABLE = 3909 +ER_GRP_RPL_UDF_ERROR = 3910 +ER_UPDATE_GTID_PURGED_WITH_GR = 3911 +ER_GROUPING_ON_TIMESTAMP_IN_DST = 3912 +ER_TABLE_NAME_CAUSES_TOO_LONG_PATH = 3913 +ER_AUDIT_LOG_INSUFFICIENT_PRIVILEGE = 3914 +OBSOLETE_ER_AUDIT_LOG_PASSWORD_HAS_BEEN_COPIED = 3915 +ER_DA_GRP_RPL_STARTED_AUTO_REJOIN = 3916 +ER_SYSVAR_CHANGE_DURING_QUERY = 3917 +ER_GLOBSTAT_CHANGE_DURING_QUERY = 3918 +ER_GRP_RPL_MESSAGE_SERVICE_INIT_FAILURE = 3919 +ER_CHANGE_MASTER_WRONG_COMPRESSION_ALGORITHM_CLIENT = 3920 +ER_CHANGE_MASTER_WRONG_COMPRESSION_LEVEL_CLIENT = 3921 +ER_WRONG_COMPRESSION_ALGORITHM_CLIENT = 3922 +ER_WRONG_COMPRESSION_LEVEL_CLIENT = 3923 +ER_CHANGE_MASTER_WRONG_COMPRESSION_ALGORITHM_LIST_CLIENT = 3924 +ER_CLIENT_PRIVILEGE_CHECKS_USER_CANNOT_BE_ANONYMOUS = 3925 +ER_CLIENT_PRIVILEGE_CHECKS_USER_DOES_NOT_EXIST = 3926 +ER_CLIENT_PRIVILEGE_CHECKS_USER_CORRUPT = 3927 +ER_CLIENT_PRIVILEGE_CHECKS_USER_NEEDS_RPL_APPLIER_PRIV = 3928 +ER_WARN_DA_PRIVILEGE_NOT_REGISTERED = 3929 +ER_CLIENT_KEYRING_UDF_KEY_INVALID = 3930 +ER_CLIENT_KEYRING_UDF_KEY_TYPE_INVALID = 3931 +ER_CLIENT_KEYRING_UDF_KEY_TOO_LONG = 3932 +ER_CLIENT_KEYRING_UDF_KEY_TYPE_TOO_LONG = 3933 +ER_JSON_SCHEMA_VALIDATION_ERROR_WITH_DETAILED_REPORT = 3934 +ER_DA_UDF_INVALID_CHARSET_SPECIFIED = 3935 +ER_DA_UDF_INVALID_CHARSET = 3936 +ER_DA_UDF_INVALID_COLLATION = 3937 +ER_DA_UDF_INVALID_EXTENSION_ARGUMENT_TYPE = 3938 +ER_MULTIPLE_CONSTRAINTS_WITH_SAME_NAME = 3939 +ER_CONSTRAINT_NOT_FOUND = 3940 +ER_ALTER_CONSTRAINT_ENFORCEMENT_NOT_SUPPORTED = 3941 +ER_TABLE_VALUE_CONSTRUCTOR_MUST_HAVE_COLUMNS = 3942 +ER_TABLE_VALUE_CONSTRUCTOR_CANNOT_HAVE_DEFAULT = 3943 +ER_CLIENT_QUERY_FAILURE_INVALID_NON_ROW_FORMAT = 3944 +ER_REQUIRE_ROW_FORMAT_INVALID_VALUE = 3945 +ER_FAILED_TO_DETERMINE_IF_ROLE_IS_MANDATORY = 3946 +ER_FAILED_TO_FETCH_MANDATORY_ROLE_LIST = 3947 +ER_CLIENT_LOCAL_FILES_DISABLED = 3948 +ER_IMP_INCOMPATIBLE_CFG_VERSION = 3949 +ER_DA_OOM = 3950 +ER_DA_UDF_INVALID_ARGUMENT_TO_SET_CHARSET = 3951 +ER_DA_UDF_INVALID_RETURN_TYPE_TO_SET_CHARSET = 3952 +ER_MULTIPLE_INTO_CLAUSES = 3953 +ER_MISPLACED_INTO = 3954 +ER_USER_ACCESS_DENIED_FOR_USER_ACCOUNT_BLOCKED_BY_PASSWORD_LOCK = 3955 +ER_WARN_DEPRECATED_YEAR_UNSIGNED = 3956 +ER_CLONE_NETWORK_PACKET = 3957 +ER_SDI_OPERATION_FAILED_MISSING_RECORD = 3958 +ER_DEPENDENT_BY_CHECK_CONSTRAINT = 3959 +ER_GRP_OPERATION_NOT_ALLOWED_GR_MUST_STOP = 3960 +ER_WARN_DEPRECATED_JSON_TABLE_ON_ERROR_ON_EMPTY = 3961 +ER_WARN_DEPRECATED_INNER_INTO = 3962 +ER_WARN_DEPRECATED_VALUES_FUNCTION_ALWAYS_NULL = 3963 +ER_WARN_DEPRECATED_SQL_CALC_FOUND_ROWS = 3964 +ER_WARN_DEPRECATED_FOUND_ROWS = 3965 +ER_MISSING_JSON_VALUE = 3966 +ER_MULTIPLE_JSON_VALUES = 3967 +ER_HOSTNAME_TOO_LONG = 3968 +ER_WARN_CLIENT_DEPRECATED_PARTITION_PREFIX_KEY = 3969 +ER_GROUP_REPLICATION_USER_EMPTY_MSG = 3970 +ER_GROUP_REPLICATION_USER_MANDATORY_MSG = 3971 +ER_GROUP_REPLICATION_PASSWORD_LENGTH = 3972 +ER_SUBQUERY_TRANSFORM_REJECTED = 3973 +ER_DA_GRP_RPL_RECOVERY_ENDPOINT_FORMAT = 3974 +ER_DA_GRP_RPL_RECOVERY_ENDPOINT_INVALID = 3975 +ER_WRONG_VALUE_FOR_VAR_PLUS_ACTIONABLE_PART = 3976 +ER_STATEMENT_NOT_ALLOWED_AFTER_START_TRANSACTION = 3977 +ER_FOREIGN_KEY_WITH_ATOMIC_CREATE_SELECT = 3978 +ER_NOT_ALLOWED_WITH_START_TRANSACTION = 3979 +ER_INVALID_JSON_ATTRIBUTE = 3980 +ER_ENGINE_ATTRIBUTE_NOT_SUPPORTED = 3981 +ER_INVALID_USER_ATTRIBUTE_JSON = 3982 +ER_INNODB_REDO_DISABLED = 3983 +ER_INNODB_REDO_ARCHIVING_ENABLED = 3984 +ER_MDL_OUT_OF_RESOURCES = 3985 +ER_IMPLICIT_COMPARISON_FOR_JSON = 3986 +ER_FUNCTION_DOES_NOT_SUPPORT_CHARACTER_SET = 3987 +ER_IMPOSSIBLE_STRING_CONVERSION = 3988 +ER_SCHEMA_READ_ONLY = 3989 +ER_RPL_ASYNC_RECONNECT_GTID_MODE_OFF = 3990 +ER_RPL_ASYNC_RECONNECT_AUTO_POSITION_OFF = 3991 +ER_DISABLE_GTID_MODE_REQUIRES_ASYNC_RECONNECT_OFF = 3992 +ER_DISABLE_AUTO_POSITION_REQUIRES_ASYNC_RECONNECT_OFF = 3993 +ER_INVALID_PARAMETER_USE = 3994 +ER_CHARACTER_SET_MISMATCH = 3995 +ER_WARN_VAR_VALUE_CHANGE_NOT_SUPPORTED = 3996 +ER_INVALID_TIME_ZONE_INTERVAL = 3997 +ER_INVALID_CAST = 3998 +ER_HYPERGRAPH_NOT_SUPPORTED_YET = 3999 +ER_WARN_HYPERGRAPH_EXPERIMENTAL = 4000 +ER_DA_NO_ERROR_LOG_PARSER_CONFIGURED = 4001 +ER_DA_ERROR_LOG_TABLE_DISABLED = 4002 +ER_DA_ERROR_LOG_MULTIPLE_FILTERS = 4003 +ER_DA_CANT_OPEN_ERROR_LOG = 4004 +ER_USER_REFERENCED_AS_DEFINER = 4005 +ER_CANNOT_USER_REFERENCED_AS_DEFINER = 4006 +ER_REGEX_NUMBER_TOO_BIG = 4007 +ER_SPVAR_NONINTEGER_TYPE = 4008 +WARN_UNSUPPORTED_ACL_TABLES_READ = 4009 +ER_BINLOG_UNSAFE_ACL_TABLE_READ_IN_DML_DDL = 4010 +ER_STOP_REPLICA_MONITOR_IO_THREAD_TIMEOUT = 4011 +ER_STARTING_REPLICA_MONITOR_IO_THREAD = 4012 +ER_CANT_USE_ANONYMOUS_TO_GTID_WITH_GTID_MODE_NOT_ON = 4013 +ER_CANT_COMBINE_ANONYMOUS_TO_GTID_AND_AUTOPOSITION = 4014 +ER_ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS_REQUIRES_GTID_MODE_ON = 4015 +ER_SQL_REPLICA_SKIP_COUNTER_USED_WITH_GTID_MODE_ON = 4016 +ER_USING_ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS_AS_LOCAL_OR_UUID = 4017 +ER_CANT_SET_ANONYMOUS_TO_GTID_AND_WAIT_UNTIL_SQL_THD_AFTER_GTIDS = 4018 +ER_CANT_SET_SQL_AFTER_OR_BEFORE_GTIDS_WITH_ANONYMOUS_TO_GTID = 4019 +ER_ANONYMOUS_TO_GTID_UUID_SAME_AS_GROUP_NAME = 4020 +ER_CANT_USE_SAME_UUID_AS_GROUP_NAME = 4021 +ER_GRP_RPL_RECOVERY_CHANNEL_STILL_RUNNING = 4022 +ER_INNODB_INVALID_AUTOEXTEND_SIZE_VALUE = 4023 +ER_INNODB_INCOMPATIBLE_WITH_TABLESPACE = 4024 +ER_INNODB_AUTOEXTEND_SIZE_OUT_OF_RANGE = 4025 +ER_CANNOT_USE_AUTOEXTEND_SIZE_CLAUSE = 4026 +ER_ROLE_GRANTED_TO_ITSELF = 4027 +ER_TABLE_MUST_HAVE_A_VISIBLE_COLUMN = 4028 +ER_INNODB_COMPRESSION_FAILURE = 4029 +ER_WARN_ASYNC_CONN_FAILOVER_NETWORK_NAMESPACE = 4030 +ER_CLIENT_INTERACTION_TIMEOUT = 4031 +ER_INVALID_CAST_TO_GEOMETRY = 4032 +ER_INVALID_CAST_POLYGON_RING_DIRECTION = 4033 +ER_GIS_DIFFERENT_SRIDS_AGGREGATION = 4034 +ER_RELOAD_KEYRING_FAILURE = 4035 +ER_SDI_GET_KEYS_INVALID_TABLESPACE = 4036 +ER_CHANGE_RPL_SRC_WRONG_COMPRESSION_ALGORITHM_SIZE = 4037 +ER_WARN_DEPRECATED_TLS_VERSION_FOR_CHANNEL_CLI = 4038 +ER_CANT_USE_SAME_UUID_AS_VIEW_CHANGE_UUID = 4039 +ER_ANONYMOUS_TO_GTID_UUID_SAME_AS_VIEW_CHANGE_UUID = 4040 +ER_GRP_RPL_VIEW_CHANGE_UUID_FAIL_GET_VARIABLE = 4041 +ER_WARN_ADUIT_LOG_MAX_SIZE_AND_PRUNE_SECONDS = 4042 +ER_WARN_ADUIT_LOG_MAX_SIZE_CLOSE_TO_ROTATE_ON_SIZE = 4043 +ER_KERBEROS_CREATE_USER = 4044 +ER_INSTALL_PLUGIN_CONFLICT_CLIENT = 4045 +ER_DA_ERROR_LOG_COMPONENT_FLUSH_FAILED = 4046 +ER_WARN_SQL_AFTER_MTS_GAPS_GAP_NOT_CALCULATED = 4047 +ER_INVALID_ASSIGNMENT_TARGET = 4048 +ER_OPERATION_NOT_ALLOWED_ON_GR_SECONDARY = 4049 +ER_GRP_RPL_FAILOVER_CHANNEL_STATUS_PROPAGATION = 4050 +ER_WARN_AUDIT_LOG_FORMAT_UNIX_TIMESTAMP_ONLY_WHEN_JSON = 4051 +ER_INVALID_MFA_PLUGIN_SPECIFIED = 4052 +ER_IDENTIFIED_BY_UNSUPPORTED = 4053 +ER_INVALID_PLUGIN_FOR_REGISTRATION = 4054 +ER_PLUGIN_REQUIRES_REGISTRATION = 4055 +ER_MFA_METHOD_EXISTS = 4056 +ER_MFA_METHOD_NOT_EXISTS = 4057 +ER_AUTHENTICATION_POLICY_MISMATCH = 4058 +ER_PLUGIN_REGISTRATION_DONE = 4059 +ER_INVALID_USER_FOR_REGISTRATION = 4060 +ER_USER_REGISTRATION_FAILED = 4061 +ER_MFA_METHODS_INVALID_ORDER = 4062 +ER_MFA_METHODS_IDENTICAL = 4063 +ER_INVALID_MFA_OPERATIONS_FOR_PASSWORDLESS_USER = 4064 +ER_CHANGE_REPLICATION_SOURCE_NO_OPTIONS_FOR_GTID_ONLY = 4065 +ER_CHANGE_REP_SOURCE_CANT_DISABLE_REQ_ROW_FORMAT_WITH_GTID_ONLY = 4066 +ER_CHANGE_REP_SOURCE_CANT_DISABLE_AUTO_POSITION_WITH_GTID_ONLY = 4067 +ER_CHANGE_REP_SOURCE_CANT_DISABLE_GTID_ONLY_WITHOUT_POSITIONS = 4068 +ER_CHANGE_REP_SOURCE_CANT_DISABLE_AUTO_POS_WITHOUT_POSITIONS = 4069 +ER_CHANGE_REP_SOURCE_GR_CHANNEL_WITH_GTID_MODE_NOT_ON = 4070 +ER_CANT_USE_GTID_ONLY_WITH_GTID_MODE_NOT_ON = 4071 +ER_WARN_C_DISABLE_GTID_ONLY_WITH_SOURCE_AUTO_POS_INVALID_POS = 4072 +ER_DA_SSL_FIPS_MODE_ERROR = 4073 +CR_UNKNOWN_ERROR = 2000 +CR_SOCKET_CREATE_ERROR = 2001 +CR_CONNECTION_ERROR = 2002 +CR_CONN_HOST_ERROR = 2003 +CR_IPSOCK_ERROR = 2004 +CR_UNKNOWN_HOST = 2005 +CR_SERVER_GONE_ERROR = 2006 +CR_VERSION_ERROR = 2007 +CR_OUT_OF_MEMORY = 2008 +CR_WRONG_HOST_INFO = 2009 +CR_LOCALHOST_CONNECTION = 2010 +CR_TCP_CONNECTION = 2011 +CR_SERVER_HANDSHAKE_ERR = 2012 +CR_SERVER_LOST = 2013 +CR_COMMANDS_OUT_OF_SYNC = 2014 +CR_NAMEDPIPE_CONNECTION = 2015 +CR_NAMEDPIPEWAIT_ERROR = 2016 +CR_NAMEDPIPEOPEN_ERROR = 2017 +CR_NAMEDPIPESETSTATE_ERROR = 2018 +CR_CANT_READ_CHARSET = 2019 +CR_NET_PACKET_TOO_LARGE = 2020 +CR_EMBEDDED_CONNECTION = 2021 +CR_PROBE_SLAVE_STATUS = 2022 +CR_PROBE_SLAVE_HOSTS = 2023 +CR_PROBE_SLAVE_CONNECT = 2024 +CR_PROBE_MASTER_CONNECT = 2025 +CR_SSL_CONNECTION_ERROR = 2026 +CR_MALFORMED_PACKET = 2027 +CR_WRONG_LICENSE = 2028 +CR_NULL_POINTER = 2029 +CR_NO_PREPARE_STMT = 2030 +CR_PARAMS_NOT_BOUND = 2031 +CR_DATA_TRUNCATED = 2032 +CR_NO_PARAMETERS_EXISTS = 2033 +CR_INVALID_PARAMETER_NO = 2034 +CR_INVALID_BUFFER_USE = 2035 +CR_UNSUPPORTED_PARAM_TYPE = 2036 +CR_SHARED_MEMORY_CONNECTION = 2037 +CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = 2038 +CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = 2039 +CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = 2040 +CR_SHARED_MEMORY_CONNECT_MAP_ERROR = 2041 +CR_SHARED_MEMORY_FILE_MAP_ERROR = 2042 +CR_SHARED_MEMORY_MAP_ERROR = 2043 +CR_SHARED_MEMORY_EVENT_ERROR = 2044 +CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = 2045 +CR_SHARED_MEMORY_CONNECT_SET_ERROR = 2046 +CR_CONN_UNKNOW_PROTOCOL = 2047 +CR_INVALID_CONN_HANDLE = 2048 +CR_UNUSED_1 = 2049 +CR_FETCH_CANCELED = 2050 +CR_NO_DATA = 2051 +CR_NO_STMT_METADATA = 2052 +CR_NO_RESULT_SET = 2053 +CR_NOT_IMPLEMENTED = 2054 +CR_SERVER_LOST_EXTENDED = 2055 +CR_STMT_CLOSED = 2056 +CR_NEW_STMT_METADATA = 2057 +CR_ALREADY_CONNECTED = 2058 +CR_AUTH_PLUGIN_CANNOT_LOAD = 2059 +CR_DUPLICATE_CONNECTION_ATTR = 2060 +CR_AUTH_PLUGIN_ERR = 2061 +CR_INSECURE_API_ERR = 2062 +CR_FILE_NAME_TOO_LONG = 2063 +CR_SSL_FIPS_MODE_ERR = 2064 +CR_DEPRECATED_COMPRESSION_NOT_SUPPORTED = 2065 +CR_COMPRESSION_WRONGLY_CONFIGURED = 2066 +CR_KERBEROS_USER_NOT_FOUND = 2067 +CR_LOAD_DATA_LOCAL_INFILE_REJECTED = 2068 +CR_LOAD_DATA_LOCAL_INFILE_REALPATH_FAIL = 2069 +CR_DNS_SRV_LOOKUP_FAILED = 2070 +CR_MANDATORY_TRACKER_NOT_FOUND = 2071 +CR_INVALID_FACTOR_NO = 2072 +# End MySQL Errors + +# Start X Plugin Errors +ER_X_BAD_MESSAGE = 5000 +ER_X_CAPABILITIES_PREPARE_FAILED = 5001 +ER_X_CAPABILITY_NOT_FOUND = 5002 +ER_X_INVALID_PROTOCOL_DATA = 5003 +ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_VALUE_LENGTH = 5004 +ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_KEY_LENGTH = 5005 +ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_EMPTY_KEY = 5006 +ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_LENGTH = 5007 +ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_TYPE = 5008 +ER_X_CAPABILITY_SET_NOT_ALLOWED = 5009 +ER_X_SERVICE_ERROR = 5010 +ER_X_SESSION = 5011 +ER_X_INVALID_ARGUMENT = 5012 +ER_X_MISSING_ARGUMENT = 5013 +ER_X_BAD_INSERT_DATA = 5014 +ER_X_CMD_NUM_ARGUMENTS = 5015 +ER_X_CMD_ARGUMENT_TYPE = 5016 +ER_X_CMD_ARGUMENT_VALUE = 5017 +ER_X_BAD_UPSERT_DATA = 5018 +ER_X_DUPLICATED_CAPABILITIES = 5019 +ER_X_CMD_ARGUMENT_OBJECT_EMPTY = 5020 +ER_X_CMD_INVALID_ARGUMENT = 5021 +ER_X_BAD_UPDATE_DATA = 5050 +ER_X_BAD_TYPE_OF_UPDATE = 5051 +ER_X_BAD_COLUMN_TO_UPDATE = 5052 +ER_X_BAD_MEMBER_TO_UPDATE = 5053 +ER_X_BAD_STATEMENT_ID = 5110 +ER_X_BAD_CURSOR_ID = 5111 +ER_X_BAD_SCHEMA = 5112 +ER_X_BAD_TABLE = 5113 +ER_X_BAD_PROJECTION = 5114 +ER_X_DOC_ID_MISSING = 5115 +ER_X_DUPLICATE_ENTRY = 5116 +ER_X_DOC_REQUIRED_FIELD_MISSING = 5117 +ER_X_PROJ_BAD_KEY_NAME = 5120 +ER_X_BAD_DOC_PATH = 5121 +ER_X_CURSOR_EXISTS = 5122 +ER_X_CURSOR_REACHED_EOF = 5123 +ER_X_PREPARED_STATMENT_CAN_HAVE_ONE_CURSOR = 5131 +ER_X_PREPARED_EXECUTE_ARGUMENT_NOT_SUPPORTED = 5133 +ER_X_PREPARED_EXECUTE_ARGUMENT_CONSISTENCY = 5134 +ER_X_EXPR_BAD_OPERATOR = 5150 +ER_X_EXPR_BAD_NUM_ARGS = 5151 +ER_X_EXPR_MISSING_ARG = 5152 +ER_X_EXPR_BAD_TYPE_VALUE = 5153 +ER_X_EXPR_BAD_VALUE = 5154 +ER_X_INVALID_COLLECTION = 5156 +ER_X_INVALID_ADMIN_COMMAND = 5157 +ER_X_EXPECT_NOT_OPEN = 5158 +ER_X_EXPECT_NO_ERROR_FAILED = 5159 +ER_X_EXPECT_BAD_CONDITION = 5160 +ER_X_EXPECT_BAD_CONDITION_VALUE = 5161 +ER_X_INVALID_NAMESPACE = 5162 +ER_X_BAD_NOTICE = 5163 +ER_X_CANNOT_DISABLE_NOTICE = 5164 +ER_X_BAD_CONFIGURATION = 5165 +ER_X_MYSQLX_ACCOUNT_MISSING_PERMISSIONS = 5167 +ER_X_EXPECT_FIELD_EXISTS_FAILED = 5168 +ER_X_BAD_LOCKING = 5169 +ER_X_FRAME_COMPRESSION_DISABLED = 5170 +ER_X_DECOMPRESSION_FAILED = 5171 +ER_X_BAD_COMPRESSED_FRAME = 5174 +ER_X_CAPABILITY_COMPRESSION_INVALID_ALGORITHM = 5175 +ER_X_CAPABILITY_COMPRESSION_INVALID_SERVER_STYLE = 5176 +ER_X_CAPABILITY_COMPRESSION_INVALID_CLIENT_STYLE = 5177 +ER_X_CAPABILITY_COMPRESSION_INVALID_OPTION = 5178 +ER_X_CAPABILITY_COMPRESSION_MISSING_REQUIRED_FIELDS = 5179 +ER_X_DOCUMENT_DOESNT_MATCH_EXPECTED_SCHEMA = 5180 +ER_X_COLLECTION_OPTION_DOESNT_EXISTS = 5181 +ER_X_INVALID_VALIDATION_SCHEMA = 5182 +# End X Plugin Errors diff --git a/mysql/connector/errors.py b/mysql/connector/errors.py new file mode 100644 index 0000000..53ebf72 --- /dev/null +++ b/mysql/connector/errors.py @@ -0,0 +1,336 @@ +# Copyright (c) 2009, 2023, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Python exceptions.""" +from typing import Dict, Mapping, Optional, Tuple, Type, Union + +from .locales import get_client_error +from .types import StrOrBytes +from .utils import read_bytes, read_int + + +class Error(Exception): + """Exception that is base class for all other error exceptions""" + + def __init__( + self, + msg: Optional[str] = None, + errno: Optional[int] = None, + values: Optional[Tuple[Union[int, str], ...]] = None, + sqlstate: Optional[str] = None, + ) -> None: + super().__init__() + self.msg = msg + self._full_msg = self.msg + self.errno = errno or -1 + self.sqlstate = sqlstate + + if not self.msg and (2000 <= self.errno < 3000): + self.msg = get_client_error(self.errno) + if values is not None: + try: + self.msg = self.msg % values + except TypeError as err: + self.msg = f"{self.msg} (Warning: {err})" + elif not self.msg: + self._full_msg = self.msg = "Unknown error" + + if self.msg and self.errno != -1: + fields = {"errno": self.errno, "msg": self.msg} + if self.sqlstate: + fmt = "{errno} ({state}): {msg}" + fields["state"] = self.sqlstate + else: + fmt = "{errno}: {msg}" + self._full_msg = fmt.format(**fields) + + self.args = (self.errno, self._full_msg, self.sqlstate) + + def __str__(self) -> str: + return self._full_msg + + +class Warning(Exception): # pylint: disable=redefined-builtin + """Exception for important warnings""" + + +class InterfaceError(Error): + """Exception for errors related to the interface""" + + +class DatabaseError(Error): + """Exception for errors related to the database""" + + +class InternalError(DatabaseError): + """Exception for errors internal database errors""" + + +class OperationalError(DatabaseError): + """Exception for errors related to the database's operation""" + + +class ProgrammingError(DatabaseError): + """Exception for errors programming errors""" + + +class IntegrityError(DatabaseError): + """Exception for errors regarding relational integrity""" + + +class DataError(DatabaseError): + """Exception for errors reporting problems with processed data""" + + +class NotSupportedError(DatabaseError): + """Exception for errors when an unsupported database feature was used""" + + +class PoolError(Error): + """Exception for errors relating to connection pooling""" + + +ErrorClassTypes = Union[ + Type[Error], + Type[InterfaceError], + Type[DatabaseError], + Type[InternalError], + Type[OperationalError], + Type[ProgrammingError], + Type[IntegrityError], + Type[DataError], + Type[NotSupportedError], + Type[PoolError], +] +ErrorTypes = Union[ + Error, + InterfaceError, + DatabaseError, + InternalError, + OperationalError, + ProgrammingError, + IntegrityError, + DataError, + NotSupportedError, + PoolError, + Warning, +] +# _CUSTOM_ERROR_EXCEPTIONS holds custom exceptions and is used by the +# function custom_error_exception. _ERROR_EXCEPTIONS (at bottom of module) +# is similar, but hardcoded exceptions. +_CUSTOM_ERROR_EXCEPTIONS: Dict[int, ErrorClassTypes] = {} + + +def custom_error_exception( + error: Optional[Union[int, Dict[int, Optional[ErrorClassTypes]]]] = None, + exception: Optional[ErrorClassTypes] = None, +) -> Mapping[int, Optional[ErrorClassTypes]]: + """Define custom exceptions for MySQL server errors + + This function defines custom exceptions for MySQL server errors and + returns the current set customizations. + + If error is a MySQL Server error number, then you have to pass also the + exception class. + + The error argument can also be a dictionary in which case the key is + the server error number, and value the exception to be raised. + + If none of the arguments are given, then custom_error_exception() will + simply return the current set customizations. + + To reset the customizations, simply supply an empty dictionary. + + Examples: + import mysql.connector + from mysql.connector import errorcode + + # Server error 1028 should raise a DatabaseError + mysql.connector.custom_error_exception( + 1028, mysql.connector.DatabaseError) + + # Or using a dictionary: + mysql.connector.custom_error_exception({ + 1028: mysql.connector.DatabaseError, + 1029: mysql.connector.OperationalError, + }) + + # Reset + mysql.connector.custom_error_exception({}) + + Returns a dictionary. + """ + global _CUSTOM_ERROR_EXCEPTIONS # pylint: disable=global-statement + + if isinstance(error, dict) and not error: + _CUSTOM_ERROR_EXCEPTIONS = {} + return _CUSTOM_ERROR_EXCEPTIONS + + if not error and not exception: + return _CUSTOM_ERROR_EXCEPTIONS + + if not isinstance(error, (int, dict)): + raise ValueError("The error argument should be either an integer or dictionary") + + if isinstance(error, int): + error = {error: exception} + + for errno, _exception in error.items(): + if not isinstance(errno, int): + raise ValueError("Error number should be an integer") + try: + if _exception is None or not issubclass(_exception, Exception): + raise TypeError + except TypeError as err: + raise ValueError("Exception should be subclass of Exception") from err + _CUSTOM_ERROR_EXCEPTIONS[errno] = _exception + + return _CUSTOM_ERROR_EXCEPTIONS + + +def get_mysql_exception( + errno: int, + msg: Optional[str] = None, + sqlstate: Optional[str] = None, + warning: Optional[bool] = False, +) -> ErrorTypes: + """Get the exception matching the MySQL error + + This function will return an exception based on the SQLState. The given + message will be passed on in the returned exception. + + The exception returned can be customized using the + mysql.connector.custom_error_exception() function. + + Returns an Exception + """ + try: + return _CUSTOM_ERROR_EXCEPTIONS[errno](msg=msg, errno=errno, sqlstate=sqlstate) + except KeyError: + # Error was not mapped to particular exception + pass + + try: + return _ERROR_EXCEPTIONS[errno](msg=msg, errno=errno, sqlstate=sqlstate) + except KeyError: + # Error was not mapped to particular exception + pass + + if not sqlstate: + if warning: + return Warning(errno, msg) + return DatabaseError(msg=msg, errno=errno) + + try: + return _SQLSTATE_CLASS_EXCEPTION[sqlstate[0:2]]( + msg=msg, errno=errno, sqlstate=sqlstate + ) + except KeyError: + # Return default InterfaceError + return DatabaseError(msg=msg, errno=errno, sqlstate=sqlstate) + + +def get_exception(packet: bytes) -> ErrorTypes: + """Returns an exception object based on the MySQL error + + Returns an exception object based on the MySQL error in the given + packet. + + Returns an Error-Object. + """ + errno = errmsg = None + + try: + if packet[4] != 255: + raise ValueError("Packet is not an error packet") + except IndexError as err: + return InterfaceError(f"Failed getting Error information ({err})") + + sqlstate: Optional[StrOrBytes] = None + try: + packet = packet[5:] + packet, errno = read_int(packet, 2) + if packet[0] != 35: + # Error without SQLState + if isinstance(packet, (bytes, bytearray)): + errmsg = packet.decode("utf8") + else: + errmsg = packet + else: + packet, sqlstate = read_bytes(packet[1:], 5) + sqlstate = sqlstate.decode("utf8") + errmsg = packet.decode("utf8") + except (IndexError, UnicodeError) as err: + return InterfaceError(f"Failed getting Error information ({err})") + return get_mysql_exception(errno, errmsg, sqlstate) # type: ignore[arg-type] + + +_SQLSTATE_CLASS_EXCEPTION: Dict[str, ErrorClassTypes] = { + "02": DataError, # no data + "07": DatabaseError, # dynamic SQL error + "08": OperationalError, # connection exception + "0A": NotSupportedError, # feature not supported + "21": DataError, # cardinality violation + "22": DataError, # data exception + "23": IntegrityError, # integrity constraint violation + "24": ProgrammingError, # invalid cursor state + "25": ProgrammingError, # invalid transaction state + "26": ProgrammingError, # invalid SQL statement name + "27": ProgrammingError, # triggered data change violation + "28": ProgrammingError, # invalid authorization specification + "2A": ProgrammingError, # direct SQL syntax error or access rule violation + "2B": DatabaseError, # dependent privilege descriptors still exist + "2C": ProgrammingError, # invalid character set name + "2D": DatabaseError, # invalid transaction termination + "2E": DatabaseError, # invalid connection name + "33": DatabaseError, # invalid SQL descriptor name + "34": ProgrammingError, # invalid cursor name + "35": ProgrammingError, # invalid condition number + "37": ProgrammingError, # dynamic SQL syntax error or access rule violation + "3C": ProgrammingError, # ambiguous cursor name + "3D": ProgrammingError, # invalid catalog name + "3F": ProgrammingError, # invalid schema name + "40": InternalError, # transaction rollback + "42": ProgrammingError, # syntax error or access rule violation + "44": InternalError, # with check option violation + "HZ": OperationalError, # remote database access + "XA": IntegrityError, + "0K": OperationalError, + "HY": DatabaseError, # default when no SQLState provided by MySQL server +} + +_ERROR_EXCEPTIONS: Dict[int, ErrorClassTypes] = { + 1243: ProgrammingError, + 1210: ProgrammingError, + 2002: InterfaceError, + 2013: OperationalError, + 2049: NotSupportedError, + 2055: OperationalError, + 2061: InterfaceError, + 2026: InterfaceError, +} diff --git a/mysql/connector/locales/__init__.py b/mysql/connector/locales/__init__.py new file mode 100644 index 0000000..f6727ee --- /dev/null +++ b/mysql/connector/locales/__init__.py @@ -0,0 +1,80 @@ +# Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Translations.""" + +from typing import List, Optional, Union + +__all__: List[str] = ["get_client_error"] + +from .. import errorcode + + +def get_client_error(error: Union[int, str], language: str = "eng") -> Optional[str]: + """Lookup client error + + This function will lookup the client error message based on the given + error and return the error message. If the error was not found, + None will be returned. + + Error can be either an integer or a string. For example: + error: 2000 + error: CR_UNKNOWN_ERROR + + The language attribute can be used to retrieve a localized message, when + available. + + Returns a string or None. + """ + try: + tmp = __import__( + f"mysql.connector.locales.{language}", + globals(), + locals(), + ["client_error"], + ) + except ImportError: + raise ImportError( + f"No localization support for language '{language}'" + ) from None + client_error = tmp.client_error + + if isinstance(error, int): + errno = error + for key, value in errorcode.__dict__.items(): + if value == errno: + error = key + break + + if isinstance(error, (str)): + try: + return getattr(client_error, error) + except AttributeError: + return None + + raise ValueError("error argument needs to be either an integer or string") diff --git a/mysql/connector/locales/eng/__init__.py b/mysql/connector/locales/eng/__init__.py new file mode 100644 index 0000000..2e1c02b --- /dev/null +++ b/mysql/connector/locales/eng/__init__.py @@ -0,0 +1,30 @@ +# Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""English Content +""" diff --git a/mysql/connector/locales/eng/client_error.py b/mysql/connector/locales/eng/client_error.py new file mode 100644 index 0000000..89fb355 --- /dev/null +++ b/mysql/connector/locales/eng/client_error.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""MySQL Error Messages.""" + +# This file was auto-generated. +_GENERATED_ON = "2021-08-11" +_MYSQL_VERSION = (8, 0, 27) + +# pylint: disable=line-too-long +# Start MySQL Error messages +CR_UNKNOWN_ERROR = "Unknown MySQL error" +CR_SOCKET_CREATE_ERROR = "Can't create UNIX socket (%s)" +CR_CONNECTION_ERROR = ( + "Can't connect to local MySQL server through socket '%-.100s' (%s)" +) +CR_CONN_HOST_ERROR = "Can't connect to MySQL server on '%-.100s:%u' (%s)" +CR_IPSOCK_ERROR = "Can't create TCP/IP socket (%s)" +CR_UNKNOWN_HOST = "Unknown MySQL server host '%-.100s' (%s)" +CR_SERVER_GONE_ERROR = "MySQL server has gone away" +CR_VERSION_ERROR = "Protocol mismatch; server version = %s, client version = %s" +CR_OUT_OF_MEMORY = "MySQL client ran out of memory" +CR_WRONG_HOST_INFO = "Wrong host info" +CR_LOCALHOST_CONNECTION = "Localhost via UNIX socket" +CR_TCP_CONNECTION = "%-.100s via TCP/IP" +CR_SERVER_HANDSHAKE_ERR = "Error in server handshake" +CR_SERVER_LOST = "Lost connection to MySQL server during query" +CR_COMMANDS_OUT_OF_SYNC = "Commands out of sync; you can't run this command now" +CR_NAMEDPIPE_CONNECTION = "Named pipe: %-.32s" +CR_NAMEDPIPEWAIT_ERROR = "Can't wait for named pipe to host: %-.64s pipe: %-.32s (%s)" +CR_NAMEDPIPEOPEN_ERROR = "Can't open named pipe to host: %-.64s pipe: %-.32s (%s)" +CR_NAMEDPIPESETSTATE_ERROR = ( + "Can't set state of named pipe to host: %-.64s pipe: %-.32s (%s)" +) +CR_CANT_READ_CHARSET = "Can't initialize character set %-.32s (path: %-.100s)" +CR_NET_PACKET_TOO_LARGE = "Got packet bigger than 'max_allowed_packet' bytes" +CR_EMBEDDED_CONNECTION = "Embedded server" +CR_PROBE_SLAVE_STATUS = "Error on SHOW SLAVE STATUS:" +CR_PROBE_SLAVE_HOSTS = "Error on SHOW SLAVE HOSTS:" +CR_PROBE_SLAVE_CONNECT = "Error connecting to slave:" +CR_PROBE_MASTER_CONNECT = "Error connecting to master:" +CR_SSL_CONNECTION_ERROR = "SSL connection error: %-.100s" +CR_MALFORMED_PACKET = "Malformed packet" +CR_WRONG_LICENSE = "This client library is licensed only for use with MySQL servers having '%s' license" +CR_NULL_POINTER = "Invalid use of null pointer" +CR_NO_PREPARE_STMT = "Statement not prepared" +CR_PARAMS_NOT_BOUND = "No data supplied for parameters in prepared statement" +CR_DATA_TRUNCATED = "Data truncated" +CR_NO_PARAMETERS_EXISTS = "No parameters exist in the statement" +CR_INVALID_PARAMETER_NO = "Invalid parameter number" +CR_INVALID_BUFFER_USE = ( + "Can't send long data for non-string/non-binary data types (parameter: %s)" +) +CR_UNSUPPORTED_PARAM_TYPE = "Using unsupported buffer type: %s (parameter: %s)" +CR_SHARED_MEMORY_CONNECTION = "Shared memory: %-.100s" +CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = ( + "Can't open shared memory; client could not create request event (%s)" +) +CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = ( + "Can't open shared memory; no answer event received from server (%s)" +) +CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = ( + "Can't open shared memory; server could not allocate file mapping (%s)" +) +CR_SHARED_MEMORY_CONNECT_MAP_ERROR = ( + "Can't open shared memory; server could not get pointer to file mapping (%s)" +) +CR_SHARED_MEMORY_FILE_MAP_ERROR = ( + "Can't open shared memory; client could not allocate file mapping (%s)" +) +CR_SHARED_MEMORY_MAP_ERROR = ( + "Can't open shared memory; client could not get pointer to file mapping (%s)" +) +CR_SHARED_MEMORY_EVENT_ERROR = ( + "Can't open shared memory; client could not create %s event (%s)" +) +CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = ( + "Can't open shared memory; no answer from server (%s)" +) +CR_SHARED_MEMORY_CONNECT_SET_ERROR = ( + "Can't open shared memory; cannot send request event to server (%s)" +) +CR_CONN_UNKNOW_PROTOCOL = "Wrong or unknown protocol" +CR_INVALID_CONN_HANDLE = "Invalid connection handle" +CR_UNUSED_1 = "Connection using old (pre-4.1.1) authentication protocol refused (client option 'secure_auth' enabled)" +CR_FETCH_CANCELED = "Row retrieval was canceled by mysql_stmt_close() call" +CR_NO_DATA = "Attempt to read column without prior row fetch" +CR_NO_STMT_METADATA = "Prepared statement contains no metadata" +CR_NO_RESULT_SET = ( + "Attempt to read a row while there is no result set associated with the statement" +) +CR_NOT_IMPLEMENTED = "This feature is not implemented yet" +CR_SERVER_LOST_EXTENDED = "Lost connection to MySQL server at '%s', system error: %s" +CR_STMT_CLOSED = "Statement closed indirectly because of a preceding %s() call" +CR_NEW_STMT_METADATA = "The number of columns in the result set differs from the number of bound buffers. You must reset the statement, rebind the result set columns, and execute the statement again" +CR_ALREADY_CONNECTED = ( + "This handle is already connected. Use a separate handle for each connection." +) +CR_AUTH_PLUGIN_CANNOT_LOAD = "Authentication plugin '%s' cannot be loaded: %s" +CR_DUPLICATE_CONNECTION_ATTR = "There is an attribute with the same name already" +CR_AUTH_PLUGIN_ERR = "Authentication plugin '%s' reported error: %s" +CR_INSECURE_API_ERR = "Insecure API function call: '%s' Use instead: '%s'" +CR_FILE_NAME_TOO_LONG = "File name is too long" +CR_SSL_FIPS_MODE_ERR = "Set FIPS mode ON/STRICT failed" +CR_DEPRECATED_COMPRESSION_NOT_SUPPORTED = ( + "Compression protocol not supported with asynchronous protocol" +) +CR_COMPRESSION_WRONGLY_CONFIGURED = ( + "Connection failed due to wrongly configured compression algorithm" +) +CR_KERBEROS_USER_NOT_FOUND = ( + "SSO user not found, Please perform SSO authentication using kerberos." +) +CR_LOAD_DATA_LOCAL_INFILE_REJECTED = ( + "LOAD DATA LOCAL INFILE file request rejected due to restrictions on access." +) +CR_LOAD_DATA_LOCAL_INFILE_REALPATH_FAIL = ( + "Determining the real path for '%s' failed with error (%s): %s" +) +CR_DNS_SRV_LOOKUP_FAILED = "DNS SRV lookup failed with error : %s" +CR_MANDATORY_TRACKER_NOT_FOUND = ( + "Client does not recognise tracker type %s marked as mandatory by server." +) +CR_INVALID_FACTOR_NO = "Invalid first argument for MYSQL_OPT_USER_PASSWORD option. Valid value should be between 1 and 3 inclusive." +# End MySQL Error messages diff --git a/mysql/connector/logger.py b/mysql/connector/logger.py new file mode 100644 index 0000000..c64162a --- /dev/null +++ b/mysql/connector/logger.py @@ -0,0 +1,33 @@ +# Copyright (c) 2022, 2023, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Setup of the `mysql.connector` logger.""" + +import logging + +logger = logging.getLogger("mysql.connector") diff --git a/mysql/connector/network.py b/mysql/connector/network.py new file mode 100644 index 0000000..e3ff973 --- /dev/null +++ b/mysql/connector/network.py @@ -0,0 +1,744 @@ +# Copyright (c) 2012, 2023, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# mypy: disable-error-code="attr-defined" + +"""Module implementing low-level socket communication with MySQL servers. +""" + +import os +import socket +import struct +import warnings +import zlib + +from abc import ABC, abstractmethod +from collections import deque + +try: + import ssl + + TLS_VERSIONS = { + "TLSv1": ssl.PROTOCOL_TLSv1, + "TLSv1.1": ssl.PROTOCOL_TLSv1_1, + "TLSv1.2": ssl.PROTOCOL_TLSv1_2, + } + # TLSv1.3 included in PROTOCOL_TLS, but PROTOCOL_TLS is not included on 3.4 + TLS_VERSIONS["TLSv1.3"] = ( + ssl.PROTOCOL_TLS + if hasattr(ssl, "PROTOCOL_TLS") + else ssl.PROTOCOL_SSLv23 # Alias of PROTOCOL_TLS + ) + TLS_V1_3_SUPPORTED = hasattr(ssl, "HAS_TLSv1_3") and ssl.HAS_TLSv1_3 +except ImportError: + # If import fails, we don't have SSL support. + TLS_V1_3_SUPPORTED = False + +from typing import Any, Deque, List, Optional, Tuple, Union + +from .errors import InterfaceError, NotSupportedError, OperationalError +from .types import StrOrBytesPath + +MIN_COMPRESS_LENGTH: int = 50 +MAX_PAYLOAD_LENGTH: int = 2**24 - 1 +PACKET_HEADER_LENGTH: int = 4 +COMPRESSED_PACKET_HEADER_LENGTH: int = 7 + + +def _strioerror(err: IOError) -> str: + """Reformat the IOError error message. + + This function reformats the IOError error message. + """ + return str(err) if not err.errno else f"{err.errno} {err.strerror}" + + +class NetworkBroker(ABC): + """Broker class interface. + + The network object is a broker used as a delegate by a socket object. Whenever the + socket wants to deliver or get packets to or from the MySQL server it needs to rely + on its network broker (netbroker). + + The netbroker sends `payloads` and receives `packets`. + + A packet is a bytes sequence, it has a header and body (referred to as payload). + The first `PACKET_HEADER_LENGTH` or `COMPRESSED_PACKET_HEADER_LENGTH` + (as appropriate) bytes correspond to the `header`, the remaining ones represent the + `payload`. + + The maximum payload length allowed to be sent per packet to the server is + `MAX_PAYLOAD_LENGTH`. When `send` is called with a payload whose length is greater + than `MAX_PAYLOAD_LENGTH` the netbroker breaks it down into packets, so the caller + of `send` can provide payloads of arbitrary length. + + Finally, data received by the netbroker comes directly from the server, expect to + get a packet for each call to `recv`. The received packet contains a header and + payload, the latter respecting `MAX_PAYLOAD_LENGTH`. + """ + + @abstractmethod + def send( + self, + sock: socket.socket, + address: str, + payload: bytes, + packet_number: Optional[int] = None, + compressed_packet_number: Optional[int] = None, + ) -> None: + """Send `payload` to the MySQL server. + + If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is + broken down into packets. + + Args: + sock: Object holding the socket connection. + address: Socket's location. + payload: Packet's body to send. + packet_number: Sequence id (packet ID) to attach to the header when sending + plain packets. + compressed_packet_number: Same as `packet_number` but used when sending + compressed packets. + + Raises: + :class:`OperationalError`: If something goes wrong while sending packets to + the MySQL server. + """ + + @abstractmethod + def recv(self, sock: socket.socket, address: str) -> bytearray: + """Get the next available packet from the MySQL server. + + Args: + sock: Object holding the socket connection. + address: Socket's location. + + Returns: + packet: A packet from the MySQL server. + + Raises: + :class:`OperationalError`: If something goes wrong while receiving packets + from the MySQL server. + :class:`InterfaceError`: If something goes wrong while receiving packets + from the MySQL server. + """ + + +class NetworkBrokerPlain(NetworkBroker): + """Broker class for MySQL socket communication.""" + + def __init__(self) -> None: + self._pktnr: int = -1 # packet number + + def _set_next_pktnr(self) -> None: + """Increment packet id.""" + self._pktnr = (self._pktnr + 1) % 256 + + def _send_pkt(self, sock: socket.socket, address: str, pkt: bytes) -> None: + """Write packet to the comm channel.""" + try: + sock.sendall(pkt) + except IOError as err: + raise OperationalError( + errno=2055, values=(address, _strioerror(err)) + ) from err + except AttributeError as err: + raise OperationalError(errno=2006) from err + + def _recv_chunk(self, sock: socket.socket, size: int = 0) -> bytearray: + """Read `size` bytes from the comm channel.""" + pkt = bytearray(size) + pkt_view = memoryview(pkt) + while size: + read = sock.recv_into(pkt_view, size) + if read == 0 and size > 0: + raise InterfaceError(errno=2013) + pkt_view = pkt_view[read:] + size -= read + return pkt + + def send( + self, + sock: socket.socket, + address: str, + payload: bytes, + packet_number: Optional[int] = None, + compressed_packet_number: Optional[int] = None, + ) -> None: + """Send payload to the MySQL server. + + If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is + broken down into packets. + """ + if packet_number is None: + self._set_next_pktnr() + else: + self._pktnr = packet_number + + # If the payload is larger than or equal to MAX_PAYLOAD_LENGTH + # the length is set to 2^24 - 1 (ff ff ff) and additional + # packets are sent with the rest of the payload until the + # payload of a packet is less than MAX_PAYLOAD_LENGTH. + if len(payload) >= MAX_PAYLOAD_LENGTH: + offset = 0 + for _ in range(len(payload) // MAX_PAYLOAD_LENGTH): + # payload_len, sequence_id, payload + self._send_pkt( + sock, + address, + b"\xff\xff\xff" + + struct.pack(" bytearray: + """Receive `one` packet from the MySQL server.""" + try: + # Read the header of the MySQL packet + header = self._recv_chunk(sock, size=PACKET_HEADER_LENGTH) + + # Pull the payload length and sequence id + payload_len, self._pktnr = ( + struct.unpack(" None: + super().__init__() + self._compressed_pktnr = -1 + self._queue_read: Deque[bytearray] = deque() + + @staticmethod + def _prepare_packets(payload: bytes, pktnr: int) -> List[bytes]: + """Prepare a payload for sending to the MySQL server.""" + pkts = [] + + # If the payload is larger than or equal to MAX_PAYLOAD_LENGTH + # the length is set to 2^24 - 1 (ff ff ff) and additional + # packets are sent with the rest of the payload until the + # payload of a packet is less than MAX_PAYLOAD_LENGTH. + if len(payload) >= MAX_PAYLOAD_LENGTH: + offset = 0 + for _ in range(len(payload) // MAX_PAYLOAD_LENGTH): + # payload length + sequence id + payload + pkts.append( + b"\xff\xff\xff" + + struct.pack(" None: + """Increment packet id.""" + self._compressed_pktnr = (self._compressed_pktnr + 1) % 256 + + def _send_pkt(self, sock: socket.socket, address: str, pkt: bytes) -> None: + """Compress packet and write it to the comm channel.""" + compressed_pkt = zlib.compress(pkt) + pkt = ( + struct.pack(" None: + """Send `payload` as compressed packets to the MySQL server. + + If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is + broken down into packets. + """ + # get next packet numbers + if packet_number is None: + self._set_next_pktnr() + else: + self._pktnr = packet_number + if compressed_packet_number is None: + self._set_next_compressed_pktnr() + else: + self._compressed_pktnr = compressed_packet_number + + payload_prep = bytearray(b"").join(self._prepare_packets(payload, self._pktnr)) + if len(payload) >= MAX_PAYLOAD_LENGTH - PACKET_HEADER_LENGTH: + # sending a MySQL payload of the size greater or equal to 2^24 - 5 + # via compression leads to at least one extra compressed packet + # WHY? let's say len(payload) is MAX_PAYLOAD_LENGTH - 3; when preparing + # the payload, a header of size PACKET_HEADER_LENGTH is pre-appended + # to the payload. This means that len(payload_prep) is + # MAX_PAYLOAD_LENGTH - 3 + PACKET_HEADER_LENGTH = MAX_PAYLOAD_LENGTH + 1 + # surpassing the maximum allowed payload size per packet. + offset = 0 + + # send several MySQL packets + for _ in range(len(payload_prep) // MAX_PAYLOAD_LENGTH): + self._send_pkt( + sock, address, payload_prep[offset : offset + MAX_PAYLOAD_LENGTH] + ) + self._set_next_compressed_pktnr() + offset += MAX_PAYLOAD_LENGTH + self._send_pkt(sock, address, payload_prep[offset:]) + else: + # send one MySQL packet + # For small packets it may be too costly to compress the packet. + # Usually payloads less than 50 bytes (MIN_COMPRESS_LENGTH) + # aren't compressed (see MySQL source code Documentation). + if len(payload) > MIN_COMPRESS_LENGTH: + # perform compression + self._send_pkt(sock, address, payload_prep) + else: + # skip compression + super()._send_pkt( + sock, + address, + struct.pack(" None: + """Handle reading of a compressed packet.""" + # compressed_pll stands for compressed payload length. + # Recalling that if uncompressed payload length == 0, the packet + # comes in uncompressed, so no decompression is needed. + compressed_pkt = super()._recv_chunk(sock, size=compressed_pll) + pkt = ( + compressed_pkt + if uncompressed_pll == 0 + else bytearray(zlib.decompress(compressed_pkt)) + ) + + offset = 0 + while offset < len(pkt): + # pll stands for payload length + pll = struct.unpack( + " len(pkt) - offset: + # More bytes need to be consumed + # Read the header of the next MySQL packet + header = super()._recv_chunk(sock, size=COMPRESSED_PACKET_HEADER_LENGTH) + + # compressed payload length, sequence id, uncompressed payload length + ( + compressed_pll, + self._compressed_pktnr, + uncompressed_pll, + ) = ( + struct.unpack(" bytearray: + """Receive `one` or `several` packets from the MySQL server, enqueue them, and + return the packet at the head. + """ + if not self._queue_read: + try: + # Read the header of the next MySQL packet + header = super()._recv_chunk(sock, size=COMPRESSED_PACKET_HEADER_LENGTH) + + # compressed payload length, sequence id, uncompressed payload length + ( + compressed_pll, + self._compressed_pktnr, + uncompressed_pll, + ) = ( + struct.unpack(" None: + """Network layer where transactions are made with plain (uncompressed) packets + is enabled by default. + """ + # holds the socket connection + self.sock: Optional[socket.socket] = None + self._connection_timeout: Optional[int] = None + self.server_host: Optional[str] = None + self._netbroker: NetworkBroker = NetworkBrokerPlain() + + def switch_to_compressed_mode(self) -> None: + """Enable network layer where transactions are made with compressed packets.""" + self._netbroker = NetworkBrokerCompressed() + + def shutdown(self) -> None: + """Shut down the socket before closing it.""" + try: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + except (AttributeError, OSError): + pass + + def close_connection(self) -> None: + """Close the socket.""" + try: + self.sock.close() + except (AttributeError, OSError): + pass + + def __del__(self) -> None: + self.shutdown() + + def set_connection_timeout(self, timeout: Optional[int]) -> None: + """Set the connection timeout.""" + self._connection_timeout = timeout + if self.sock: + self.sock.settimeout(timeout) + + def switch_to_ssl( + self, + ca: StrOrBytesPath, + cert: StrOrBytesPath, + key: StrOrBytesPath, + verify_cert: bool = False, + verify_identity: bool = False, + cipher_suites: Optional[str] = None, + tls_versions: Optional[List[str]] = None, + ) -> None: + """Switch the socket to use SSL""" + if not self.sock: + raise InterfaceError(errno=2048) + + try: + if verify_cert: + cert_reqs = ssl.CERT_REQUIRED + elif verify_identity: + cert_reqs = ssl.CERT_OPTIONAL + else: + cert_reqs = ssl.CERT_NONE + + if tls_versions is None or not tls_versions: + context = ssl.create_default_context() + if not verify_identity: + context.check_hostname = False + else: + tls_versions.sort(reverse=True) + + tls_version = tls_versions[0] + if ( + not TLS_V1_3_SUPPORTED + and tls_version == "TLSv1.3" + and len(tls_versions) > 1 + ): + tls_version = tls_versions[1] + ssl_protocol = TLS_VERSIONS[tls_version] + context = ssl.SSLContext(ssl_protocol) + + if tls_version == "TLSv1.3": + if "TLSv1.2" not in tls_versions: + context.options |= ssl.OP_NO_TLSv1_2 + if "TLSv1.1" not in tls_versions: + context.options |= ssl.OP_NO_TLSv1_1 + if "TLSv1" not in tls_versions: + context.options |= ssl.OP_NO_TLSv1 + + context.check_hostname = False + context.verify_mode = cert_reqs + context.load_default_certs() + + if ca: + try: + context.load_verify_locations(ca) + except (IOError, ssl.SSLError) as err: + self.sock.close() + raise InterfaceError(f"Invalid CA Certificate: {err}") from err + if cert: + try: + context.load_cert_chain(cert, key) + except (IOError, ssl.SSLError) as err: + self.sock.close() + raise InterfaceError(f"Invalid Certificate/Key: {err}") from err + if cipher_suites: + context.set_ciphers(cipher_suites) + + if hasattr(self, "server_host"): + self.sock = context.wrap_socket( + self.sock, server_hostname=self.server_host + ) + else: + self.sock = context.wrap_socket(self.sock) + + if verify_identity: + context.check_hostname = True + hostnames: List[str] = [self.server_host] if self.server_host else [] + if os.name == "nt" and self.server_host == "localhost": + hostnames = ["localhost", "127.0.0.1"] + aliases = socket.gethostbyaddr(self.server_host) + hostnames.extend([aliases[0]] + aliases[1]) + match_found = False + errs = [] + for hostname in hostnames: + try: + # Deprecated in Python 3.7 without a replacement and + # should be removed in the future, since OpenSSL now + # performs hostname matching + # pylint: disable=deprecated-method + ssl.match_hostname(self.sock.getpeercert(), hostname) + # pylint: enable=deprecated-method + except ssl.CertificateError as err: + errs.append(str(err)) + else: + match_found = True + break + if not match_found: + self.sock.close() + raise InterfaceError( + f"Unable to verify server identity: {', '.join(errs)}" + ) + except NameError as err: + raise NotSupportedError("Python installation has no SSL support") from err + except (ssl.SSLError, IOError) as err: + raise InterfaceError( + errno=2055, values=(self.address, _strioerror(err)) + ) from err + except ssl.CertificateError as err: + raise InterfaceError(str(err)) from err + except NotImplementedError as err: + raise InterfaceError(str(err)) from err + + def send( + self, + payload: bytes, + packet_number: Optional[int] = None, + compressed_packet_number: Optional[int] = None, + ) -> None: + """Send `payload` to the MySQL server.""" + return self._netbroker.send( + self.sock, + self.address, + payload, + packet_number=packet_number, + compressed_packet_number=compressed_packet_number, + ) + + def recv(self) -> bytearray: + """Get packet from the MySQL server comm channel.""" + return self._netbroker.recv(self.sock, self.address) + + @abstractmethod + def open_connection(self) -> None: + """Open the socket.""" + + @property + @abstractmethod + def address(self) -> str: + """Get the location of the socket.""" + + +class MySQLUnixSocket(MySQLSocket): + """MySQL socket class using UNIX sockets. + + Opens a connection through the UNIX socket of the MySQL Server. + """ + + def __init__(self, unix_socket: str = "/tmp/mysql.sock") -> None: + super().__init__() + self.unix_socket: str = unix_socket + self._address: str = unix_socket + + @property + def address(self) -> str: + return self._address + + def open_connection(self) -> None: + try: + self.sock = socket.socket( + socket.AF_UNIX, socket.SOCK_STREAM # pylint: disable=no-member + ) + self.sock.settimeout(self._connection_timeout) + self.sock.connect(self.unix_socket) + except IOError as err: + raise InterfaceError( + errno=2002, values=(self.address, _strioerror(err)) + ) from err + except Exception as err: + raise InterfaceError(str(err)) from err + + def switch_to_ssl( + self, *args: Any, **kwargs: Any # pylint: disable=unused-argument + ) -> None: + """Switch the socket to use SSL.""" + warnings.warn( + "SSL is disabled when using unix socket connections", + Warning, + ) + + +class MySQLTCPSocket(MySQLSocket): + """MySQL socket class using TCP/IP. + + Opens a TCP/IP connection to the MySQL Server. + """ + + def __init__( + self, + host: str = "127.0.0.1", + port: int = 3306, + force_ipv6: bool = False, + ) -> None: + super().__init__() + self.server_host: str = host + self.server_port: int = port + self.force_ipv6: bool = force_ipv6 + self._family: int = 0 + self._address: str = f"{host}:{port}" + + @property + def address(self) -> str: + return self._address + + def open_connection(self) -> None: + """Open the TCP/IP connection to the MySQL server.""" + # pylint: disable=no-member + # Get address information + addrinfo: Union[ + Tuple[None, None, None, None, None], + Tuple[ + socket.AddressFamily, + socket.SocketKind, + int, + str, + Union[Tuple[str, int], Tuple[str, int, int, int]], + ], + ] = (None, None, None, None, None) + try: + addrinfos = socket.getaddrinfo( + self.server_host, + self.server_port, + 0, + socket.SOCK_STREAM, + socket.SOL_TCP, + ) + # If multiple results we favor IPv4, unless IPv6 was forced. + for info in addrinfos: + if self.force_ipv6 and info[0] == socket.AF_INET6: + addrinfo = info + break + if info[0] == socket.AF_INET: + addrinfo = info + break + if self.force_ipv6 and addrinfo[0] is None: + raise InterfaceError(f"No IPv6 address found for {self.server_host}") + if addrinfo[0] is None: + addrinfo = addrinfos[0] + except IOError as err: + raise InterfaceError( + errno=2003, values=(self.address, _strioerror(err)) + ) from err + + (self._family, socktype, proto, _, sockaddr) = addrinfo + + # Instanciate the socket and connect + try: + self.sock = socket.socket(self._family, socktype, proto) + self.sock.settimeout(self._connection_timeout) + self.sock.connect(sockaddr) + except IOError as err: + raise InterfaceError( + errno=2003, + values=( + self.server_host, + self.server_port, + _strioerror(err), + ), + ) from err + except Exception as err: + raise OperationalError(str(err)) from err diff --git a/mysql/connector/opentelemetry/__init__.py b/mysql/connector/opentelemetry/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mysql/connector/opentelemetry/constants.py b/mysql/connector/opentelemetry/constants.py new file mode 100644 index 0000000..7e0090c --- /dev/null +++ b/mysql/connector/opentelemetry/constants.py @@ -0,0 +1,56 @@ +"""Constants used by the opentelemetry instrumentation implementation.""" +# mypy: disable-error-code="no-redef,assignment" + +# pylint: disable=unused-import +OTEL_ENABLED = True +try: + # try to load otel from the system + from opentelemetry import trace # check api + from opentelemetry.sdk.trace import TracerProvider # check sdk + from opentelemetry.semconv.trace import SpanAttributes # check semconv +except ImportError: + # falling back to the bundled installation + try: + from mysql.opentelemetry import trace + from mysql.opentelemetry.sdk.trace import TracerProvider + from mysql.opentelemetry.semconv.trace import SpanAttributes + except ImportError: + # bundled installation has missing dependencies + OTEL_ENABLED = False + + +OPTION_CNX_SPAN = "_span" +""" +Connection option name used to inject the connection span. +This connection option name must not be used, is reserved. +""" + +OPTION_CNX_TRACER = "_tracer" +""" +Connection option name used to inject the opentelemetry tracer. +This connection option name must not be used, is reserved. +""" + +CONNECTION_SPAN_NAME = "connection" +""" +Connection span name to be used by the instrumentor. +""" + +FIRST_SUPPORTED_VERSION = "8.1.0" +""" +First mysql-connector-python version to support opentelemetry instrumentation. +""" + +TRACEPARENT_HEADER_NAME = "traceparent" + +DB_SYSTEM = "mysql" +DEFAULT_THREAD_NAME = "main" +DEFAULT_THREAD_ID = 0 + +# Reference: https://github.com/open-telemetry/opentelemetry-specification/blob/main/ +# specification/trace/semantic_conventions/span-general.md +NET_SOCK_FAMILY = "net.sock.family" +NET_SOCK_PEER_ADDR = "net.sock.peer.addr" +NET_SOCK_PEER_PORT = "net.sock.peer.port" +NET_SOCK_HOST_ADDR = "net.sock.host.addr" +NET_SOCK_HOST_PORT = "net.sock.host.port" diff --git a/mysql/connector/opentelemetry/context_propagation.py b/mysql/connector/opentelemetry/context_propagation.py new file mode 100644 index 0000000..da8d60b --- /dev/null +++ b/mysql/connector/opentelemetry/context_propagation.py @@ -0,0 +1,92 @@ +"""Trace context propagation utilities.""" +# mypy: disable-error-code="no-redef" +# pylint: disable=invalid-name + +from typing import TYPE_CHECKING, Any, Callable, Union + +from .constants import OTEL_ENABLED, TRACEPARENT_HEADER_NAME + +if OTEL_ENABLED: + from .instrumentation import OTEL_SYSTEM_AVAILABLE + + if OTEL_SYSTEM_AVAILABLE: + # pylint: disable=import-error + # load otel from the system + from opentelemetry import trace + from opentelemetry.trace.span import format_span_id, format_trace_id + else: + # load otel from the bundled installation + from mysql.opentelemetry import trace + from mysql.opentelemetry.trace.span import format_span_id, format_trace_id + + +if TYPE_CHECKING: + from ..connection import MySQLConnection + from ..connection_cext import CMySQLConnection + + +def build_traceparent_header(span: Any) -> str: + """Build a traceparent header according to the provided span. + + The context information from the provided span is used to build the traceparent + header that will be propagated to the MySQL server. For particulars regarding + the header creation, refer to [1]. + + This method assumes version 0 of the W3C specification. + + Args: + span (opentelemetry.trace.span.Span): current span in trace. + + Returns: + traceparent_header (str): HTTP header field that identifies requests in a + tracing system. + + References: + [1]: https://www.w3.org/TR/trace-context/#traceparent-header + """ + ctx = span.get_span_context() + + version = "00" # version 0 of the W3C specification + trace_id = format_trace_id(ctx.trace_id) + span_id = format_span_id(ctx.span_id) + trace_flags = "00" # sampled flag is off + + return "-".join([version, trace_id, span_id, trace_flags]) + + +def with_context_propagation(method: Callable) -> Callable: + """Perform trace context propagation. + + The trace context is propagated via query attributes. The `traceparent` header + from W3C specification [1] is used, in this sense, the attribute name is + `traceparent` (is RESERVED, avoid using it), and its value is built as per + instructed in [1]. + + If opentelemetry API/SDK is unavailable or there is no recording span, + trace context propagation is skipped. + + References: + [1]: https://www.w3.org/TR/trace-context/#traceparent-header + """ + + def wrapper( + cnx: Union["MySQLConnection", "CMySQLConnection"], *args: Any, **kwargs: Any + ) -> Any: + """Context propagation decorator.""" + if not OTEL_ENABLED or not cnx.otel_context_propagation: + return method(cnx, *args, **kwargs) + + current_span = trace.get_current_span() + tp_header = None + if current_span.is_recording(): + tp_header = build_traceparent_header(current_span) + cnx.query_attrs_append(value=(TRACEPARENT_HEADER_NAME, tp_header)) + + try: + result = method(cnx, *args, **kwargs) + finally: + if tp_header is not None: + cnx.query_attrs_remove(name=TRACEPARENT_HEADER_NAME) + return result + + return wrapper diff --git a/mysql/connector/opentelemetry/instrumentation.py b/mysql/connector/opentelemetry/instrumentation.py new file mode 100644 index 0000000..cc8b376 --- /dev/null +++ b/mysql/connector/opentelemetry/instrumentation.py @@ -0,0 +1,514 @@ +"""MySQL instrumentation supporting mysql-connector.""" +# mypy: disable-error-code="no-redef" +# pylint: disable=protected-access,global-statement,invalid-name + +from __future__ import annotations + +import functools +import re + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any, Callable, Collection, Dict, Optional, Union + +# pylint: disable=cyclic-import +if TYPE_CHECKING: + # `TYPE_CHECKING` is always False at run time, hence circular import + # will not happen at run time (no error happens whatsoever). + # Since pylint is a static checker it happens that `TYPE_CHECKING` + # is True when analyzing the code which makes pylint believe there + # is a circular import issue when there isn't. + + from ..abstracts import MySQLConnectionAbstract + from ..connection import MySQLConnection + from ..cursor import MySQLCursor + from ..pooling import PooledMySQLConnection + + try: + from ..connection_cext import CMySQLConnection + from ..cursor_cext import CMySQLCursor + except ImportError: + # The cext is not available. + pass + +from ... import connector +from ..constants import CNX_POOL_ARGS, DEFAULT_CONFIGURATION +from ..logger import logger +from ..version import VERSION_TEXT + +try: + # pylint: disable=unused-import + # try to load otel from the system + from opentelemetry import trace # check api + from opentelemetry.sdk.trace import TracerProvider # check sdk + from opentelemetry.semconv.trace import SpanAttributes # check semconv + + OTEL_SYSTEM_AVAILABLE = True +except ImportError: + try: + # falling back to the bundled installation + from mysql.opentelemetry import trace + from mysql.opentelemetry.semconv.trace import SpanAttributes + + OTEL_SYSTEM_AVAILABLE = False + except ImportError as missing_dependencies_err: + raise connector.errors.ProgrammingError( + "Bundled installation has missing dependencies. " + "Please use `pip install mysql-connector-python[opentelemetry]`, " + "or for an editable install use `pip install -e '.[opentelemetry]'`, " + "to install the dependencies required by the bundled opentelemetry package." + ) from missing_dependencies_err + + +from .constants import ( + CONNECTION_SPAN_NAME, + DB_SYSTEM, + DEFAULT_THREAD_ID, + DEFAULT_THREAD_NAME, + FIRST_SUPPORTED_VERSION, + NET_SOCK_FAMILY, + NET_SOCK_HOST_ADDR, + NET_SOCK_HOST_PORT, + NET_SOCK_PEER_ADDR, + NET_SOCK_PEER_PORT, + OPTION_CNX_SPAN, + OPTION_CNX_TRACER, +) + +leading_comment_remover: re.Pattern = re.compile(r"^/\*.*?\*/") + + +def record_exception_event(span: trace.Span, exc: Optional[Exception]) -> None: + """Records an exeception event.""" + if not span or not span.is_recording() or not exc: + return + + span.set_status(trace.Status(trace.StatusCode.ERROR)) + span.record_exception(exc) + + +def end_span(span: trace.Span) -> None: + """Ends span.""" + if not span or not span.is_recording(): + return + + span.end() + + +def get_operation_name(operation: str) -> str: + """Parse query to extract operation name.""" + if operation and isinstance(operation, str): + # Strip leading comments so we get the operation name. + return leading_comment_remover.sub("", operation).split()[0] + return "" + + +def set_connection_span_attrs( + cnx: Optional["MySQLConnectionAbstract"], + cnx_span: trace.Span, + cnx_kwargs: Optional[Dict[str, Any]] = None, +) -> None: + """Defines connection span attributes. If `cnx` is None then we use `cnx_kwargs` + to get basic net information. Basic net attributes are defined such as: + + * DB_SYSTEM + * NET_TRANSPORT + * NET_SOCK_FAMILY + + Socket-level attributes [*] are also defined [**]. + + [*]: Socket-level attributes identify peer and host that are directly connected to + each other. Since instrumentations may have limited knowledge on network + information, instrumentations SHOULD populate such attributes to the best of + their knowledge when populate them at all. + + [**]: `CMySQLConnection` connections have no access to socket-level + details so socket-level attributes aren't included. `MySQLConnection` + connections, on the other hand, do include socket-level attributes. + + References: + [1]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/ + specification/trace/semantic_conventions/span-general.md + """ + # pylint: disable=broad-exception-caught + if not cnx_span or not cnx_span.is_recording(): + return + + if cnx_kwargs is None: + cnx_kwargs = {} + + is_tcp = not cnx._unix_socket if cnx else "unix_socket" not in cnx_kwargs + + attrs: Dict[str, Any] = { + SpanAttributes.DB_SYSTEM: DB_SYSTEM, + SpanAttributes.NET_TRANSPORT: "ip_tcp" if is_tcp else "inproc", + NET_SOCK_FAMILY: "inet" if is_tcp else "unix", + } + + # Only socket and tcp connections are supported. + if is_tcp: + attrs[SpanAttributes.NET_PEER_NAME] = ( + cnx._host if cnx else cnx_kwargs.get("host", DEFAULT_CONFIGURATION["host"]) + ) + attrs[SpanAttributes.NET_PEER_PORT] = ( + cnx._port if cnx else cnx_kwargs.get("port", DEFAULT_CONFIGURATION["port"]) + ) + + if hasattr(cnx, "_socket") and cnx._socket: + try: + ( + attrs[NET_SOCK_PEER_ADDR], + sock_peer_port, + ) = cnx._socket.sock.getpeername() + + ( + attrs[NET_SOCK_HOST_ADDR], + attrs[NET_SOCK_HOST_PORT], + ) = cnx._socket.sock.getsockname() + except Exception as sock_err: + logger.warning("Connection socket is down %s", sock_err) + else: + if attrs[SpanAttributes.NET_PEER_PORT] != sock_peer_port: + # NET_SOCK_PEER_PORT is recommended if different than net.peer.port + # and if net.sock.peer.addr is set. + attrs[NET_SOCK_PEER_PORT] = sock_peer_port + else: + # For Unix domain socket, net.sock.peer.addr attribute represents + # destination name and net.peer.name SHOULD NOT be set. + attrs[NET_SOCK_PEER_ADDR] = ( + cnx._unix_socket if cnx else cnx_kwargs.get("unix_socket") + ) + + if hasattr(cnx, "_socket") and cnx._socket: + try: + attrs[NET_SOCK_HOST_ADDR] = cnx._socket.sock.getsockname() + except Exception as sock_err: + logger.warning("Connection socket is down %s", sock_err) + + cnx_span.set_attributes(attrs) + + +def instrument_execution( + query_method: Callable, + tracer: trace.Tracer, + connection_span_link: trace.Link, + wrapped: Union["MySQLCursor", "CMySQLCursor"], + *args: Any, + **kwargs: Any, +) -> Callable: + """Instruments the execution of `query_method`. + + A query span with a link to the corresponding connection span is generated. + """ + connection: Union["MySQLConnection", "CMySQLConnection"] = ( + getattr(wrapped, "_connection") + if hasattr(wrapped, "_connection") + else getattr(wrapped, "_cnx") + ) + + # SpanAttributes.DB_NAME: connection.database or ""; introduces performance + # degradation, at this time the database attribute is something nice to have but + # not a requirement. + query_span_attributes: Dict = { + SpanAttributes.DB_SYSTEM: DB_SYSTEM, + SpanAttributes.DB_USER: connection._user, + SpanAttributes.THREAD_ID: DEFAULT_THREAD_ID, + SpanAttributes.THREAD_NAME: DEFAULT_THREAD_NAME, + "cursor_type": wrapped.__class__.__name__, + } + with tracer.start_as_current_span( + name=get_operation_name(args[0]) or "SQL statement", + kind=trace.SpanKind.CLIENT, + links=[connection_span_link], + attributes=query_span_attributes, + ): + return query_method(*args, **kwargs) + + +class BaseMySQLTracer(ABC): + """Base class that provides basic object wrapper functionality.""" + + @abstractmethod + def __init__(self) -> None: + """Must be implemented by subclasses.""" + + def __getattr__(self, attr: str) -> Any: + """Gets an attribute. + + Attributes defined in the wrapper object have higher precedence + than those wrapped object equivalent. Attributes not found in + the wrapper are then searched in the wrapped object. + """ + if attr in self.__dict__: + # this object has it + return getattr(self, attr) + # proxy to the wrapped object + return getattr(self._wrapped, attr) + + def __setattr__(self, name: str, value: Any) -> None: + if "_wrapped" not in self.__dict__: + self.__dict__["_wrapped"] = value + return + + if name in self.__dict__: + # this object has it + super().__setattr__(name, value) + return + # proxy to the wrapped object + self._wrapped.__setattr__(name, value) + + def __enter__(self) -> Any: + """Magic method.""" + self._wrapped.__enter__() + return self + + def __exit__(self, *args: Any, **kwargs: Any) -> None: + """Magic method.""" + self._wrapped.__exit__(*args, **kwargs) + + def get_wrapped_class(self) -> str: + """Gets the wrapped class name.""" + return self._wrapped.__class__.__name__ + + +class TracedMySQLCursor(BaseMySQLTracer): + """Wrapper class for a `MySQLCursor` or `CMySQLCursor` object.""" + + def __init__( + self, + wrapped: Union["MySQLCursor", "CMySQLCursor"], + tracer: trace.Tracer, + connection_span: trace.Span, + ): + """Constructor.""" + self._wrapped: Union["MySQLCursor", "CMySQLCursor"] = wrapped + self._tracer: trace.Tracer = tracer + self._connection_span_link: trace.Link = trace.Link( + connection_span.get_span_context() + ) + + def execute(self, *args: Any, **kwargs: Any) -> Any: + """Instruments execute method.""" + return instrument_execution( + self._wrapped.execute, + self._tracer, + self._connection_span_link, + self._wrapped, + *args, + **kwargs, + ) + + def executemany(self, *args: Any, **kwargs: Any) -> Any: + """Instruments executemany method.""" + return instrument_execution( + self._wrapped.executemany, + self._tracer, + self._connection_span_link, + self._wrapped, + *args, + **kwargs, + ) + + def callproc(self, *args: Any, **kwargs: Any) -> Any: + """Instruments callproc method.""" + return instrument_execution( + self._wrapped.callproc, + self._tracer, + self._connection_span_link, + self._wrapped, + *args, + **kwargs, + ) + + +class TracedMySQLConnection(BaseMySQLTracer): + """Wrapper class for a `MySQLConnection` or `CMySQLConnection` object.""" + + def __init__(self, wrapped: Union["MySQLConnection", "CMySQLConnection"]) -> None: + """Constructor.""" + self._wrapped: Union["MySQLConnection", "CMySQLConnection"] = wrapped + + # call `sql_mode` so its value is cached internally and querying it does not + # interfere when recording query span events later. + _ = self._wrapped.sql_mode + + def cursor(self, *args: Any, **kwargs: Any) -> TracedMySQLCursor: + """Wraps the cursor object.""" + return TracedMySQLCursor( + wrapped=self._wrapped.cursor(*args, **kwargs), + tracer=self._tracer, + connection_span=self._span, + ) + + +def instrument_connect( + connect: Callable[ + ..., Union["MySQLConnection", "CMySQLConnection", "PooledMySQLConnection"] + ], + tracer_provider: Optional[trace.TracerProvider] = None, +) -> Callable[ + ..., Union["MySQLConnection", "CMySQLConnection", "PooledMySQLConnection"] +]: + """Retrurn the instrumented version of `connect`.""" + + # let's preserve `connect` identity. + @functools.wraps(connect) + def wrapper( + *args: Any, **kwargs: Any + ) -> Union["MySQLConnection", "CMySQLConnection", "PooledMySQLConnection"]: + """Wraps the connection object returned by the method `connect`. + + Instrumentation for PooledConnections is not supported. + """ + if any(key in kwargs for key in CNX_POOL_ARGS): + logger.warning("Instrumentation for pooled connections not supported") + return connect(*args, **kwargs) + + tracer = trace.get_tracer( + instrumenting_module_name="MySQL Connector/Python", + instrumenting_library_version=VERSION_TEXT, + tracer_provider=tracer_provider, + ) + + # The connection span is passed in as an argument so the connection object can + # keep a pointer to it. + kwargs[OPTION_CNX_SPAN] = tracer.start_span( + name=CONNECTION_SPAN_NAME, kind=trace.SpanKind.CLIENT + ) + kwargs[OPTION_CNX_TRACER] = tracer + + # Add basic net information. + set_connection_span_attrs(None, kwargs[OPTION_CNX_SPAN], kwargs) + + # Connection may fail at this point, in case it does, basic net info is already + # included so the user can check the net configuration she/he provided. + cnx = connect(*args, **kwargs) + + # connection went ok, let's refine the net information. + set_connection_span_attrs(cnx, cnx._span, kwargs) # type: ignore[arg-type] + + return TracedMySQLConnection( + wrapped=cnx, # type: ignore[return-value, arg-type] + ) + + return wrapper + + +class MySQLInstrumentor: + """MySQL instrumentation supporting mysql-connector-python.""" + + _instance: Optional[MySQLInstrumentor] = None + + def __new__(cls, *args: Any, **kwargs: Any) -> MySQLInstrumentor: + """Singlenton. + + Restricts the instantiation to a singular instance. + """ + if cls._instance is None: + # create instance + cls._instance = object.__new__(cls, *args, **kwargs) + # keep a pointer to the uninstrumented connect method + setattr(cls._instance, "_original_connect", connector.connect) + return cls._instance + + def instrumentation_dependencies(self) -> Collection[str]: + """Return a list of python packages with versions + that the will be instrumented (e.g., versions >= 8.1.0).""" + return [f"mysql-connector-python >= {FIRST_SUPPORTED_VERSION}"] + + def instrument(self, **kwargs: Any) -> None: + """Instrument the library. + + Args: + trace_module: reference to the 'trace' module from opentelemetry. + tracer_provider (optional): TracerProvider instance. + + NOTE: Instrumentation for pooled connections not supported. + """ + if connector.connect != getattr(self, "_original_connect"): + logger.warning("MySQL Connector/Python module already instrumented.") + return + connector.connect = instrument_connect( + connect=getattr(self, "_original_connect"), + tracer_provider=kwargs.get("tracer_provider"), + ) + + def instrument_connection( + self, + connection: Union["MySQLConnection", "CMySQLConnection"], + tracer_provider: Optional[trace.TracerProvider] = None, + ) -> Union["MySQLConnection", "CMySQLConnection"]: + """Enable instrumentation in a MySQL connection. + + Args: + connection: uninstrumented connection instance. + trace_module: reference to the 'trace' module from opentelemetry. + tracer_provider (optional): TracerProvider instance. + + Returns: + connection: instrumented connection instace. + + NOTE: Instrumentation for pooled connections not supported. + """ + if isinstance(connection, TracedMySQLConnection): + logger.warning("Connection already instrumented.") + return connection + + if not hasattr(connection, "_span") or not hasattr(connection, "_tracer"): + logger.warning( + "Instrumentation for class %s not supported.", + connection.__class__.__name__, + ) + return connection + + tracer = trace.get_tracer( + instrumenting_module_name="MySQL Connector/Python", + instrumenting_library_version=VERSION_TEXT, + tracer_provider=tracer_provider, + ) + connection._span = tracer.start_span( + name=CONNECTION_SPAN_NAME, kind=trace.SpanKind.CLIENT + ) + connection._tracer = tracer + + set_connection_span_attrs(connection, connection._span) + + return TracedMySQLConnection(wrapped=connection) # type: ignore[return-value] + + def uninstrument(self, **kwargs: Any) -> None: + """Uninstrument the library.""" + # pylint: disable=unused-argument + if connector.connect == getattr(self, "_original_connect"): + logger.warning("MySQL Connector/Python module already uninstrumented.") + return + connector.connect = getattr(self, "_original_connect") + + def uninstrument_connection( + self, connection: Union["MySQLConnection", "CMySQLConnection"] + ) -> Union["MySQLConnection", "CMySQLConnection"]: + """Disable instrumentation in a MySQL connection. + + Args: + connection: instrumented connection instance. + + Returns: + connection: uninstrumented connection instace. + + NOTE: Instrumentation for pooled connections not supported. + """ + if not hasattr(connection, "_span"): + logger.warning( + "Uninstrumentation for class %s not supported.", + connection.__class__.__name__, + ) + return connection + + if not isinstance(connection, TracedMySQLConnection): + logger.warning("Connection already uninstrumented.") + return connection + + # stop connection span recording + if connection._span and connection._span.is_recording(): + connection._span.end() + connection._span = None + + return connection._wrapped diff --git a/mysql/connector/optionfiles.py b/mysql/connector/optionfiles.py new file mode 100644 index 0000000..13f7132 --- /dev/null +++ b/mysql/connector/optionfiles.py @@ -0,0 +1,357 @@ +# Copyright (c) 2014, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, as +# published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an +# additional permission to link the program and your derivative works +# with the separately licensed software that they have included with +# MySQL. +# +# Without limiting anything contained in the foregoing, this file, +# which is part of MySQL Connector/Python, is also subject to the +# Universal FOSS Exception, version 1.0, a copy of which can be found at +# http://oss.oracle.com/licenses/universal-foss-exception. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# mypy: disable-error-code="attr-defined" + +"""Implements parser to parse MySQL option files.""" + +import codecs +import io +import os +import re + +from configparser import ConfigParser as SafeConfigParser, MissingSectionHeaderError +from typing import Any, Dict, List, Optional, Tuple, Union + +from .constants import CNX_POOL_ARGS, DEFAULT_CONFIGURATION + +DEFAULT_EXTENSIONS: Dict[str, Tuple[str, ...]] = { + "nt": ("ini", "cnf"), + "posix": ("cnf",), +} + + +def read_option_files(**config: Union[str, List[str]]) -> Dict[str, Any]: + """ + Read option files for connection parameters. + + Checks if connection arguments contain option file arguments, and then + reads option files accordingly. + """ + if "option_files" in config: + try: + if isinstance(config["option_groups"], str): + config["option_groups"] = [config["option_groups"]] + groups = config["option_groups"] + del config["option_groups"] + except KeyError: + groups = ["client", "connector_python"] + + if isinstance(config["option_files"], str): + config["option_files"] = [config["option_files"]] + option_parser = MySQLOptionsParser( + list(config["option_files"]), keep_dashes=False + ) + del config["option_files"] + + config_from_file = option_parser.get_groups_as_dict_with_priority(*groups) + config_options: Dict[str, Tuple[str, int]] = {} + for group in groups: + try: + for option, value in config_from_file[group].items(): + try: + if option == "socket": + option = "unix_socket" + + if option not in CNX_POOL_ARGS and option != "failover": + _ = DEFAULT_CONFIGURATION[option] + + if ( + option not in config_options + or config_options[option][1] <= value[1] + ): + config_options[option] = value + except KeyError: + if group == "connector_python": + raise AttributeError( + f"Unsupported argument '{option}'" + ) from None + except KeyError: + continue + + not_evaluate = ("password", "passwd") + for option, value in config_options.items(): + if option not in config: + try: + if option in not_evaluate: + config[option] = value[0] + else: + config[option] = eval(value[0]) # pylint: disable=eval-used + except (NameError, SyntaxError): + config[option] = value[0] + + return config + + +class MySQLOptionsParser(SafeConfigParser): + """This class implements methods to parse MySQL option files""" + + def __init__( + self, files: Optional[Union[List[str], str]] = None, keep_dashes: bool = True + ) -> None: + """Initialize + + If defaults is True, default option files are read first + + Raises ValueError if defaults is set to True but defaults files + cannot be found. + """ + + # Regular expression to allow options with no value(For Python v2.6) + self.optcre: re.Pattern = re.compile( + r"(?P